diff options
author | robocoder <anthon.pang@gmail.com> | 2009-09-18 03:29:27 +0400 |
---|---|---|
committer | robocoder <anthon.pang@gmail.com> | 2009-09-18 03:29:27 +0400 |
commit | 0494a63c73790b3d62788e3f5f6eefca468ea22b (patch) | |
tree | 471116a26dd71c2a0208dddb94e6fc2931f73318 /core | |
parent | fd16c9ce87e010d1e5652c362e23ae05b41fd854 (diff) |
fixes #904 - MySQL error codes; unsupported adapters can map these to driver-specific SQLSTATE (see example)
fixes #980 - Piwik Installation support for "MySQL Improved" (mysqli) extension
fixes #984 - Set client connection charset to utf8.
Fixed tracker profiling data not recorded until after report generated.
More refactoring and database abstraction:
- Installation gets a list of adapters instead of hardcoding in the plugin
- checking for database-specific system requirements deferred to the adapter
- error detection moved to adapter but we still use MySQL error codes rather than defining new constants
Note: unit tests don't run with MYSQLI -- Zend Framework's Mysqli adapater doesn't support prepare() yet
git-svn-id: http://dev.piwik.org/svn/trunk@1473 59fd770c-687e-43c8-a1e3-f5a4ff64c105
Diffstat (limited to 'core')
-rw-r--r-- | core/Archive/Single.php | 14 | ||||
-rw-r--r-- | core/ArchiveProcessing.php | 21 | ||||
-rw-r--r-- | core/Db.php | 85 | ||||
-rw-r--r-- | core/Db/Mysqli.php | 64 | ||||
-rw-r--r-- | core/Db/Pdo/Mysql.php | 86 | ||||
-rw-r--r-- | core/Db/Pdo/Pgsql.php | 147 | ||||
-rw-r--r-- | core/Piwik.php | 44 | ||||
-rw-r--r-- | core/Tracker.php | 2 | ||||
-rw-r--r-- | core/Tracker/Action.php | 4 | ||||
-rw-r--r-- | core/Tracker/Db.php | 26 | ||||
-rw-r--r-- | core/Tracker/Db/Mysqli.php | 98 | ||||
-rw-r--r-- | core/Tracker/Db/Pdo/Mysql.php | 34 | ||||
-rw-r--r-- | core/Tracker/Db/Pdo/Pgsql.php | 82 | ||||
-rw-r--r-- | core/Tracker/GoalManager.php | 2 | ||||
-rw-r--r-- | core/Tracker/Visit.php | 7 | ||||
-rw-r--r-- | core/Updater.php | 5 | ||||
-rw-r--r-- | core/Updates/0.4.2.php | 4 | ||||
-rw-r--r-- | core/Updates/0.4.3.php | 14 |
18 files changed, 671 insertions, 68 deletions
diff --git a/core/Archive/Single.php b/core/Archive/Single.php index 52b3f7c7e9..464ecf7f31 100644 --- a/core/Archive/Single.php +++ b/core/Archive/Single.php @@ -255,7 +255,7 @@ class Piwik_Archive_Single extends Piwik_Archive } // uncompress when selecting from the BLOB table - if($typeValue == 'blob') + if($typeValue == 'blob' && $db->hasBlobDataType()) { $value = gzuncompress($value); } @@ -331,6 +331,7 @@ class Piwik_Archive_Single extends Piwik_Archive $tableBlob = $this->archiveProcessing->getTableArchiveBlobName(); $db = Zend_Registry::get('db'); + $hasBlobs = $db->hasBlobDataType(); $query = $db->query("SELECT value, name FROM $tableBlob WHERE idarchive = ? @@ -342,8 +343,15 @@ class Piwik_Archive_Single extends Piwik_Archive { $value = $row['value']; $name = $row['name']; - - $this->blobCached[$name] = gzuncompress($value); + + if($hasBlobs) + { + $this->blobCached[$name] = gzuncompress($value); + } + else + { + $this->blobCached[$name] = $value; + } } } diff --git a/core/ArchiveProcessing.php b/core/ArchiveProcessing.php index c46b2794d7..91eed568a4 100644 --- a/core/ArchiveProcessing.php +++ b/core/ArchiveProcessing.php @@ -101,6 +101,13 @@ abstract class Piwik_ArchiveProcessing * @var int */ protected $maxTimestampArchive; + + /** + * Compress blobs + * + * @var bool + */ + protected $compressBlob; /** * Id of the current site @@ -263,6 +270,9 @@ abstract class Piwik_ArchiveProcessing $this->maxTimestampArchive = Piwik_Date::today()->getTimestamp(); } } + + $db = Zend_Registry::get('db'); + $this->compressBlob = $db->hasBlobDataType(); } /** @@ -466,7 +476,16 @@ abstract class Piwik_ArchiveProcessing destroy($records); return true; } - $record = new Piwik_ArchiveProcessing_Record_Blob($name, $value); + + if($this->compressBlob) + { + $record = new Piwik_ArchiveProcessing_Record_Blob($name, $value); + } + else + { + $record = new Piwik_ArchiveProcessing_Record($name, $value); + } + $this->insertRecord($record); destroy($record); return true; diff --git a/core/Db.php b/core/Db.php index 19f23b3db7..7d26980e6d 100644 --- a/core/Db.php +++ b/core/Db.php @@ -17,6 +17,7 @@ class Piwik_Db { /** * Create adapter + * * @return mixed (Piwik_Db_Mysqli, Piwik_Db_Pdo_Mysql, etc) */ public static function factory($adapterName, $config) @@ -25,4 +26,88 @@ class Piwik_Db $adapter = new $adapterName($config); return $adapter; } + + /* + * Recursive glob() + * + * @return array + */ + private static function globr($sDir, $sPattern, $nFlags = NULL) + { + $sDir = escapeshellcmd($sDir); + $aFiles = glob("$sDir/$sPattern", $nFlags); + foreach (glob("$sDir/*", GLOB_ONLYDIR) as $sSubDir) + { + $aSubFiles = self::globr($sSubDir, $sPattern, $nFlags); + $aFiles = array_merge($aFiles, $aSubFiles); + } + return $aFiles; + } + + /** + * Get list of adapters + * + * @return array + */ + public static function getAdapters() + { + $path = PIWIK_INCLUDE_PATH . '/core/Db'; + $pathLength = strlen($path) + 1; + $adapters = self::globr($path, '*.php'); + $adapterNames = array(); + foreach($adapters as $adapter) + { + $adapterName = str_replace('/', '_', substr($adapter, $pathLength, -strlen('.php'))); + $className = 'Piwik_Db_'.$adapterName; + if(call_user_func(array($className, 'isEnabled'))) + { + $adapterNames[strtoupper($adapterName)] = call_user_func(array($className, 'getDefaultPort')); + } + } + return $adapterNames; + } +} + +interface Piwik_Db_iAdapter +{ + /** + * Reset the configuration variables in this adapter. + */ + public function resetConfig(); + + /** + * Return default port. + * + * @return int + */ + public static function getDefaultPort(); + + /** + * Check database server version + * + * @throws Exception if database version is less than required version + */ + public function checkServerVersion(); + + /** + * Returns true if this adapter's required extensions are enabled + * + * @return bool + */ + public static function isEnabled(); + + /** + * Returns true if this adapter supports blobs as fields + * + * @return bool + */ + public function hasBlobDataType(); + + /** + * Test error number + * + * @param string $errno + * @return bool + */ + public function isErrNo($errno); } diff --git a/core/Db/Mysqli.php b/core/Db/Mysqli.php index c0f32becd1..4b5dd09d09 100644 --- a/core/Db/Mysqli.php +++ b/core/Db/Mysqli.php @@ -13,8 +13,14 @@ /** * @package Piwik */ -class Piwik_Db_Mysqli extends Zend_Db_Adapter_Mysqli +class Piwik_Db_Mysqli extends Zend_Db_Adapter_Mysqli implements Piwik_Db_iAdapter { + public function __construct($config) + { + $config['charset'] = 'utf8'; + parent::__construct($config); + } + /** * Reset the configuration variables in this adapter. */ @@ -22,4 +28,60 @@ class Piwik_Db_Mysqli extends Zend_Db_Adapter_Mysqli { $this->_config = array(); } + + /** + * Return default port. + * + * @return int + */ + public static function getDefaultPort() + { + return 3306; + } + + /** + * Check MySQL version + */ + public function checkServerVersion() + { +// $databaseVersion = $this->getServerVersion(); + $databaseVersion = $this->fetchOne('SELECT VERSION()', array()); + $requiredVersion = Zend_Registry::get('config')->General->minimum_mysql_version; + if(version_compare($databaseVersion, $requiredVersion) === -1) + { + throw new Exception(Piwik_TranslateException('Core_ExceptionDatabaseVersion', array('MySQL', $databaseVersion, $requiredVersion))); + } + } + + /** + * Returns true if this adapter's required extensions are enabled + * + * @return bool + */ + public static function isEnabled() + { + $extensions = @get_loaded_extensions(); + return in_array('mysqli', $extensions) && function_exists('mysqli_set_charset'); + } + + /** + * Returns true if this adapter supports blobs as fields + * + * @return bool + */ + public function hasBlobDataType() + { + return true; + } + + /** + * Test error number + * + * @param string $errno + * @return bool + */ + public function isErrNo($errno) + { + return mysqli_errno($this->_connection) == $errno; + } } diff --git a/core/Db/Pdo/Mysql.php b/core/Db/Pdo/Mysql.php index 0daac70d6a..5d6f1241ce 100644 --- a/core/Db/Pdo/Mysql.php +++ b/core/Db/Pdo/Mysql.php @@ -13,8 +13,35 @@ /** * @package Piwik */ -class Piwik_Db_Pdo_Mysql extends Zend_Db_Adapter_Pdo_Mysql +class Piwik_Db_Pdo_Mysql extends Zend_Db_Adapter_Pdo_Mysql implements Piwik_Db_iAdapter { + public function __construct($config) + { + $config['driver_options'] = array(PDO::MYSQL_ATTR_INIT_COMMAND, "SET NAMES 'utf8'"); + parent::__construct($config); + } + + /** + * Returns connection handle + * + * @return resource + */ + public function getConnection() + { + if($this->_connection) + { + return $this->_connection; + } + + $this->_connect(); + + // see http://framework.zend.com/issues/browse/ZF-1398 + $this->_connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true); + $this->_connection->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true); + + return $this->_connection; + } + /** * Reset the configuration variables in this adapter. */ @@ -22,4 +49,61 @@ class Piwik_Db_Pdo_Mysql extends Zend_Db_Adapter_Pdo_Mysql { $this->_config = array(); } + + /** + * Return default port. + * + * @return int + */ + public static function getDefaultPort() + { + return 3306; + } + + /** + * Check MySQL version + */ + public function checkServerVersion() + { +// $databaseVersion = $this->getServerVersion(); + $databaseVersion = $this->fetchOne('SELECT VERSION()', array()); + $requiredVersion = Zend_Registry::get('config')->General->minimum_mysql_version; + if(version_compare($databaseVersion, $requiredVersion) === -1) + { + throw new Exception(Piwik_TranslateException('Core_ExceptionDatabaseVersion', array('MySQL', $databaseVersion, $requiredVersion))); + } + } + + /** + * Returns true if this adapter's required extensions are enabled + * + * @return bool + */ + public static function isEnabled() + { + $extensions = @get_loaded_extensions(); + return in_array('PDO', $extensions) && in_array('pdo_mysql', $extensions); + } + + /** + * Returns true if this adapter supports blobs as fields + * + * @return bool + */ + public function hasBlobDataType() + { + return true; + } + + /** + * Test error number + * + * @param string $errno + * @return bool + */ + public function isErrNo($errno) + { + $errInfo = $this->errorInfo(); + return $errInfo[1] == $errno; + } } diff --git a/core/Db/Pdo/Pgsql.php b/core/Db/Pdo/Pgsql.php new file mode 100644 index 0000000000..511bbab56d --- /dev/null +++ b/core/Db/Pdo/Pgsql.php @@ -0,0 +1,147 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later + * @version $Id$ + * + * @category Piwik + * @package Piwik + */ + +/** + * @package Piwik + */ +class Piwik_Db_Pdo_Pgsql extends Zend_Db_Adapter_Pdo_Pgsql implements Piwik_Db_iAdapter +{ + /** + * Reset the configuration variables in this adapter. + */ + public function resetConfig() + { + $this->_config = array(); + } + + /** + * Return default port. + * + * @return int + */ + public static function getDefaultPort() + { + return 5432; + } + + /** + * Check PostgreSQL version + */ + public function checkServerVersion() + { +// $databaseVersion = $this->getServerVersion(); + $databaseVersion = $this->fetchOne('show server_version', array()); + $requiredVersion = Zend_Registry::get('config')->General->minimum_pgsql_version; + if(version_compare($databaseVersion, $requiredVersion) === -1) + { + throw new Exception(Piwik_TranslateException('Core_ExceptionDatabaseVersion', array('PostgreSQL', $databaseVersion, $requiredVersion))); + } + } + + /** + * Returns true if this adapter's required extensions are enabled + * + * @return bool + */ + public static function isEnabled() + { + /** + * @todo This adapter is incomplete. + */ + return false; + $extensions = @get_loaded_extensions(); + return in_array('PDO', $extensions) && in_array('pdo_pgsql', $extensions); + } + + /** + * Returns true if this adapter supports blobs as fields + * + * @return bool + */ + public function hasBlobDataType() + { + // large objects must be loaded from a file using a non-SQL API + // and then referenced by the object ID (oid); + // the alternative, bytea fields, incur a space and time + // penalty for encoding/decoding + return false; + } + + /** + * Pre-process SQL to handle MySQL-isms + * + * @return string + */ + public function preprocessSql($query) + { + $search = array( + // In MySQL, OPTION is still a reserved keyword; Piwik uses + // backticking in case table_prefix is empty. + '`', + + // MySQL implicitly does 'ORDER BY column' when there's a + // 'GROUP BY column'; Piwik uses 'ORDER BY NULL' when order + // doesn't matter, for better performance. + 'ORDER BY NULL', + ); + + $replace = array( + '', + '', + ); + + $query = str_replace($search, $replace, $query); + } + + /** + * Test error number + * + * @param string $errno + * @return bool + */ + public function isErrNo($errno) + { + // map MySQL driver-specific error codes to PostgreSQL SQLSTATE + $map = array( + // MySQL: Unknown database '%s' + // PostgreSQL: database "%s" does not exist + '1049' => '08006', + + // MySQL: Table '%s' already exists + // PostgreSQL: relation "%s" already exists + '1050' => '42P07', + + // MySQL: Unknown column '%s' in '%s' + // PostgreSQL: column "%s" does not exist + '1054' => '42703', + + // MySQL: Duplicate column name '%s' + // PostgreSQL: column "%s" of relation "%s" already exists + '1060' => '42701', + + // MySQL: Duplicate entry '%s' for key '%s' + // PostgreSQL: duplicate key violates unique constraint + '1062' => '23505', + + // MySQL: Can't DROP '%s'; check that column/key exists + // PostgreSQL: index "%s" does not exist + '1091' => '42704', + + // MySQL: Table '%s.%s' doesn't exist + // PostgreSQL: relation "%s" does not exist + '1146' => '42P01', + ); + + $errInfo = $this->errorInfo(); + return $errInfo[0] == $map[$errno]; + } +} diff --git a/core/Piwik.php b/core/Piwik.php index 257417329c..1a66d9d719 100644 --- a/core/Piwik.php +++ b/core/Piwik.php @@ -246,17 +246,16 @@ class Piwik if(is_null($db)) { - $db = Zend_Registry::get('db'); + $db = Piwik_Tracker::getDatabase(); } $tableName = Piwik_Common::prefixTable('log_profiling'); - $all = $db->fetchAll('SELECT *, sum_time_ms / count as avg_time_ms - FROM '.$tableName ); + $all = $db->fetchAll('SELECT * FROM '.$tableName ); if($all === false) { return; } - usort($all, 'maxSumMsFirst'); + uasort($all, 'maxSumMsFirst'); $infoIndexedByQuery = array(); foreach($all as $infoQuery) @@ -298,6 +297,7 @@ class Piwik 'sumTimeMs' => $existing['count'] + $query->getElapsedSecs() * 1000); $infoIndexedByQuery[$query->getQuery()] = $new; } + if(!function_exists('sortTimeDesc')) { function sortTimeDesc($a,$b) @@ -341,9 +341,7 @@ class Piwik $avgTimeMs = $timeMs / $count; $avgTimeString = " (average = <b>". round($avgTimeMs,1) . "ms</b>)"; } - $query = str_replace(array("\t","\n","\r\n","\r"), "_toberemoved_", $query); - $query = str_replace('_toberemoved__toberemoved_','',$query); - $query = str_replace('_toberemoved_', ' ',$query); + $query = preg_replace('/([\t\n\r ]+)/', ' ', $query); $output .= "Executed <b>$count</b> time". ($count==1?'':'s') ." in <b>".$timeMs."ms</b> $avgTimeString <pre>\t$query</pre>"; } Piwik::log($output); @@ -1385,7 +1383,7 @@ class Piwik $tablesInstalled = array_intersect($allMyTables, $allTables); // at this point we have only the piwik tables which is good - // but we still miss the piwik generated tables (using the class Piwik_TablePartitioning)` + // but we still miss the piwik generated tables (using the class Piwik_TablePartitioning) $idSiteInSql = "no"; if(!is_null($idSite)) { @@ -1439,16 +1437,14 @@ class Piwik unset($dbInfos['host']); unset($dbInfos['port']); } + + // not used by Zend Framework + unset($dbInfos['tables_prefix']); + unset($dbInfos['adapter']); + $db = Piwik_Db::factory($config->database->adapter, $dbInfos); $db->getConnection(); - if($config->database->adapter == 'PDO_MYSQL') - { - // see http://framework.zend.com/issues/browse/ZF-1398 - $db->getConnection()->setAttribute(PDO::ATTR_EMULATE_PREPARES, true); - $db->getConnection()->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true); - } - Zend_Db_Table::setDefaultAdapter($db); $db->resetConfig(); // we don't want this information to appear in the logs } @@ -1460,11 +1456,29 @@ class Piwik Zend_Registry::get('db')->closeConnection(); } + /** + * Returns the MySQL database server version + * + * @deprecated 0.4.4 + */ static public function getMysqlVersion() { return Piwik_FetchOne("SELECT VERSION()"); } + /** + * Checks the database server version against the required minimum + * version. + * + * @see config/global.ini.php + * @since 0.4.4 + * @throws Exception if server version is less than the required version + */ + static public function checkDatabaseVersion() + { + Zend_Registry::get('db')->checkServerVersion(); + } + static public function createLogObject() { $configAPI = Zend_Registry::get('config')->log; diff --git a/core/Tracker.php b/core/Tracker.php index 88b4bedc6d..e49ecc06c2 100644 --- a/core/Tracker.php +++ b/core/Tracker.php @@ -71,6 +71,7 @@ class Piwik_Tracker } catch(Piwik_Tracker_Visit_Excluded $e) { } } + $this->end(); } @@ -130,6 +131,7 @@ class Piwik_Tracker if($GLOBALS['PIWIK_TRACKER_DEBUG'] === true) { + self::$db->recordProfiling(); Piwik::printSqlProfilingReportTracker(self::$db); } diff --git a/core/Tracker/Action.php b/core/Tracker/Action.php index 1cbb72156c..5afe0738cf 100644 --- a/core/Tracker/Action.php +++ b/core/Tracker/Action.php @@ -138,10 +138,10 @@ class Piwik_Tracker_Action implements Piwik_Tracker_Action_Interface ); // the action name has not been found, create it - if($idAction === false) + if($idAction === false || $idAction == '') { Piwik_Tracker::getDatabase()->query("/* SHARDING_ID_SITE = ".$this->idSite." */ - INSERT INTO ". Piwik_Common::prefixTable('log_action'). "( name, type ) + INSERT INTO ". Piwik_Common::prefixTable('log_action'). " ( name, type ) VALUES (?,?)", array($this->getActionName(),$this->getActionType()) ); diff --git a/core/Tracker/Db.php b/core/Tracker/Db.php index 6616e74c74..193c1a71a5 100644 --- a/core/Tracker/Db.php +++ b/core/Tracker/Db.php @@ -31,7 +31,7 @@ abstract class Piwik_Tracker_Db * You can then use Piwik::printSqlProfilingReportTracker(); * to display the SQLProfiling report and see which queries take time, etc. */ - public function enableProfiling() + public static function enableProfiling() { self::$profiling = true; } @@ -39,7 +39,7 @@ abstract class Piwik_Tracker_Db /** * Disables the SQL profiling logging. */ - public function disableProfiling() + public static function disableProfiling() { self::$profiling = false; } @@ -121,10 +121,6 @@ abstract class Piwik_Tracker_Db */ public function disconnect() { - if(self::$profiling) - { - $this->recordProfiling(); - } $this->connection = null; } @@ -159,6 +155,14 @@ abstract class Piwik_Tracker_Db } /** + * 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 @@ -176,4 +180,12 @@ abstract class Piwik_Tracker_Db * @return int */ abstract public function lastInsertId(); - } + + /** + * Test error number + * + * @param string $errno + * @return bool True if error number matches; false otherwise + */ + abstract public function isErrNo($errno); +} diff --git a/core/Tracker/Db/Mysqli.php b/core/Tracker/Db/Mysqli.php index 61454c4be1..90c70e7e45 100644 --- a/core/Tracker/Db/Mysqli.php +++ b/core/Tracker/Db/Mysqli.php @@ -18,8 +18,10 @@ */ class Piwik_Tracker_Db_Mysqli extends Piwik_Tracker_Db { - private $connection = null; + protected $connection = null; private $host; + private $port; + private $socket; private $dbname; private $username; private $password; @@ -31,15 +33,21 @@ class Piwik_Tracker_Db_Mysqli extends Piwik_Tracker_Db { if(isset($dbInfo['unix_socket']) && $dbInfo['unix_socket'][0] == '/') { - $this->host = ':' . $dbInfo['unix_socket']; + $this->host = null; + $this->port = null; + $this->socket = $dbInfo['unix_socket']; } else if ($dbInfo['port'][0] == '/') { - $this->host = ':' . $dbInfo['port']; + $this->host = null; + $this->port = null; + $this->socket = $dbInfo['port']; } else { - $this->host = $dbInfo['host'] . ':' . $dbInfo['port']; + $this->host = $dbInfo['host']; + $this->port = $dbInfo['port']; + $this->socket = null; } $this->dbname = $dbInfo['dbname']; $this->username = $dbInfo['username']; @@ -63,8 +71,16 @@ class Piwik_Tracker_Db_Mysqli extends Piwik_Tracker_Db $timer = $this->initProfiler(); } - $this->connection = mysql_connect($this->host, $this->username, $this->password); - $result = mysql_select_db($this->dbname); + $this->connection = mysqli_connect($this->host, $this->username, $this->password, $this->dbname, $this->port, $this->socket); + if(!$this->connection || mysqli_connect_errno()) + { + throw new Exception("Connect failed: " . mysqli_connect_error()); + } + + if(!mysqli_set_charset($this->connection, 'utf8')) + { + throw new Exception("Set Charset failed: " . mysqli_error($this->connection)); + } $this->password = ''; @@ -79,10 +95,7 @@ class Piwik_Tracker_Db_Mysqli extends Piwik_Tracker_Db */ public function disconnect() { - if(self::$profiling) - { - $this->recordProfiling(); - } + mysqli_close($this->connection); $this->connection = null; } @@ -97,12 +110,23 @@ class Piwik_Tracker_Db_Mysqli extends Piwik_Tracker_Db public function fetchAll( $query, $parameters = array() ) { try { + if(self::$profiling) + { + $timer = $this->initProfiler(); + } + $query = $this->prepare( $query, $parameters ); - $rs = mysql_query($query); - while($row = mysql_fetch_array($rs, MYSQL_ASSOC)) + $rs = mysqli_query($this->connection, $query); + while($row = mysqli_fetch_array($rs, MYSQL_ASSOC)) { $rows[] = $row; } + mysqli_free_result($rs); + + if(self::$profiling) + { + $this->recordQueryProfile($query, $timer); + } return $rows; } catch (Exception $e) { throw new Exception("Error query: ".$e->getMessage()); @@ -121,13 +145,24 @@ class Piwik_Tracker_Db_Mysqli extends Piwik_Tracker_Db public function fetch( $query, $parameters = array() ) { try { + if(self::$profiling) + { + $timer = $this->initProfiler(); + } + $query = $this->prepare( $query, $parameters ); - $rs = mysql_query($query); + $rs = mysqli_query($this->connection, $query); if($rs === false) { return false; } - $row = mysql_fetch_array($rs, MYSQL_ASSOC); + $row = mysqli_fetch_array($rs, MYSQL_ASSOC); + mysqli_free_result($rs); + + if(self::$profiling) + { + $this->recordQueryProfile($query, $timer); + } return $row; } catch (Exception $e) { throw new Exception("Error query: ".$e->getMessage()); @@ -149,6 +184,7 @@ class Piwik_Tracker_Db_Mysqli extends Piwik_Tracker_Db { return false; } + try { if(self::$profiling) { @@ -160,7 +196,11 @@ class Piwik_Tracker_Db_Mysqli extends Piwik_Tracker_Db $parameters = array( $parameters ); } $query = $this->prepare( $query, $parameters ); - $result = mysql_query($query); + $result = mysqli_query($this->connection, $query); + if(!is_bool($result)) + { + mysqli_free_result($result); + } if(self::$profiling) { @@ -173,7 +213,7 @@ class Piwik_Tracker_Db_Mysqli extends Piwik_Tracker_Db Parameters: ".var_export($parameters, true)); } } - + /** * Returns the last inserted ID in the DB * @@ -181,7 +221,7 @@ class Piwik_Tracker_Db_Mysqli extends Piwik_Tracker_Db */ public function lastInsertId() { - return mysql_insert_id(); + return mysqli_insert_id($this->connection); } /** @@ -199,7 +239,29 @@ class Piwik_Tracker_Db_Mysqli extends Piwik_Tracker_Db } $query = str_replace('?', "'%s'", $query); array_unshift($parameters, $query); - $query = call_user_func_array(sprintf, $parameters); + $query = call_user_func_array('sprintf', $parameters); return $query; } + + /** + * Test error number + * + * @param string $errno + * @return bool + */ + public function isErrNo($errno) + { + return mysqli_errno($this->_connection) == $errno; + } + + /** + * Return number of affected rows in last query + * + * @param mixed $queryResult Result from query() + * @return int + */ + public function rowCount($queryResult) + { + return mysqli_affected_rows($this->connection); + } } diff --git a/core/Tracker/Db/Pdo/Mysql.php b/core/Tracker/Db/Pdo/Mysql.php index 5c67be8973..bb8ced6da9 100644 --- a/core/Tracker/Db/Pdo/Mysql.php +++ b/core/Tracker/Db/Pdo/Mysql.php @@ -18,7 +18,7 @@ */ class Piwik_Tracker_Db_Pdo_Mysql extends Piwik_Tracker_Db { - private $connection = null; + protected $connection = null; private $dsn; private $username; private $password; @@ -60,8 +60,10 @@ class Piwik_Tracker_Db_Pdo_Mysql extends Piwik_Tracker_Db { $timer = $this->initProfiler(); } + + $config = array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'"); - $this->connection = new PDO($this->dsn, $this->username, $this->password); + $this->connection = new PDO($this->dsn, $this->username, $this->password, $config); $this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // we may want to setAttribute(PDO::ATTR_TIMEOUT ) to a few seconds (default is 60) in case the DB is locked // the piwik.php would stay waiting for the database... bad! @@ -79,10 +81,6 @@ class Piwik_Tracker_Db_Pdo_Mysql extends Piwik_Tracker_Db */ public function disconnect() { - if(self::$profiling) - { - $this->recordProfiling(); - } $this->connection = null; } @@ -146,6 +144,7 @@ class Piwik_Tracker_Db_Pdo_Mysql extends Piwik_Tracker_Db { return false; } + try { if(self::$profiling) { @@ -181,4 +180,27 @@ class Piwik_Tracker_Db_Pdo_Mysql extends Piwik_Tracker_Db { return $this->connection->lastInsertId(); } + + /** + * Test error number + * + * @param string $errno + * @return bool + */ + public function isErrNo($errno) + { + $errInfo = $this->errorInfo(); + return $errInfo[1] == $errno; + } + + /** + * Return number of affected rows in last query + * + * @param mixed $queryResult Result from query() + * @return int + */ + public function rowCount($queryResult) + { + return $queryResult->rowCount(); + } } diff --git a/core/Tracker/Db/Pdo/Pgsql.php b/core/Tracker/Db/Pdo/Pgsql.php new file mode 100644 index 0000000000..d415cbd632 --- /dev/null +++ b/core/Tracker/Db/Pdo/Pgsql.php @@ -0,0 +1,82 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later + * @version $Id$ + * + * @category Piwik + * @package Piwik + */ + +/** + * PDO PostgreSQL wrapper + * + * @package Piwik + * @subpackage Piwik_Tracker + */ +class Piwik_Tracker_Db_Pdo_Pgsql extends Piwik_Tracker_Db_Pdo_Mysql +{ + /** + * Builds the DB object + */ + public function __construct( $dbInfo, $driverName = 'pgsql') + { + parent::__construct( $dbInfo, $driverName ); + } + + /** + * Test error number + * + * @param string $errno + * @return bool + */ + public function isErrNo($errno) + { + // map MySQL driver-specific error codes to PostgreSQL SQLSTATE + $map = array( + // MySQL: Unknown database '%s' + // PostgreSQL: database "%s" does not exist + '1049' => '08006', + + // MySQL: Table '%s' already exists + // PostgreSQL: relation "%s" already exists + '1050' => '42P07', + + // MySQL: Unknown column '%s' in '%s' + // PostgreSQL: column "%s" does not exist + '1054' => '42703', + + // MySQL: Duplicate column name '%s' + // PostgreSQL: column "%s" of relation "%s" already exists + '1060' => '42701', + + // MySQL: Duplicate entry '%s' for key '%s' + // PostgreSQL: duplicate key violates unique constraint + '1062' => '23505', + + // MySQL: Can't DROP '%s'; check that column/key exists + // PostgreSQL: index "%s" does not exist + '1091' => '42704', + + // MySQL: Table '%s.%s' doesn't exist + // PostgreSQL: relation "%s" does not exist + '1146' => '42P01', + ); + + $errInfo = $this->errorInfo(); + return $errInfo[0] == $map[$errno]; + } + + /** + * Return number of affected rows in last query + * + * @param mixed $queryResult Result from query() + * @return int + */ + public function rowCount($queryResult) + { + return $queryResult->rowCount(); + } +} diff --git a/core/Tracker/GoalManager.php b/core/Tracker/GoalManager.php index 1208ce60a1..074f9fdcbf 100644 --- a/core/Tracker/GoalManager.php +++ b/core/Tracker/GoalManager.php @@ -211,7 +211,7 @@ class Piwik_Tracker_GoalManager VALUES ($bindFields) ", array_values($newGoal) ); } catch( Exception $e) { - if(strpos($e->getMessage(), '1062') !== false) + if(Piwik_Tracker::isErrNo('1062')) { // integrity violation when same visit converts to the same goal twice printDebug("--> Goal already recorded for this (idvisit, idgoal)"); diff --git a/core/Tracker/Visit.php b/core/Tracker/Visit.php index 3b129ef102..323a720806 100644 --- a/core/Tracker/Visit.php +++ b/core/Tracker/Visit.php @@ -230,7 +230,8 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface visit_total_actions = visit_total_actions + 1, "; $this->visitorInfo['visit_exit_idaction'] = $actionId; } - $statement = Piwik_Tracker::getDatabase()->query("/* SHARDING_ID_SITE = ". $this->idsite ." */ + + $result = Piwik_Tracker::getDatabase()->query("/* SHARDING_ID_SITE = ". $this->idsite ." */ UPDATE ". Piwik_Common::prefixTable('log_visit')." SET $sqlActionIdUpdate $sqlUpdateGoalConverted @@ -243,10 +244,12 @@ class Piwik_Tracker_Visit implements Piwik_Tracker_Visit_Interface $this->visitorInfo['idvisit'], $this->visitorInfo['visitor_idcookie'] ) ); - if($statement->rowCount() == 0) + + if(Piwik_Tracker::getDatabase()->rowCount($result) == 0) { throw new Piwik_Tracker_Visit_VisitorNotFoundInDatabase("The visitor with visitor_idcookie=".$this->visitorInfo['visitor_idcookie']." and idvisit=".$this->visitorInfo['idvisit']." wasn't found in the DB, we fallback to a new visitor"); } + $this->visitorInfo['idsite'] = $this->idsite; $this->visitorInfo['visit_server_date'] = $this->getCurrentDate(); diff --git a/core/Updater.php b/core/Updater.php index a7647cb838..b5f6aa990b 100644 --- a/core/Updater.php +++ b/core/Updater.php @@ -192,7 +192,8 @@ class Piwik_Updater try { $currentVersion = Piwik_GetOption('version_'.$name); } catch( Exception $e) { - if(preg_match('/1146/', $e->getMessage())) + // mysql error 1146: table doesn't exist + if(Zend_Registry::get('db')->isErrNo('1146')) { // case when the option table is not yet created (before 0.2.10) $currentVersion = false; @@ -245,7 +246,7 @@ class Piwik_Updater try { Piwik_Query( $update ); } catch(Exception $e) { - if(($ignoreError === false) || !preg_match($ignoreError, $e->getMessage())) + if(($ignoreError === false) || !Zend_Registry::get('db')->isErrNo($ignoreError)) { $message = $file .":\nError trying to execute the query '". $update ."'.\nThe error was: ". $e->getMessage(); throw new Piwik_Updater_UpdateErrorException($message); diff --git a/core/Updates/0.4.2.php b/core/Updates/0.4.2.php index abb1e38ba2..ef91786033 100644 --- a/core/Updates/0.4.2.php +++ b/core/Updates/0.4.2.php @@ -20,9 +20,9 @@ class Piwik_Updates_0_4_2 implements Piwik_iUpdate { Piwik_Updater::updateDatabase(__FILE__, array( 'ALTER TABLE `'. Piwik::prefixTable('log_visit') .'` - ADD `config_java` TINYINT(1) NOT NULL AFTER `config_flash`' => '/1060/', + ADD `config_java` TINYINT(1) NOT NULL AFTER `config_flash`' => '1060', 'ALTER TABLE `'. Piwik::prefixTable('log_visit') .'` - ADD `config_quicktime` TINYINT(1) NOT NULL AFTER `config_director`' => '/1060/', + ADD `config_quicktime` TINYINT(1) NOT NULL AFTER `config_director`' => '1060', 'ALTER TABLE `'. Piwik::prefixTable('log_visit') .'` ADD `config_gears` TINYINT(1) NOT NULL AFTER `config_windowsmedia`, ADD `config_silverlight` TINYINT(1) NOT NULL AFTER `config_gears`' => false, diff --git a/core/Updates/0.4.3.php b/core/Updates/0.4.3.php index 95e7c35512..b4524186a1 100644 --- a/core/Updates/0.4.3.php +++ b/core/Updates/0.4.3.php @@ -20,7 +20,7 @@ class Piwik_Updates_0_4_3 implements Piwik_iUpdate Piwik_Updater::updateDatabase(__FILE__, array( // 0.1.7 [463] 'ALTER IGNORE TABLE `'. Piwik::prefixTable('log_visit') .'` - CHANGE `location_provider` `location_provider` VARCHAR( 100 ) DEFAULT NULL' => '/1054/', + CHANGE `location_provider` `location_provider` VARCHAR( 100 ) DEFAULT NULL' => '1054', // 0.1.7 [470] 'ALTER TABLE `'. Piwik::prefixTable('logger_api_call') .'` CHANGE `parameter_names_default_values` `parameter_names_default_values` TEXT, @@ -34,10 +34,10 @@ class Piwik_Updates_0_4_3 implements Piwik_iUpdate CHANGE `message` `message` TEXT' => false, // 0.2.2 [489] 'ALTER IGNORE TABLE `'. Piwik::prefixTable('site') .'` - CHANGE `feedburnerName` `feedburnerName` VARCHAR( 100 ) DEFAULT NULL' => '/1054/', + CHANGE `feedburnerName` `feedburnerName` VARCHAR( 100 ) DEFAULT NULL' => '1054', // 0.2.12 [673] // Note: requires INDEX privilege - 'DROP INDEX index_idaction ON `'. Piwik::prefixTable('log_action') .'`' => '/1072|1091/', + 'DROP INDEX index_idaction ON `'. Piwik::prefixTable('log_action') .'`' => '1091', // 0.2.27 [826] 'ALTER IGNORE TABLE `'. Piwik::prefixTable('log_visit') .'` CHANGE `visit_goal_converted` `visit_goal_converted` TINYINT(1) NOT NULL' => false, @@ -47,14 +47,14 @@ class Piwik_Updates_0_4_3 implements Piwik_iUpdate 'ALTER TABLE `'. Piwik::prefixTable('user') .'` CHANGE `login` `login` VARCHAR( 100 ) NOT NULL' => false, 'ALTER TABLE `'. Piwik::prefixTable('user_dashboard') .'` - CHANGE `login` `login` VARCHAR( 100 ) NOT NULL' => '/1146/', + CHANGE `login` `login` VARCHAR( 100 ) NOT NULL' => '1146', 'ALTER TABLE `'. Piwik::prefixTable('user_language') .'` - CHANGE `login` `login` VARCHAR( 100 ) NOT NULL' => '/1146/', + CHANGE `login` `login` VARCHAR( 100 ) NOT NULL' => '1146', // 0.2.33 [1020] 'ALTER TABLE `'. Piwik::prefixTable('user_dashboard') .'` - CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci ' => '/1146/', + CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci ' => '1146', 'ALTER TABLE `'. Piwik::prefixTable('user_language') .'` - CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci ' => '/1146/', + CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci ' => '1146', // 0.4 [1140] 'ALTER TABLE `'. Piwik::prefixTable('log_visit') .'` CHANGE `location_ip` `location_ip` BIGINT UNSIGNED NOT NULL' => false, |