| /** | |
| * 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\Tracker; | |
| use Exception; | |
| use Piwik\Common; | |
| use Piwik\Config; | |
| use Piwik\Db\Adapter; | |
| use Piwik\Db\TransactionalDatabaseInterface; | |
| use Piwik\Db\TransactionalDatabaseStaticTrait; | |
| use Piwik\Piwik; | |
| use Piwik\Timer; | |
| use Piwik\Tracker\Db\DbException; | |
| /** | |
| * Simple database wrapper. | |
| * We can't afford to have a dependency with the Zend_Db module in Tracker. | |
| * We wrote this simple class | |
| * | |
| */ | |
| abstract class Db implements TransactionalDatabaseInterface | |
| { | |
| use TransactionalDatabaseStaticTrait; | |
| protected static $profiling = false; | |
| protected $queriesProfiling = array(); | |
| protected $connection = null; | |
| /** | |
| * Enables the SQL profiling. | |
| * For each query, saves in the DB the time spent on this query. | |
| * Very useful to see the slow query under heavy load. | |
| * You can then use Piwik::displayDbTrackerProfile(); | |
| * to display the SQLProfiling report and see which queries take time, etc. | |
| */ | |
| public static function enableProfiling() | |
| { | |
| self::$profiling = true; | |
| } | |
| /** | |
| * Disables the SQL profiling logging. | |
| */ | |
| public static function disableProfiling() | |
| { | |
| self::$profiling = false; | |
| } | |
| /** | |
| * Returns true if the SQL profiler is enabled | |
| * Only used by the unit test that tests that the profiler is off on a production server | |
| * | |
| * @return bool | |
| */ | |
| public static function isProfilingEnabled() | |
| { | |
| return self::$profiling; | |
| } | |
| /** | |
| * Initialize profiler | |
| * | |
| * @return Timer | |
| */ | |
| protected function initProfiler() | |
| { | |
| return new Timer(); | |
| } | |
| /** | |
| * Record query profile | |
| * | |
| * @param string $query | |
| * @param Timer $timer | |
| */ | |
| protected function recordQueryProfile($query, $timer) | |
| { | |
| if (!isset($this->queriesProfiling[$query])) { | |
| $this->queriesProfiling[$query] = array('sum_time_ms' => 0, 'count' => 0); | |
| } | |
| $time = $timer->getTimeMs(2); | |
| $time += $this->queriesProfiling[$query]['sum_time_ms']; | |
| $count = $this->queriesProfiling[$query]['count'] + 1; | |
| $this->queriesProfiling[$query] = array('sum_time_ms' => $time, 'count' => $count); | |
| } | |
| /** | |
| * When destroyed, if SQL profiled enabled, logs the SQL profiling information | |
| */ | |
| public function recordProfiling() | |
| { | |
| if (is_null($this->connection)) { | |
| return; | |
| } | |
| // turn off the profiler so we don't profile the following queries | |
| self::$profiling = false; | |
| foreach ($this->queriesProfiling as $query => $info) { | |
| $time = $info['sum_time_ms']; | |
| $time = Common::forceDotAsSeparatorForDecimalPoint($time); | |
| $count = $info['count']; | |
| $queryProfiling = "INSERT INTO " . Common::prefixTable('log_profiling') . " | |
| (query,count,sum_time_ms) VALUES (?,$count,$time) | |
| ON DUPLICATE KEY UPDATE count=count+$count,sum_time_ms=sum_time_ms+$time"; | |
| $this->query($queryProfiling, array($query)); | |
| } | |
| // turn back on profiling | |
| self::$profiling = true; | |
| } | |
| /** | |
| * Connects to the DB | |
| * | |
| * @throws \Piwik\Tracker\Db\DbException if there was an error connecting the DB | |
| */ | |
| abstract public function connect(); | |
| /** | |
| * Disconnects from the server | |
| */ | |
| public function disconnect() | |
| { | |
| $this->connection = null; | |
| } | |
| /** | |
| * Returns an array containing all the rows of a query result, using optional bound parameters. | |
| * | |
| * @param string $query Query | |
| * @param array $parameters Parameters to bind | |
| * @see query() | |
| * @throws \Piwik\Tracker\Db\DbException if an exception occurred | |
| */ | |
| abstract public function fetchAll($query, $parameters = array()); | |
| /** | |
| * Returns the first row of a query result, using optional bound parameters. | |
| * | |
| * @param string $query Query | |
| * @param array $parameters Parameters to bind | |
| * @see also query() | |
| * | |
| * @throws DbException if an exception occurred | |
| */ | |
| abstract public function fetch($query, $parameters = array()); | |
| /** | |
| * This function is a proxy to fetch(), used to maintain compatibility with Zend_Db interface | |
| * | |
| * @see fetch() | |
| * @param string $query Query | |
| * @param array $parameters Parameters to bind | |
| * @return array|false | |
| */ | |
| public function fetchRow($query, $parameters = array()) | |
| { | |
| return $this->fetch($query, $parameters); | |
| } | |
| /** | |
| * This function is a proxy to fetch(), used to maintain compatibility with Zend_Db interface | |
| * | |
| * @see fetch() | |
| * @param string $query Query | |
| * @param array $parameters Parameters to bind | |
| * @return bool|mixed | |
| */ | |
| public function fetchOne($query, $parameters = array()) | |
| { | |
| $result = $this->fetch($query, $parameters); | |
| return is_array($result) && !empty($result) ? reset($result) : false; | |
| } | |
| /** | |
| * This function is a proxy to fetch(), used to maintain compatibility with Zend_Db + PDO interface | |
| * | |
| * @see fetch() | |
| * @param string $query Query | |
| * @param array $parameters Parameters to bind | |
| * @return array|false | |
| */ | |
| public function exec($query, $parameters = array()) | |
| { | |
| return $this->fetch($query, $parameters); | |
| } | |
| /** | |
| * Return number of affected rows in last query | |
| * | |
| * @param mixed $queryResult Result from query() | |
| * @return int | |
| */ | |
| abstract public function rowCount($queryResult); | |
| /** | |
| * Executes a query, using optional bound parameters. | |
| * | |
| * @param string $query Query | |
| * @param array $parameters Parameters to bind array('idsite'=> 1) | |
| * | |
| * @return PDOStatement or false if failed | |
| * @throws DbException if an exception occurred | |
| */ | |
| abstract public function query($query, $parameters = array()); | |
| /** | |
| * Returns the last inserted ID in the DB | |
| * Wrapper of PDO::lastInsertId() | |
| * | |
| * @return int | |
| */ | |
| abstract public function lastInsertId(); | |
| /** | |
| * Test error number | |
| * | |
| * @param Exception $e | |
| * @param string|int $errno | |
| * @return bool True if error number matches; false otherwise | |
| */ | |
| abstract public function isErrNo($e, $errno); | |
| /** | |
| * Factory to create database objects | |
| * | |
| * @param array $configDb Database configuration | |
| * @throws Exception | |
| * @return \Piwik\Tracker\Db\Mysqli|\Piwik\Tracker\Db\Pdo\Mysql | |
| */ | |
| public static function factory($configDb) | |
| { | |
| /** | |
| * Triggered before a connection to the database is established by the Tracker. | |
| * | |
| * This event can be used to change the database connection settings used by the Tracker. | |
| * | |
| * @param array $dbInfos Reference to an array containing database connection info, | |
| * including: | |
| * | |
| * - **host**: The host name or IP address to the MySQL database. | |
| * - **username**: The username to use when connecting to the | |
| * database. | |
| * - **password**: The password to use when connecting to the | |
| * database. | |
| * - **dbname**: The name of the Piwik MySQL database. | |
| * - **port**: The MySQL database port to use. | |
| * - **adapter**: either `'PDO\MYSQL'` or `'MYSQLI'` | |
| * - **type**: The MySQL engine to use, for instance 'InnoDB' | |
| */ | |
| Piwik::postEvent('Tracker.getDatabaseConfig', array(&$configDb)); | |
| $className = 'Piwik\Tracker\Db\\' . str_replace(' ', '\\', ucwords(str_replace(array('_', '\\'), ' ', strtolower($configDb['adapter'])))); | |
| if (!class_exists($className)) { | |
| throw new Exception('Unsupported database adapter ' . $configDb['adapter']); | |
| } | |
| return new $className($configDb); | |
| } | |
| public static function connectPiwikTrackerDb() | |
| { | |
| $db = null; | |
| $configDb = Config::getInstance()->database; | |
| if (!isset($configDb['port'])) { | |
| // before 0.2.4 there is no port specified in config file | |
| $configDb['port'] = '3306'; | |
| } | |
| $db = self::factory($configDb); | |
| try { | |
| $db->connect(); | |
| } catch (\Exception $e) { | |
| $msg = Adapter::overriddenExceptionMessage($e->getMessage()); | |
| if ('' !== $msg) { | |
| throw new \Exception($msg); | |
| } | |
| throw $e; | |
| } | |
| $trackerConfig = Config::getInstance()->Tracker; | |
| if (!empty($trackerConfig['innodb_lock_wait_timeout']) && $trackerConfig['innodb_lock_wait_timeout'] > 0) { | |
| // we set this here because we only want to set this config if a connection is actually created. | |
| $time = (int) $trackerConfig['innodb_lock_wait_timeout']; | |
| $db->query('SET @@innodb_lock_wait_timeout = ' . $time); | |
| } | |
| return $db; | |
| } | |
| } | |