=== modified file 'includes/database.inc' --- includes/database.inc 2007-11-30 12:19:10 +0000 +++ includes/database.inc 2007-12-01 10:55:04 +0000 @@ -330,6 +330,40 @@ function db_escape_table($string) { } /** + * Keeps a name_lower field in sync with a name field. + * + * If the database supports triggers, this function does nothing. Otherwise, + * it scans the table for records where the name_lower is not LOWER(name) + * and fixes those. + * + * @param $table + * Name of the table to operate on. + */ +function db_sync_name_lower($table) { + static $db_has_trigger; + if (!isset($db_has_trigger)) { + $db_has_trigger = variable_get('db_has_trigger', FALSE); + } + if (!$db_has_trigger) { + // We had LOWER(name) = LOWER(%s) here which iterated over the whole table, + // changed name to lowercase and compared that to a string. This does the + // exact same amount of work. + $id_field = $table[0] .'id'; + $result = db_query("SELECT $id_field AS id FROM {". $table ."} WHERE name_lower != LOWER(name)"); + $ids = array(); + while ($object = db_fetch_object($result)) { + $ids[] = $object->id; + $placeholders[] = '%d'; + } + if ($ids) { + db_query('UPDATE {'. $table ."} SET name_lower = LOWER(name) WHERE $id_field IN (". implode(', ', $placeholders) .')', $ids); + } + } +} + + + +/** * @} End of "defgroup database". */ === modified file 'includes/database.mysql-common.inc' --- includes/database.mysql-common.inc 2007-10-02 16:15:56 +0000 +++ includes/database.mysql-common.inc 2007-12-01 10:55:04 +0000 @@ -42,6 +42,10 @@ function db_query($query) { return _db_query($query); } +function db_has_trigger() { + return @db_result(db_query('SHOW TRIGGERS')); +} + /** * @ingroup schemaapi * @{ === modified file 'includes/database.pgsql.inc' --- includes/database.pgsql.inc 2007-10-17 12:47:28 +0000 +++ includes/database.pgsql.inc 2007-12-01 10:55:04 +0000 @@ -32,6 +32,10 @@ function db_status_report() { return $form; } +function db_has_trigger() { + return db_result(db_query("SELECT has_language_privilege(oid,'USAGE') FROM pg_language WHERE lanname='plpgsql'")); +} + /** * Returns the version of the database server currently in use. * === modified file 'modules/system/system.admin.inc' --- modules/system/system.admin.inc 2007-11-27 21:06:28 +0000 +++ modules/system/system.admin.inc 2007-12-01 10:55:04 +0000 @@ -1599,6 +1599,8 @@ function system_clean_url_settings() { * If true, only returns a boolean whether there are system status errors. */ function system_status($check = FALSE) { + // Keep triggers info fresh. + variable_set('db_has_trigger', (bool)db_has_trigger()); // Load .install files include_once './includes/install.inc'; drupal_load_updates(); === modified file 'modules/system/system.install' --- modules/system/system.install 2007-11-30 18:11:29 +0000 +++ modules/system/system.install 2007-12-01 10:58:33 +0000 @@ -304,7 +304,8 @@ function system_install() { // 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, data) VALUES('%s', '%s', %d, '%s')", 'placeholder-for-uid-1', 'placeholder-for-uid-1', time(), serialize(array())); + $name = 'placeholder-for-uid-1'; + db_query("INSERT INTO {users} (name, name_lower, mail, created, data) VALUES ('%s', '%s', '%s', %d, '%s')", $name, $name, $name, time(), 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'", ''); @@ -352,6 +353,11 @@ function system_install() { db_query("INSERT INTO {variable} (name, value) VALUES ('%s','%s')", 'filter_html_1', 'i:1;'); db_query("INSERT INTO {variable} (name, value) VALUES ('%s', '%s')", 'node_options_forum', 'a:1:{i:0;s:6:"status";}'); + + if ($GLOBALS['db_type'] == 'pgsql') { + @db_query('CREATE FUNCTION name_lower() RETURNS TRIGGER language plpgsql AS $f$ BEGIN NEW.name_lower := lower(NEW.name); RETURN NEW; END; $f$'); + } + variable_set('db_has_trigger', (bool)db_has_trigger()); } /** @@ -2731,6 +2737,45 @@ function system_update_6040() { } /** + * Add name_lower to users and term_data and try to add triggers, too. + */ +function system_update_6041() { + $ret = array(); + db_add_field($ret, 'users', 'name_lower', array('type' => 'varchar', 'length' => 60, 'not null' => TRUE, 'default' => '')); + $ret[] = update_sql('UPDATE {users} SET name_lower = LOWER(name)'); + db_add_index($ret, 'users', 'name_lower', array('name_lower')); + switch ($GLOBALS['db_type']) { + case 'mysql': + case 'mysqli': + @db_query('CREATE TRIGGER user_name_lower_before_insert BEFORE INSERT ON {users} FOR EACH ROW SET NEW.name_lower = LOWER(NEW.name)'); + @db_query('CREATE TRIGGER user_name_lower_before_update BEFORE UPDATE ON {users} FOR EACH ROW SET NEW.name_lower = LOWER(NEW.name)'); + break; + case 'pgsql': + @db_query('CREATE FUNCTION name_lower() RETURNS TRIGGER language plpgsql AS $f$ BEGIN NEW.name_lower := lower(NEW.name); RETURN NEW; END; $f$'); + @db_query('CREATE TRIGGER user_name_lower_before_insert BEFORE INSERT ON {users} FOR EACH ROW EXECUTE PROCEDURE name_lower();'); + @db_query('CREATE TRIGGER user_name_lower_before_update BEFORE UPDATE ON {users} FOR EACH ROW EXECUTE PROCEDURE name_lower();'); + break; + } + if (db_table_exists('term_data')) { + db_add_field($ret, 'term_data', 'name_lower', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '')); + db_add_index($ret, 'term_data', 'name_lower', array('name_lower')); + $ret[] = update_sql('UPDATE {term_data} SET name_lower = LOWER(name)'); + switch ($GLOBALS['db_type']) { + case 'mysql': + case 'mysqli': + @db_query('CREATE TRIGGER term_data_name_lower_before_insert BEFORE INSERT ON {term_data} FOR EACH ROW SET NEW.name_lower = LOWER(NEW.name)'); + @db_query('CREATE TRIGGER term_data_name_lower_before_update BEFORE UPDATE ON {term_data} FOR EACH ROW SET NEW.name_lower = LOWER(NEW.name)'); + break; + case 'pgsql': + @db_query('CREATE TRIGGER term_data_name_lower_before_insert BEFORE INSERT ON {term_data} FOR EACH ROW EXECUTE PROCEDURE name_lower();'); + @db_query('CREATE TRIGGER term_data_name_lower_before_update BEFORE UPDATE ON {term_data} FOR EACH ROW EXECUTE PROCEDURE name_lower();'); + break; + } + } + variable_set('db_has_trigger', (bool)db_has_trigger()); +} + +/** * @} End of "defgroup updates-5.x-to-6.x" * The next series of updates should start at 7000. */ === modified file 'modules/taxonomy/taxonomy.install' --- modules/taxonomy/taxonomy.install 2007-10-21 18:59:01 +0000 +++ modules/taxonomy/taxonomy.install 2007-12-01 10:55:04 +0000 @@ -28,6 +28,13 @@ function taxonomy_schema() { 'default' => '', 'description' => t('The term name.'), ), + 'name_lower' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + 'description' => t('The term name in lowercase.'), + ), 'description' => array( 'type' => 'text', 'not null' => FALSE, @@ -43,7 +50,10 @@ function taxonomy_schema() { ), ), 'primary key' => array('tid'), - 'indexes' => array('vid' => array('vid')), + 'indexes' => array( + 'vid' => array('vid'), + 'name_lower' => array('name_lower'), + ), ); $schema['term_hierarchy'] = array( @@ -274,3 +284,16 @@ function taxonomy_schema() { return $schema; } +function taxonomy_install() { + switch ($GLOBALS['db_type']) { + case 'mysql': + case 'mysqli': + @db_query('CREATE TRIGGER term_data_name_lower_before_insert BEFORE INSERT ON {term_data} FOR EACH ROW SET NEW.name_lower = LOWER(NEW.name)'); + @db_query('CREATE TRIGGER term_data_name_lower_before_update BEFORE UPDATE ON {term_data} FOR EACH ROW SET NEW.name_lower = LOWER(NEW.name)'); + break; + case 'pgsql': + @db_query('CREATE TRIGGER term_data_name_lower_before_insert BEFORE INSERT ON {term_data} FOR EACH ROW EXECUTE PROCEDURE name_lower();'); + @db_query('CREATE TRIGGER term_data_name_lower_before_update BEFORE UPDATE ON {term_data} FOR EACH ROW EXECUTE PROCEDURE name_lower();'); + break; + } +} === modified file 'modules/taxonomy/taxonomy.module' --- modules/taxonomy/taxonomy.module 2007-11-28 10:29:19 +0000 +++ modules/taxonomy/taxonomy.module 2007-12-01 10:55:04 +0000 @@ -359,6 +359,8 @@ function taxonomy_save_term(&$form_value } cache_clear_all(); + // Keep name_lower in sync. + db_sync_name_lower('term_data'); return $status; } @@ -949,7 +951,8 @@ function _taxonomy_term_children($tid) { * An array of matching term objects. */ function taxonomy_get_term_by_name($name) { - $db_result = db_query(db_rewrite_sql("SELECT t.tid, t.* FROM {term_data} t WHERE LOWER(t.name) LIKE LOWER('%s')", 't', 'tid'), trim($name)); + db_sync_name_lower('term_data'); + $db_result = db_query(db_rewrite_sql("SELECT t.tid, t.* FROM {term_data} t WHERE t.name_lower = '%s'", 't', 'tid'), trim($name)); $result = array(); while ($term = db_fetch_object($db_result)) { $result[] = $term; === modified file 'modules/taxonomy/taxonomy.pages.inc' --- modules/taxonomy/taxonomy.pages.inc 2007-11-11 06:56:44 +0000 +++ modules/taxonomy/taxonomy.pages.inc 2007-12-01 10:55:04 +0000 @@ -110,7 +110,7 @@ function taxonomy_autocomplete($vid, $st $last_string = trim(array_pop($array)); $matches = array(); if ($last_string != '') { - $result = db_query_range(db_rewrite_sql("SELECT t.tid, t.name FROM {term_data} t WHERE t.vid = %d AND LOWER(t.name) LIKE LOWER('%%%s%%')", 't', 'tid'), $vid, $last_string, 0, 10); + $result = db_query_range(db_rewrite_sql("SELECT t.tid, t.name FROM {term_data} t WHERE t.vid = %d AND t.name_lower LIKE LOWER('%s%%')", 't', 'tid'), $vid, $last_string, 0, 10); $prefix = count($array) ? implode(', ', $array) .', ' : ''; === modified file 'modules/user/user.install' --- modules/user/user.install 2007-11-04 14:33:06 +0000 +++ modules/user/user.install 2007-12-01 10:59:00 +0000 @@ -142,6 +142,13 @@ function user_schema() { 'default' => '', 'description' => t('Unique user name.'), ), + 'name_lower' => array( + 'type' => 'varchar', + 'length' => 60, + 'not null' => TRUE, + 'default' => '', + 'description' => t('The user name in lowercase.'), + ), 'pass' => array( 'type' => 'varchar', 'length' => 32, @@ -253,8 +260,11 @@ function user_schema() { 'indexes' => array( 'access' => array('access'), 'created' => array('created') + 'name_lower' => array('name_lower'), + ), + 'unique keys' => array( + 'name' => array('name'), ), - 'unique keys' => array('name' => array('name')), 'primary key' => array('uid'), ); @@ -282,3 +292,16 @@ function user_schema() { return $schema; } +function user_install() { + switch ($GLOBALS['db_type']) { + case 'mysql': + case 'mysqli': + @db_query('CREATE TRIGGER user_name_lower_before_insert BEFORE INSERT ON {users} FOR EACH ROW SET NEW.name_lower = LOWER(NEW.name)'); + @db_query('CREATE TRIGGER user_name_lower_before_update BEFORE UPDATE ON {users} FOR EACH ROW SET NEW.name_lower = LOWER(NEW.name)'); + break; + case 'pgsql': + @db_query('CREATE TRIGGER user_name_lower_before_insert BEFORE INSERT ON {users} FOR EACH ROW EXECUTE PROCEDURE name_lower();'); + @db_query('CREATE TRIGGER user_name_lower_before_update BEFORE UPDATE ON {users} FOR EACH ROW EXECUTE PROCEDURE name_lower();'); + break; + } +} === modified file 'modules/user/user.module' --- modules/user/user.module 2007-11-26 16:36:42 +0000 +++ modules/user/user.module 2007-12-01 10:55:04 +0000 @@ -144,17 +144,23 @@ function user_load($array = array()) { } foreach ($array as $key => $value) { - if ($key == 'uid' || $key == 'status') { - $query[] = "$key = %d"; - $params[] = $value; - } - else if ($key == 'pass') { - $query[] = "pass = '%s'"; - $params[] = md5($value); - } - else { - $query[]= "LOWER($key) = LOWER('%s')"; - $params[] = $value; + switch ($key) { + case 'uid': + case 'status': + $query[] = "$key = %d"; + $params[] = $value; + break; + case 'pass': + $query[] = "pass = '%s'"; + $params[] = md5($value); + break; + case 'name': + $query[]= "name_lower = LOWER('%s')"; + $params[] = $value; + break; + default: + $query[]= "LOWER($key) = LOWER('%s')"; + $params[] = $value; } } $result = db_query('SELECT * FROM {users} u WHERE '. implode(' AND ', $query), $params); @@ -348,6 +354,8 @@ function user_save($account, $array = ar user_set_authmaps($user, $authmaps); } + // Keep name_lower in sync. + db_sync_name_lower('users'); return $user; } @@ -489,7 +497,7 @@ function user_access($string, $account = * @return boolean TRUE for blocked users, FALSE for active */ function user_is_blocked($name) { - $deny = db_fetch_object(db_query("SELECT name FROM {users} WHERE status = 0 AND name = LOWER('%s')", $name)); + $deny = db_fetch_object(db_query("SELECT name FROM {users} WHERE status = 0 AND name_lower = LOWER('%s')", $name)); return $deny; } @@ -544,15 +552,16 @@ function user_search($op = 'search', $ke $find = array(); // Replace wildcards with MySQL/PostgreSQL wildcards. $keys = preg_replace('!\*+!', '%', $keys); + db_sync_name_lower('users'); if (user_access('administer users')) { // Administrators can also search in the otherwise private email field. - $result = pager_query("SELECT name, uid, mail FROM {users} WHERE LOWER(name) LIKE LOWER('%%%s%%') OR LOWER(mail) LIKE LOWER('%%%s%%')", 15, 0, NULL, $keys, $keys); + $result = pager_query("SELECT name, uid, mail FROM {users} WHERE name_lower LIKE LOWER('%%%s%%') OR LOWER(mail) LIKE LOWER('%%%s%%')", 15, 0, NULL, $keys, $keys); while ($account = db_fetch_object($result)) { $find[] = array('title' => $account->name .' ('. $account->mail .')', 'link' => url('user/'. $account->uid, array('absolute' => TRUE))); } } else { - $result = pager_query("SELECT name, uid FROM {users} WHERE LOWER(name) LIKE LOWER('%%%s%%')", 15, 0, NULL, $keys); + $result = pager_query("SELECT name, uid FROM {users} WHERE name_lower LIKE LOWER('%%%s%%')", 15, 0, NULL, $keys); while ($account = db_fetch_object($result)) { $find[] = array('title' => $account->name, 'link' => url('user/'. $account->uid, array('absolute' => TRUE))); } @@ -1427,7 +1436,7 @@ function _user_edit_validate($uid, &$edi if ($error = user_validate_name($edit['name'])) { form_set_error('name', $error); } - else if (db_result(db_query("SELECT COUNT(*) FROM {users} WHERE uid != %d AND LOWER(name) = LOWER('%s')", $uid, $edit['name'])) > 0) { + else if (db_result(db_query("SELECT COUNT(*) FROM {users} WHERE uid != %d AND name_lower = LOWER('%s')", $uid, $edit['name'])) > 0) { form_set_error('name', t('The name %name is already taken.', array('%name' => $edit['name']))); } else if (drupal_is_denied('user', $edit['name'])) { === modified file 'modules/user/user.pages.inc' --- modules/user/user.pages.inc 2007-11-28 10:29:19 +0000 +++ modules/user/user.pages.inc 2007-12-01 10:55:04 +0000 @@ -12,7 +12,7 @@ function user_autocomplete($string = '') { $matches = array(); if ($string) { - $result = db_query_range("SELECT name FROM {users} WHERE LOWER(name) LIKE LOWER('%s%%')", $string, 0, 10); + $result = db_query_range("SELECT name FROM {users} WHERE name_lower LIKE LOWER('%s%%')", $string, 0, 10); while ($user = db_fetch_object($result)) { $matches[$user->name] = check_plain($user->name); }