Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'core/DataAccess/LogQueryBuilder.php')
-rw-r--r--core/DataAccess/LogQueryBuilder.php211
1 files changed, 24 insertions, 187 deletions
diff --git a/core/DataAccess/LogQueryBuilder.php b/core/DataAccess/LogQueryBuilder.php
index c65f36bf10..186ed0d96e 100644
--- a/core/DataAccess/LogQueryBuilder.php
+++ b/core/DataAccess/LogQueryBuilder.php
@@ -10,11 +10,23 @@
namespace Piwik\DataAccess;
use Exception;
-use Piwik\Common;
+use Piwik\DataAccess\LogQueryBuilder\JoinGenerator;
+use Piwik\DataAccess\LogQueryBuilder\JoinTables;
+use Piwik\Plugin\LogTablesProvider;
use Piwik\Segment\SegmentExpression;
class LogQueryBuilder
{
+ /**
+ * @var LogTablesProvider
+ */
+ private $logTableProvider;
+
+ public function __construct(LogTablesProvider $logTablesProvider)
+ {
+ $this->logTableProvider = $logTablesProvider;
+ }
+
public function getSelectQueryString(SegmentExpression $segmentExpression, $select, $from, $where, $bind, $groupBy,
$orderBy, $limitAndOffset)
{
@@ -31,9 +43,11 @@ class LogQueryBuilder
$bind = array_merge($bind, $segmentSql['bind']);
}
- $joins = $this->generateJoinsString($from);
- $joinWithSubSelect = $joins['joinWithSubSelect'];
- $from = $joins['sql'];
+ $tables = new JoinTables($this->logTableProvider, $from);
+ $join = new JoinGenerator($tables);
+ $join->generate();
+ $from = $join->getJoinString();
+ $joinWithSubSelect = $join->shouldJoinWithSelect();
// hack for https://github.com/piwik/piwik/issues/9194#issuecomment-164321612
$useSpecialConversionGroupBy = (!empty($segmentSql)
@@ -55,192 +69,15 @@ class LogQueryBuilder
);
}
- private function hasJoinedTableAlreadyManually($tableToFind, $joinToFind, $tables)
+ private function getKnownTables()
{
- foreach ($tables as $index => $table) {
- if (is_array($table)
- && !empty($table['table'])
- && $table['table'] === $tableToFind
- && (!isset($table['tableAlias']) || $table['tableAlias'] === $tableToFind)
- && isset($table['joinOn']) && $table['joinOn'] === $joinToFind) {
- return true;
- }
+ $names = array();
+ foreach ($this->logTableProvider->getAllLogTables() as $logTable) {
+ $names[] = $logTable->getName();
}
-
- return false;
+ return $names;
}
- private function findIndexOfManuallyAddedTable($tableToFind, $tables)
- {
- foreach ($tables as $index => $table) {
- if (is_array($table)
- && !empty($table['table'])
- && $table['table'] === $tableToFind
- && (!isset($table['tableAlias']) || $table['tableAlias'] === $tableToFind)) {
- return $index;
- }
- }
- }
-
- private function hasTableAddedManually($tableToFind, $tables)
- {
- $table = $this->findIndexOfManuallyAddedTable($tableToFind, $tables);
-
- return isset($table);
- }
-
- /**
- * Generate the join sql based on the needed tables
- * @param array $tables tables to join
- * @throws Exception if tables can't be joined
- * @return array
- */
- private function generateJoinsString(&$tables)
- {
- $knownTables = array("log_action", "log_visit", "log_link_visit_action", "log_conversion", "log_conversion_item");
- $visitsAvailable = $linkVisitActionsTableAvailable = $conversionsAvailable = $conversionItemAvailable = $actionsTableAvailable = false;
- $defaultLogActionJoin = "log_link_visit_action.idaction_url = log_action.idaction";
-
- $joinWithSubSelect = false;
- $sql = '';
-
- // make sure the tables are joined in the right order
- // base table first, then action before conversion
- // this way, conversions can be left joined on idvisit
- $actionIndex = array_search("log_link_visit_action", $tables);
- $conversionIndex = array_search("log_conversion", $tables);
- if ($actionIndex > 0 && $conversionIndex > 0 && $actionIndex > $conversionIndex) {
- $tables[$actionIndex] = "log_conversion";
- $tables[$conversionIndex] = "log_link_visit_action";
- }
- // same as above: action before visit
- $actionIndex = array_search("log_link_visit_action", $tables);
- $visitIndex = array_search("log_visit", $tables);
- if ($actionIndex > 0 && $visitIndex > 0 && $actionIndex > $visitIndex) {
- $tables[$actionIndex] = "log_visit";
- $tables[$visitIndex] = "log_link_visit_action";
- }
-
- // we need to add log_link_visit_action dynamically to join eg visit with action
- $linkVisitAction = array_search("log_link_visit_action", $tables);
- $actionIndex = array_search("log_action", $tables);
- if ($linkVisitAction === false && $actionIndex > 0) {
- $tables[] = "log_link_visit_action";
- }
-
- if ($actionIndex > 0
- && $this->hasTableAddedManually('log_action', $tables)
- && !$this->hasJoinedTableAlreadyManually('log_action', $defaultLogActionJoin, $tables)) {
- // we cannot join the same table with same alias twice, therefore we need to combine the join via AND
- $tableIndex = $this->findIndexOfManuallyAddedTable('log_action', $tables);
- $defaultLogActionJoin = '(' . $tables[$tableIndex]['joinOn'] . ' AND ' . $defaultLogActionJoin . ')';
- unset($tables[$tableIndex]);
- }
-
- $linkVisitAction = array_search("log_link_visit_action", $tables);
- $actionIndex = array_search("log_action", $tables);
- if ($linkVisitAction > 0 && $actionIndex > 0 && $linkVisitAction > $actionIndex) {
- $tables[$actionIndex] = "log_link_visit_action";
- $tables[$linkVisitAction] = "log_action";
- }
-
- foreach ($tables as $i => $table) {
- if (is_array($table)) {
- // join condition provided
- $alias = isset($table['tableAlias']) ? $table['tableAlias'] : $table['table'];
- $sql .= "
- LEFT JOIN " . Common::prefixTable($table['table']) . " AS " . $alias
- . " ON " . $table['joinOn'];
- continue;
- }
-
- if (!in_array($table, $knownTables)) {
- throw new Exception("Table '$table' can't be used for segmentation");
- }
-
- $tableSql = Common::prefixTable($table) . " AS $table";
-
- if ($i == 0) {
- // first table
- $sql .= $tableSql;
- } else {
-
- if ($linkVisitActionsTableAvailable && $table === 'log_action') {
- $join = $defaultLogActionJoin;
-
- if ($this->hasJoinedTableAlreadyManually($table, $join, $tables)) {
- $actionsTableAvailable = true;
- continue;
- }
-
- } elseif ($linkVisitActionsTableAvailable && $table == "log_conversion") {
- // have actions, need conversions => join on idvisit
- $join = "log_conversion.idvisit = log_link_visit_action.idvisit";
- } elseif ($linkVisitActionsTableAvailable && $table == "log_visit") {
- // have actions, need visits => join on idvisit
- $join = "log_visit.idvisit = log_link_visit_action.idvisit";
-
- if ($this->hasJoinedTableAlreadyManually($table, $join, $tables)) {
- $visitsAvailable = true;
- continue;
- }
-
- } elseif ($visitsAvailable && $table == "log_link_visit_action") {
- // have visits, need actions => we have to use a more complex join
- // we don't hande this here, we just return joinWithSubSelect=true in this case
- $joinWithSubSelect = true;
- $join = "log_link_visit_action.idvisit = log_visit.idvisit";
-
- if ($this->hasJoinedTableAlreadyManually($table, $join, $tables)) {
- $linkVisitActionsTableAvailable = true;
- continue;
- }
-
- } elseif ($conversionsAvailable && $table == "log_link_visit_action") {
- // have conversions, need actions => join on idvisit
- $join = "log_conversion.idvisit = log_link_visit_action.idvisit";
- } elseif (($visitsAvailable && $table == "log_conversion")
- || ($conversionsAvailable && $table == "log_visit")
- ) {
- // have visits, need conversion (or vice versa) => join on idvisit
- // notice that joining conversions on visits has lower priority than joining it on actions
- $join = "log_conversion.idvisit = log_visit.idvisit";
-
- // if conversions are joined on visits, we need a complex join
- if ($table == "log_conversion") {
- $joinWithSubSelect = true;
- }
- } elseif ($conversionItemAvailable && $table === 'log_visit') {
- $join = "log_conversion_item.idvisit = log_visit.idvisit";
- } elseif ($conversionItemAvailable && $table === 'log_link_visit_action') {
- $join = "log_conversion_item.idvisit = log_link_visit_action.idvisit";
- } elseif ($conversionItemAvailable && $table === 'log_conversion') {
- $join = "log_conversion_item.idvisit = log_conversion.idvisit";
- } else {
- throw new Exception("Table '$table' can't be joined for segmentation");
- }
-
- // the join sql the default way
- $sql .= "
- LEFT JOIN $tableSql ON $join";
- }
-
- // remember which tables are available
- $visitsAvailable = ($visitsAvailable || $table == "log_visit");
- $linkVisitActionsTableAvailable = ($linkVisitActionsTableAvailable || $table == "log_link_visit_action");
- $actionsTableAvailable = ($actionsTableAvailable || $table == "log_action");
- $conversionsAvailable = ($conversionsAvailable || $table == "log_conversion");
- $conversionItemAvailable = ($conversionItemAvailable || $table == "log_conversion_item");
- }
-
- $return = array(
- 'sql' => $sql,
- 'joinWithSubSelect' => $joinWithSubSelect
- );
- return $return;
- }
-
-
/**
* Build a select query where actions have to be joined on visits (or conversions)
* In this case, the query gets wrapped in another query so that grouping by visit is possible
@@ -256,7 +93,7 @@ class LogQueryBuilder
*/
private function buildWrappedSelectQuery($select, $from, $where, $groupBy, $orderBy, $limitAndOffset, $innerGroupBy = null)
{
- $matchTables = "(log_visit|log_conversion_item|log_conversion|log_action)";
+ $matchTables = '(' . implode('|', $this->getKnownTables()) . ')';
preg_match_all("/". $matchTables ."\.[a-z0-9_\*]+/", $select, $matches);
$neededFields = array_unique($matches[0]);