Download tests/PHPUnit/Integration/DataAccess/LogAggregatorTest.php from Leon4gr45/matomo: direct link, hf CLI and curl.
- Browser
- Download file 29.7 kB
-
https://huggingface.co/spaces/Leon4gr45/matomo/resolve/main/tests/PHPUnit/Integration/DataAccess/LogAggregatorTest.php
- Command line
-
hf download hf://spaces/Leon4gr45/matomo/tests/PHPUnit/Integration/DataAccess/LogAggregatorTest.php
-
curl -L -o LogAggregatorTest.php https://huggingface.co/spaces/Leon4gr45/matomo/resolve/main/tests/PHPUnit/Integration/DataAccess/LogAggregatorTest.php
29.7 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\Tests\Integration\DataAccess; | |
| use Piwik\ArchiveProcessor\Parameters; | |
| use Piwik\Config; | |
| use Piwik\Config\DatabaseConfig; | |
| use Piwik\Common; | |
| use Piwik\DataAccess\LogAggregator; | |
| use Piwik\Date; | |
| use Piwik\Db; | |
| use Piwik\Db\Schema; | |
| use Piwik\Metrics; | |
| use Piwik\Period; | |
| use Piwik\Segment; | |
| use Piwik\Site; | |
| use Piwik\Tests\Fixtures\OneVisitorTwoVisits; | |
| use Piwik\Tests\Framework\TestDataHelper\LogHelper; | |
| use Piwik\Tests\Framework\TestCase\IntegrationTestCase; | |
| use Piwik\Tests\Framework\TestCase\SystemTestCase; | |
| use Piwik\Tracker\Action; | |
| use Piwik\Tracker\GoalManager; | |
| use Piwik\Updater\Migration\Db as DbMigration; | |
| /** | |
| * @group Core | |
| * @group DataAccess | |
| * @group LogAggregator | |
| * @group LogAggregatorTest | |
| */ | |
| class LogAggregatorTest extends IntegrationTestCase | |
| { | |
| /** | |
| * @var OneVisitorTwoVisits | |
| */ | |
| public static $fixture; | |
| /** | |
| * @var LogAggregator | |
| */ | |
| private $logAggregator; | |
| /** | |
| * @var Site | |
| */ | |
| private $site; | |
| /** | |
| * @var Period | |
| */ | |
| private $period; | |
| public function setUp(): void | |
| { | |
| parent::setUp(); | |
| $idSite = 1; | |
| $this->site = new Site($idSite); | |
| $date = Date::factory('2010-03-06'); | |
| $this->period = Period\Factory::build('month', $date); | |
| $segment = new Segment('', [$this->site->getId()]); | |
| $params = new Parameters($this->site, $this->period, $segment); | |
| $this->logAggregator = new LogAggregator($params); | |
| } | |
| /** | |
| * @dataProvider getSelectDimensionTestData | |
| */ | |
| public function testGetSelectDimensions($dimensions, $tableName, $expectedResult) | |
| { | |
| $class = new \ReflectionClass(LogAggregator::class); | |
| $method = $class->getMethod('getSelectDimensions'); | |
| if (PHP_VERSION_ID < 80100) { | |
| $method->setAccessible(true); | |
| } | |
| $output = $method->invoke($this->logAggregator, $dimensions, $tableName); | |
| $this->assertEquals($expectedResult, $output); | |
| } | |
| public function getSelectDimensionTestData(): iterable | |
| { | |
| yield 'normal column names' => [ | |
| ['column', 'column2'], | |
| 'log_visit', | |
| ['log_visit.column AS `column`', 'log_visit.column2 AS `column2`'], | |
| ]; | |
| yield 'normal column names with alias' => [ | |
| ['alias' => 'column', 'alias2' => 'column2'], | |
| 'log_conversion', | |
| ['log_conversion.column AS `alias`', 'log_conversion.column2 AS `alias2`'], | |
| ]; | |
| yield 'normal column names with and without alias' => [ | |
| ['alias' => 'column', 'column2'], | |
| 'log_conversion', | |
| ['log_conversion.column AS `alias`', 'log_conversion.column2 AS `column2`'], | |
| ]; | |
| yield 'column expression' => [ | |
| ["CONCAT(log_visit.config_os, ';', COALESCE(log_visit.config_os_version, ''))"], | |
| 'log_conversion', | |
| ["CONCAT(log_visit.config_os, ';', COALESCE(log_visit.config_os_version, ''))"], | |
| ]; | |
| yield 'column expression with alias' => [ | |
| ['alias' => "CONCAT(log_visit.config_os, ';', COALESCE(log_visit.config_os_version, ''))"], | |
| 'log_conversion', | |
| ["CONCAT(log_visit.config_os, ';', COALESCE(log_visit.config_os_version, '')) AS `alias`"], | |
| ]; | |
| yield 'mixed dimension content' => [ | |
| ['alias' => "CONCAT(log_visit.config_os, ';', COALESCE(log_visit.config_os_version, ''))", 'mycolumn', 'newalias' => 'column2'], | |
| 'log_conversion', | |
| ["CONCAT(log_visit.config_os, ';', COALESCE(log_visit.config_os_version, '')) AS `alias`", 'log_conversion.mycolumn AS `mycolumn`', 'log_conversion.column2 AS `newalias`'], | |
| ]; | |
| } | |
| /** | |
| * @dataProvider getGroupByDimensionTestData | |
| */ | |
| public function testGetGroupByDimensions($dimensions, $tableName, $expectedResult) | |
| { | |
| $class = new \ReflectionClass(LogAggregator::class); | |
| $method = $class->getMethod('getGroupByDimensions'); | |
| if (PHP_VERSION_ID < 80100) { | |
| $method->setAccessible(true); | |
| } | |
| $output = $method->invoke($this->logAggregator, $dimensions, $tableName); | |
| $this->assertEquals($expectedResult, $output); | |
| } | |
| public function getGroupByDimensionTestData(): iterable | |
| { | |
| yield 'normal column names' => [ | |
| ['column', 'column2'], | |
| 'log_visit', | |
| ['log_visit.column', 'log_visit.column2'], | |
| ]; | |
| yield 'normal column names with alias' => [ | |
| ['alias' => 'column', 'alias2' => 'column2'], | |
| 'log_conversion', | |
| ['alias', 'alias2'], | |
| ]; | |
| yield 'normal column names with and without alias' => [ | |
| ['alias' => 'column', 'column2'], | |
| 'log_conversion', | |
| ['alias', 'log_conversion.column2'], | |
| ]; | |
| yield 'column expression' => [ | |
| ["CONCAT(log_visit.config_os, ';', COALESCE(log_visit.config_os_version, ''))"], | |
| 'log_conversion', | |
| ["CONCAT(log_visit.config_os, ';', COALESCE(log_visit.config_os_version, ''))"], | |
| ]; | |
| yield 'column expression with alias' => [ | |
| ['alias' => "CONCAT(log_visit.config_os, ';', COALESCE(log_visit.config_os_version, ''))"], | |
| 'log_conversion', | |
| ['alias'], | |
| ]; | |
| yield 'mixed dimension content' => [ | |
| ['alias' => "CONCAT(log_visit.config_os, ';', COALESCE(log_visit.config_os_version, ''))", 'mycolumn', 'newalias' => 'column2'], | |
| 'log_conversion', | |
| ['alias', 'log_conversion.mycolumn', 'newalias'], | |
| ]; | |
| } | |
| public function testGenerateQuery() | |
| { | |
| $query = $this->logAggregator->generateQuery('test, test2', 'log_visit', '1=1', false, '5'); | |
| $expected = array( | |
| 'sql' => 'SELECT /* sites 1 */ /* 2010-03-01,2010-03-31 */ | |
| test, test2 | |
| FROM | |
| log_visit AS log_visit | |
| WHERE | |
| 1=1 | |
| ORDER BY | |
| 5', | |
| 'bind' => array ( | |
| 0 => '2010-03-01 00:00:00', | |
| 1 => '2010-03-31 23:59:59', | |
| 2 => 1, | |
| ), | |
| ); | |
| $this->assertSame($expected, $query); | |
| } | |
| public function testGenerateQueryWithSegmentShouldNotUseTmpTableWhenNotEnabled() | |
| { | |
| $segment = new Segment('userId==1', array($this->site->getId())); | |
| $params = new Parameters($this->site, $this->period, $segment); | |
| $this->logAggregator = new LogAggregator($params); | |
| $query = $this->logAggregator->generateQuery('test, test2', 'log_visit', '1=1', false, '5'); | |
| $expected = array( | |
| 'sql' => 'SELECT /* segmenthash 4eaf469650796451c610972d0ca1e9e8 */ /* sites 1 */ /* 2010-03-01,2010-03-31 */ | |
| test, test2 | |
| FROM | |
| log_visit AS log_visit | |
| WHERE | |
| ( 1=1 ) | |
| AND | |
| (log_visit.user_id = ?) | |
| ORDER BY | |
| 5', | |
| 'bind' => array ( | |
| '2010-03-01 00:00:00', | |
| '2010-03-31 23:59:59', | |
| 1, | |
| '1', | |
| ), | |
| ); | |
| $this->assertSame($expected, $query); | |
| } | |
| public function testGenerateQueryWithSegmentShouldUseTmpTableWhenEnabled() | |
| { | |
| $segment = new Segment('userId==1', array($this->site->getId())); | |
| $params = new Parameters($this->site, $this->period, $segment); | |
| $this->logAggregator = new LogAggregator($params); | |
| $this->logAggregator->allowUsageSegmentCache(); | |
| $query = $this->logAggregator->generateQuery('test, test2', 'log_visit', '1=1', false, '5'); | |
| $expected = array( | |
| 'sql' => 'SELECT /* segmenthash 4eaf469650796451c610972d0ca1e9e8 */ /* sites 1 */ /* 2010-03-01,2010-03-31 */ | |
| test, test2 | |
| FROM | |
| logtmpsegment0e053be69df974017fba4276a0d4347d AS logtmpsegment0e053be69df974017fba4276a0d4347d INNER JOIN log_visit AS log_visit ON log_visit.idvisit = logtmpsegment0e053be69df974017fba4276a0d4347d.idvisit | |
| WHERE | |
| 1=1 | |
| ORDER BY | |
| 5', | |
| 'bind' => array ( | |
| '2010-03-01 00:00:00', | |
| '2010-03-31 23:59:59', | |
| 1, | |
| ), | |
| ); | |
| $this->assertSame($expected, $query); | |
| } | |
| public function testGenerateQueryWithSegmentVisitLogRightJoinShouldKeepWhereCondition() | |
| { | |
| $segment = new Segment('userId==1', [$this->site->getId()]); | |
| $params = new Parameters($this->site, $this->period, $segment); | |
| $this->logAggregator = new LogAggregator($params); | |
| $this->logAggregator->allowUsageSegmentCache(); | |
| $select = "MINUTE(log_link_visit_action.server_time) AS 'CoreHome.ServerMinute', max(log_link_visit_action.pageview_position) AS 'max_actions_pageviewposition'"; | |
| $from = ['log_link_visit_action', ['table' => 'log_visit', 'join' => 'RIGHT JOIN']]; | |
| $where = 'log_visit.visit_last_action_time >= ? | |
| AND log_visit.visit_last_action_time <= ? | |
| AND log_visit.idsite IN (?)'; | |
| $orderBy = 'max_actions_pageviewposition'; | |
| $query = $this->logAggregator->generateQuery($select, $from, $where, false, $orderBy); | |
| $expected = [ | |
| 'sql' => "SELECT /* segmenthash 4eaf469650796451c610972d0ca1e9e8 */ /* sites 1 */ /* 2010-03-01,2010-03-31 */ | |
| MINUTE(log_link_visit_action.server_time) AS 'CoreHome.ServerMinute', max(log_link_visit_action.pageview_position) AS 'max_actions_pageviewposition' | |
| FROM | |
| logtmpsegment0e053be69df974017fba4276a0d4347d AS logtmpsegment0e053be69df974017fba4276a0d4347d INNER JOIN log_link_visit_action AS log_link_visit_action ON log_link_visit_action.idvisit = logtmpsegment0e053be69df974017fba4276a0d4347d.idvisit RIGHT JOIN log_visit AS log_visit ON log_visit.idvisit = logtmpsegment0e053be69df974017fba4276a0d4347d.idvisit | |
| WHERE | |
| log_visit.visit_last_action_time >= ? | |
| AND log_visit.visit_last_action_time <= ? | |
| AND log_visit.idsite IN (?) | |
| ORDER BY | |
| max_actions_pageviewposition", | |
| 'bind' => [ | |
| '2010-03-01 00:00:00', | |
| '2010-03-31 23:59:59', | |
| 1, | |
| ], | |
| ]; | |
| $this->assertSame($expected, $query); | |
| } | |
| public function testGenerateQueryWithSegmentVisitLogJoinRightJoinOnOtherTableShouldKeepWhereCondition() | |
| { | |
| $segment = new Segment('userId==1', [$this->site->getId()]); | |
| $params = new Parameters($this->site, $this->period, $segment); | |
| $this->logAggregator = new LogAggregator($params); | |
| $this->logAggregator->allowUsageSegmentCache(); | |
| $select = "log_link_visit_action.server_time AS 'CoreHome.ServerMinute', log_visit.visit_total_searches AS 'total_searches', log_conversion.items AS 'items'"; | |
| $from = ['log_link_visit_action', 'log_visit', ['table' => 'log_conversion', 'join' => 'right join']]; | |
| $where = 'log_conversion.server_time >= ? | |
| AND log_conversion.server_time <= ? | |
| AND log_conversion.idsite IN (?)'; | |
| $orderBy = 'max_actions_pageviewposition'; | |
| $query = $this->logAggregator->generateQuery($select, $from, $where, false, $orderBy); | |
| $expected = [ | |
| 'sql' => "SELECT /* segmenthash 4eaf469650796451c610972d0ca1e9e8 */ /* sites 1 */ /* 2010-03-01,2010-03-31 */ | |
| log_link_visit_action.server_time AS 'CoreHome.ServerMinute', log_visit.visit_total_searches AS 'total_searches', log_conversion.items AS 'items' | |
| FROM | |
| logtmpsegment0e053be69df974017fba4276a0d4347d AS logtmpsegment0e053be69df974017fba4276a0d4347d INNER JOIN log_link_visit_action AS log_link_visit_action ON log_link_visit_action.idvisit = logtmpsegment0e053be69df974017fba4276a0d4347d.idvisit LEFT JOIN log_visit AS log_visit ON log_visit.idvisit = logtmpsegment0e053be69df974017fba4276a0d4347d.idvisit right join log_conversion AS log_conversion ON log_conversion.idvisit = logtmpsegment0e053be69df974017fba4276a0d4347d.idvisit | |
| WHERE | |
| log_conversion.server_time >= ? | |
| AND log_conversion.server_time <= ? | |
| AND log_conversion.idsite IN (?) | |
| ORDER BY | |
| max_actions_pageviewposition", | |
| 'bind' => [ | |
| '2010-03-01 00:00:00', | |
| '2010-03-31 23:59:59', | |
| 1, | |
| ], | |
| ]; | |
| $this->assertSame($expected, $query); | |
| } | |
| public function testSetMaxExecutionTimeOfArchivingQueries() | |
| { | |
| if (SystemTestCase::isMysqli()) { | |
| // See https://github.com/matomo-org/matomo/issues/17871 | |
| $this->markTestSkipped('Max execution query hint does not work for Mysqli.'); | |
| } | |
| // limit query to one milli second | |
| Config::getInstance()->General['archiving_query_max_execution_time'] = 0.001; | |
| try { | |
| $this->logAggregator->getDb()->query('SELECT *, SLEEP(5) FROM ' . Common::prefixTable('log_visit')); | |
| $this->fail('Query was not aborted by max execution limit'); | |
| } catch (\Zend_Db_Statement_Exception $e) { | |
| $isMaxExecutionTimeError = $this->logAggregator->getDb()->isErrNo($e, DbMigration::ERROR_CODE_MAX_EXECUTION_TIME_EXCEEDED_QUERY_INTERRUPTED) | |
| || $this->logAggregator->getDb()->isErrNo($e, DbMigration::ERROR_CODE_MAX_EXECUTION_TIME_EXCEEDED_SORT_ABORTED) | |
| || strpos($e->getMessage(), 'maximum statement execution time exceeded') !== false | |
| || strpos($e->getMessage(), 'max_statement_time exceeded') !== false; | |
| $this->assertTrue($isMaxExecutionTimeError, $e->getMessage()); | |
| } | |
| } | |
| private function setSqlRequirePrimaryKeySetting($val) | |
| { | |
| try { | |
| $this->logAggregator->getDb()->exec('SET SESSION sql_require_primary_key=' . $val); | |
| } catch (\Exception $e) { | |
| if ($this->logAggregator->getDb()->isErrNo($e, 1193)) { | |
| // ignore General error: 1193 Unknown system variable 'sql_require_primary_key' | |
| try { | |
| // on mariadb this might work | |
| $this->logAggregator->getDb()->exec('SET GLOBAL innodb_force_primary_key = ' . ($val ? 'on' : 'off')); | |
| } catch (\Exception $e) { | |
| if ($this->logAggregator->getDb()->isErrNo($e, 1193)) { | |
| // ignore General error: 1193 Unknown system variable 'sql_require_primary_key' | |
| return; | |
| } elseif ($this->logAggregator->getDb()->isErrNo($e, 1229)) { | |
| try { | |
| // Mariadb: General error: 1229 Variable 'innodb_force_primary_key' is a GLOBAL variable and should be set with SET GLOBAL | |
| $this->logAggregator->getDb()->exec('SET GLOBAL innodb_force_primary_key=' . $val); | |
| } catch (\Exception $e) { | |
| return; | |
| } | |
| } else { | |
| throw $e; | |
| } | |
| } | |
| } else { | |
| throw $e; | |
| } | |
| } | |
| } | |
| public function testGenerateQueryWithSegmentShouldUseTmpTableWhenEnabledAndPrimaryKeyRequired() | |
| { | |
| $segment = new Segment('userId==2', array($this->site->getId())); | |
| $params = new Parameters($this->site, $this->period, $segment); | |
| $this->logAggregator = new LogAggregator($params); | |
| $this->logAggregator->allowUsageSegmentCache(); | |
| $this->setSqlRequirePrimaryKeySetting(1); | |
| $query = $this->logAggregator->generateQuery('test, test2', 'log_visit', '1=1', false, '5'); | |
| $this->setSqlRequirePrimaryKeySetting(0);// reset variable | |
| $expected = array( | |
| 'sql' => 'SELECT /* segmenthash 4a4d16d6897e7fed2d5d151016a5a19c */ /* sites 1 */ /* 2010-03-01,2010-03-31 */ | |
| test, test2 | |
| FROM | |
| logtmpsegment4ef74412006a3160b17ca5fe99a5f866 AS logtmpsegment4ef74412006a3160b17ca5fe99a5f866 INNER JOIN log_visit AS log_visit ON log_visit.idvisit = logtmpsegment4ef74412006a3160b17ca5fe99a5f866.idvisit | |
| WHERE | |
| 1=1 | |
| ORDER BY | |
| 5', | |
| 'bind' => array ( | |
| '2010-03-01 00:00:00', | |
| '2010-03-31 23:59:59', | |
| 1, | |
| ), | |
| ); | |
| $this->assertSame($expected, $query); | |
| } | |
| public function testGenerateQuerySwitchesSupportsUncommittedToTrueWhenSupports() | |
| { | |
| $segment = new Segment('userId==1111', array($this->site->getId())); | |
| $params = new Parameters($this->site, $this->period, $segment); | |
| $this->logAggregator = new LogAggregator($params); | |
| $this->logAggregator->allowUsageSegmentCache(); | |
| $this->setSqlRequirePrimaryKeySetting(1); | |
| $db = Db::get(); | |
| $db->setSupportsTransactionLevelForNonLockingReads(null); | |
| $this->logAggregator->generateQuery('test, test2', 'log_visit', '1=1', false, '5'); | |
| $this->setSqlRequirePrimaryKeySetting(0); | |
| $this->assertTrue($db->getSupportsTransactionLevelForNonLockingReads()); | |
| } | |
| public function testGetSegmentTmpTableName() | |
| { | |
| $this->assertEquals('logtmpsegmentcc2efa0acbd5f209e8ee8618e72f3f9b', $this->logAggregator->getSegmentTmpTableName()); | |
| } | |
| public function testGetSegmentTmpTableNameWithLongPrefix() | |
| { | |
| Config::getInstance()->database['tables_prefix'] = 'myverylongtableprefixtestfoobartest'; | |
| $this->assertEquals('logtmpsegmentcc2efa0acbd5f209', $this->logAggregator->getSegmentTmpTableName()); | |
| } | |
| public function testGetSegmentTableSqlShouldAddJoinHintAsCommentIfEnabled() | |
| { | |
| DatabaseConfig::setConfigValue('enable_segment_first_table_join_prefix', '1'); | |
| $query = $this->getSegmentSql(); | |
| $expected = [ | |
| 'sql' => "SELECT /*+ JOIN_PREFIX(log_visit) */ | |
| distinct log_visit.idvisit as idvisit | |
| FROM | |
| log_visit AS log_visit | |
| WHERE | |
| ( log_visit.visit_last_action_time >= ? | |
| AND log_visit.visit_last_action_time <= ? | |
| AND log_visit.idsite IN (?) ) | |
| AND | |
| (log_visit.user_id = ?) | |
| ORDER BY | |
| log_visit.idvisit ASC", | |
| 'bind' => [ | |
| '2010-03-01 00:00:00', | |
| '2010-03-31 23:59:59', | |
| 1, | |
| '1', | |
| ], | |
| ]; | |
| $this->assertSame($expected, $query); | |
| } | |
| public function testGetSegmentTableSqlShouldNotAddJoinHintAsCommentIfDisabled() | |
| { | |
| DatabaseConfig::setConfigValue('enable_segment_first_table_join_prefix', '0'); | |
| $query = $this->getSegmentSql(); | |
| $expected = [ | |
| 'sql' => " | |
| SELECT | |
| distinct log_visit.idvisit as idvisit | |
| FROM | |
| log_visit AS log_visit | |
| WHERE | |
| ( log_visit.visit_last_action_time >= ? | |
| AND log_visit.visit_last_action_time <= ? | |
| AND log_visit.idsite IN (?) ) | |
| AND | |
| (log_visit.user_id = ?) | |
| ORDER BY | |
| log_visit.idvisit ASC", | |
| 'bind' => [ | |
| '2010-03-01 00:00:00', | |
| '2010-03-31 23:59:59', | |
| 1, | |
| '1', | |
| ], | |
| ]; | |
| $this->assertSame($expected, $query); | |
| } | |
| private function getSegmentSql() | |
| { | |
| $segment = new Segment('userId==1', [$this->site->getId()]); | |
| $params = new Parameters($this->site, $this->period, $segment); | |
| $this->logAggregator = new LogAggregator($params); | |
| return $this->logAggregator->getSegmentTableSql(); | |
| } | |
| public function testGenerateQueryWithQueryHintShouldAddQueryHintAsComment() | |
| { | |
| $this->logAggregator->setQueryOriginHint('MyPluginName'); | |
| $query = $this->logAggregator->generateQuery('test, test2', 'log_visit', '1=1', false, '5'); | |
| $expected = array( | |
| 'sql' => 'SELECT /* sites 1 */ /* 2010-03-01,2010-03-31 */ /* MyPluginName */ | |
| test, test2 | |
| FROM | |
| log_visit AS log_visit | |
| WHERE | |
| 1=1 | |
| ORDER BY | |
| 5', | |
| 'bind' => array ( | |
| 0 => '2010-03-01 00:00:00', | |
| 1 => '2010-03-31 23:59:59', | |
| 2 => 1, | |
| ), | |
| ); | |
| $this->assertSame($expected, $query); | |
| } | |
| public function testGenerateQueryShouldAddJoinQueryHintAsCommentIfEnabled() | |
| { | |
| DatabaseConfig::setConfigValue('enable_first_table_join_prefix', '1'); | |
| $this->logAggregator->setQueryOriginHint('MyPluginName'); | |
| $query = $this->logAggregator->generateQuery('test, test2', 'log_visit', '1=1', false, '5'); | |
| $expected = [ | |
| 'sql' => 'SELECT /*+ JOIN_PREFIX(log_visit) */ /* sites 1 */ /* 2010-03-01,2010-03-31 */ /* MyPluginName */ | |
| test, test2 | |
| FROM | |
| log_visit AS log_visit | |
| WHERE | |
| 1=1 | |
| ORDER BY | |
| 5', | |
| 'bind' => [ | |
| 0 => '2010-03-01 00:00:00', | |
| 1 => '2010-03-31 23:59:59', | |
| 2 => 1, | |
| ], | |
| ]; | |
| $this->assertSame($expected, $query); | |
| } | |
| public function testQueryVisitsByDimensionShouldAddJoinQueryHintOriginHintMaxExecutionTimeHintIfEnabled() | |
| { | |
| $dimensions = [ | |
| 'CASE WHEN HOUR(log_visit.visit_first_action_time) <= 11 THEN \'l\'' . | |
| 'ELSE \'r\'' . | |
| 'END AS label', | |
| ]; | |
| DatabaseConfig::setConfigValue('enable_first_table_join_prefix', '1'); | |
| DatabaseConfig::setConfigValue('schema', 'Mysql'); | |
| Schema::unsetInstance(); | |
| $this->logAggregator->setQueryOriginHint('MyPluginName'); | |
| $query = $this->logAggregator->getQueryByDimensionSql( | |
| $dimensions, | |
| false, | |
| [], | |
| false, | |
| false, | |
| false, | |
| 5, | |
| false | |
| ); | |
| $expected = [ | |
| 'sql' => "SELECT /*+ MAX_EXECUTION_TIME(5000) JOIN_PREFIX(log_visit) */ /* sites 1 */ /* 2010-03-01,2010-03-31 */ /* MyPluginName */ | |
| CASE WHEN HOUR(log_visit.visit_first_action_time) <= 11 THEN 'l'ELSE 'r'END AS label, | |
| count(distinct log_visit.idvisitor) AS `1`, | |
| count(*) AS `2`, | |
| sum(log_visit.visit_total_actions) AS `3`, | |
| max(log_visit.visit_total_actions) AS `4`, | |
| sum(log_visit.visit_total_time) AS `5`, | |
| sum(case log_visit.visit_total_actions when 1 then 1 when 0 then 1 else 0 end) AS `6`, | |
| sum(case log_visit.visit_goal_converted when 1 then 1 else 0 end) AS `7`, | |
| count(distinct log_visit.user_id) AS `39` | |
| FROM | |
| log_visit AS log_visit | |
| WHERE | |
| log_visit.visit_last_action_time >= ? | |
| AND log_visit.visit_last_action_time <= ? | |
| AND log_visit.idsite IN (?) | |
| GROUP BY | |
| label", | |
| 'bind' => [ | |
| 0 => '2010-03-01 00:00:00', | |
| 1 => '2010-03-31 23:59:59', | |
| 2 => 1, | |
| ], | |
| ]; | |
| $this->assertSame($expected, $query); | |
| } | |
| public function testQueryVisitsByDimensionWithComplexDimensionSelect() | |
| { | |
| $dimensions = [ | |
| 'CASE WHEN HOUR(log_visit.visit_first_action_time) <= 11 THEN \'l\'' . | |
| 'ELSE \'r\'' . | |
| 'END AS label', | |
| ]; | |
| /** @var \Zend_Db_Statement $query */ | |
| $query = $this->logAggregator->queryVisitsByDimension($dimensions); | |
| $result = $query->fetchAll(); | |
| $expected = [ | |
| [ | |
| 'label' => 'l', | |
| 1 => '1', | |
| 2 => '1', | |
| 3 => '7', | |
| 4 => '7', | |
| 5 => '1621', | |
| 6 => '0', | |
| 7 => '1', | |
| 39 => '0', | |
| ], | |
| [ | |
| 'label' => 'r', | |
| 1 => '1', | |
| 2 => '1', | |
| 3 => '1', | |
| 4 => '1', | |
| 5 => '1', | |
| 6 => '1', | |
| 7 => '1', | |
| 39 => '0', | |
| ], | |
| ]; | |
| $this->assertEquals($expected, $result); | |
| } | |
| public function testQueryEcommerceItemsExcludesPricesAboveMaxAllowedRevenueFromRevenueAndPriceMetrics() | |
| { | |
| Db::query( | |
| 'INSERT INTO ' . Common::prefixTable('log_action') . ' (name, hash, type) VALUES (?, ?, ?)', | |
| ['Overflow SKU', 0, Action::TYPE_ECOMMERCE_ITEM_SKU] | |
| ); | |
| $idActionSku = Db::fetchOne('SELECT LAST_INSERT_ID()'); | |
| $logInserter = new LogHelper(); | |
| $visit = $logInserter->insertVisit([ | |
| 'visit_last_action_time' => '2010-03-06 12:00:00', | |
| ]); | |
| $logInserter->insertConversionItem($visit['idvisit'], 'normal-order', [ | |
| 'server_time' => '2010-03-06 12:00:00', | |
| 'idaction_sku' => $idActionSku, | |
| 'price' => 40, | |
| 'quantity' => 4, | |
| ]); | |
| $logInserter->insertConversionItem($visit['idvisit'], 'zero-quantity-order', [ | |
| 'server_time' => '2010-03-06 12:00:00', | |
| 'idaction_sku' => $idActionSku, | |
| 'price' => -50, | |
| 'quantity' => 0, | |
| ]); | |
| // Above the tracking bound but small enough that quantity * price would NOT overflow | |
| // MySQL DOUBLE: must still be excluded so archiving matches what tracking now rejects. | |
| $logInserter->insertConversionItem($visit['idvisit'], 'above-cap-order', [ | |
| 'server_time' => '2010-03-06 12:00:00', | |
| 'idaction_sku' => $idActionSku, | |
| 'price' => GoalManager::MAX_ALLOWED_REVENUE * 2, | |
| 'quantity' => 1, | |
| ]); | |
| // Extreme legacy value whose quantity * price would overflow MySQL DOUBLE (error 1690). | |
| $logInserter->insertConversionItem($visit['idvisit'], 'overflow-order', [ | |
| 'server_time' => '2010-03-06 12:00:00', | |
| 'idaction_sku' => $idActionSku, | |
| 'price' => '1e308', | |
| 'quantity' => 999999, | |
| ]); | |
| $query = $this->logAggregator->queryEcommerceItems('idaction_sku'); | |
| $rows = $query->fetchAll(); | |
| $this->assertCount(1, $rows); | |
| $this->assertSame('Overflow SKU', $rows[0]['label']); | |
| // Only the in-range rows contribute: revenue = 40 * 4 (+ 0 for the zero-quantity row). | |
| $this->assertSame(160.0, (float) $rows[0][Metrics::INDEX_ECOMMERCE_ITEM_REVENUE]); | |
| // The item price metric excludes both out-of-range rows too: price sum = 40 + (-50). | |
| $this->assertSame(-10.0, (float) $rows[0][Metrics::INDEX_ECOMMERCE_ITEM_PRICE]); | |
| } | |
| public function testQueryConversionsByDimensionExcludesOrderRevenueAboveMaxAllowedRevenue() | |
| { | |
| $logInserter = new LogHelper(); | |
| $visit = $logInserter->insertVisit([ | |
| 'visit_last_action_time' => '2010-03-06 12:00:00', | |
| ]); | |
| $conversionDefaults = [ | |
| 'idgoal' => GoalManager::IDGOAL_ORDER, | |
| 'server_time' => '2010-03-06 12:00:00', | |
| ]; | |
| // In-range order: the only conversion whose money values should be counted. | |
| // The log_conversion primary key is (idvisit, idgoal, buster), so each order uses a | |
| // distinct buster to sit in the same idgoal group without colliding. | |
| $logInserter->insertConversion($visit['idvisit'], array_merge($conversionDefaults, [ | |
| 'idorder' => 'normal-order', | |
| 'buster' => 1, | |
| 'revenue' => 100, | |
| 'revenue_subtotal' => 90, | |
| 'revenue_tax' => 5, | |
| 'revenue_shipping' => 4, | |
| 'revenue_discount' => 1, | |
| ])); | |
| // Above the tracking bound but not large enough to overflow on its own: must still be | |
| // excluded so archiving matches what tracking now rejects. | |
| $logInserter->insertConversion($visit['idvisit'], array_merge($conversionDefaults, [ | |
| 'idorder' => 'above-cap-order', | |
| 'buster' => 2, | |
| 'revenue' => GoalManager::MAX_ALLOWED_REVENUE * 2, | |
| 'revenue_subtotal' => GoalManager::MAX_ALLOWED_REVENUE * 2, | |
| 'revenue_tax' => GoalManager::MAX_ALLOWED_REVENUE * 2, | |
| 'revenue_shipping' => GoalManager::MAX_ALLOWED_REVENUE * 2, | |
| 'revenue_discount' => GoalManager::MAX_ALLOWED_REVENUE * 2, | |
| ])); | |
| // Extreme legacy value: an unguarded SUM() would either abort archiving with error 1690 | |
| // (MariaDB / MySQL 8) or silently collapse the whole group to 0 (MySQL 5.7). | |
| $logInserter->insertConversion($visit['idvisit'], array_merge($conversionDefaults, [ | |
| 'idorder' => 'overflow-order', | |
| 'buster' => 3, | |
| 'revenue' => '1e308', | |
| 'revenue_subtotal' => '1e308', | |
| 'revenue_tax' => '1e308', | |
| 'revenue_shipping' => '1e308', | |
| 'revenue_discount' => '1e308', | |
| ])); | |
| $query = $this->logAggregator->queryConversionsByDimension(); | |
| $rows = $query->fetchAll(); | |
| // The fixture also has conversions for other goals; isolate the ecommerce-order group. | |
| $orderRows = array_values(array_filter($rows, static function ($row) { | |
| return (int) $row['idgoal'] === GoalManager::IDGOAL_ORDER; | |
| })); | |
| $this->assertCount(1, $orderRows); | |
| $row = $orderRows[0]; | |
| // Only the in-range order contributes to every order-level money metric. | |
| $this->assertSame(100.0, (float) $row[Metrics::INDEX_GOAL_REVENUE]); | |
| $this->assertSame(90.0, (float) $row[Metrics::INDEX_GOAL_ECOMMERCE_REVENUE_SUBTOTAL]); | |
| $this->assertSame(5.0, (float) $row[Metrics::INDEX_GOAL_ECOMMERCE_REVENUE_TAX]); | |
| $this->assertSame(4.0, (float) $row[Metrics::INDEX_GOAL_ECOMMERCE_REVENUE_SHIPPING]); | |
| $this->assertSame(1.0, (float) $row[Metrics::INDEX_GOAL_ECOMMERCE_REVENUE_DISCOUNT]); | |
| // The three orders are still all counted; only their out-of-range money is excluded. | |
| $this->assertSame(3, (int) $row[Metrics::INDEX_GOAL_NB_CONVERSIONS]); | |
| } | |
| } | |
| LogAggregatorTest::$fixture = new OneVisitorTwoVisits(); | |