Download core/ArchiveProcessor/RecordBuilder.php from Leon4gr45/matomo: direct link, hf CLI and curl.
- Browser
- Download file 30.4 kB
-
https://huggingface.co/spaces/Leon4gr45/matomo/resolve/main/core/ArchiveProcessor/RecordBuilder.php
- Command line
-
hf download hf://spaces/Leon4gr45/matomo/core/ArchiveProcessor/RecordBuilder.php
-
curl -L -o RecordBuilder.php https://huggingface.co/spaces/Leon4gr45/matomo/resolve/main/core/ArchiveProcessor/RecordBuilder.php
30.4 kB
| /** | |
| * 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\ArchiveProcessor; | |
| use Piwik\Archive; | |
| use Piwik\ArchiveProcessor; | |
| use Piwik\Common; | |
| use Piwik\DataTable; | |
| use Piwik\DataTable\Row; | |
| use Piwik\Piwik; | |
| /** | |
| * Inherit from this class to define archiving logic for one or more records. | |
| */ | |
| abstract class RecordBuilder | |
| { | |
| /** | |
| * @var int|null | |
| */ | |
| protected $maxRowsInTable; | |
| /** | |
| * @var int|null | |
| */ | |
| protected $maxRowsInSubtable; | |
| /** | |
| * @var string|int|null | |
| */ | |
| protected $columnToSortByBeforeTruncation; | |
| /** | |
| * @var array|null | |
| */ | |
| protected $columnAggregationOps; | |
| /** | |
| * @var array<string|int,string|int>|null | |
| */ | |
| protected $columnToRenameAfterAggregation; | |
| /** | |
| * @param array|null $columnAggregationOps | |
| * @param array<string|int,string|int>|null $columnToRenameAfterAggregation | |
| */ | |
| public function __construct( | |
| ?int $maxRowsInTable = null, | |
| ?int $maxRowsInSubtable = null, | |
| ?string $columnToSortByBeforeTruncation = null, | |
| ?array $columnAggregationOps = null, | |
| ?array $columnToRenameAfterAggregation = null | |
| ) { | |
| $this->maxRowsInTable = $maxRowsInTable; | |
| $this->maxRowsInSubtable = $maxRowsInSubtable; | |
| $this->columnToSortByBeforeTruncation = $columnToSortByBeforeTruncation; | |
| $this->columnAggregationOps = $columnAggregationOps; | |
| $this->columnToRenameAfterAggregation = $columnToRenameAfterAggregation; | |
| } | |
| public function isEnabled(ArchiveProcessor $archiveProcessor): bool | |
| { | |
| return true; | |
| } | |
| /** | |
| * Uses the protected `aggregate()` function to build records by aggregating log table data directly, then | |
| * inserts them as archive data. | |
| */ | |
| public function buildFromLogs(ArchiveProcessor $archiveProcessor): void | |
| { | |
| if (!$this->isEnabled($archiveProcessor)) { | |
| return; | |
| } | |
| $recordsBuilt = $this->getRecordMetadata($archiveProcessor); | |
| $recordMetadataByName = []; | |
| foreach ($recordsBuilt as $recordMetadata) { | |
| if (!($recordMetadata instanceof Record)) { | |
| continue; | |
| } | |
| $recordMetadataByName[$recordMetadata->getName()] = $recordMetadata; | |
| } | |
| $numericRecords = []; | |
| $records = $this->aggregate($archiveProcessor); | |
| foreach ($records as $recordName => $recordValue) { | |
| if (empty($recordMetadataByName[$recordName])) { | |
| if ($recordValue instanceof DataTable) { | |
| Common::destroy($recordValue); | |
| } | |
| continue; | |
| } | |
| if ($recordValue instanceof DataTable) { | |
| $record = $recordMetadataByName[$recordName]; | |
| $maxRowsInTable = $record->getMaxRowsInTable() ?? $this->maxRowsInTable; | |
| $maxRowsInSubtable = $record->getMaxRowsInSubtable() ?? $this->maxRowsInSubtable; | |
| $columnToSortByBeforeTruncation = $record->getColumnToSortByBeforeTruncation() ?? $this->columnToSortByBeforeTruncation; | |
| $this->insertBlobRecord($archiveProcessor, $recordName, $recordValue, $maxRowsInTable, $maxRowsInSubtable, $columnToSortByBeforeTruncation); | |
| Common::destroy($recordValue); | |
| } else { | |
| // collect numeric records so we can insert them all at once | |
| $numericRecords[$recordName] = $recordValue; | |
| } | |
| } | |
| unset($records); | |
| if (!empty($numericRecords)) { | |
| $archiveProcessor->insertNumericRecords($numericRecords); | |
| } | |
| } | |
| /** | |
| * Builds records for non-day periods by aggregating day records together, then inserts | |
| * them as archive data. | |
| */ | |
| public function buildForNonDayPeriod(ArchiveProcessor $archiveProcessor): void | |
| { | |
| if (!$this->isEnabled($archiveProcessor)) { | |
| return; | |
| } | |
| $requestedReports = $archiveProcessor->getParams()->getArchiveOnlyReportAsArray(); | |
| $foundRequestedReports = $archiveProcessor->getParams()->getFoundRequestedReports(); | |
| $recordsBuilt = $this->getRecordMetadata($archiveProcessor); | |
| $numericRecords = array_filter($recordsBuilt, function (Record $r) { | |
| return $r->getType() == Record::TYPE_NUMERIC; | |
| }); | |
| $blobRecords = array_filter($recordsBuilt, function (Record $r) { | |
| return $r->getType() == Record::TYPE_BLOB; | |
| }); | |
| $blobRecordsByName = []; | |
| foreach ($blobRecords as $blobRecord) { | |
| $blobRecordsByName[$blobRecord->getName()] = $blobRecord; | |
| } | |
| $aggregatedCounts = []; | |
| foreach ($blobRecords as $record) { | |
| $flatRecordName = $record->getBuiltFromFlatRecord(); | |
| if ( | |
| empty($flatRecordName) | |
| || !in_array($flatRecordName, $requestedReports) | |
| ) { | |
| continue; | |
| } | |
| // If the flat record is requested directly, also force aggregation of the corresponding | |
| // hierarchical record so the API can still read the expected hierarchical blob. | |
| if (!in_array($record->getName(), $requestedReports)) { | |
| $requestedReports[] = $record->getName(); | |
| } | |
| // We are about to rebuild this record from flat data, so treat it as not-found and | |
| // make sure it is re-aggregated even if a previous archive row exists. | |
| $indexInFoundRecords = array_search($record->getName(), $foundRequestedReports); | |
| if ($indexInFoundRecords !== false) { | |
| unset($foundRequestedReports[$indexInFoundRecords]); | |
| } | |
| } | |
| // make sure if there are requested numeric records that depend on blob records, that the blob records will be archived first | |
| foreach ($numericRecords as $record) { | |
| if ( | |
| empty($record->getCountOfRecordName()) | |
| || !in_array($record->getName(), $requestedReports) | |
| ) { | |
| continue; | |
| } | |
| $dependentRecordName = $record->getCountOfRecordName(); | |
| if (!in_array($dependentRecordName, $requestedReports)) { | |
| $requestedReports[] = $dependentRecordName; | |
| } | |
| // we need to aggregate the blob record to get the count, so even if it's found, we must re-aggregate it | |
| // TODO: this could potentially be optimized away, but it would be non-trivial given the current ArchiveProcessor API | |
| $indexInFoundRecords = array_search($dependentRecordName, $foundRequestedReports); | |
| if ($indexInFoundRecords !== false) { | |
| unset($foundRequestedReports[$indexInFoundRecords]); | |
| } | |
| } | |
| $processedFlatRecords = []; | |
| foreach ($blobRecords as $record) { | |
| if ( | |
| !empty($requestedReports) | |
| && (!in_array($record->getName(), $requestedReports) | |
| || in_array($record->getName(), $foundRequestedReports)) | |
| ) { | |
| continue; | |
| } | |
| if (isset($processedFlatRecords[$record->getName()])) { | |
| continue; | |
| } | |
| $maxRowsInTable = $record->getMaxRowsInTable() ?? $this->maxRowsInTable; | |
| $maxRowsInSubtable = $record->getMaxRowsInSubtable() ?? $this->maxRowsInSubtable; | |
| $columnToSortByBeforeTruncation = $record->getColumnToSortByBeforeTruncation() ?? $this->columnToSortByBeforeTruncation; | |
| $columnToRenameAfterAggregation = $record->getColumnToRenameAfterAggregation() ?? $this->columnToRenameAfterAggregation; | |
| $columnAggregationOps = $record->getBlobColumnAggregationOps() ?? $this->columnAggregationOps; | |
| if ( | |
| $this->aggregateBuiltFromFlatRecordForNonDay( | |
| $archiveProcessor, | |
| $record, | |
| $blobRecordsByName, | |
| $columnAggregationOps, | |
| $columnToRenameAfterAggregation, | |
| $columnToSortByBeforeTruncation, | |
| $processedFlatRecords | |
| ) | |
| ) { | |
| continue; | |
| } | |
| // only do recursive row counts if there is a numeric record that depends on it | |
| $countRecursiveRows = $countLeafRows = []; | |
| foreach ($numericRecords as $numeric) { | |
| if ( | |
| $numeric->getCountOfRecordName() == $record->getName() | |
| ) { | |
| if ($numeric->getCountOfRecordNameIsRecursive()) { | |
| $countRecursiveRows[] = $numeric->getCountOfRecordName(); | |
| } | |
| if ($numeric->getCountOfRecordNameIsForLeafs()) { | |
| $countLeafRows[] = $numeric->getCountOfRecordName(); | |
| } | |
| } | |
| } | |
| $recordTransform = $record->getAggregatedRecordTransform(); | |
| $postAggregationTransform = $recordTransform === null ? null | |
| : function (DataTable $table) use ($recordTransform, $archiveProcessor, $record): void { | |
| $recordTransform($table, $archiveProcessor, $record); | |
| }; | |
| $counts = $archiveProcessor->aggregateDataTableRecords( | |
| $record->getName(), | |
| $maxRowsInTable, | |
| $maxRowsInSubtable, | |
| $columnToSortByBeforeTruncation, | |
| $columnAggregationOps, | |
| $columnToRenameAfterAggregation, | |
| $countRecursiveRows, | |
| $countLeafRows, | |
| $postAggregationTransform | |
| ); | |
| $aggregatedCounts = array_merge($aggregatedCounts, $counts); | |
| } | |
| if (!empty($numericRecords)) { | |
| // handle metrics that are aggregated using metric values from child periods | |
| $autoAggregateMetrics = array_filter($numericRecords, function (Record $r) { | |
| return empty($r->getCountOfRecordName()); | |
| }); | |
| $autoAggregateMetrics = array_map(function (Record $r) { | |
| return $r->getName(); | |
| }, $autoAggregateMetrics); | |
| if (!empty($requestedReports)) { | |
| $autoAggregateMetrics = array_filter($autoAggregateMetrics, function ($name) use ($requestedReports, $foundRequestedReports) { | |
| return in_array($name, $requestedReports) && !in_array($name, $foundRequestedReports); | |
| }); | |
| } | |
| $autoAggregateMetrics = array_values($autoAggregateMetrics); | |
| if (!empty($autoAggregateMetrics)) { | |
| $archiveProcessor->aggregateNumericMetrics($autoAggregateMetrics, $this->columnAggregationOps); | |
| } | |
| // handle metrics that are set to counts of blob records | |
| $recordCountMetricValues = []; | |
| $recordCountMetrics = array_filter($numericRecords, function (Record $r) { | |
| return !empty($r->getCountOfRecordName()); | |
| }); | |
| foreach ($recordCountMetrics as $record) { | |
| $dependentRecordName = $record->getCountOfRecordName(); | |
| if (empty($aggregatedCounts[$dependentRecordName])) { | |
| continue; // dependent record not archived, so skip this metric | |
| } | |
| $count = $aggregatedCounts[$dependentRecordName]; | |
| if ($record->getCountOfRecordNameIsForLeafs()) { | |
| $recordCountMetricValues[$record->getName()] = $count['leafs']; | |
| } elseif ($record->getCountOfRecordNameIsRecursive()) { | |
| $recordCountMetricValues[$record->getName()] = $count['recursive']; | |
| } else { | |
| $recordCountMetricValues[$record->getName()] = $count['level0']; | |
| } | |
| $transform = $record->getMultiplePeriodTransform(); | |
| if (!empty($transform)) { | |
| $recordCountMetricValues[$record->getName()] = $transform($recordCountMetricValues[$record->getName()], $count); | |
| } | |
| } | |
| if (!empty($recordCountMetricValues)) { | |
| $archiveProcessor->insertNumericRecords($recordCountMetricValues); | |
| } | |
| } | |
| } | |
| protected function aggregateBuiltFromFlatRecordForNonDay( | |
| ArchiveProcessor $archiveProcessor, | |
| Record $hierarchicalRecord, | |
| array $blobRecordsByName, | |
| ?array $columnAggregationOps, | |
| ?array $columnToRenameAfterAggregation, | |
| ?string $columnToSortByBeforeTruncation, | |
| array &$processedFlatRecords | |
| ): bool { | |
| $flatRecordName = $hierarchicalRecord->getBuiltFromFlatRecord(); | |
| if (empty($flatRecordName)) { | |
| return false; | |
| } | |
| $flatToHierarchyPathCallback = $hierarchicalRecord->getFlatToHierarchyPathCallback(); | |
| if (!is_callable($flatToHierarchyPathCallback)) { | |
| return false; | |
| } | |
| $flatRecord = $blobRecordsByName[$flatRecordName] ?? null; | |
| if (empty($flatRecord)) { | |
| return false; | |
| } | |
| $flatColumnAggregationOps = $flatRecord->getBlobColumnAggregationOps() ?? $this->columnAggregationOps; | |
| $flatColumnToRenameAfterAggregation = $flatRecord->getColumnToRenameAfterAggregation() ?? $this->columnToRenameAfterAggregation; | |
| $flatColumnToSortByBeforeTruncation = $flatRecord->getColumnToSortByBeforeTruncation() ?? $this->columnToSortByBeforeTruncation; | |
| $flatMaxRowsInTable = $flatRecord->getMaxRowsInTable() ?? $this->maxRowsInTable; | |
| [$flatTable, $hasFlatSourceData, $periodsWithFlatRecord] = $this->aggregateRootDataTableFromBlobs( | |
| $archiveProcessor, | |
| $flatRecordName, | |
| $flatColumnAggregationOps, | |
| $flatColumnToRenameAfterAggregation | |
| ); | |
| $allSubperiodKeys = $this->getAllSubperiodKeys($archiveProcessor); | |
| $periodsWithoutFlatRecord = array_diff_key($allSubperiodKeys, $periodsWithFlatRecord); | |
| $hasLegacyFallbackData = false; | |
| $legacyReducerCallback = $hierarchicalRecord->getLegacyHierarchyToFlatReducerCallback(); | |
| if (!empty($periodsWithoutFlatRecord) && is_callable($legacyReducerCallback)) { | |
| $hasLegacyFallbackData = $this->aggregateLegacyHierarchyPeriodsIntoFlatTable( | |
| $archiveProcessor, | |
| $hierarchicalRecord->getName(), | |
| $flatTable, | |
| $legacyReducerCallback, | |
| $hierarchicalRecord, | |
| $columnAggregationOps, | |
| $columnToRenameAfterAggregation, | |
| $periodsWithoutFlatRecord | |
| ); | |
| } | |
| if (!$hasFlatSourceData && !$hasLegacyFallbackData) { | |
| Common::destroy($flatTable); | |
| return false; | |
| } | |
| $flatTransform = $flatRecord->getAggregatedRecordTransform(); | |
| if (null !== $flatTransform) { | |
| $flatTransform($flatTable, $archiveProcessor, $flatRecord); | |
| } | |
| $flatSerialized = $flatTable->getSerialized( | |
| $flatMaxRowsInTable, | |
| null, | |
| $flatColumnToSortByBeforeTruncation | |
| ); | |
| $archiveProcessor->insertBlobRecord($flatRecordName, $flatSerialized); | |
| unset($flatSerialized); | |
| $processedFlatRecords[$flatRecordName] = true; | |
| $hierarchicalTable = $this->buildHierarchicalTableFromFlatTableAndConsumeRows( | |
| $flatTable, | |
| $columnAggregationOps, | |
| function (Row $flatRow) use ($flatToHierarchyPathCallback, $archiveProcessor, $hierarchicalRecord) { | |
| return call_user_func($flatToHierarchyPathCallback, $flatRow, $archiveProcessor, $hierarchicalRecord); | |
| } | |
| ); | |
| $this->beforeInsertBuiltFromFlatHierarchyRecord($archiveProcessor, $hierarchicalRecord, $hierarchicalTable, $flatTable); | |
| $hierarchicalTransform = $hierarchicalRecord->getAggregatedRecordTransform(); | |
| if (null !== $hierarchicalTransform) { | |
| $hierarchicalTransform($hierarchicalTable, $archiveProcessor, $hierarchicalRecord); | |
| } | |
| $hierarchicalSerialized = $hierarchicalTable->getSerialized( | |
| null, | |
| null, | |
| $columnToSortByBeforeTruncation | |
| ); | |
| $archiveProcessor->insertBlobRecord($hierarchicalRecord->getName(), $hierarchicalSerialized); | |
| unset($hierarchicalSerialized); | |
| Common::destroy($hierarchicalTable); | |
| Common::destroy($flatTable); | |
| return true; | |
| } | |
| protected function aggregateLegacyHierarchyPeriodsIntoFlatTable( | |
| ArchiveProcessor $archiveProcessor, | |
| string $recordName, | |
| DataTable $flatTable, | |
| callable $legacyReducerCallback, | |
| Record $hierarchicalRecord, | |
| ?array $columnsAggregationOperation, | |
| ?array $columnsToRenameAfterAggregation, | |
| ?array $periodsToInclude | |
| ): bool { | |
| $currentPeriod = null; | |
| $currentPeriodRows = []; | |
| $hasRows = false; | |
| foreach ($this->querySingleBlobRows($archiveProcessor, $recordName) as $archiveDataRow) { | |
| $period = $archiveDataRow['date1'] . ',' . $archiveDataRow['date2']; | |
| if ($periodsToInclude !== null && !isset($periodsToInclude[$period])) { | |
| continue; | |
| } | |
| if ($currentPeriod !== null && $period !== $currentPeriod) { | |
| $hasRows = $this->reduceLegacyHierarchyPeriodRowsIntoFlatTable( | |
| $currentPeriodRows, | |
| $recordName, | |
| $flatTable, | |
| $legacyReducerCallback, | |
| $archiveProcessor, | |
| $hierarchicalRecord, | |
| $columnsAggregationOperation, | |
| $columnsToRenameAfterAggregation | |
| ) || $hasRows; | |
| $currentPeriodRows = []; | |
| } | |
| $currentPeriod = $period; | |
| $currentPeriodRows[] = $archiveDataRow; | |
| } | |
| if (!empty($currentPeriodRows)) { | |
| $hasRows = $this->reduceLegacyHierarchyPeriodRowsIntoFlatTable( | |
| $currentPeriodRows, | |
| $recordName, | |
| $flatTable, | |
| $legacyReducerCallback, | |
| $archiveProcessor, | |
| $hierarchicalRecord, | |
| $columnsAggregationOperation, | |
| $columnsToRenameAfterAggregation | |
| ) || $hasRows; | |
| } | |
| return $hasRows; | |
| } | |
| protected function reduceLegacyHierarchyPeriodRowsIntoFlatTable( | |
| array $periodRows, | |
| string $recordName, | |
| DataTable $flatTable, | |
| callable $legacyReducerCallback, | |
| ArchiveProcessor $archiveProcessor, | |
| Record $hierarchicalRecord, | |
| ?array $columnsAggregationOperation, | |
| ?array $columnsToRenameAfterAggregation | |
| ): bool { | |
| [$legacyHierarchicalTable, $hasRows] = BlobTableAggregator::aggregateBlobRows( | |
| $periodRows, | |
| $recordName, | |
| $columnsAggregationOperation, | |
| function (DataTable $table) use ($archiveProcessor, $columnsToRenameAfterAggregation): void { | |
| $archiveProcessor->renameColumnsAfterAggregation($table, $columnsToRenameAfterAggregation); | |
| } | |
| ); | |
| if ($hasRows) { | |
| call_user_func($legacyReducerCallback, $legacyHierarchicalTable, $flatTable, $archiveProcessor, $hierarchicalRecord); | |
| } | |
| Common::destroy($legacyHierarchicalTable); | |
| return $hasRows; | |
| } | |
| /** | |
| * Hook executed after the hierarchy table has been rebuilt from the flat table and before | |
| * the hierarchical blob record is serialized and inserted. | |
| * | |
| * Intended for plugin-specific finalization (for example, metadata or column cleanup) when | |
| * using setBuiltFromFlatRecord(). The flat table has already been serialized at this point | |
| * and may have been fully consumed while rebuilding the hierarchy. | |
| */ | |
| protected function beforeInsertBuiltFromFlatHierarchyRecord( | |
| ArchiveProcessor $archiveProcessor, | |
| Record $hierarchicalRecord, | |
| DataTable $hierarchicalTable, | |
| DataTable $flatTable | |
| ): void { | |
| } | |
| protected function buildHierarchicalTableFromFlatTable( | |
| DataTable $flatTable, | |
| ?array $columnAggregationOps, | |
| callable $flatToHierarchyPathCallback, | |
| array $defaultHierarchyRowColumns = [] | |
| ): DataTable { | |
| $hierarchicalTable = new DataTable(); | |
| if (!empty($columnAggregationOps)) { | |
| $hierarchicalTable->setMetadata(DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME, $columnAggregationOps); | |
| } | |
| foreach ($flatTable->getRows() as $flatRow) { | |
| if ($flatRow->isSummaryRow()) { | |
| if ($this->isSummaryRowEmpty($flatRow)) { | |
| continue; | |
| } | |
| $summaryRow = $hierarchicalTable->getRowFromId(DataTable::ID_SUMMARY_ROW); | |
| if ($summaryRow === false) { | |
| $summaryRow = clone $flatRow; | |
| $summaryRow->setIsSummaryRow(); | |
| $hierarchicalTable->addSummaryRow($summaryRow); | |
| continue; | |
| } | |
| $this->sumRowIntoDestination($flatRow, $summaryRow, $columnAggregationOps); | |
| continue; | |
| } | |
| $path = call_user_func($flatToHierarchyPathCallback, $flatRow); | |
| if (!is_array($path) || empty($path)) { | |
| continue; | |
| } | |
| [$destinationRow, $level] = $hierarchicalTable->walkPath($path, $defaultHierarchyRowColumns, 0); | |
| if (!$destinationRow instanceof Row) { | |
| continue; | |
| } | |
| $this->sumRowIntoDestination($flatRow, $destinationRow, $columnAggregationOps); | |
| } | |
| return $hierarchicalTable; | |
| } | |
| protected function buildHierarchicalTableFromFlatTableAndConsumeRows( | |
| DataTable $flatTable, | |
| ?array $columnAggregationOps, | |
| callable $flatToHierarchyPathCallback, | |
| array $defaultHierarchyRowColumns = [] | |
| ): DataTable { | |
| $hierarchicalTable = new DataTable(); | |
| if (!empty($columnAggregationOps)) { | |
| $hierarchicalTable->setMetadata(DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME, $columnAggregationOps); | |
| } | |
| while (($flatRow = $flatTable->shiftRow()) instanceof Row) { | |
| $path = call_user_func($flatToHierarchyPathCallback, $flatRow); | |
| if (is_array($path) && !empty($path)) { | |
| [$destinationRow, $level] = $hierarchicalTable->walkPath($path, $defaultHierarchyRowColumns, 0); | |
| if ($destinationRow instanceof Row) { | |
| $this->sumRowIntoDestination($flatRow, $destinationRow, $columnAggregationOps); | |
| } | |
| } | |
| Common::destroy($flatRow); | |
| } | |
| $summaryRow = $flatTable->getSummaryRow(); | |
| if ($summaryRow instanceof Row && !$this->isSummaryRowEmpty($summaryRow)) { | |
| $destinationSummaryRow = $hierarchicalTable->getRowFromId(DataTable::ID_SUMMARY_ROW); | |
| if ($destinationSummaryRow === false) { | |
| $destinationSummaryRow = clone $summaryRow; | |
| $destinationSummaryRow->setIsSummaryRow(); | |
| $hierarchicalTable->addSummaryRow($destinationSummaryRow); | |
| } else { | |
| $this->sumRowIntoDestination($summaryRow, $destinationSummaryRow, $columnAggregationOps); | |
| } | |
| } | |
| $flatTable->deleteRow(DataTable::ID_SUMMARY_ROW); | |
| Common::destroy($summaryRow); | |
| return $hierarchicalTable; | |
| } | |
| protected function sumRowIntoDestination(Row $source, Row $destination, ?array $columnAggregationOps): void | |
| { | |
| $sourceCopy = clone $source; | |
| // Preserve original column representation (eg "0.0620" strings) when | |
| // destination does not have a value yet. This keeps day flat-first | |
| // output consistent with legacy day archiving. | |
| foreach ($sourceCopy->getColumns() as $columnName => $columnValue) { | |
| if ($columnName === 'label') { | |
| continue; | |
| } | |
| if ($destination->getColumn($columnName) !== false) { | |
| continue; | |
| } | |
| $destination->setColumn($columnName, $columnValue); | |
| $sourceCopy->deleteColumn($columnName); | |
| } | |
| $destination->sumRow($sourceCopy, true, $columnAggregationOps ?? []); | |
| } | |
| protected function isSummaryRowEmpty(Row $summaryRow): bool | |
| { | |
| foreach ($summaryRow->getColumns() as $name => $value) { | |
| if ($name === 'label') { | |
| continue; | |
| } | |
| if (!empty($value)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| /** | |
| * Aggregates a root blob record while discovering periods that contain the root record in a single pass. | |
| * | |
| * @return array{0: DataTable, 1: bool, 2: array<string, bool>} | |
| */ | |
| protected function aggregateRootDataTableFromBlobs( | |
| ArchiveProcessor $archiveProcessor, | |
| string $recordName, | |
| ?array $columnsAggregationOperation, | |
| ?array $columnsToRenameAfterAggregation | |
| ): array { | |
| $periodsWithRootRecord = []; | |
| [$result, $hasRows] = BlobTableAggregator::aggregateBlobRows( | |
| $this->querySingleBlobRows($archiveProcessor, $recordName), | |
| $recordName, | |
| $columnsAggregationOperation, | |
| function (DataTable $table) use ($archiveProcessor, $columnsToRenameAfterAggregation): void { | |
| $archiveProcessor->renameColumnsAfterAggregation($table, $columnsToRenameAfterAggregation); | |
| }, | |
| function (array $archiveDataRow) use (&$periodsWithRootRecord, $recordName): bool { | |
| $period = $archiveDataRow['date1'] . ',' . $archiveDataRow['date2']; | |
| if ($archiveDataRow['name'] === $recordName) { | |
| $periodsWithRootRecord[$period] = true; | |
| return true; | |
| } | |
| return isset($periodsWithRootRecord[$period]); | |
| } | |
| ); | |
| return [$result, $hasRows, $periodsWithRootRecord]; | |
| } | |
| protected function querySingleBlobRows(ArchiveProcessor $archiveProcessor, string $recordName): iterable | |
| { | |
| $archive = Archive::factory( | |
| $archiveProcessor->getParams()->getSegment(), | |
| $archiveProcessor->getParams()->getPeriod()->getSubperiods(), | |
| [$archiveProcessor->getParams()->getSite()->getId()] | |
| ); | |
| if (!method_exists($archive, 'querySingleBlob')) { | |
| return []; | |
| } | |
| return $archive->querySingleBlob($recordName); | |
| } | |
| protected function getAllSubperiodKeys(ArchiveProcessor $archiveProcessor): array | |
| { | |
| $result = []; | |
| foreach ($archiveProcessor->getParams()->getPeriod()->getSubperiods() as $period) { | |
| $result[$period->getDateStart()->toString() . ',' . $period->getDateEnd()->toString()] = true; | |
| } | |
| return $result; | |
| } | |
| /** | |
| * Returns metadata for records primarily used when aggregating over non-day periods. Every numeric/blob | |
| * record your RecordBuilder creates should have an associated piece of record metadata. | |
| * | |
| * @return Record[] | |
| */ | |
| abstract public function getRecordMetadata(ArchiveProcessor $archiveProcessor): array; | |
| /** | |
| * Derived classes should define this method to aggregate log data for a single day and return the records | |
| * to store indexed by record names. | |
| * | |
| * @return array<string, DataTable|int|float|string> Record values indexed by their record name, eg, `['MyPlugin_MyRecord' => new DataTable()]` | |
| */ | |
| abstract protected function aggregate(ArchiveProcessor $archiveProcessor): array; | |
| protected function insertBlobRecord( | |
| ArchiveProcessor $archiveProcessor, | |
| string $recordName, | |
| DataTable $record, | |
| ?int $maxRowsInTable, | |
| ?int $maxRowsInSubtable, | |
| ?string $columnToSortByBeforeTruncation | |
| ): void { | |
| $serialized = $record->getSerialized( | |
| $maxRowsInTable ?? $this->maxRowsInTable, | |
| $maxRowsInSubtable ?? $this->maxRowsInSubtable, | |
| $columnToSortByBeforeTruncation ?? $this->columnToSortByBeforeTruncation | |
| ); | |
| $archiveProcessor->insertBlobRecord($recordName, $serialized); | |
| unset($serialized); | |
| } | |
| public function getMaxRowsInTable(): ?int | |
| { | |
| return $this->maxRowsInTable; | |
| } | |
| public function getMaxRowsInSubtable(): ?int | |
| { | |
| return $this->maxRowsInSubtable; | |
| } | |
| public function getColumnToSortByBeforeTruncation(): ?string | |
| { | |
| return $this->columnToSortByBeforeTruncation; | |
| } | |
| public function getPluginName(): string | |
| { | |
| return Piwik::getPluginNameOfMatomoClass(get_class($this)); | |
| } | |
| /** | |
| * Returns an extra hint for LogAggregator to add to log aggregation SQL. Can be overridden if you'd | |
| * like the origin hint to have more information. | |
| */ | |
| public function getQueryOriginHint(): string | |
| { | |
| $recordBuilderName = get_class($this); | |
| $recordBuilderName = explode('\\', $recordBuilderName); | |
| return end($recordBuilderName); | |
| } | |
| /** | |
| * Returns true if at least one of the given reports is handled by this RecordBuilder instance | |
| * when invoked with the given ArchiveProcessor. | |
| * | |
| * @param ArchiveProcessor $archiveProcessor Archiving parameters, like idSite, can influence the list of | |
| * all records a RecordBuilder produces, so it is required here. | |
| * @param string[] $requestedReports The list of requested reports to check for. | |
| */ | |
| public function isBuilderForAtLeastOneOf(ArchiveProcessor $archiveProcessor, array $requestedReports): bool | |
| { | |
| $recordMetadata = $this->getRecordMetadata($archiveProcessor); | |
| foreach ($recordMetadata as $record) { | |
| if (in_array($record->getName(), $requestedReports)) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| } | |