Issue #2454625 by amateescu: SQLite: Fix SQLITE_SCHEMA errors in web tests

8.0.x
webchick 2015-03-29 11:33:54 -07:00
parent a7a5236432
commit cfdf10cf68
3 changed files with 74 additions and 75 deletions

View File

@ -520,7 +520,7 @@ abstract class Connection implements \Serializable {
* An associative array of options to control how the query is run. See * An associative array of options to control how the query is run. See
* the documentation for DatabaseConnection::defaultOptions() for details. * the documentation for DatabaseConnection::defaultOptions() for details.
* *
* @return \Drupal\Core\Database\StatementInterface * @return \Drupal\Core\Database\StatementInterface|int|null
* This method will return one of: the executed statement, the number of * This method will return one of: the executed statement, the number of
* rows affected by the query (not the number matched), or the generated * rows affected by the query (not the number matched), or the generated
* insert ID of the last query, depending on the value of * insert ID of the last query, depending on the value of
@ -529,12 +529,11 @@ abstract class Connection implements \Serializable {
* this method will return NULL and may throw an exception if * this method will return NULL and may throw an exception if
* $options['throw_exception'] is TRUE. * $options['throw_exception'] is TRUE.
* *
* @throws \PDOException * @throws \Drupal\Core\Database\DatabaseExceptionWrapper
* @throws \Drupal\Core\Database\IntegrityConstraintViolationException * @throws \Drupal\Core\Database\IntegrityConstraintViolationException
* @throws \InvalidArgumentException * @throws \InvalidArgumentException
*/ */
public function query($query, array $args = array(), $options = array()) { public function query($query, array $args = array(), $options = array()) {
// Use default values if not already set. // Use default values if not already set.
$options += $this->defaultOptions(); $options += $this->defaultOptions();
@ -562,19 +561,48 @@ abstract class Connection implements \Serializable {
$stmt->allowRowCount = TRUE; $stmt->allowRowCount = TRUE;
return $stmt->rowCount(); return $stmt->rowCount();
case Database::RETURN_INSERT_ID: case Database::RETURN_INSERT_ID:
return $this->connection->lastInsertId(); $sequence_name = isset($options['sequence_name']) ? $options['sequence_name'] : NULL;
return $this->connection->lastInsertId($sequence_name);
case Database::RETURN_NULL: case Database::RETURN_NULL:
return; return NULL;
default: default:
throw new \PDOException('Invalid return directive: ' . $options['return']); throw new \PDOException('Invalid return directive: ' . $options['return']);
} }
} }
catch (\PDOException $e) { catch (\PDOException $e) {
// Most database drivers will return NULL here, but some of them
// (e.g. the SQLite driver) may need to re-run the query, so the return
// value will be the same as for static::query().
return $this->handleQueryException($e, $query, $args, $options);
}
}
/**
* Wraps and re-throws any PDO exception thrown by static::query().
*
* @param \PDOException $e
* The exception thrown by static::query().
* @param $query
* The query executed by static::query().
* @param array $args
* An array of arguments for the prepared statement.
* @param array $options
* An associative array of options to control how the query is run.
*
* @return \Drupal\Core\Database\StatementInterface|int|null
* Most database drivers will return NULL when a PDO exception is thrown for
* a query, but some of them may need to re-run the query, so they can also
* return a \Drupal\Core\Database\StatementInterface object or an integer.
*
* @throws \Drupal\Core\Database\DatabaseExceptionWrapper
* @throws \Drupal\Core\Database\IntegrityConstraintViolationException
*/
protected function handleQueryException(\PDOException $e, $query, array $args = array(), $options = array()) {
if ($options['throw_exception']) { if ($options['throw_exception']) {
// Wrap the exception in another exception, because PHP does not allow // Wrap the exception in another exception, because PHP does not allow
// overriding Exception::getMessage(). Its message is the extra database // overriding Exception::getMessage(). Its message is the extra database
// debug information. // debug information.
$query_string = ($query instanceof StatementInterface) ? $stmt->getQueryString() : $query; $query_string = ($query instanceof StatementInterface) ? $query->getQueryString() : $query;
$message = $e->getMessage() . ": " . $query_string . "; " . print_r($args, TRUE); $message = $e->getMessage() . ": " . $query_string . "; " . print_r($args, TRUE);
// Match all SQLSTATE 23xxx errors. // Match all SQLSTATE 23xxx errors.
if (substr($e->getCode(), -6, -3) == '23') { if (substr($e->getCode(), -6, -3) == '23') {
@ -586,9 +614,9 @@ abstract class Connection implements \Serializable {
throw $exception; throw $exception;
} }
return NULL; return NULL;
} }
}
/** /**
* Expands out shorthand placeholders. * Expands out shorthand placeholders.

View File

@ -102,67 +102,22 @@ class Connection extends DatabaseConnection {
return $pdo; return $pdo;
} }
/**
* {@inheritdoc}
*/
public function query($query, array $args = array(), $options = array()) { public function query($query, array $args = array(), $options = array()) {
$options += $this->defaultOptions(); $options += $this->defaultOptions();
// The PDO PostgreSQL driver has a bug which // The PDO PostgreSQL driver has a bug which doesn't type cast booleans
// doesn't type cast booleans correctly when // correctly when parameters are bound using associative arrays.
// parameters are bound using associative // @see http://bugs.php.net/bug.php?id=48383
// arrays.
// See http://bugs.php.net/bug.php?id=48383
foreach ($args as &$value) { foreach ($args as &$value) {
if (is_bool($value)) { if (is_bool($value)) {
$value = (int) $value; $value = (int) $value;
} }
} }
try { return parent::query($query, $args, $options);
if ($query instanceof StatementInterface) {
$stmt = $query;
$stmt->execute(NULL, $options);
}
else {
$this->expandArguments($query, $args);
$stmt = $this->prepareQuery($query);
$stmt->execute($args, $options);
}
switch ($options['return']) {
case Database::RETURN_STATEMENT:
return $stmt;
case Database::RETURN_AFFECTED:
$stmt->allowRowCount = TRUE;
return $stmt->rowCount();
case Database::RETURN_INSERT_ID:
return $this->connection->lastInsertId($options['sequence_name']);
case Database::RETURN_NULL:
return;
default:
throw new \PDOException('Invalid return directive: ' . $options['return']);
}
}
catch (\PDOException $e) {
if ($options['throw_exception']) {
// Match all SQLSTATE 23xxx errors.
if (substr($e->getCode(), -6, -3) == '23') {
$e = new IntegrityConstraintViolationException($e->getMessage(), $e->getCode(), $e);
}
else {
$e = new DatabaseExceptionWrapper($e->getMessage(), 0, $e);
}
// Add additional debug information.
if ($query instanceof StatementInterface) {
$e->query_string = $stmt->getQueryString();
}
else {
$e->query_string = $query;
}
$e->args = $args;
throw $e;
}
return NULL;
}
} }
public function prepareQuery($query) { public function prepareQuery($query) {

View File

@ -12,7 +12,6 @@ use Drupal\Core\Database\DatabaseNotFoundException;
use Drupal\Core\Database\TransactionNoActiveException; use Drupal\Core\Database\TransactionNoActiveException;
use Drupal\Core\Database\TransactionNameNonUniqueException; use Drupal\Core\Database\TransactionNameNonUniqueException;
use Drupal\Core\Database\TransactionCommitFailedException; use Drupal\Core\Database\TransactionCommitFailedException;
use Drupal\Core\Database\Driver\sqlite\Statement;
use Drupal\Core\Database\Connection as DatabaseConnection; use Drupal\Core\Database\Connection as DatabaseConnection;
/** /**
@ -334,6 +333,23 @@ class Connection extends DatabaseConnection {
return $modified; return $modified;
} }
/**
* {@inheritdoc}
*/
protected function handleQueryException(\PDOException $e, $query, array $args = array(), $options = array()) {
// The database schema might be changed by another process in between the
// time that the statement was prepared and the time the statement was run
// (e.g. usually happens when running tests). In this case, we need to
// re-run the query.
// @see http://www.sqlite.org/faq.html#q15
// @see http://www.sqlite.org/rescode.html#schema
if (!empty($e->errorInfo[1]) && $e->errorInfo[1] === 17) {
return $this->query($query, $args, $options);
}
parent::handleQueryException($e, $query, $args, $options);
}
public function queryRange($query, $from, $count, array $args = array(), array $options = array()) { public function queryRange($query, $from, $count, array $args = array(), array $options = array()) {
return $this->query($query . ' LIMIT ' . (int) $from . ', ' . (int) $count, $args, $options); return $this->query($query . ' LIMIT ' . (int) $from . ', ' . (int) $count, $args, $options);
} }