| <?php |
|
|
| |
| |
| |
| |
| |
| |
|
|
| namespace Piwik\Tracker\Db\Pdo; |
|
|
| use Exception; |
| use PDO; |
| use PDOException; |
| use PDOStatement; |
| use Piwik\Tracker\Db; |
| use Piwik\Tracker\Db\DbException; |
|
|
| |
| |
| |
| |
| class Mysql extends Db |
| { |
| |
| |
| |
| protected $connection = null; |
|
|
| |
| |
| |
| protected $dsn; |
|
|
| |
| |
| |
| private $username; |
|
|
| |
| |
| |
| private $password; |
|
|
| |
| |
| |
| protected $charset; |
|
|
| |
| |
| |
| private $collation; |
|
|
| protected $mysqlOptions = []; |
|
|
| protected $activeTransaction = null; |
|
|
| |
| |
| |
| |
| |
| |
| public function __construct($dbInfo, $driverName = 'mysql') |
| { |
| if (isset($dbInfo['unix_socket']) && substr($dbInfo['unix_socket'], 0, 1) == '/') { |
| $this->dsn = $driverName . ':dbname=' . $dbInfo['dbname'] . ';unix_socket=' . $dbInfo['unix_socket']; |
| } elseif (!empty($dbInfo['port']) && substr($dbInfo['port'], 0, 1) == '/') { |
| $this->dsn = $driverName . ':dbname=' . $dbInfo['dbname'] . ';unix_socket=' . $dbInfo['port']; |
| } else { |
| $this->dsn = $driverName . ':dbname=' . $dbInfo['dbname'] . ';host=' . $dbInfo['host'] . ';port=' . $dbInfo['port']; |
| } |
|
|
| $this->username = $dbInfo['username']; |
| $this->password = $dbInfo['password']; |
|
|
| if (isset($dbInfo['charset'])) { |
| $this->charset = $dbInfo['charset']; |
| $this->dsn .= ';charset=' . $this->charset; |
|
|
| if (!empty($dbInfo['collation'])) { |
| $this->collation = $dbInfo['collation']; |
| } |
| } |
|
|
| if (isset($dbInfo['enable_ssl']) && $dbInfo['enable_ssl']) { |
| if (!empty($dbInfo['ssl_key'])) { |
| $this->mysqlOptions[PDO::MYSQL_ATTR_SSL_KEY] = $dbInfo['ssl_key']; |
| } |
| if (!empty($dbInfo['ssl_cert'])) { |
| $this->mysqlOptions[PDO::MYSQL_ATTR_SSL_CERT] = $dbInfo['ssl_cert']; |
| } |
| if (!empty($dbInfo['ssl_ca'])) { |
| $this->mysqlOptions[PDO::MYSQL_ATTR_SSL_CA] = $dbInfo['ssl_ca']; |
| } |
| if (!empty($dbInfo['ssl_ca_path'])) { |
| $this->mysqlOptions[PDO::MYSQL_ATTR_SSL_CAPATH] = $dbInfo['ssl_ca_path']; |
| } |
| if (!empty($dbInfo['ssl_cipher'])) { |
| $this->mysqlOptions[PDO::MYSQL_ATTR_SSL_CIPHER] = $dbInfo['ssl_cipher']; |
| } |
| if (!empty($dbInfo['ssl_no_verify']) && defined('PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT')) { |
| $this->mysqlOptions[PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = false; |
| } |
| } |
| } |
|
|
| public function __destruct() |
| { |
| $this->connection = null; |
| } |
|
|
| |
| |
| |
| |
| |
| public function connect() |
| { |
| if (self::$profiling) { |
| $timer = $this->initProfiler(); |
| } |
|
|
| |
| |
| |
| |
| $this->mysqlOptions[PDO::MYSQL_ATTR_FOUND_ROWS] = true; |
| $this->mysqlOptions[PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION; |
|
|
| try { |
| $this->establishConnection(); |
| } catch (Exception $e) { |
| if ($this->isMysqlServerHasGoneAwayError($e)) { |
| |
| |
| $this->reconnect($e); |
| } else { |
| throw $e; |
| } |
| } |
|
|
| if (self::$profiling && isset($timer)) { |
| $this->recordQueryProfile('connect', $timer); |
| } |
| } |
|
|
| |
| |
| |
| |
| public function isMysqlServerHasGoneAwayError(Exception $e) |
| { |
| return $this->isErrNo($e, \Piwik\Updater\Migration\Db::ERROR_CODE_MYSQL_SERVER_HAS_GONE_AWAY) |
| || stripos($e->getMessage(), 'MySQL server has gone away') !== false; |
| } |
|
|
| |
| |
| |
| public function disconnect() |
| { |
| $this->connection = null; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public function fetchAll($query, $parameters = array()) |
| { |
| try { |
| $sth = $this->query($query, $parameters); |
| if ($sth === false) { |
| return false; |
| } |
| return $sth->fetchAll(PDO::FETCH_ASSOC); |
| } catch (PDOException $e) { |
| throw new DbException("Error query: " . $e->getMessage()); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| public function fetchCol($sql, $bind = array()) |
| { |
| try { |
| $sth = $this->query($sql, $bind); |
| if ($sth === false) { |
| return false; |
| } |
| $result = $sth->fetchAll(PDO::FETCH_COLUMN, 0); |
| return $result; |
| } catch (PDOException $e) { |
| throw new DbException("Error query: " . $e->getMessage()); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public function fetch($query, $parameters = array()) |
| { |
| try { |
| $sth = $this->query($query, $parameters); |
| if ($sth === false) { |
| return false; |
| } |
| return $sth->fetch(PDO::FETCH_ASSOC); |
| } catch (PDOException $e) { |
| throw new DbException("Error query: " . $e->getMessage()); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| public function query($query, $parameters = array()) |
| { |
| try { |
| return $this->executeQuery($query, $parameters); |
| } catch (Exception $e) { |
| $isSelectQuery = stripos(trim($query), 'select ') === 0; |
|
|
| if ( |
| $isSelectQuery |
| && null === $this->activeTransaction |
| && $this->isMysqlServerHasGoneAwayError($e) |
| ) { |
| |
| |
| |
| |
| $this->reconnect($e); |
| return $this->executeQuery($query, $parameters); |
| } else { |
| $message = $e->getMessage() . " In query: $query Parameters: " . var_export($parameters, true); |
| throw new DbException("Error query: " . $message, (int) $e->getCode()); |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| public function reconnect(Exception $e) |
| { |
| $this->disconnect(); |
| usleep(100 * 1000); |
| try { |
| $this->establishConnection(); |
| } catch (Exception $exceptionReconnect) { |
| |
| |
| throw $e; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| private function executeQuery($query, $parameters = array()) |
| { |
| if (is_null($this->connection)) { |
| return false; |
| } |
|
|
| try { |
| if (self::$profiling) { |
| $timer = $this->initProfiler(); |
| } |
|
|
| if (!is_array($parameters)) { |
| $parameters = array($parameters); |
| } |
| $sth = $this->connection->prepare($query); |
| $sth->execute($parameters); |
|
|
| if (self::$profiling && isset($timer)) { |
| $this->recordQueryProfile($query, $timer); |
| } |
| return $sth; |
| } catch (PDOException $e) { |
| $message = $e->getMessage() . " In query: $query Parameters: " . var_export($parameters, true); |
| throw new DbException("Error query: " . $message, (int) $e->getCode()); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| public function lastInsertId() |
| { |
| return $this->connection->lastInsertId(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| public function isErrNo($e, $errno) |
| { |
| return \Piwik\Db\Adapter\Pdo\Mysql::isPdoErrorNumber($e, $errno); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| public function rowCount($queryResult) |
| { |
| return $queryResult->rowCount(); |
| } |
|
|
| |
| |
| |
| |
| public function beginTransaction() |
| { |
| if ($this->activeTransaction !== null) { |
| return null; |
| } |
|
|
| try { |
| $success = $this->connection->beginTransaction(); |
| } catch (Exception $e) { |
| if ($this->isMysqlServerHasGoneAwayError($e)) { |
| |
| |
| $this->reconnect($e); |
| $success = $this->connection->beginTransaction(); |
| } else { |
| throw $e; |
| } |
| } |
|
|
| if ($success) { |
| $this->activeTransaction = uniqid(); |
| return $this->activeTransaction; |
| } |
|
|
| return null; |
| } |
|
|
| |
| |
| |
| |
| public function commit($xid) |
| { |
| if ($this->activeTransaction != $xid || $this->activeTransaction === null) { |
| return; |
| } |
|
|
| $this->activeTransaction = null; |
|
|
| if (!$this->connection->commit()) { |
| throw new DbException("Commit failed"); |
| } |
| } |
|
|
| |
| |
| |
| |
| public function rollBack($xid) |
| { |
| if ($this->activeTransaction != $xid || $this->activeTransaction === null) { |
| return; |
| } |
|
|
| $this->activeTransaction = null; |
|
|
| if (!$this->connection->rollBack()) { |
| throw new DbException("Rollback failed"); |
| } |
| } |
|
|
| private function establishConnection(): void |
| { |
| $this->connection = @new PDO($this->dsn, $this->username, $this->password, $this->mysqlOptions); |
|
|
| |
| |
|
|
| |
| |
| |
| |
| |
| if (!empty($this->charset)) { |
| $sql = "SET NAMES '" . $this->charset . "'"; |
|
|
| if (!empty($this->collation)) { |
| $sql .= " COLLATE '" . $this->collation . "'"; |
| } |
|
|
| $this->connection->exec($sql); |
| } |
| } |
| } |
|
|