PDF rausgenommen
This commit is contained in:
@ -0,0 +1,316 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\DataAccess\LogQueryBuilder;
|
||||
|
||||
use Exception;
|
||||
use Piwik\Common;
|
||||
use Piwik\Tracker\LogTable;
|
||||
|
||||
class JoinGenerator
|
||||
{
|
||||
/**
|
||||
* @var JoinTables
|
||||
*/
|
||||
protected $tables;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $joinWithSubSelect = false;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $joinString = '';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $nonVisitJoins = array();
|
||||
|
||||
public function __construct(JoinTables $tables)
|
||||
{
|
||||
$this->tables = $tables;
|
||||
$this->addMissingTablesNeededForJoins();
|
||||
}
|
||||
|
||||
private function addMissingTablesNeededForJoins()
|
||||
{
|
||||
foreach ($this->tables as $index => $table) {
|
||||
if (is_array($table)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$logTable = $this->tables->getLogTable($table);
|
||||
|
||||
if (!$logTable->getColumnToJoinOnIdVisit()) {
|
||||
$tableNameToJoin = $logTable->getLinkTableToBeAbleToJoinOnVisit();
|
||||
|
||||
if (empty($tableNameToJoin) && $logTable->getWaysToJoinToOtherLogTables()) {
|
||||
foreach ($logTable->getWaysToJoinToOtherLogTables() as $otherLogTable => $column) {
|
||||
if ($this->tables->hasJoinedTable($otherLogTable)) {
|
||||
$this->tables->addTableDependency($table, $otherLogTable);
|
||||
continue;
|
||||
}
|
||||
if ($this->tables->isTableJoinableOnVisit($otherLogTable) || $this->tables->isTableJoinableOnAction($otherLogTable)) {
|
||||
$this->addMissingTablesForOtherTableJoin($otherLogTable, $table);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($index > 0 && !$this->tables->hasJoinedTable($tableNameToJoin)) {
|
||||
$this->tables->addTableToJoin($tableNameToJoin);
|
||||
}
|
||||
|
||||
if ($this->tables->hasJoinedTable($tableNameToJoin)) {
|
||||
$this->generateNonVisitJoins($table, $tableNameToJoin, $index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->tables as $index => $table) {
|
||||
if (is_array($table)) {
|
||||
if (!isset($table['tableAlias'])) {
|
||||
$tableName = $table['table'];
|
||||
$numTables = count($this->tables);
|
||||
for ($j = $index + 1; $j < $numTables; $j++) {
|
||||
if (!isset($this->tables[$j])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tableOther = $this->tables[$j];
|
||||
if (is_string($tableOther) && $tableOther === $tableName) {
|
||||
unset($this->tables[$j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif (is_string($table)) {
|
||||
$numTables = count($this->tables);
|
||||
|
||||
for ($j = $index + 1; $j < $numTables; $j++) {
|
||||
if (isset($this->tables[$j]) && is_array($this->tables[$j]) && !isset($this->tables[$j]['tableAlias'])) {
|
||||
$tableOther = $this->tables[$j];
|
||||
if ($table === $tableOther['table']) {
|
||||
$message = sprintf('Please reorganize the joined tables as the table %s in %s cannot be joined correctly. We recommend to join tables with arrays first. %s', $table, json_encode($this->tables), json_encode(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10)));
|
||||
throw new Exception($message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function addMissingTablesForOtherTableJoin($tableName, $dependentTable)
|
||||
{
|
||||
$this->tables->addTableDependency($dependentTable, $tableName);
|
||||
|
||||
if ($this->tables->hasJoinedTable($tableName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$table = $this->tables->getLogTable($tableName);
|
||||
|
||||
if ($table->getColumnToJoinOnIdAction() || $table->getColumnToJoinOnIdAction() || $table->getLinkTableToBeAbleToJoinOnVisit()) {
|
||||
$this->tables->addTableToJoin($tableName);
|
||||
return;
|
||||
}
|
||||
|
||||
$otherTableJoins = $table->getWaysToJoinToOtherLogTables();
|
||||
|
||||
foreach ($otherTableJoins as $logTable => $column) {
|
||||
$this->addMissingTablesForOtherTableJoin($logTable, $tableName);
|
||||
}
|
||||
|
||||
$this->tables->addTableToJoin($tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the join sql based on the needed tables
|
||||
* @throws Exception if tables can't be joined
|
||||
* @return array
|
||||
*/
|
||||
public function generate()
|
||||
{
|
||||
/** @var LogTable[] $availableLogTables */
|
||||
$availableLogTables = array();
|
||||
|
||||
$this->tables->sort();
|
||||
|
||||
foreach ($this->tables as $i => $table) {
|
||||
if (is_array($table)) {
|
||||
|
||||
// join condition provided
|
||||
$alias = isset($table['tableAlias']) ? $table['tableAlias'] : $table['table'];
|
||||
|
||||
if (isset($table['join'])) {
|
||||
$this->joinString .= ' ' . $table['join'];
|
||||
} else {
|
||||
$this->joinString .= ' LEFT JOIN';
|
||||
}
|
||||
|
||||
if (!isset($table['joinOn']) && $this->tables->getLogTable($table['table'])) {
|
||||
$logTable = $this->tables->getLogTable($table['table']);
|
||||
if (!empty($availableLogTables)) {
|
||||
$table['joinOn'] = $this->findJoinCriteriasForTables($logTable, $availableLogTables);
|
||||
}
|
||||
if (!isset($table['tableAlias'])) {
|
||||
// eg array('table' => 'log_link_visit_action', 'join' => 'RIGHT JOIN')
|
||||
// we treat this like a regular string table which we can join automatically
|
||||
$availableLogTables[$table['table']] = $logTable;
|
||||
}
|
||||
}
|
||||
|
||||
$this->joinString .= ' ' . Common::prefixTable($table['table']) . " AS " . $alias
|
||||
. " ON " . $table['joinOn'];
|
||||
continue;
|
||||
}
|
||||
|
||||
$tableSql = Common::prefixTable($table) . " AS $table";
|
||||
|
||||
$logTable = $this->tables->getLogTable($table);
|
||||
|
||||
if ($i == 0) {
|
||||
// first table
|
||||
$this->joinString .= $tableSql;
|
||||
} else {
|
||||
|
||||
$join = $this->findJoinCriteriasForTables($logTable, $availableLogTables);
|
||||
|
||||
if ($join === null) {
|
||||
$availableLogTables[$table] = $logTable;
|
||||
continue;
|
||||
}
|
||||
|
||||
// the join sql the default way
|
||||
$this->joinString .= " LEFT JOIN $tableSql ON " . $join;
|
||||
}
|
||||
|
||||
$availableLogTables[$table] = $logTable;
|
||||
}
|
||||
}
|
||||
|
||||
public function getJoinString()
|
||||
{
|
||||
return $this->joinString;
|
||||
}
|
||||
|
||||
public function shouldJoinWithSelect()
|
||||
{
|
||||
return $this->joinWithSubSelect;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LogTable $logTable
|
||||
* @param LogTable[] $availableLogTables
|
||||
* @return string|null returns null in case the table is already joined, or the join string if the table needs
|
||||
* to be joined
|
||||
* @throws Exception if table cannot be joined for segmentation
|
||||
*/
|
||||
public function findJoinCriteriasForTables(LogTable $logTable, $availableLogTables)
|
||||
{
|
||||
$join = null;
|
||||
$alternativeJoin = null;
|
||||
$table = $logTable->getName();
|
||||
|
||||
foreach ($availableLogTables as $availableLogTable) {
|
||||
if ($logTable->getColumnToJoinOnIdVisit() && $availableLogTable->getColumnToJoinOnIdVisit()) {
|
||||
|
||||
$join = sprintf("%s.%s = %s.%s", $table, $logTable->getColumnToJoinOnIdVisit(),
|
||||
$availableLogTable->getName(), $availableLogTable->getColumnToJoinOnIdVisit());
|
||||
$alternativeJoin = sprintf("%s.%s = %s.%s", $availableLogTable->getName(), $availableLogTable->getColumnToJoinOnIdVisit(),
|
||||
$table, $logTable->getColumnToJoinOnIdVisit());
|
||||
|
||||
if ($availableLogTable->shouldJoinWithSubSelect()) {
|
||||
$this->joinWithSubSelect = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if ($logTable->getColumnToJoinOnIdAction() && $availableLogTable->getColumnToJoinOnIdAction()) {
|
||||
if (isset($this->nonVisitJoins[$logTable->getName()][$availableLogTable->getName()])) {
|
||||
$join = $this->nonVisitJoins[$logTable->getName()][$availableLogTable->getName()];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$otherJoins = $logTable->getWaysToJoinToOtherLogTables();
|
||||
foreach ($otherJoins as $joinTable => $column) {
|
||||
if($availableLogTable->getName() == $joinTable) {
|
||||
$join = sprintf("`%s`.`%s` = `%s`.`%s`", $table, $column, $availableLogTable->getName(), $column);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!isset($join)) {
|
||||
throw new Exception("Table '$table' can't be joined for segmentation");
|
||||
}
|
||||
|
||||
if ($this->tables->hasJoinedTableManually($table, $join)
|
||||
|| $this->tables->hasJoinedTableManually($table, $alternativeJoin)) {
|
||||
// already joined, no need to join it again
|
||||
return null;
|
||||
}
|
||||
|
||||
return $join;
|
||||
}
|
||||
|
||||
/**
|
||||
* This code is a bit tricky. We have to execute this right at the beginning before actually iterating over all the
|
||||
* tables and generating the join string as we may have to delete a table from the tables. If we did not delete
|
||||
* this table upfront, we would have maybe already added a joinString for that table, even though it will be later
|
||||
* removed by another table. This means if we wouldn't delete/unset that table upfront, we would need to alter
|
||||
* an already generated join string which would not be really nice code as well.
|
||||
*
|
||||
* Next problem is, because we are deleting a table, we have to remember the "joinOn" string for that table in a
|
||||
* property "nonVisitJoins". Otherwise we would not be able to generate the correct "joinOn" string when actually
|
||||
* iterating over all the tables to generate that string.
|
||||
*
|
||||
* @param $tableName
|
||||
* @param $tableNameToJoin
|
||||
* @param $index
|
||||
*/
|
||||
protected function generateNonVisitJoins($tableName, $tableNameToJoin, $index)
|
||||
{
|
||||
$logTable = $this->tables->getLogTable($tableName);
|
||||
$logTableToJoin = $this->tables->getLogTable($tableNameToJoin);
|
||||
|
||||
$nonVisitJoin = sprintf("%s.%s = %s.%s", $logTableToJoin->getName(), $logTableToJoin->getColumnToJoinOnIdAction(),
|
||||
$tableName, $logTable->getColumnToJoinOnIdAction());
|
||||
|
||||
$altNonVisitJoin = sprintf("%s.%s = %s.%s", $tableName, $logTable->getColumnToJoinOnIdAction(),
|
||||
$logTableToJoin->getName(), $logTableToJoin->getColumnToJoinOnIdAction());
|
||||
|
||||
if ($index > 0
|
||||
&& $this->tables->hasAddedTableManually($tableName)
|
||||
&& !$this->tables->hasJoinedTableManually($tableName, $nonVisitJoin)
|
||||
&& !$this->tables->hasJoinedTableManually($tableName, $altNonVisitJoin)) {
|
||||
$tableIndex = $this->tables->findIndexOfManuallyAddedTable($tableName);
|
||||
$nonVisitJoin = '(' . $this->tables[$tableIndex]['joinOn'] . ' AND ' . $nonVisitJoin . ')';
|
||||
unset($this->tables[$tableIndex]);
|
||||
}
|
||||
|
||||
if (!isset($this->nonVisitJoins[$tableName])) {
|
||||
$this->nonVisitJoins[$tableName] = array();
|
||||
}
|
||||
|
||||
if (!isset($this->nonVisitJoins[$tableNameToJoin])) {
|
||||
$this->nonVisitJoins[$tableNameToJoin] = array();
|
||||
}
|
||||
|
||||
$this->nonVisitJoins[$tableName][$tableNameToJoin] = $nonVisitJoin;
|
||||
$this->nonVisitJoins[$tableNameToJoin][$tableName] = $nonVisitJoin;
|
||||
}
|
||||
}
|
@ -0,0 +1,358 @@
|
||||
<?php
|
||||
/**
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Piwik\DataAccess\LogQueryBuilder;
|
||||
|
||||
use Exception;
|
||||
use Piwik\Plugin\LogTablesProvider;
|
||||
|
||||
class JoinTables extends \ArrayObject
|
||||
{
|
||||
/**
|
||||
* @var LogTablesProvider
|
||||
*/
|
||||
private $logTableProvider;
|
||||
|
||||
// NOTE: joins can be specified explicitly as arrays w/ 'joinOn' keys or implicitly as table names. when
|
||||
// table names are used, the joins dependencies are assumed based on how we want to order those joins.
|
||||
// the below table list the possible dependencies of each table, and is specifically designed to enforce
|
||||
// the following order:
|
||||
// log_link_visit_action, log_action, log_visit, log_conversion, log_conversion_item
|
||||
// which means if an array is supplied where log_visit comes before log_link_visitAction, it will
|
||||
// be moved to after it.
|
||||
private $implicitTableDependencies = [
|
||||
'log_link_visit_action' => [
|
||||
// empty
|
||||
],
|
||||
'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',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Tables constructor.
|
||||
* @param LogTablesProvider $logTablesProvider
|
||||
* @param array $tables
|
||||
*/
|
||||
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()
|
||||
{
|
||||
// we do not use $this->uasort as we do not want to maintain keys
|
||||
$tables = $this->getTables();
|
||||
|
||||
// the first entry is always the FROM table
|
||||
$firstTable = array_shift($tables);
|
||||
|
||||
$dependencies = $this->parseDependencies($tables);
|
||||
|
||||
$sorted = [$firstTable];
|
||||
$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];
|
||||
|
||||
// only include dependencies that are in the list of requested tables (ie, if we want to
|
||||
// query from log_conversion joining on log_link_visit_action, we don't want to add log_visit
|
||||
// to the sql statement)
|
||||
$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) { // sanity check, in case the dependent table is not in the list of tables to query
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!empty($visited[$dependentTableToVisit])) { // skip if already visited
|
||||
continue;
|
||||
}
|
||||
|
||||
// visit dependent table...
|
||||
$this->visitTableListDfsSingle($tables, $dependencies, $visitor, $dependentTableToVisit, $visited);
|
||||
}
|
||||
}
|
||||
|
||||
// ...then visit current table
|
||||
$visitor($tableToVisit);
|
||||
}
|
||||
|
||||
private function findTableIndex($tables, $tableToSearchFor)
|
||||
{
|
||||
foreach ($tables as $key => $info) {
|
||||
$tableName = null;
|
||||
if (is_string($info)) {
|
||||
$tableName = $info;
|
||||
} else if (is_array($info)) {
|
||||
$tableName = isset($info['tableAlias']) ? $info['tableAlias'] : $info['table'];
|
||||
}
|
||||
|
||||
if ($tableName == $tableToSearchFor) {
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user