diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php index 30e9e01..a5ca652 100644 --- a/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php +++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php @@ -35,6 +35,13 @@ class Connection extends DatabaseConnection { const DATABASE_NOT_FOUND = 7; /** + * Last used cursor number. + * + * @var type int + */ + private $_cursor_ctr; + + /** * Constructs a connection object. */ public function __construct(PDO $connection, array $connection_options) { @@ -56,6 +63,8 @@ public function __construct(PDO $connection, array $connection_options) { if (isset($connection_options['init_commands'])) { $this->connection->exec(implode('; ', $connection_options['init_commands'])); } + + $this->_cursor_ctr = 0; } /** @@ -132,6 +141,10 @@ public function query($query, array $args = array(), $options = array()) { $stmt->execute($args, $options); } + if (strpos($stmt->queryString,'SAVEPOINT mimic_innodb_not_released;') !== FALSE) { + $this->connection->prepare("RELEASE SAVEPOINT mimic_innodb_not_released")->execute(); + } + switch ($options['return']) { case Database::RETURN_STATEMENT: return $stmt; @@ -146,6 +159,11 @@ public function query($query, array $args = array(), $options = array()) { } } catch (PDOException $e) { + + if (preg_match("/SAVEPOINT (mimic_innodb_(released|not_released))/",$stmt->queryString,$matches)) { + $this->connection->prepare("ROLLBACK TO SAVEPOINT " . $matches[1])->execute(); + } + if ($options['throw_exception']) { // Match all SQLSTATE 23xxx errors. if (substr($e->getCode(), -6, -3) == '23') { @@ -173,7 +191,27 @@ public function prepareQuery($query) { // @todo This workaround only affects bytea fields, but the involved field // types involved in the query are unknown, so there is no way to // conditionally execute this for affected queries only. - return parent::prepareQuery(preg_replace('/ ([^ ]+) +(I*LIKE|NOT +I*LIKE) /i', ' ${1}::text ${2} ', $query)); + $query = preg_replace('/ ([^ ]+) +(I*LIKE|NOT +I*LIKE) /i', ' ${1}::text ${2} ', $query); + + // While in transaction context, put a SAVEPOINT around every query that isn't + // itself a SAVEPOINT operation. This means that a failed query can't cause + // the transaction to abort, which mimics the behavior of innodb. + if ($this->inTransaction() && + (stripos($query,'SAVEPOINT ') === FALSE) && + (stripos($query,'RELEASE ') === FALSE)) { + if (preg_match('/^[\s]*SELECT /i', $query)) { + // In the case of SELECT the SAVEPOINT can also be released in the same + // query. Otherwise the RELEASE is a separate query. + $csr = 'csr' . $this->_cursor_ctr++; + $query = 'SAVEPOINT mimic_innodb_released; DECLARE '. $csr .' CURSOR FOR ' . + $query . '; RELEASE SAVEPOINT mimic_innodb_released; FETCH ALL ' . $csr; + } + else { + $query = 'SAVEPOINT mimic_innodb_not_released; ' . $query; + } + } + + return parent::prepareQuery($query); } public function queryRange($query, $from, $count, array $args = array(), array $options = array()) {