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:
Damian Mooyman 2014-07-25 14:14:59 +12:00
parent 76eb3dbfa6
commit 81c0a3499b

View File

@ -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);