=== 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);
     }

