From 37fcdbc67cd5591107c93016d1d3bb109be5e9e6 Mon Sep 17 00:00:00 2001 From: Angie Byron Date: Thu, 7 Jan 2010 04:51:26 +0000 Subject: [PATCH] #633678 by Josh Waihi, chx, Crell, David Strauss, and Damien Tournoud: Make sequence API work on non-MySQL databases. --- includes/database/database.inc | 21 +----------- includes/database/pgsql/database.inc | 48 +++++++++++++++++++++++++++ includes/database/sqlite/database.inc | 21 ++++++++++++ 3 files changed, 70 insertions(+), 20 deletions(-) diff --git a/includes/database/database.inc b/includes/database/database.inc index 7ab0078df1d..916ca4cc65f 100644 --- a/includes/database/database.inc +++ b/includes/database/database.inc @@ -1178,26 +1178,7 @@ abstract class DatabaseConnection extends PDO { * An integer number larger than any number returned by earlier calls and * also larger than the $existing_id if one was passed in. */ - public function nextId($existing_id = 0) { - $transaction = $this->startTransaction(); - // We can safely use literal queries here instead of the slower query - // builder because if a given database breaks here then it can simply - // override nextId. However, this is unlikely as we deal with short strings - // and integers and no known databases require special handling for those - // simple cases. If another transaction wants to write the same row, it will - // wait until this transaction commits. - $stmt = $this->query('UPDATE {sequences} SET value = GREATEST(value, :existing_id) + 1', array( - ':existing_id' => $existing_id, - )); - if (!$stmt->rowCount()) { - $this->query('INSERT INTO {sequences} (value) VALUES (:existing_id + 1)', array( - ':existing_id' => $existing_id, - )); - } - // The transaction gets committed when the transaction object gets destroyed - // because it gets out of scope. - return $new_value; - } + abstract public function nextId($existing_id = 0); } /** diff --git a/includes/database/pgsql/database.inc b/includes/database/pgsql/database.inc index 8b60b957b69..218d10865c6 100644 --- a/includes/database/pgsql/database.inc +++ b/includes/database/pgsql/database.inc @@ -11,6 +11,11 @@ * @{ */ +/** + * The name by which to obtain a lock for retrive the next insert id. + */ +define('POSTGRESQL_NEXTID_LOCK', 1000); + class DatabaseConnection_pgsql extends DatabaseConnection { public function __construct(array $connection_options = array()) { @@ -127,6 +132,49 @@ class DatabaseConnection_pgsql extends DatabaseConnection { return isset($specials[$operator]) ? $specials[$operator] : NULL; } + + /** + * Retrive a the next id in a sequence. + * + * PostgreSQL has built in sequences. We'll use these instead of inserting + * and updating a sequences table. + */ + public function nextId($existing = 0) { + + // Retrive the name of the sequence. This information cannot be cached + // because the prefix may change, for example, like it does in simpletests. + $sequence_name = $this->makeSequenceName('sequences', 'value'); + + // When PostgreSQL gets a value too small then it will lock the table, + // retry the INSERT and if it's still too small then alter the sequence. + $id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField(); + if ($id > $existing) { + return $id; + } + + // PostgreSQL advisory locks are simply locks to be used by an + // application such as Drupal. This will prevent other Drupal proccesses + // from altering the sequence while we are. + $this->query("SELECT pg_advisory_lock(" . POSTGRESQL_NEXTID_LOCK . ")"); + + // While waiting to obtain the lock, the sequence may have been altered + // so lets try again to obtain an adequate value. + $id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField(); + if ($id > $existing) { + $this->query("SELECT pg_advisory_unlock(" . POSTGRESQL_NEXTID_LOCK . ")"); + return $id; + } + + // Reset the sequence to a higher value than the existing id. + $this->query("ALTER SEQUENCE " . $sequence_name . " RESTART WITH " . ($existing + 1)); + + // Retrive the next id. We know this will be as high as we want it. + $id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField(); + + $this->query("SELECT pg_advisory_unlock(" . POSTGRESQL_NEXTID_LOCK . ")"); + + return $id; + } } /** diff --git a/includes/database/sqlite/database.inc b/includes/database/sqlite/database.inc index e762a6ba7a2..eaeacb32df0 100644 --- a/includes/database/sqlite/database.inc +++ b/includes/database/sqlite/database.inc @@ -167,6 +167,27 @@ class DatabaseConnection_sqlite extends DatabaseConnection { // DatabaseStatement_sqlite::execute() and cannot be cached. return $this->prepare($this->prefixTables($query)); } + + public function nextId($existing_id = 0) { + $transaction = $this->startTransaction(); + // We can safely use literal queries here instead of the slower query + // builder because if a given database breaks here then it can simply + // override nextId. However, this is unlikely as we deal with short strings + // and integers and no known databases require special handling for those + // simple cases. If another transaction wants to write the same row, it will + // wait until this transaction commits. + $stmt = $this->query('UPDATE {sequences} SET value = GREATEST(value, :existing_id) + 1', array( + ':existing_id' => $existing_id, + )); + if (!$stmt->rowCount()) { + $this->query('INSERT INTO {sequences} (value) VALUES (:existing_id + 1)', array( + ':existing_id' => $existing_id, + )); + } + // The transaction gets committed when the transaction object gets destroyed + // because it gets out of scope. + return $new_value; + } } /**