ArchiveInvalidator
1 year ago
ArchiveInvalidator.php
1 month ago
ArchivePurger.php
1 month ago
ArchiveQuery.php
1 year ago
ArchiveQueryFactory.php
1 year ago
ArchiveState.php
1 year ago
Chunk.php
1 year ago
DataCollection.php
1 month ago
DataTableFactory.php
1 month ago
Parameters.php
2 years ago
Chunk.php
120 lines
| 1 | <?php |
| 2 | |
| 3 | /** |
| 4 | * Matomo - free/libre analytics platform |
| 5 | * |
| 6 | * @link https://matomo.org |
| 7 | * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later |
| 8 | */ |
| 9 | namespace Piwik\Archive; |
| 10 | |
| 11 | /** |
| 12 | * This class is used to split blobs of DataTables into chunks. Each blob used to be stored under one blob in the |
| 13 | * archive table. For better efficiency we do now combine multiple DataTable into one blob entry. |
| 14 | * |
| 15 | * Chunks are identified by having the recordName $recordName_chunk_0_99, $recordName_chunk_100_199 (this chunk stores |
| 16 | * the subtable 100-199). |
| 17 | */ |
| 18 | class Chunk |
| 19 | { |
| 20 | public const ARCHIVE_APPENDIX_SUBTABLES = 'chunk'; |
| 21 | public const NUM_TABLES_IN_CHUNK = 100; |
| 22 | /** |
| 23 | * Gets the record name to use for a given tableId/subtableId. |
| 24 | * |
| 25 | * @param string $recordName eg 'Actions_ActionsUrl' |
| 26 | * @param int $tableId eg '5' for tableId '5' |
| 27 | * @return string eg 'Actions_ActionsUrl_chunk_0_99' as the table should be stored under this blob id. |
| 28 | */ |
| 29 | public function getRecordNameForTableId($recordName, $tableId) |
| 30 | { |
| 31 | $chunk = floor($tableId / self::NUM_TABLES_IN_CHUNK); |
| 32 | $start = $chunk * self::NUM_TABLES_IN_CHUNK; |
| 33 | $end = $start + self::NUM_TABLES_IN_CHUNK - 1; |
| 34 | return $recordName . $this->getAppendix() . $start . '_' . $end; |
| 35 | } |
| 36 | /** |
| 37 | * Moves the given blobs into chunks and assigns a proper record name containing the chunk number. |
| 38 | * |
| 39 | * @param string $recordName The original archive record name, eg 'Actions_ActionsUrl' |
| 40 | * @param array $blobs An array containing a mapping of tableIds to blobs. Eg array(0 => 'blob', 1 => 'subtableBlob', ...) |
| 41 | * @return array An array where each blob is moved into a chunk, indexed by recordNames. |
| 42 | * eg array('Actions_ActionsUrl_chunk_0_99' => array(0 => 'blob', 1 => 'subtableBlob', ...), |
| 43 | * 'Actions_ActionsUrl_chunk_100_199' => array(...)) |
| 44 | */ |
| 45 | public function moveArchiveBlobsIntoChunks($recordName, $blobs) |
| 46 | { |
| 47 | $chunks = array(); |
| 48 | foreach ($blobs as $tableId => $blob) { |
| 49 | $name = $this->getRecordNameForTableId($recordName, $tableId); |
| 50 | if (!array_key_exists($name, $chunks)) { |
| 51 | $chunks[$name] = array(); |
| 52 | } |
| 53 | $chunks[$name][$tableId] = $blob; |
| 54 | } |
| 55 | return $chunks; |
| 56 | } |
| 57 | /** |
| 58 | * Detects whether a recordName like 'Actions_ActionUrls_chunk_0_99' or 'Actions_ActionUrls' belongs to a |
| 59 | * chunk or not. |
| 60 | * |
| 61 | * To be a valid recordName that belongs to a chunk it must end with '_chunk_NUMERIC_NUMERIC'. |
| 62 | * |
| 63 | * @param string $recordName |
| 64 | * @return bool |
| 65 | */ |
| 66 | public function isRecordNameAChunk($recordName) |
| 67 | { |
| 68 | $posAppendix = $this->getEndPosOfChunkAppendix($recordName); |
| 69 | if (\false === $posAppendix) { |
| 70 | return \false; |
| 71 | } |
| 72 | // will contain "0_99" of "chunk_0_99" |
| 73 | $blobId = substr($recordName, $posAppendix); |
| 74 | return $this->isChunkRange($blobId); |
| 75 | } |
| 76 | private function isChunkRange($blobId) |
| 77 | { |
| 78 | $blobId = explode('_', $blobId); |
| 79 | return 2 === count($blobId) && is_numeric($blobId[0]) && is_numeric($blobId[1]); |
| 80 | } |
| 81 | /** |
| 82 | * When having a record like 'Actions_ActionUrls_chunk_0_99" it will return the raw recordName 'Actions_ActionUrls'. |
| 83 | * |
| 84 | * @param string $recordName |
| 85 | * @return string |
| 86 | */ |
| 87 | public function getRecordNameWithoutChunkAppendix($recordName) |
| 88 | { |
| 89 | if (!$this->isRecordNameAChunk($recordName)) { |
| 90 | return $recordName; |
| 91 | } |
| 92 | $posAppendix = $this->getStartPosOfChunkAppendix($recordName); |
| 93 | if (\false === $posAppendix) { |
| 94 | return $recordName; |
| 95 | } |
| 96 | return substr($recordName, 0, $posAppendix); |
| 97 | } |
| 98 | /** |
| 99 | * Returns the string that is appended to the original record name. This appendix identifes a record name is a |
| 100 | * chunk. |
| 101 | * @return string |
| 102 | */ |
| 103 | public function getAppendix() |
| 104 | { |
| 105 | return '_' . self::ARCHIVE_APPENDIX_SUBTABLES . '_'; |
| 106 | } |
| 107 | private function getStartPosOfChunkAppendix($recordName) |
| 108 | { |
| 109 | return strpos($recordName, $this->getAppendix()); |
| 110 | } |
| 111 | private function getEndPosOfChunkAppendix($recordName) |
| 112 | { |
| 113 | $pos = strpos($recordName, $this->getAppendix()); |
| 114 | if ($pos === \false) { |
| 115 | return \false; |
| 116 | } |
| 117 | return $pos + strlen($this->getAppendix()); |
| 118 | } |
| 119 | } |
| 120 |