| <?php |
|
|
| |
| |
| |
| |
| |
| |
|
|
| namespace Piwik\DataAccess; |
|
|
| use Piwik\Common; |
| use Piwik\Config as PiwikConfig; |
| use Piwik\Container\StaticContainer; |
| use Piwik\Db; |
| use Piwik\Plugin\Dimension\DimensionMetadataProvider; |
| use Piwik\Plugin\LogTablesProvider; |
|
|
| |
| |
| |
| class RawLogDao |
| { |
| public const DELETE_UNUSED_ACTIONS_TEMP_TABLE_NAME = 'tmp_log_actions_to_keep'; |
|
|
| |
| |
| |
| private $dimensionMetadataProvider; |
|
|
| |
| |
| |
| private $logTablesProvider; |
|
|
| public function __construct(?DimensionMetadataProvider $provider = null, ?LogTablesProvider $logTablesProvider = null) |
| { |
| $this->dimensionMetadataProvider = $provider ?: StaticContainer::get('Piwik\Plugin\Dimension\DimensionMetadataProvider'); |
| $this->logTablesProvider = $logTablesProvider ?: StaticContainer::get('Piwik\Plugin\LogTablesProvider'); |
| } |
|
|
| |
| |
| |
| |
| public function updateVisits(array $values, $idVisit) |
| { |
| $sql = "UPDATE " . Common::prefixTable('log_visit') |
| . " SET " . $this->getColumnSetExpressions(array_keys($values)) |
| . " WHERE idvisit = ?"; |
|
|
| $this->update($sql, $values, $idVisit); |
| } |
|
|
| |
| |
| |
| |
| public function updateConversions(array $values, $idVisit) |
| { |
| $sql = "UPDATE " . Common::prefixTable('log_conversion') |
| . " SET " . $this->getColumnSetExpressions(array_keys($values)) |
| . " WHERE idvisit = ?"; |
|
|
| $this->update($sql, $values, $idVisit); |
| } |
|
|
| |
| |
| |
| |
| |
| public function countVisitsWithDatesLimit($from, $to) |
| { |
| $sql = "SELECT COUNT(*) AS num_rows" |
| . " FROM `" . Common::prefixTable('log_visit') . "`" |
| . " WHERE visit_last_action_time >= ? AND visit_last_action_time < ?"; |
|
|
| $bind = array($from, $to); |
|
|
| return (int) Db::fetchOne($sql, $bind); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public function forAllLogs($logTable, $fields, $conditions, $iterationStep, $callback, $willDelete) |
| { |
| $lastId = 0; |
|
|
| if ($willDelete) { |
| |
| |
| |
| $idField = null; |
| $bindFunction = function ($bind, $lastId) { |
| return $bind; |
| }; |
| } else { |
| |
| |
| |
| $idField = $this->getIdFieldForLogTable($logTable); |
| $bindFunction = function ($bind, $lastId) { |
| return array_merge(array($lastId), $bind); |
| }; |
| } |
|
|
| [$query, $bind] = $this->createLogIterationQuery($logTable, $idField, $fields, $conditions, $iterationStep); |
|
|
| do { |
| $rows = Db::fetchAll($query, call_user_func($bindFunction, $bind, $lastId)); |
| if (!empty($rows)) { |
| if ($idField) { |
| $lastId = $rows[count($rows) - 1][$idField]; |
| } |
| $callback($rows); |
| } |
| } while (count($rows) == $iterationStep); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| public function deleteConversionItems($visitIds) |
| { |
| $sql = "DELETE FROM `" . Common::prefixTable('log_conversion_item') . "` WHERE idvisit IN " |
| . $this->getInFieldExpressionWithInts($visitIds); |
|
|
| $statement = Db::query($sql); |
| return $statement->rowCount(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| public function deleteUnusedLogActions() |
| { |
| if (!Db::isLockPrivilegeGranted()) { |
| throw new \Exception("RawLogDao.deleteUnusedLogActions() requires table locking permission in order to complete without error."); |
| } |
|
|
| |
| $maxIds = $this->getMaxIdsInLogTables(); |
|
|
| |
| $max_rows_per_query = PiwikConfig::getInstance()->Deletelogs['delete_logs_unused_actions_max_rows_per_query']; |
|
|
| $this->createTempTableForStoringUsedActions(); |
|
|
| |
| $this->insertActionsToKeep($maxIds, $deleteOlderThanMax = true, $max_rows_per_query); |
|
|
| |
| $this->lockLogTables(); |
| $this->insertActionsToKeep($maxIds, $deleteOlderThanMax = false, $max_rows_per_query); |
|
|
| |
| |
| $this->deleteUnusedActions(); |
|
|
| Db::unlockAllTables(); |
|
|
| $this->dropTempTableForStoringUsedActions(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public function hasSiteVisitsBetweenTimeframe($fromDateTime, $toDateTime, $idSite) |
| { |
| $sites = Db::fetchOne("SELECT 1 |
| FROM `" . Common::prefixTable('log_visit') . "` |
| WHERE idsite = ? |
| AND visit_last_action_time >= ? |
| AND visit_last_action_time <= ? |
| LIMIT 1", array($idSite, $fromDateTime, $toDateTime)); |
|
|
| return (bool) $sites; |
| } |
|
|
| |
| |
| |
| |
| protected function getColumnSetExpressions(array $columnsToSet) |
| { |
| $columnsToSet = array_map( |
| function ($column) { |
| return $column . ' = ?'; |
| }, |
| $columnsToSet |
| ); |
|
|
| return implode(', ', $columnsToSet); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| protected function update($sql, array $values, $idVisit) |
| { |
| return Db::query($sql, array_merge(array_values($values), array($idVisit))); |
| } |
|
|
| protected function getIdFieldForLogTable($logTable) |
| { |
| $idColumns = $this->getTableIdColumns(); |
|
|
| if (isset($idColumns[$logTable])) { |
| return $idColumns[$logTable]; |
| } |
|
|
| throw new \InvalidArgumentException("Unknown log table '$logTable'."); |
| } |
|
|
| |
| |
| private function createLogIterationQuery($logTable, $idField, $fields, $conditions, $iterationStep) |
| { |
| $bind = array(); |
|
|
| $sql = "SELECT " . implode(', ', $fields) . " FROM `" . Common::prefixTable($logTable) . "` WHERE "; |
|
|
| $parts = array(); |
|
|
| if ($idField) { |
| $parts[] = "$idField > ?"; |
| } |
|
|
| foreach ($conditions as $condition) { |
| [$column, $operator, $value] = $condition; |
|
|
| if (is_array($value)) { |
| $parts[] = "$column IN (" . Common::getSqlStringFieldsArray($value) . ")"; |
|
|
| $bind = array_merge($bind, $value); |
| } else { |
| $parts[] = "$column $operator ?"; |
|
|
| $bind[] = $value; |
| } |
| } |
| $sql .= implode(' AND ', $parts); |
|
|
| if ($idField) { |
| $sql .= " ORDER BY $idField ASC"; |
| } |
|
|
| $sql .= " LIMIT " . (int)$iterationStep; |
|
|
| return array($sql, $bind); |
| } |
|
|
| private function getInFieldExpressionWithInts($idVisits) |
| { |
| $sql = "("; |
|
|
| $isFirst = true; |
| foreach ($idVisits as $idVisit) { |
| if ($isFirst) { |
| $isFirst = false; |
| } else { |
| $sql .= ', '; |
| } |
|
|
| $sql .= (int)$idVisit; |
| } |
|
|
| $sql .= ")"; |
|
|
| return $sql; |
| } |
|
|
| protected function getMaxIdsInLogTables() |
| { |
| $idColumns = $this->getTableIdColumns(); |
| $tables = array_keys($idColumns); |
|
|
| $result = array(); |
| foreach ($tables as $table) { |
| $idCol = $idColumns[$table]; |
| $result[$table] = Db::fetchOne("SELECT MAX($idCol) FROM `" . Common::prefixTable($table) . "`"); |
| } |
|
|
| return $result; |
| } |
|
|
| private function createTempTableForStoringUsedActions() |
| { |
| $sql = "CREATE TEMPORARY TABLE " . Common::prefixTable(self::DELETE_UNUSED_ACTIONS_TEMP_TABLE_NAME) . " ( |
| idaction INTEGER(10) UNSIGNED NOT NULL, |
| PRIMARY KEY (idaction) |
| )"; |
| Db::query($sql); |
| } |
|
|
| private function dropTempTableForStoringUsedActions() |
| { |
| $sql = "DROP TABLE " . Common::prefixTable(self::DELETE_UNUSED_ACTIONS_TEMP_TABLE_NAME); |
| Db::query($sql); |
| } |
|
|
| |
| protected function insertActionsToKeep($maxIds, $olderThan = true, $insertIntoTempIterationStep = 100000) |
| { |
| $tempTableName = Common::prefixTable(self::DELETE_UNUSED_ACTIONS_TEMP_TABLE_NAME); |
|
|
| $idColumns = $this->getTableIdColumns(); |
| foreach ($this->dimensionMetadataProvider->getActionReferenceColumnsByTable() as $table => $columns) { |
| $idCol = $idColumns[$table]; |
| |
| $sql = "SELECT " . implode(',', $columns) . " FROM `" . Common::prefixTable($table) . "` WHERE $idCol >= ? AND $idCol < ?"; |
|
|
| if ($olderThan) { |
| |
| $start = (int)Db::fetchOne("SELECT MIN($idCol) FROM `" . Common::prefixTable($table) . "`"); |
| $finish = $maxIds[$table]; |
| } else { |
| $start = $maxIds[$table]; |
| $finish = (int)Db::fetchOne("SELECT MAX($idCol) FROM `" . Common::prefixTable($table) . "`"); |
| } |
| |
| |
| |
|
|
| |
| for ($i = $start; $i <= $finish; $i += $insertIntoTempIterationStep) { |
| $currentParams = array($i, $i + $insertIntoTempIterationStep); |
| $result = Db::fetchAll($sql, $currentParams); |
| |
| $keepValues = []; |
| foreach ($result as $row) { |
| $keepValues = array_merge($keepValues, array_filter(array_values($row), "is_numeric")); |
| if (count($keepValues) >= 1000) { |
| $insert = 'INSERT IGNORE INTO ' . $tempTableName . ' VALUES ('; |
| $insert .= implode('),(', $keepValues); |
| $insert .= ')'; |
|
|
| Db::exec($insert); |
| $keepValues = []; |
| } |
| } |
|
|
| $insert = 'INSERT IGNORE INTO ' . $tempTableName . ' VALUES ('; |
| $insert .= implode('),(', $keepValues); |
| $insert .= ')'; |
|
|
| Db::exec($insert); |
| } |
| } |
| } |
|
|
| private function lockLogTables() |
| { |
| $tables = $this->getTableIdColumns(); |
| unset($tables['log_action']); |
| $tableNames = array_keys($tables); |
|
|
| $readLocks = array(); |
| foreach ($tableNames as $tableName) { |
| $readLocks[] = Common::prefixTable($tableName); |
| } |
|
|
| Db::lockTables( |
| $readLocks, |
| $writeLocks = Common::prefixTables('log_action') |
| ); |
| } |
|
|
| private function deleteUnusedActions() |
| { |
| [$logActionTable, $tempTableName] = Common::prefixTables("log_action", self::DELETE_UNUSED_ACTIONS_TEMP_TABLE_NAME); |
|
|
| $deleteSql = "DELETE LOW_PRIORITY QUICK IGNORE `$logActionTable` |
| FROM `$logActionTable` |
| LEFT JOIN `$tempTableName` tmp ON tmp.idaction = `$logActionTable`.idaction |
| WHERE tmp.idaction IS NULL"; |
|
|
| Db::query($deleteSql); |
| } |
|
|
| protected function getTableIdColumns() |
| { |
| $columns = array(); |
|
|
| foreach ($this->logTablesProvider->getAllLogTables() as $logTable) { |
| $idColumn = $logTable->getIdColumn(); |
|
|
| if (!empty($idColumn)) { |
| $columns[$logTable->getName()] = $idColumn; |
| } |
| } |
|
|
| return $columns; |
| } |
| } |
|
|