| <?php |
|
|
| |
| |
| |
| |
| |
| |
|
|
| namespace Piwik\DataAccess\LogQueryBuilder; |
|
|
| use Exception; |
| use Piwik\DataAccess\LogAggregator; |
| use Piwik\Plugin\LogTablesProvider; |
|
|
| class JoinTables extends \ArrayObject |
| { |
| |
| |
| |
| private $logTableProvider; |
|
|
| |
| |
| |
| |
| |
| |
| |
| private $implicitTableDependencies = [ |
| 'log_link_visit_action' => [ |
| |
| ], |
| 'log_action' => [ |
| 'log_link_visit_action', |
| 'log_conversion', |
| 'log_conversion_item', |
| 'log_visit', |
| ], |
| 'log_visit' => [ |
| 'log_link_visit_action', |
| 'log_action', |
| ], |
| 'log_conversion' => [ |
| 'log_link_visit_action', |
| 'log_action', |
| 'log_visit', |
| ], |
| 'log_conversion_item' => [ |
| 'log_link_visit_action', |
| 'log_action', |
| 'log_visit', |
| 'log_conversion', |
| ], |
| ]; |
|
|
| |
| |
| |
| |
| public function __construct(LogTablesProvider $logTablesProvider, $tables) |
| { |
| $this->logTableProvider = $logTablesProvider; |
|
|
| foreach ($tables as $table) { |
| $this->checkTableCanBeUsedForSegmentation($table); |
| } |
|
|
| $this->exchangeArray(array_values($tables)); |
| } |
|
|
| public function getTables() |
| { |
| return $this->getArrayCopy(); |
| } |
|
|
| public function addTableToJoin($tableName) |
| { |
| $this->checkTableCanBeUsedForSegmentation($tableName); |
| $this->append($tableName); |
| } |
|
|
| public function hasJoinedTable($tableName) |
| { |
| $tables = in_array($tableName, $this->getTables()); |
| if ($tables) { |
| return true; |
| } |
|
|
| foreach ($this as $table) { |
| if (is_array($table)) { |
| if (!isset($table['tableAlias']) && $table['table'] === $table) { |
| return true; |
| } elseif (isset($table['tableAlias']) && $table['tableAlias'] === $table) { |
| return true; |
| } |
| } |
| } |
|
|
| return false; |
| } |
|
|
| public function hasJoinedTableManually($tableToFind, $joinToFind) |
| { |
| foreach ($this as $table) { |
| if ( |
| is_array($table) |
| && !empty($table['table']) |
| && $table['table'] === $tableToFind |
| && (!isset($table['tableAlias']) || $table['tableAlias'] === $tableToFind) |
| && (!isset($table['join']) || strtolower($table['join']) === 'left join') |
| && isset($table['joinOn']) && $table['joinOn'] === $joinToFind |
| ) { |
| return true; |
| } |
| } |
|
|
| return false; |
| } |
|
|
| public function getLogTable($tableName) |
| { |
| return $this->logTableProvider->getLogTable($tableName); |
| } |
|
|
| public function findIndexOfManuallyAddedTable($tableNameToFind) |
| { |
| foreach ($this as $index => $table) { |
| if ( |
| is_array($table) |
| && !empty($table['table']) |
| && $table['table'] === $tableNameToFind |
| && (!isset($table['join']) || strtolower($table['join']) === 'left join') |
| && (!isset($table['tableAlias']) || $table['tableAlias'] === $tableNameToFind) |
| ) { |
| return $index; |
| } |
| } |
| } |
|
|
| public function hasAddedTableManually($tableToFind) |
| { |
| $table = $this->findIndexOfManuallyAddedTable($tableToFind); |
|
|
| return isset($table); |
| } |
|
|
| public function sort() |
| { |
| |
| $tables = $this->getTables(); |
|
|
| |
| $firstTable = array_shift($tables); |
| $sorted = [$firstTable]; |
|
|
| |
| if (is_array($firstTable) && !empty($firstTable['table'])) { |
| $firstTable = $firstTable['table']; |
| } |
|
|
| if (strpos($firstTable, LogAggregator::LOG_TABLE_SEGMENT_TEMPORARY_PREFIX) === 0) { |
| |
| $sorted[] = array_shift($tables); |
| } |
|
|
| $dependencies = $this->parseDependencies($tables); |
|
|
| $this->visitTableListDfs($tables, $dependencies, function ($tableInfo) use (&$sorted) { |
| $sorted[] = $tableInfo; |
| }); |
|
|
| $this->exchangeArray($sorted); |
| } |
|
|
| public function isTableJoinableOnVisit($tableToCheck) |
| { |
| $table = $this->getLogTable($tableToCheck); |
|
|
| if (empty($table)) { |
| return false; |
| } |
|
|
| if ($table->getColumnToJoinOnIdVisit()) { |
| return true; |
| } |
|
|
| if ($table->getLinkTableToBeAbleToJoinOnVisit()) { |
| return true; |
| } |
|
|
| $otherWays = $table->getWaysToJoinToOtherLogTables(); |
|
|
| if (empty($otherWays)) { |
| return false; |
| } |
|
|
| foreach ($otherWays as $logTable => $column) { |
| if ($logTable == 'log_visit' || $this->isTableJoinableOnVisit($logTable)) { |
| return true; |
| } |
| } |
|
|
| return false; |
| } |
|
|
| public function isTableJoinableOnAction($tableToCheck) |
| { |
| $table = $this->getLogTable($tableToCheck); |
|
|
| if (empty($table)) { |
| return false; |
| } |
|
|
| if ($table->getColumnToJoinOnIdAction()) { |
| return true; |
| } |
|
|
| $otherWays = $table->getWaysToJoinToOtherLogTables(); |
|
|
| if (empty($otherWays)) { |
| return false; |
| } |
|
|
| foreach ($otherWays as $logTable => $column) { |
| if ($logTable == 'log_action' || $this->isTableJoinableOnAction($logTable)) { |
| return true; |
| } |
| } |
|
|
| return false; |
| } |
|
|
| public function addTableDependency($table, $dependentTable) |
| { |
| if (!empty($this->implicitTableDependencies[$table])) { |
| return; |
| } |
|
|
| $this->implicitTableDependencies[$table] = [$dependentTable]; |
| } |
|
|
| private function checkTableCanBeUsedForSegmentation($tableName) |
| { |
| if (!is_array($tableName) && !$this->getLogTable($tableName)) { |
| throw new Exception("Table '$tableName' can't be used for segmentation"); |
| } |
| } |
|
|
| private function parseDependencies(array $tables) |
| { |
| $dependencies = []; |
| foreach ($tables as $key => &$fromInfo) { |
| if (is_string($fromInfo)) { |
| $dependencies[$key] = $this->assumeImplicitJoinDependencies($tables, $fromInfo); |
| continue; |
| } |
|
|
| if (empty($fromInfo['joinOn'])) { |
| continue; |
| } |
|
|
| $table = isset($fromInfo['tableAlias']) ? $fromInfo['tableAlias'] : $fromInfo['table']; |
| $tablesInExpr = $this->parseSqlTables($fromInfo['joinOn'], $table); |
| $dependencies[$key] = $tablesInExpr; |
| } |
| return $dependencies; |
| } |
|
|
| private function assumeImplicitJoinDependencies($allTablesToQuery, $table) |
| { |
| $implicitTableDependencies = $this->implicitTableDependencies; |
|
|
| $result = []; |
| if (isset($implicitTableDependencies[$table])) { |
| $result = $implicitTableDependencies[$table]; |
|
|
| |
| |
| |
| $result = array_filter($result, function ($table) use ($allTablesToQuery) { |
| return $this->isInTableArray($allTablesToQuery, $table); |
| }); |
| } |
| return $result; |
| } |
|
|
| private function isInTableArray($tables, $table) |
| { |
| foreach ($tables as $entry) { |
| if ( |
| is_string($entry) |
| && $entry == $table |
| ) { |
| return true; |
| } |
|
|
| if ( |
| is_array($entry) |
| && $entry['table'] == $table |
| ) { |
| return true; |
| } |
| } |
| return false; |
| } |
|
|
| private function parseSqlTables($joinOn, $self) |
| { |
| preg_match_all('/\b([a-zA-Z0-9_`]+)\.[a-zA-Z0-9_`]+\b/', $joinOn, $matches); |
|
|
| $tables = []; |
| foreach ($matches[1] as $table) { |
| if ($table === $self) { |
| continue; |
| } |
|
|
| $tables[] = $table; |
| } |
| return $tables; |
| } |
|
|
| private function visitTableListDfs($tables, $dependencies, $visitor) |
| { |
| $visited = []; |
| foreach ($tables as $index => $tableInfo) { |
| if (empty($visited[$index])) { |
| $this->visitTableListDfsSingle($tables, $dependencies, $visitor, $index, $visited); |
| } |
| } |
| } |
|
|
| private function visitTableListDfsSingle($tables, $dependencies, $visitor, $tableToVisitIndex, &$visited) |
| { |
| $visited[$tableToVisitIndex] = true; |
| $tableToVisit = $tables[$tableToVisitIndex]; |
|
|
| if (!empty($dependencies[$tableToVisitIndex])) { |
| foreach ($dependencies[$tableToVisitIndex] as $dependencyTableName) { |
| $dependentTableToVisit = $this->findTableIndex($tables, $dependencyTableName); |
| if ($dependentTableToVisit === null) { |
| continue; |
| } |
|
|
| if (!empty($visited[$dependentTableToVisit])) { |
| continue; |
| } |
|
|
| |
| $this->visitTableListDfsSingle($tables, $dependencies, $visitor, $dependentTableToVisit, $visited); |
| } |
| } |
|
|
| |
| $visitor($tableToVisit); |
| } |
|
|
| private function findTableIndex($tables, $tableToSearchFor) |
| { |
| foreach ($tables as $key => $info) { |
| $tableName = null; |
| if (is_string($info)) { |
| $tableName = $info; |
| } elseif (is_array($info)) { |
| $tableName = isset($info['tableAlias']) ? $info['tableAlias'] : $info['table']; |
| } |
|
|
| if ($tableName == $tableToSearchFor) { |
| return $key; |
| } |
| } |
| return null; |
| } |
| } |
|
|