New sequences API.
From: damz
---
actions.inc | 2 -
database/database.inc | 56 ++++++++++++++++++++++++-
database/mysql/database.inc | 20 +++++++++
simpletest/tests/database_test.test | 27 ++++++++++++
system/system.install | 80 +++++++++++++++++------------------
system/system.module | 22 +++++-----
system/system.queue.inc | 4 --
user/user.install | 3 +
user/user.module | 30 +++++++------
9 files changed, 172 insertions(+), 72 deletions(-)
diff --git includes/actions.inc includes/actions.inc
index 080f932..25496d4 100644
--- includes/actions.inc
+++ includes/actions.inc
@@ -324,7 +324,7 @@ function actions_save($function, $type, $params, $desc, $aid = NULL) {
// aid is the callback for singleton actions so we need to keep a separate
// table for numeric aids.
if (!$aid) {
- $aid = db_insert('actions_aid')->useDefaults(array('aid'))->execute();
+ $aid = db_next_id('actions');
}
db_merge('actions')
diff --git includes/database/database.inc includes/database/database.inc
index 1dcd693..b600283 100644
--- includes/database/database.inc
+++ includes/database/database.inc
@@ -1046,6 +1046,38 @@ abstract class DatabaseConnection extends PDO {
public function commit() {
throw new ExplicitTransactionsNotSupportedException();
}
+
+ /**
+ * Retrieves an unique id from a given sequence.
+ *
+ * Use this function if for some reason you can't use a serial field.
+ *
+ * @param $name
+ * The name of the sequence.
+ * @param $existing_id
+ * After a database import, it might be that the sequences table is behind,
+ * so by passing in a minimum id, it can be assured that we never issue the
+ * same id.
+ * @return
+ * An integer number larger than any number returned by earlier calls
+ * to this function with the same sequence argument.
+ */
+ public function nextId($name, $existing_id) {
+ $transaction = $this->startTransaction();
+ $current_value = $this
+ ->query('SELECT value FROM {sequences} WHERE name = :name', array(':name' => $name))
+ ->fetchField();
+ $new_value = max($current_value, $existing_id) + 1;
+ // As we start the sequence with 1, if $current_value is 0 then the sequence
+ // does not exist.
+ if ($current_value) {
+ $this->query('UPDATE {sequences} SET value = :value WHERE name = :name', array(':name' => $name, ':value' => $new_value));
+ }
+ else {
+ $this->query('INSERT INTO {sequences} (name, value) VALUES (:name, :value)', array(':name' => $name, ':value' => $new_value));
+ }
+ return $new_value;
+ }
}
/**
@@ -1348,7 +1380,7 @@ abstract class Database {
// We need to pass around the simpletest database prefix in the request
// and we put that in the user_agent header.
- if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^simpletest\d+$/", $_SERVER['HTTP_USER_AGENT'])) {
+ if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^simpletest\d+$/", $_SERVER['HTTP_USER_AGENT'])) {
$db_prefix .= $_SERVER['HTTP_USER_AGENT'];
}
return $new_connection;
@@ -2041,6 +2073,26 @@ function db_driver() {
}
/**
+ * Retrieves an unique id from a given sequence.
+ *
+ * Use this function if for some reason you can't use a serial field,
+ * normally a serial field with db_last_insert_id is preferred.
+ *
+ * @param $name
+ * The name of the sequence.
+ * @param $existing_id
+ * After a database import, it might be that the sequences table is behind,
+ * so by passing in a minimum id, it can be assured that we never issue the
+ * same id.
+ * @return
+ * An integer number larger than any number returned before for this
+ * sequence.
+ */
+function db_next_id($name, $existing_id = 0) {
+ return Database::getConnection()->nextId($name, $existing_id);
+}
+
+/**
* @} End of "defgroup database".
*/
@@ -2438,7 +2490,7 @@ function _db_error_page($error = '') {
function db_ignore_slave() {
$connection_info = Database::getConnectionInfo();
// Only set ignore_slave_server if there are slave servers
- // being used, which is assumed if there are more than one.
+ // being used, which is assumed if there are more than one.
if (count($connection_info) > 1) {
// Five minutes is long enough to allow the slave to break and resume
// interrupted replication without causing problems on the Drupal site
diff --git includes/database/mysql/database.inc includes/database/mysql/database.inc
index b91d1d4..2cf03e8 100644
--- includes/database/mysql/database.inc
+++ includes/database/mysql/database.inc
@@ -77,6 +77,26 @@ class DatabaseConnection_mysql extends DatabaseConnection {
// (?query('INSERT INTO {sequences} (name, value) VALUES (:name, :value)', array(':name' => $name, ':value' => $existing_id));
+ }
+ catch (PDOException $e) {
+ // The most likely case for an exception is that the sequence already
+ // exists and that's exactly what we wanted to assure.
+ }
+ // LAST_INSERT_ID(x) stores x to be retrieved with LAST_INSERT_ID().
+ // This way there is no window for race condition between the UPDATE and
+ // the SELECT.
+ $this->query('UPDATE {sequences} SET value = LAST_INSERT_ID(IF(:existing_id > value, :existing_id_1, value) + 1) WHERE name = :name', array(
+ ':existing_id' => $existing_id,
+ ':existing_id_1' => $existing_id,
+ ':name' => $name,
+ ));
+ return $this->query('SELECT LAST_INSERT_ID()')->fetchField();
+ }
}
diff --git modules/simpletest/tests/database_test.test modules/simpletest/tests/database_test.test
index b6ef342..382b071 100644
--- modules/simpletest/tests/database_test.test
+++ modules/simpletest/tests/database_test.test
@@ -2746,3 +2746,30 @@ class DatabaseTransactionTestCase extends DatabaseTestCase {
}
}
}
+
+/**
+ * Check the sequences API.
+ */
+class DatabaseNextIdCase extends DrupalWebTestCase {
+ function getInfo() {
+ return array(
+ 'name' => t('Sequences API'),
+ 'description' => t('Test the secondary sequences API.'),
+ 'group' => t('Database'),
+ );
+ }
+
+ /**
+ * Test that the sequences API work.
+ */
+ function testDbNextId() {
+ $first = db_next_id('test');
+ $second = db_next_id('test');
+ // We can test for exact increase in here because we know there is no
+ // other process operating on these tables -- normally we could only
+ // expect $second > $first.
+ $this->assertEqual($first + 1, $second, t('The second call from a sequence provides a number increased by one.'));
+ $result = db_next_id('test', 1000);
+ $this->assertEqual($result, 1001, t('Sequence provides a larger number than the existing ID.'));
+ }
+}
diff --git modules/system/system.install modules/system/system.install
index e0385e3..f5fbdb1 100644
--- modules/system/system.install
+++ modules/system/system.install
@@ -355,21 +355,7 @@ function system_install() {
// Load system theme data appropriately.
system_get_theme_data();
- // Inserting uid 0 here confuses MySQL -- the next user might be created as
- // uid 2 which is not what we want. So we insert the first user here, the
- // anonymous user. uid is 1 here for now, but very soon it will be changed
- // to 0.
db_query("INSERT INTO {users} (name, mail) VALUES('%s', '%s')", '', '');
- // We need some placeholders here as name and mail are uniques and data is
- // presumed to be a serialized array. Install will change uid 1 immediately
- // anyways. So we insert the superuser here, the uid is 2 here for now, but
- // very soon it will be changed to 1.
- db_query("INSERT INTO {users} (name, mail, created, status, data) VALUES('%s', '%s', %d, %d, '%s')", 'placeholder-for-uid-1', 'placeholder-for-uid-1', REQUEST_TIME, 1, serialize(array()));
- // This sets the above two users uid 0 (anonymous). We avoid an explicit 0
- // otherwise MySQL might insert the next auto_increment value.
- db_query("UPDATE {users} SET uid = uid - uid WHERE name = '%s'", '');
- // This sets uid 1 (superuser). We skip uid 2 but that's not a big problem.
- db_query("UPDATE {users} SET uid = 1 WHERE name = '%s'", 'placeholder-for-uid-1');
// Built-in roles.
$rid_anonymous = db_insert('role')
@@ -382,8 +368,8 @@ function system_install() {
->values(array('name' => 'authenticated user'))
->execute();
- // Sanity check to ensure the anonymous and authenticated role IDs are the
- // same as the drupal defined constants. In certain situations, this will
+ // Sanity check to ensure the anonymous and authenticated role IDs are the
+ // same as the drupal defined constants. In certain situations, this will
// not be true.
if ($rid_anonymous != DRUPAL_ANONYMOUS_RID) {
db_update('role')
@@ -585,19 +571,6 @@ function system_schema() {
'primary key' => array('aid'),
);
- $schema['actions_aid'] = array(
- 'description' => 'Stores action IDs for non-default actions.',
- 'fields' => array(
- 'aid' => array(
- 'description' => 'Primary Key: Unique actions ID.',
- 'type' => 'serial',
- 'unsigned' => TRUE,
- 'not null' => TRUE,
- ),
- ),
- 'primary key' => array('aid'),
- );
-
$schema['batch'] = array(
'description' => 'Stores details about batches (processes that run in multiple HTTP requests).',
'fields' => array(
@@ -1207,18 +1180,6 @@ function system_schema() {
),
);
- $schema['queue_consumer_id'] = array(
- 'description' => 'Stores queue consumer IDs, used to auto-increment the consumer ID so that a unique consumer ID is used.',
- 'fields' => array(
- 'consumer_id' => array(
- 'type' => 'serial',
- 'not null' => TRUE,
- 'description' => 'Primary Key: Unique consumer ID used to make sure only one consumer gets one item.',
- ),
- ),
- 'primary key' => array('consumer_id'),
- );
-
$schema['registry'] = array(
'description' => "Each record is a function, class, or interface name and the file it is in.",
'fields' => array(
@@ -1294,6 +1255,26 @@ function system_schema() {
'primary key' => array('filename'),
);
+ $schema['sequences'] = array(
+ 'description' => 'Stores IDs.',
+ 'fields' => array(
+ 'value' => array(
+ 'description' => 'The .',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ),
+ 'name' => array(
+ 'description' => "Primary key: A session ID. The value is generated by PHP's Session API.",
+ 'type' => 'varchar',
+ 'length' => 64,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ ),
+ 'primary key' => array('name'),
+ );
+
$schema['sessions'] = array(
'description' => "Drupal's session handlers read and write into the sessions table. Each record represents a user session, either anonymous or authenticated.",
'fields' => array(
@@ -2248,6 +2229,23 @@ function system_update_7029() {
}
/**
+ * Reuse the actions_aid table as sequences.
+ */
+function system_update_7030() {
+ $ret = array();
+ db_drop_primary_key($ret, 'actions_aid');
+ db_change_field($ret, 'actions_aid', 'aid', 'sid', array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE), array('primary key' => array('sid')));
+ db_rename_table($ret, 'actions_aid', 'sequences');
+ $max_sid = db_query('SELECT MAX(sid) FROM {sequences}')->fetchField();
+ db_delete('sequences')->condition('sid', $max_sid, '<');
+ $max_uid = db_query('SELECT MAX(uid) FROM {users}')->fetchField();
+ if ($max_uid > $max_sid) {
+ db_update('sequences')->fields(array('sid' => $max_uid))->execute();
+ }
+ return $ret;
+}
+
+/**
* @} End of "defgroup updates-6.x-to-7.x"
* The next series of updates should start at 8000.
*/
diff --git modules/system/system.module modules/system/system.module
index fa0f5c7..cb6721f 100644
--- modules/system/system.module
+++ modules/system/system.module
@@ -1200,7 +1200,7 @@ function system_filetransfer_backends() {
'weight' => 0,
);
}
-
+
if (ini_get('allow_url_fopen')) {
$backends['ftp_wrapper'] = array(
'title' => t('FTP using file streams'),
@@ -1209,7 +1209,7 @@ function system_filetransfer_backends() {
'weight' => 10,
);
}
-
+
// SSH2 lib connection is only available if the proper PHP extension is
// installed.
if (function_exists('ssh2_connect')) {
@@ -1235,7 +1235,7 @@ function system_filetransfer_backends() {
function system_get_filetransfer_settings_form($filetransfer_backend_name, $defaults) {
$available_backends = module_invoke_all('filetransfer_backends');
$form = call_user_func($available_backends[$filetransfer_backend_name]['settings_form']);
-
+
foreach ($form as $name => &$element) {
if (isset($defaults[$name])) {
$element['#default_value'] = $defaults[$name];
@@ -1267,30 +1267,30 @@ function system_filetransfer_backend_form_ssh() {
*/
function _system_filetransfer_backend_form_common() {
$form = array();
-
+
$form['hostname'] = array (
'#type' => 'textfield',
'#title' => t('Host'),
'#default_value' => 'localhost',
);
-
+
$form['port'] = array (
'#type' => 'textfield',
'#title' => t('Port'),
'#default_value' => NULL,
);
-
+
$form['username'] = array (
'#type' => 'textfield',
'#title' => t('Username'),
);
-
+
$form['password'] = array (
'#type' => 'password',
'#title' => t('Password'),
'#description' => t('This is not saved in the database and is only used to test the connection'),
);
-
+
return $form;
}
@@ -3025,17 +3025,17 @@ function system_retrieve_file($url, $destination = NULL, $overwrite = TRUE) {
}
$parsed_url = parse_url($url);
$local = is_dir(file_directory_path() . '/' . $destination) ? $destination . '/' . basename($parsed_url['path']) : $destination;
-
+
if (!$overwrite && file_exists($local)) {
drupal_set_message(t('@remote could not be saved. @local already exists', array('@remote' => $url, '@local' => $local)), 'error');
return FALSE;
}
-
+
$result = drupal_http_request($url);
if ($result->code != 200 || !file_save_data($result->data, $local)) {
drupal_set_message(t('@remote could not be saved.', array('@remote' => $url)), 'error');
return FALSE;
}
-
+
return $local;
}
diff --git modules/system/system.queue.inc modules/system/system.queue.inc
index 36bcd7f..c6eff35 100644
--- modules/system/system.queue.inc
+++ modules/system/system.queue.inc
@@ -195,9 +195,7 @@ class SystemQueue implements DrupalQueueInterface {
public function claimItem($lease_time = 30) {
if (!isset($this->consumerId)) {
- $this->consumerId = db_insert('queue_consumer_id')
- ->useDefaults(array('consumer_id'))
- ->execute();
+ $this->consumerId = db_next_id('queue_consumer_id');
}
// Claim an item by updating its consumer_id and expire fields. If claim
// is not successful another thread may have claimed the item in the
diff --git modules/user/user.install modules/user/user.install
index 7a63b2f..1abde8c 100644
--- modules/user/user.install
+++ modules/user/user.install
@@ -102,10 +102,11 @@ function user_schema() {
'description' => 'Stores user data.',
'fields' => array(
'uid' => array(
- 'type' => 'serial',
+ 'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'description' => 'Primary Key: Unique user ID.',
+ 'default' => 0,
),
'name' => array(
'type' => 'varchar',
diff --git modules/user/user.module modules/user/user.module
index 3749e1f..5b300c9 100644
--- modules/user/user.module
+++ modules/user/user.module
@@ -325,11 +325,11 @@ function user_load_by_name($name) {
*
* @param $account
* (optional) The user object to modify or add. If you want to modify
- * an existing user account, you will need to ensure that (a) $account
- * is an object, and (b) you have set $account->uid to the numeric
+ * an existing user account, you will need to ensure that (a) $account
+ * is an object, and (b) you have set $account->uid to the numeric
* user ID of the user account you wish to modify. If you
* want to create a new user account, you can set $account->is_new to
- * TRUE or omit the $account->uid field.
+ * TRUE or omit the $account->uid field.
* @param $edit
* An array of fields and values to save. For example array('name'
* => 'My name'). Keys that do not belong to columns in the user-related
@@ -376,6 +376,11 @@ function user_save($account, $edit = array(), $category = 'account') {
if (!isset($account->is_new)) {
$account->is_new = empty($account->uid);
}
+
+ if ($account->is_new && empty($account->uid)) {
+ $account->uid = db_next_id('users', db_query('SELECT MAX(uid) FROM {users}')->fetchField());
+ }
+
if (is_object($account) && !$account->is_new) {
user_module_invoke('update', $edit, $account, $category);
$data = unserialize(db_query('SELECT data FROM {users} WHERE uid = :uid', array(':uid' => $account->uid))->fetchField());
@@ -398,7 +403,6 @@ function user_save($account, $edit = array(), $category = 'account') {
}
}
-
// Process picture uploads.
if (!empty($edit['picture']->fid)) {
$picture = $edit['picture'];
@@ -1594,7 +1598,7 @@ function user_login(&$form_state) {
* authentication fails. Distributed authentication modules are welcome
* to use hook_form_alter() to change this series in order to
* authenticate against their user database instead of the local users
- * table. If a distributed authentication module is successful, it
+ * table. If a distributed authentication module is successful, it
* should set $form_state['uid'] to a user ID.
*
* We use three validators instead of one since external authentication
@@ -1622,7 +1626,7 @@ function user_login_name_validate($form, &$form_state) {
/**
* A validate handler on the login form. Check supplied username/password
- * against local users table. If successful, $form_state['uid']
+ * against local users table. If successful, $form_state['uid']
* is set to the matching user ID.
*/
function user_login_authenticate_validate($form, &$form_state) {
@@ -1656,10 +1660,10 @@ function user_authenticate(&$form_state) {
// Allow alternate password hashing schemes.
require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
if (user_check_password($password, $account)) {
-
+
// Successful authentication. Set a flag for user_login_final_validate().
$form_state['uid'] = $account->uid;
-
+
// Update user to new password scheme if needed.
if (user_needs_new_hash($account)) {
$new_hash = user_hash_password($password);
@@ -1701,15 +1705,15 @@ function user_login_finalize(&$edit = array()) {
}
/**
- * Submit handler for the login form. Load $user object and perform standard login
- * tasks. The user is then redirected to the My Account page. Setting the
+ * Submit handler for the login form. Load $user object and perform standard login
+ * tasks. The user is then redirected to the My Account page. Setting the
* destination in the query string overrides the redirect.
*/
function user_login_submit($form, &$form_state) {
global $user;
$user = user_load($form_state['uid']);
user_login_finalize();
-
+
$form_state['redirect'] = 'user/' . $user->uid;
}
@@ -1737,7 +1741,7 @@ function user_external_login_register($name, $module) {
}
user_set_authmaps($account, array("authname_$module" => $name));
}
-
+
// Log user in.
$form_state['uid'] = $account->uid;
user_login_submit(array(), $form_state);
@@ -2803,7 +2807,7 @@ function user_register_submit($form, &$form_state) {
drupal_set_message(t('
Your password is %pass. You may change your password below.
', array('%pass' => $pass)));
}
- $form_state['values'] += $merge_data;
+ $form_state['values'] += $merge_data;
user_authenticate(array_merge($form_state));
$form_state['redirect'] = 'user/1/edit';