mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
BUG Remove caching of statements due to risk of instability
This would cause segfaults in rare situations where statements are reused
This commit is contained in:
parent
76eb3dbfa6
commit
81c0a3499b
@ -6,79 +6,52 @@
|
|||||||
* @subpackage model
|
* @subpackage model
|
||||||
*/
|
*/
|
||||||
class MySQLiConnector extends DBConnector {
|
class MySQLiConnector extends DBConnector {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connection to the MySQL database
|
* Connection to the MySQL database
|
||||||
*
|
*
|
||||||
* @var MySQLi
|
* @var MySQLi
|
||||||
*/
|
*/
|
||||||
protected $dbConn = null;
|
protected $dbConn = 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 MySQLiConnector->preparedQuery
|
* The most recent statement returned from MySQLiConnector->preparedQuery
|
||||||
*
|
*
|
||||||
* @var mysqli_stmt
|
* @var mysqli_stmt
|
||||||
*/
|
*/
|
||||||
protected $lastStatement = null;
|
protected $lastStatement = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store the most recent statement for later use
|
* Store the most recent statement for later use
|
||||||
*
|
*
|
||||||
* @param mysqli_stmt $statement
|
* @param mysqli_stmt $statement
|
||||||
*/
|
*/
|
||||||
public function setLastStatement($statement) {
|
public function setLastStatement($statement) {
|
||||||
$this->lastStatement = $statement;
|
$this->lastStatement = $statement;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of prepared statements, cached by SQL string
|
* Retrieve a prepared statement for a given SQL string
|
||||||
*
|
*
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $cachedStatements = array();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flush all prepared statements
|
|
||||||
*/
|
|
||||||
public function flushStatements() {
|
|
||||||
$this->cachedStatements = array();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve a prepared statement for a given SQL string, or return an already prepared version if
|
|
||||||
* one exists for the given query
|
|
||||||
*
|
|
||||||
* @param string $sql
|
* @param string $sql
|
||||||
* @param boolean &$success
|
* @param boolean &$success
|
||||||
* @return mysqli_stmt
|
* @return mysqli_stmt
|
||||||
*/
|
*/
|
||||||
public function getOrPrepareStatement($sql, &$success) {
|
public function prepareStatement($sql, &$success) {
|
||||||
// Check for cached statement
|
|
||||||
if(!empty($this->cachedStatements[$sql])) {
|
|
||||||
$success = true;
|
|
||||||
return $this->cachedStatements[$sql];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare statement with arguments
|
// Prepare statement with arguments
|
||||||
$statement = $this->dbConn->stmt_init();
|
$statement = $this->dbConn->stmt_init();
|
||||||
if($success = $statement->prepare($sql)) {
|
$success = $statement->prepare($sql);
|
||||||
// Only cache prepared statement on success
|
|
||||||
$this->cachedStatements[$sql] = $statement;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $statement;
|
return $statement;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function connect($parameters, $selectDB = false) {
|
public function connect($parameters, $selectDB = false) {
|
||||||
$this->flushStatements();
|
|
||||||
|
|
||||||
// Normally $selectDB is set to false by the MySQLDatabase controller, as per convention
|
// Normally $selectDB is set to false by the MySQLDatabase controller, as per convention
|
||||||
$selectedDB = ($selectDB && !empty($parameters['database'])) ? $parameters['database'] : null;
|
$selectedDB = ($selectDB && !empty($parameters['database'])) ? $parameters['database'] : null;
|
||||||
|
|
||||||
@ -109,7 +82,7 @@ class MySQLiConnector extends DBConnector {
|
|||||||
: 'utf8';
|
: 'utf8';
|
||||||
if (!empty($charset)) $this->dbConn->set_charset($charset);
|
if (!empty($charset)) $this->dbConn->set_charset($charset);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __destruct() {
|
public function __destruct() {
|
||||||
if ($this->dbConn) {
|
if ($this->dbConn) {
|
||||||
mysqli_close($this->dbConn);
|
mysqli_close($this->dbConn);
|
||||||
@ -129,7 +102,7 @@ class MySQLiConnector extends DBConnector {
|
|||||||
public function getVersion() {
|
public function getVersion() {
|
||||||
return $this->dbConn->server_info;
|
return $this->dbConn->server_info;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function benchmarkQuery($sql, $callback) {
|
protected function benchmarkQuery($sql, $callback) {
|
||||||
// Clear the last statement
|
// Clear the last statement
|
||||||
$this->setLastStatement(null);
|
$this->setLastStatement(null);
|
||||||
@ -156,10 +129,10 @@ class MySQLiConnector extends DBConnector {
|
|||||||
return new MySQLQuery($this, $handle);
|
return new MySQLQuery($this, $handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepares the list of parameters in preparation for passing to mysqli_stmt_bind_param
|
* Prepares the list of parameters in preparation for passing to mysqli_stmt_bind_param
|
||||||
*
|
*
|
||||||
* @param array $parameters List of parameters
|
* @param array $parameters List of parameters
|
||||||
* @param array &$blobs Out parameter for list of blobs to bind separately
|
* @param array &$blobs Out parameter for list of blobs to bind separately
|
||||||
* @return array List of parameters appropriate for mysqli_stmt_bind_param function
|
* @return array List of parameters appropriate for mysqli_stmt_bind_param function
|
||||||
@ -215,10 +188,10 @@ class MySQLiConnector extends DBConnector {
|
|||||||
}
|
}
|
||||||
return array_merge(array($types), $values);
|
return array_merge(array($types), $values);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Binds a list of parameters to a statement
|
* Binds a list of parameters to a statement
|
||||||
*
|
*
|
||||||
* @param mysqli_stmt $statement MySQLi statement
|
* @param mysqli_stmt $statement MySQLi statement
|
||||||
* @param array $parameters List of parameters to pass to bind_param
|
* @param array $parameters List of parameters to pass to bind_param
|
||||||
*/
|
*/
|
||||||
@ -233,7 +206,7 @@ class MySQLiConnector extends DBConnector {
|
|||||||
}
|
}
|
||||||
call_user_func_array( array($statement, 'bind_param'), $boundNames);
|
call_user_func_array( array($statement, 'bind_param'), $boundNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function preparedQuery($sql, $parameters, $errorLevel = E_USER_ERROR) {
|
public function preparedQuery($sql, $parameters, $errorLevel = E_USER_ERROR) {
|
||||||
// Shortcut to basic query when not given parameters
|
// Shortcut to basic query when not given parameters
|
||||||
if(empty($parameters)) return $this->query($sql, $errorLevel);
|
if(empty($parameters)) return $this->query($sql, $errorLevel);
|
||||||
@ -243,26 +216,26 @@ class MySQLiConnector extends DBConnector {
|
|||||||
|
|
||||||
// Type check, identify, and prepare parameters for passing to the statement bind function
|
// Type check, identify, and prepare parameters for passing to the statement bind function
|
||||||
$parsedParameters = $this->parsePreparedParameters($parameters, $blobs);
|
$parsedParameters = $this->parsePreparedParameters($parameters, $blobs);
|
||||||
|
|
||||||
// Benchmark query
|
// Benchmark query
|
||||||
$self = $this;
|
$self = $this;
|
||||||
$lastStatement = $this->benchmarkQuery($sql, function($sql) use($parsedParameters, $blobs, $self) {
|
$lastStatement = $this->benchmarkQuery($sql, function($sql) use($parsedParameters, $blobs, $self) {
|
||||||
|
|
||||||
$statement = $self->getOrPrepareStatement($sql, $success);
|
$statement = $self->prepareStatement($sql, $success);
|
||||||
if(!$success) return $statement;
|
if(!$success) return $statement;
|
||||||
|
|
||||||
$self->bindParameters($statement, $parsedParameters);
|
$self->bindParameters($statement, $parsedParameters);
|
||||||
|
|
||||||
// Bind any blobs given
|
// Bind any blobs given
|
||||||
foreach($blobs as $blob) {
|
foreach($blobs as $blob) {
|
||||||
$statement->send_long_data($blob['index'], $blob['value']);
|
$statement->send_long_data($blob['index'], $blob['value']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Safely execute the statement
|
// Safely execute the statement
|
||||||
$statement->execute();
|
$statement->execute();
|
||||||
return $statement;
|
return $statement;
|
||||||
});
|
});
|
||||||
|
|
||||||
// check result
|
// check result
|
||||||
$this->setLastStatement($lastStatement);
|
$this->setLastStatement($lastStatement);
|
||||||
if (!$lastStatement || $lastStatement->error) {
|
if (!$lastStatement || $lastStatement->error) {
|
||||||
@ -270,7 +243,7 @@ class MySQLiConnector extends DBConnector {
|
|||||||
$this->databaseError($this->getLastError(), $errorLevel, $sql, $values);
|
$this->databaseError($this->getLastError(), $errorLevel, $sql, $values);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// May not return result for non-select statements
|
// May not return result for non-select statements
|
||||||
if($result = $lastStatement->get_result()) {
|
if($result = $lastStatement->get_result()) {
|
||||||
return new MySQLQuery($this, $result, $lastStatement);
|
return new MySQLQuery($this, $result, $lastStatement);
|
||||||
|
Loading…
Reference in New Issue
Block a user