| /** | |
| * Matomo - free/libre analytics platform | |
| * | |
| * @link https://matomo.org | |
| * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later | |
| */ | |
| namespace Piwik\Archive; | |
| /** | |
| * This class is used to split blobs of DataTables into chunks. Each blob used to be stored under one blob in the | |
| * archive table. For better efficiency we do now combine multiple DataTable into one blob entry. | |
| * | |
| * Chunks are identified by having the recordName $recordName_chunk_0_99, $recordName_chunk_100_199 (this chunk stores | |
| * the subtable 100-199). | |
| */ | |
| class Chunk | |
| { | |
| public const ARCHIVE_APPENDIX_SUBTABLES = 'chunk'; | |
| public const NUM_TABLES_IN_CHUNK = 100; | |
| /** | |
| * Gets the record name to use for a given tableId/subtableId. | |
| * | |
| * @param string $recordName eg 'Actions_ActionsUrl' | |
| * @param int $tableId eg '5' for tableId '5' | |
| * @return string eg 'Actions_ActionsUrl_chunk_0_99' as the table should be stored under this blob id. | |
| */ | |
| public function getRecordNameForTableId($recordName, $tableId) | |
| { | |
| $chunk = (floor($tableId / self::NUM_TABLES_IN_CHUNK)); | |
| $start = $chunk * self::NUM_TABLES_IN_CHUNK; | |
| $end = $start + self::NUM_TABLES_IN_CHUNK - 1; | |
| return $recordName . $this->getAppendix() . $start . '_' . $end; | |
| } | |
| /** | |
| * Moves the given blobs into chunks and assigns a proper record name containing the chunk number. | |
| * | |
| * @param string $recordName The original archive record name, eg 'Actions_ActionsUrl' | |
| * @param array $blobs An array containing a mapping of tableIds to blobs. Eg array(0 => 'blob', 1 => 'subtableBlob', ...) | |
| * @return array An array where each blob is moved into a chunk, indexed by recordNames. | |
| * eg array('Actions_ActionsUrl_chunk_0_99' => array(0 => 'blob', 1 => 'subtableBlob', ...), | |
| * 'Actions_ActionsUrl_chunk_100_199' => array(...)) | |
| */ | |
| public function moveArchiveBlobsIntoChunks($recordName, $blobs) | |
| { | |
| $chunks = array(); | |
| foreach ($blobs as $tableId => $blob) { | |
| $name = $this->getRecordNameForTableId($recordName, $tableId); | |
| if (!array_key_exists($name, $chunks)) { | |
| $chunks[$name] = array(); | |
| } | |
| $chunks[$name][$tableId] = $blob; | |
| } | |
| return $chunks; | |
| } | |
| /** | |
| * Detects whether a recordName like 'Actions_ActionUrls_chunk_0_99' or 'Actions_ActionUrls' belongs to a | |
| * chunk or not. | |
| * | |
| * To be a valid recordName that belongs to a chunk it must end with '_chunk_NUMERIC_NUMERIC'. | |
| * | |
| * @param string $recordName | |
| * @return bool | |
| */ | |
| public function isRecordNameAChunk($recordName) | |
| { | |
| $posAppendix = $this->getEndPosOfChunkAppendix($recordName); | |
| if (false === $posAppendix) { | |
| return false; | |
| } | |
| // will contain "0_99" of "chunk_0_99" | |
| $blobId = substr($recordName, $posAppendix); | |
| return $this->isChunkRange($blobId); | |
| } | |
| private function isChunkRange($blobId) | |
| { | |
| $blobId = explode('_', $blobId); | |
| return 2 === count($blobId) && is_numeric($blobId[0]) && is_numeric($blobId[1]); | |
| } | |
| /** | |
| * When having a record like 'Actions_ActionUrls_chunk_0_99" it will return the raw recordName 'Actions_ActionUrls'. | |
| * | |
| * @param string $recordName | |
| * @return string | |
| */ | |
| public function getRecordNameWithoutChunkAppendix($recordName) | |
| { | |
| if (!$this->isRecordNameAChunk($recordName)) { | |
| return $recordName; | |
| } | |
| $posAppendix = $this->getStartPosOfChunkAppendix($recordName); | |
| if (false === $posAppendix) { | |
| return $recordName; | |
| } | |
| return substr($recordName, 0, $posAppendix); | |
| } | |
| /** | |
| * Returns the string that is appended to the original record name. This appendix identifes a record name is a | |
| * chunk. | |
| * @return string | |
| */ | |
| public function getAppendix() | |
| { | |
| return '_' . self::ARCHIVE_APPENDIX_SUBTABLES . '_'; | |
| } | |
| private function getStartPosOfChunkAppendix($recordName) | |
| { | |
| return strpos($recordName, $this->getAppendix()); | |
| } | |
| private function getEndPosOfChunkAppendix($recordName) | |
| { | |
| $pos = strpos($recordName, $this->getAppendix()); | |
| if ($pos === false) { | |
| return false; | |
| } | |
| return $pos + strlen($this->getAppendix()); | |
| } | |
| } | |