Merge pull request #3353 from tractorcow/pulls/3.2/fix-pdo

BUG Fix PDOConnector issues
This commit is contained in:
Ingo Schommer 2014-08-02 11:35:31 +12:00
commit fc1eb1cfbc
4 changed files with 62 additions and 65 deletions

View File

@ -14,20 +14,20 @@ env:
matrix: matrix:
include: include:
- php: 5.3 - php: 5.4
env: DB=PGSQL CORE_RELEASE=master
- php: 5.3
env: DB=SQLITE CORE_RELEASE=master env: DB=SQLITE CORE_RELEASE=master
- php: 5.4
env: DB=PGSQL CORE_RELEASE=master
- php: 5.4 - php: 5.4
env: DB=MYSQL CORE_RELEASE=master env: DB=MYSQL CORE_RELEASE=master
- php: 5.4
env: DB=MYSQL CORE_RELEASE=master PDO=1
- php: 5.5 - php: 5.5
env: DB=MYSQL CORE_RELEASE=master env: DB=MYSQL CORE_RELEASE=master
- php: 5.6 - php: 5.6
env: DB=MYSQL CORE_RELEASE=master env: DB=MYSQL CORE_RELEASE=master
- php: 5.4 - php: 5.4
env: DB=MYSQL CORE_RELEASE=master BEHAT_TEST=1 env: DB=MYSQL CORE_RELEASE=master BEHAT_TEST=1
- php: 5.6
env: DB=MYSQL CORE_RELEASE=master
before_script: before_script:
- composer self-update - composer self-update

View File

@ -9,10 +9,10 @@
* @subpackage model * @subpackage model
*/ */
class MySQLDatabase extends SS_Database { class MySQLDatabase extends SS_Database {
/** /**
* Default connection charset (may be overridden in $databaseConfig) * Default connection charset (may be overridden in $databaseConfig)
* *
* @config * @config
* @var String * @var String
*/ */
@ -23,9 +23,9 @@ class MySQLDatabase extends SS_Database {
if(empty($parameters['driver'])) { if(empty($parameters['driver'])) {
$parameters['driver'] = $this->getDatabaseServer(); $parameters['driver'] = $this->getDatabaseServer();
} }
// Set charset // Set charset
if( empty($parameters['charset']) if( empty($parameters['charset'])
&& ($charset = Config::inst()->get('MySQLDatabase', 'connection_charset')) && ($charset = Config::inst()->get('MySQLDatabase', 'connection_charset'))
) { ) {
$parameters['charset'] = $charset; $parameters['charset'] = $charset;
@ -42,13 +42,13 @@ class MySQLDatabase extends SS_Database {
} }
// SS_Database subclass maintains responsibility for selecting database // SS_Database subclass maintains responsibility for selecting database
// once connected in order to correctly handle schema queries about // once connected in order to correctly handle schema queries about
// existence of database, error handling at the correct level, etc // existence of database, error handling at the correct level, etc
if (!empty($parameters['database'])) { if (!empty($parameters['database'])) {
$this->selectDatabase($parameters['database'], false, false); $this->selectDatabase($parameters['database'], false, false);
} }
} }
/** /**
* Sets the character set for the MySQL database connection. * Sets the character set for the MySQL database connection.
* *
@ -68,7 +68,7 @@ class MySQLDatabase extends SS_Database {
/** /**
* Sets the SQL mode * Sets the SQL mode
* *
* @param string $mode Connection mode * @param string $mode Connection mode
*/ */
public function setSQLMode($mode) { public function setSQLMode($mode) {
@ -78,7 +78,7 @@ class MySQLDatabase extends SS_Database {
/** /**
* Sets the system timezone for the database connection * Sets the system timezone for the database connection
* *
* @param string $timezone * @param string $timezone
*/ */
public function selectTimezone($timezone) { public function selectTimezone($timezone) {
@ -112,7 +112,6 @@ class MySQLDatabase extends SS_Database {
if (!class_exists('File')) if (!class_exists('File'))
throw new Exception('MySQLDatabase->searchEngine() requires "File" class'); throw new Exception('MySQLDatabase->searchEngine() requires "File" class');
$fileFilter = '';
$keywords = $this->escapeString($keywords); $keywords = $this->escapeString($keywords);
$htmlEntityKeywords = htmlentities($keywords, ENT_NOQUOTES, 'UTF-8'); $htmlEntityKeywords = htmlentities($keywords, ENT_NOQUOTES, 'UTF-8');
@ -131,7 +130,7 @@ class MySQLDatabase extends SS_Database {
// Always ensure that only pages with ShowInSearch = 1 can be searched // Always ensure that only pages with ShowInSearch = 1 can be searched
$extraFilters['SiteTree'] .= " AND ShowInSearch <> 0"; $extraFilters['SiteTree'] .= " AND ShowInSearch <> 0";
// File.ShowInSearch was added later, keep the database driver backwards compatible // File.ShowInSearch was added later, keep the database driver backwards compatible
// by checking for its existence first // by checking for its existence first
$fields = $this->fieldList('File'); $fields = $this->fieldList('File');
if (array_key_exists('ShowInSearch', $fields)) if (array_key_exists('ShowInSearch', $fields))
@ -201,7 +200,7 @@ class MySQLDatabase extends SS_Database {
$querySQLs[] = $query->sql($parameters); $querySQLs[] = $query->sql($parameters);
$queryParameters = array_merge($queryParameters, $parameters); $queryParameters = array_merge($queryParameters, $parameters);
$totalCount += $query->unlimitedRowCount(); $totalCount += $query->unlimitedRowCount();
} }
$fullQuery = implode(" UNION ", $querySQLs) . " ORDER BY $sortBy LIMIT $limit"; $fullQuery = implode(" UNION ", $querySQLs) . " ORDER BY $sortBy LIMIT $limit";
@ -233,30 +232,30 @@ class MySQLDatabase extends SS_Database {
public function transactionStart($transactionMode = false, $sessionCharacteristics = false) { public function transactionStart($transactionMode = false, $sessionCharacteristics = false) {
// This sets the isolation level for the NEXT transaction, not the current one. // This sets the isolation level for the NEXT transaction, not the current one.
if ($transactionMode) { if ($transactionMode) {
$this->query('SET TRANSACTION ' . $transactionMode . ';'); $this->query('SET TRANSACTION ' . $transactionMode);
} }
$this->query('START TRANSACTION;'); $this->query('START TRANSACTION');
if ($sessionCharacteristics) { if ($sessionCharacteristics) {
$this->query('SET SESSION TRANSACTION ' . $sessionCharacteristics . ';'); $this->query('SET SESSION TRANSACTION ' . $sessionCharacteristics);
} }
} }
public function transactionSavepoint($savepoint) { public function transactionSavepoint($savepoint) {
$this->query("SAVEPOINT $savepoint;"); $this->query("SAVEPOINT $savepoint");
} }
public function transactionRollback($savepoint = false) { public function transactionRollback($savepoint = false) {
if ($savepoint) { if ($savepoint) {
$this->query('ROLLBACK TO ' . $savepoint . ';'); $this->query('ROLLBACK TO ' . $savepoint);
} else { } else {
$this->query('ROLLBACK'); $this->query('ROLLBACK');
} }
} }
public function transactionEnd($chain = false) { public function transactionEnd($chain = false) {
$this->query('COMMIT AND ' . ($chain ? '' : 'NO ') . 'CHAIN;'); $this->query('COMMIT AND ' . ($chain ? '' : 'NO ') . 'CHAIN');
} }
public function comparisonClause($field, $value, $exact = false, $negate = false, $caseSensitive = null, public function comparisonClause($field, $value, $exact = false, $negate = false, $caseSensitive = null,

View File

@ -6,54 +6,54 @@
* @subpackage model * @subpackage model
*/ */
class PDOConnector extends DBConnector { class PDOConnector extends DBConnector {
/** /**
* Should ATTR_EMULATE_PREPARES flag be used to emulate prepared statements? * Should ATTR_EMULATE_PREPARES flag be used to emulate prepared statements?
* *
* @config * @config
* @var boolean * @var boolean
*/ */
private static $emulate_prepare = false; private static $emulate_prepare = false;
/** /**
* The PDO connection instance * The PDO connection instance
* *
* @var PDO * @var PDO
*/ */
protected $pdoConnection = null; protected $pdoConnection = null;
/** /**
* Name of the currently selected database * Name of the currently selected database
* *
* @var string * @var string
*/ */
protected $databaseName = null; protected $databaseName = null;
/** /**
* The most recent statement returned from PDODatabase->query * The most recent statement returned from PDODatabase->query
* *
* @var PDOStatement * @var PDOStatement
*/ */
protected $lastStatement = null; protected $lastStatement = null;
/** /**
* List of prepared statements, cached by SQL string * List of prepared statements, cached by SQL string
* *
* @var array * @var array
*/ */
protected $cachedStatements = array(); protected $cachedStatements = array();
/** /**
* Flush all prepared statements * Flush all prepared statements
*/ */
public function flushStatements() { public function flushStatements() {
$this->cachedStatements = array(); $this->cachedStatements = array();
} }
/** /**
* Retrieve a prepared statement for a given SQL string, or return an already prepared version if * Retrieve a prepared statement for a given SQL string, or return an already prepared version if
* one exists for the given query * one exists for the given query
* *
* @param string $sql * @param string $sql
* @return PDOStatement * @return PDOStatement
*/ */
@ -66,10 +66,10 @@ class PDOConnector extends DBConnector {
} }
return $this->cachedStatements[$sql]; return $this->cachedStatements[$sql];
} }
/** /**
* Is PDO running in emulated mode * Is PDO running in emulated mode
* *
* @return boolean * @return boolean
*/ */
public static function is_emulate_prepare() { public static function is_emulate_prepare() {
@ -84,7 +84,7 @@ class PDOConnector extends DBConnector {
// requested via selectDatabase // requested via selectDatabase
$driver = $parameters['driver'] . ":"; $driver = $parameters['driver'] . ":";
$dsn = array(); $dsn = array();
// Typically this is false, but some drivers will request this // Typically this is false, but some drivers will request this
if($selectDB) { if($selectDB) {
// Specify complete file path immediately following driver (SQLLite3) // Specify complete file path immediately following driver (SQLLite3)
@ -92,14 +92,14 @@ class PDOConnector extends DBConnector {
$dsn[] = $parameters['filepath']; $dsn[] = $parameters['filepath'];
} elseif(!empty($parameters['database'])) { } elseif(!empty($parameters['database'])) {
// Some databases require a selected database at connection (SQLite3, Azure) // Some databases require a selected database at connection (SQLite3, Azure)
if($parameters['driver'] === 'sqlsrv') { if($parameters['driver'] === 'sqlsrv') {
$dsn[] = "Database={$parameters['database']}"; $dsn[] = "Database={$parameters['database']}";
} else { } else {
$dsn[] = "dbname={$parameters['database']}"; $dsn[] = "dbname={$parameters['database']}";
} }
} }
} }
// Syntax for sql server is slightly different // Syntax for sql server is slightly different
if($parameters['driver'] === 'sqlsrv') { if($parameters['driver'] === 'sqlsrv') {
$server = $parameters['server']; $server = $parameters['server'];
@ -128,18 +128,20 @@ class PDOConnector extends DBConnector {
// Connection commands to be run on every re-connection // Connection commands to be run on every re-connection
$options = array( $options = array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8', PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'
PDO::ATTR_EMULATE_PREPARES => self::is_emulate_prepare()
); );
if(self::is_emulate_prepare()) {
$options[PDO::ATTR_EMULATE_PREPARES] = true;
}
// May throw a PDOException if fails // May throw a PDOException if fails
if(empty($parameters['username']) || empty($parameters['password'])) { $this->pdoConnection = new PDO(
$this->pdoConnection = new PDO($driver.implode(';', $dsn)); $driver.implode(';', $dsn),
} else { empty($parameters['username']) ? '' : $parameters['username'],
$this->pdoConnection = new PDO($driver.implode(';', $dsn), $parameters['username'], empty($parameters['password']) ? '' : $parameters['password'],
$parameters['password'], $options); $options
} );
// Show selected DB if requested // Show selected DB if requested
if($this->pdoConnection && $selectDB && !empty($parameters['database'])) { if($this->pdoConnection && $selectDB && !empty($parameters['database'])) {
$this->databaseName = $parameters['database']; $this->databaseName = $parameters['database'];
@ -164,10 +166,10 @@ class PDOConnector extends DBConnector {
public function quoteString($value) { public function quoteString($value) {
return $this->pdoConnection->quote($value); return $this->pdoConnection->quote($value);
} }
/** /**
* Executes a query that doesn't return a resultset * Executes a query that doesn't return a resultset
* *
* @param string $sql * @param string $sql
* @param string $sql The SQL query to execute * @param string $sql The SQL query to execute
* @param integer $errorLevel For errors to this query, raise PHP errors * @param integer $errorLevel For errors to this query, raise PHP errors
@ -176,7 +178,7 @@ class PDOConnector extends DBConnector {
public function exec($sql, $errorLevel = E_USER_ERROR) { public function exec($sql, $errorLevel = E_USER_ERROR) {
// Check if we should only preview this query // Check if we should only preview this query
if ($this->previewWrite($sql)) return; if ($this->previewWrite($sql)) return;
// Reset last statement to prevent interference in case of error // Reset last statement to prevent interference in case of error
$this->lastStatement = null; $this->lastStatement = null;
@ -213,7 +215,7 @@ class PDOConnector extends DBConnector {
return new PDOQuery($this->lastStatement); return new PDOQuery($this->lastStatement);
} }
/** /**
* Determines the PDO::PARAM_* type for a given PHP type string * Determines the PDO::PARAM_* type for a given PHP type string
* @param string $phpType Type of object in PHP * @param string $phpType Type of object in PHP
@ -241,10 +243,10 @@ class PDOConnector extends DBConnector {
user_error("Cannot bind parameter as it is an unsupported type ($phpType)", E_USER_ERROR); user_error("Cannot bind parameter as it is an unsupported type ($phpType)", E_USER_ERROR);
} }
} }
/** /**
* Bind all parameters to a PDOStatement * Bind all parameters to a PDOStatement
* *
* @param PDOStatement $statement * @param PDOStatement $statement
* @param array $parameters * @param array $parameters
*/ */
@ -259,7 +261,7 @@ class PDOConnector extends DBConnector {
$phpType = $value['type']; $phpType = $value['type'];
$value = $value['value']; $value = $value['value'];
} }
// Check type of parameter // Check type of parameter
$type = $this->getPDOParamType($phpType); $type = $this->getPDOParamType($phpType);
if($type === PDO::PARAM_STR) $value = strval($value); if($type === PDO::PARAM_STR) $value = strval($value);
@ -268,7 +270,7 @@ class PDOConnector extends DBConnector {
$statement->bindValue($index+1, $value, $type); $statement->bindValue($index+1, $value, $type);
} }
} }
public function preparedQuery($sql, $parameters, $errorLevel = E_USER_ERROR) { public function preparedQuery($sql, $parameters, $errorLevel = E_USER_ERROR) {
// Check if we should only preview this query // Check if we should only preview this query
if ($this->previewWrite($sql)) return; if ($this->previewWrite($sql)) return;
@ -276,14 +278,14 @@ class PDOConnector extends DBConnector {
// Benchmark query // Benchmark query
$self = $this; $self = $this;
$this->lastStatement = $this->benchmarkQuery($sql, function($sql) use($parameters, $self) { $this->lastStatement = $this->benchmarkQuery($sql, function($sql) use($parameters, $self) {
// Prepare statement // Prepare statement
$statement = $self->getOrPrepareStatement($sql); $statement = $self->getOrPrepareStatement($sql);
if(!$statement) return null; if(!$statement) return null;
// Inject parameters // Inject parameters
$self->bindParameters($statement, $parameters); $self->bindParameters($statement, $parameters);
// Safely execute the statement // Safely execute the statement
$statement->execute($parameters); $statement->execute($parameters);
return $statement; return $statement;
@ -298,17 +300,17 @@ class PDOConnector extends DBConnector {
return new PDOQuery($this->lastStatement); return new PDOQuery($this->lastStatement);
} }
/** /**
* Determine if a resource has an attached error * Determine if a resource has an attached error
* *
* @param PDOStatement|PDO $resource the resource to check * @param PDOStatement|PDO $resource the resource to check
* @return boolean Flag indicating true if the resource has an error * @return boolean Flag indicating true if the resource has an error
*/ */
protected function hasError($resource) { protected function hasError($resource) {
// No error if no resource // No error if no resource
if(empty($resource)) return false; if(empty($resource)) return false;
// If the error code is empty the statement / connection has not been run yet // If the error code is empty the statement / connection has not been run yet
$code = $resource->errorCode(); $code = $resource->errorCode();
if(empty($code)) return false; if(empty($code)) return false;

View File

@ -27,10 +27,6 @@ class PDOQuery extends SS_Query {
$statement->closeCursor(); $statement->closeCursor();
} }
public function __destruct() {
$this->statement->closeCursor();
}
public function seek($row) { public function seek($row) {
$this->rowNum = $row - 1; $this->rowNum = $row - 1;
return $this->nextRecord(); return $this->nextRecord();