Index: modules/comment/comment.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/comment/comment.module,v
retrieving revision 1.691
diff -u -p -r1.691 comment.module
--- modules/comment/comment.module	6 Feb 2009 16:25:08 -0000	1.691
+++ modules/comment/comment.module	7 Feb 2009 07:47:46 -0000
@@ -1297,7 +1297,7 @@ function comment_validate($edit) {
       if ($edit['name']) {
         $query = db_select('users', 'u');
         $query->addField('u', 'uid', 'uid');
-        $taken = $query->where('LOWER(name) = :name', array(':name' => $edit['name']))
+        $taken = $query->where('username = :name', array(':name' => $edit['name']))
           ->countQuery()
           ->execute()
           ->fetchField();
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.1023
diff -u -p -r1.1023 node.module
--- modules/node/node.module	6 Feb 2009 16:25:08 -0000	1.1023
+++ modules/node/node.module	7 Feb 2009 07:47:46 -0000
@@ -3023,7 +3023,7 @@ function node_assign_owner_action_form($
   // Use dropdown for fewer than 200 users; textbox for more than that.
   if (intval($count) < 200) {
     $options = array();
-    $result = db_query("SELECT uid, name FROM {users} WHERE uid > 0 ORDER BY name");
+    $result = db_query("SELECT uid, name FROM {users} WHERE uid > 0 ORDER BY username");
     while ($data = db_fetch_object($result)) {
       $options[$data->name] = $data->name;
     }
@@ -3050,7 +3050,7 @@ function node_assign_owner_action_form($
 }
 
 function node_assign_owner_action_validate($form, $form_state) {
-  $count = db_result(db_query("SELECT COUNT(*) FROM {users} WHERE name = '%s'", $form_state['values']['owner_name']));
+  $count = db_result(db_query("SELECT COUNT(*) FROM {users} WHERE username = LOWER(:owner_name)", array(':owner_name' => $form_state['values']['owner_name'])));
   if (intval($count) != 1) {
     form_set_error('owner_name', t('Please enter a valid username.'));
   }
@@ -3058,7 +3058,7 @@ function node_assign_owner_action_valida
 
 function node_assign_owner_action_submit($form, $form_state) {
   // Username can change, so we need to store the ID, not the username.
-  $uid = db_result(db_query("SELECT uid from {users} WHERE name = '%s'", $form_state['values']['owner_name']));
+  $uid = db_result(db_query("SELECT uid from {users} WHERE username = LOWER(:owner_name)", array(':owner_name:' => $form_state['values']['owner_name'])));
   return array('owner_uid' => $uid);
 }
 
Index: modules/system/system.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.admin.inc,v
retrieving revision 1.124
diff -u -p -r1.124 system.admin.inc
--- modules/system/system.admin.inc	3 Feb 2009 18:55:31 -0000	1.124
+++ modules/system/system.admin.inc	7 Feb 2009 07:47:46 -0000
@@ -1784,7 +1784,7 @@ function system_status($check = FALSE) {
   }
   // MySQL import might have set the uid of the anonymous user to autoincrement
   // value. Let's try fixing it. See http://drupal.org/node/204411
-  db_query("UPDATE {users} SET uid = uid - uid WHERE name = '' AND pass = '' AND status = 0");
+  db_query("UPDATE {users} SET uid = uid - uid WHERE username = '' AND pass = '' AND status = 0");
 
   return theme('status_report', $requirements);
 }
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.306
diff -u -p -r1.306 system.install
--- modules/system/system.install	3 Feb 2009 12:30:14 -0000	1.306
+++ modules/system/system.install	7 Feb 2009 07:47:46 -0000
@@ -342,17 +342,17 @@ function system_install() {
   // 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')", '', '');
+  db_query("INSERT INTO {users} (name, username, mail) VALUES(:empty, :empty, :empty)", array(':empty' => ''));
   // 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()));
+  db_query("INSERT INTO {users} (name, username, mail, created, data) VALUES(:placeholder, :placeholder, :placeholder, :time, :serial)", array(':placeholder' => 'placeholder-for-uid-1', ':time' =>  REQUEST_TIME, ':serial' => 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'", '');
+  db_query("UPDATE {users} SET uid = uid - uid WHERE username = :empty", array(':empty' =>''));  
   // 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');
+  db_query("UPDATE {users} SET uid = 1 WHERE username = :placeholder", array(':placeholder' => 'placeholder-for-uid-1'));
 
   // Built-in roles.
   db_query("INSERT INTO {role} (name) VALUES ('%s')", 'anonymous user');
Index: modules/user/user.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.admin.inc,v
retrieving revision 1.37
diff -u -p -r1.37 user.admin.inc
--- modules/user/user.admin.inc	3 Feb 2009 18:55:32 -0000	1.37
+++ modules/user/user.admin.inc	7 Feb 2009 07:47:46 -0000
@@ -136,7 +136,7 @@ function user_admin_account() {
 
   $header = array(
     array(),
-    array('data' => t('Username'), 'field' => 'u.name'),
+    array('data' => t('Username'), 'field' => 'u.username'),
     array('data' => t('Status'), 'field' => 'u.status'),
     t('Roles'),
     array('data' => t('Member for'), 'field' => 'u.created', 'sort' => 'desc'),
Index: modules/user/user.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.install,v
retrieving revision 1.17
diff -u -p -r1.17 user.install
--- modules/user/user.install	20 Jan 2009 03:10:00 -0000	1.17
+++ modules/user/user.install	7 Feb 2009 07:47:46 -0000
@@ -101,7 +101,14 @@ function user_schema() {
         'length' => 60,
         'not null' => TRUE,
         'default' => '',
-        'description' => 'Unique user name.',
+        'description' => 'User\'s name with case preserved. Uniqueness is enforced on the derivative {users}.username field.',
+      ),
+      'username' => array(
+        'type' => 'varchar',
+        'length' => 60,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'Unique, lowercase version of {users}.name for query performance. This column should be used for WHERE, ORDER BY, etc. in queries instead of {users}.name.',
       ),
       'pass' => array(
         'type' => 'varchar',
@@ -196,7 +203,7 @@ function user_schema() {
       'mail' => array('mail'),
     ),
     'unique keys' => array(
-      'name' => array('name'),
+      'username' => array('username'),
     ),
     'primary key' => array('uid'),
   );
@@ -464,6 +471,18 @@ function user_update_7004(&$sandbox) {
 }
 
 /**
+ * Add username column and populate it with lowercased versions of existing names.
+ */
+function user_update_7005() {
+  $ret = array();
+  db_add_field($ret, 'users', 'username', array('type' => 'varchar', 'length' => 60, 'not null' => TRUE, 'default' => ''));
+  $ret[] = update_sql("UPDATE {users} SET username = LOWER(name)");
+  db_add_unique_key($ret, 'users', 'username', array('username'));
+  db_drop_unique_key($ret, 'users', 'name');
+  return $ret;
+}
+
+/**
  * @} End of "defgroup user-updates-6.x-to-7.x"
  * The next series of updates should start at 8000.
  */
Index: modules/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.962
diff -u -p -r1.962 user.module
--- modules/user/user.module	3 Feb 2009 18:55:32 -0000	1.962
+++ modules/user/user.module	7 Feb 2009 07:47:46 -0000
@@ -191,6 +191,10 @@ function user_load($array = array()) {
       $query[] = "pass = '%s'";
       $params[] = $value;
     }
+    elseif ($key == 'username') {
+      $query[] = "username = LOWER('%s')";
+      $params[] = $value;
+    }
     else {
       $query[]= "LOWER($key) = LOWER('%s')";
       $params[] = $value;
@@ -283,6 +287,11 @@ function user_save($account, $edit = arr
 
   $edit = (array) $edit;
 
+  // If username isn't set, populate it with name.
+  if (!empty($edit['name']) && empty($edit['username'])) {
+    $edit['username'] = $edit['name'];
+  }
+
   if (is_object($account) && $account->uid) {
     user_module_invoke('update', $edit, $account, $category);
     $data = unserialize(db_result(db_query('SELECT data FROM {users} WHERE uid = %d', $account->uid)));
@@ -337,6 +346,15 @@ function user_save($account, $edit = arr
       // return FALSE;
     }
 
+    // Ensure $user->username is lowercase if it's the same as $user->name.
+    // We use the database to do the lowercasing to ensure correct searches
+    // in case of any discrepencies in character handling between the
+    // database and PHP.
+    // LOWER() is also slightly faster than mb_strtolower() for this task.
+    if (!empty($edit['name']) && $edit['username'] == $edit['name']) {
+      db_query("UPDATE {users} SET username = LOWER(username) WHERE uid = :uid", array(':uid' => $account->uid));
+    }
+
     // If the picture changed or was unset, remove the old one. This step needs
     // to occur after updating the {users} record so that user_file_references()
     // doesn't report it in use and block the deletion.
@@ -403,6 +421,14 @@ function user_save($account, $edit = arr
       return FALSE;
     }
 
+    // Ensure $user->username is lowercase if it's the same as $user->name.
+    // We use the database to do the lowercasing to ensure that there are
+    // no discrepencies in collation handling between the database and PHP.
+    // LOWER() is also slightly faster than mb_strtolower() for this task.
+    if ($edit['username'] == $edit['name']) {
+      db_query("UPDATE {users} SET username = LOWER(username) WHERE uid = :uid", array(':uid' => $edit['uid']));
+    }
+
     // Build the initial user object.
     $user = user_load(array('uid' => $edit['uid']));
 
@@ -643,8 +669,8 @@ 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));
+function user_is_blocked($username) {
+  $deny = db_fetch_object(db_query("SELECT name FROM {users} WHERE status = 0 AND username = LOWER(:username)", array(':username' => $username)));
 
   return $deny;
 }
@@ -732,13 +758,13 @@ function user_search($op = 'search', $ke
         $keys = preg_replace('!\*+!', '%', $keys);
         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 username 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 username 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)));
           }
@@ -807,7 +833,7 @@ function user_user_validate(&$edit, &$ac
       if ($error = user_validate_name($edit['name'])) {
         form_set_error('name', $error);
       }
-      elseif (db_result(db_query("SELECT COUNT(*) FROM {users} WHERE uid != %d AND LOWER(name) = LOWER('%s')", $uid, $edit['name'])) > 0) {
+      elseif (db_result(db_query("SELECT COUNT(*) FROM {users} WHERE uid != :uid AND username = LOWER(:name)", array(':uid' => $uid, ':name' => $edit['name']))) > 0) {
         form_set_error('name', t('The name %name is already taken.', array('%name' => $edit['name'])));
       }
     }
@@ -1543,7 +1569,7 @@ function user_authenticate($form_values 
   $password = trim($form_values['pass']);
   // Name and pass keys are required.
   if (!empty($form_values['name']) && !empty($password)) {
-    $account = db_fetch_object(db_query("SELECT * FROM {users} WHERE name = '%s' AND status = 1", $form_values['name']));
+    $account = db_fetch_object(db_query("SELECT * FROM {users} WHERE username = LOWER(:name) AND status = 1", array(':name' => $form_values['name'])));
     if ($account) {
       // Allow alternate password hashing schemes.
       require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
Index: modules/user/user.pages.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.pages.inc,v
retrieving revision 1.28
diff -u -p -r1.28 user.pages.inc
--- modules/user/user.pages.inc	3 Feb 2009 17:30:13 -0000	1.28
+++ modules/user/user.pages.inc	7 Feb 2009 07:47:46 -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(:name)", array(':name' => $string .'%'), 0, 10);
+    $result = db_query_range("SELECT name FROM {users} WHERE username LIKE LOWER(:name)", array(':name' => $string .'%'), 0, 10);
     while ($user = db_fetch_object($result)) {
       $matches[$user->name] = check_plain($user->name);
     }
Index: modules/user/user.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.test,v
retrieving revision 1.27
diff -u -p -r1.27 user.test
--- modules/user/user.test	31 Jan 2009 16:50:57 -0000	1.27
+++ modules/user/user.test	7 Feb 2009 07:47:46 -0000
@@ -27,6 +27,8 @@ class UserRegistrationTestCase extends D
     $edit = array();
     $edit['name'] = $name = $this->randomName();
     $edit['mail'] = $mail = $edit['name'] . '@example.com';
+    $this->drupalGet('user/register');
+    $this->assertResponse('200');
     $this->drupalPost('user/register', $edit, t('Create new account'));
     $this->assertText(t('Your password and further instructions have been sent to your e-mail address.'), t('User registered successfully.'));
 
@@ -36,7 +38,10 @@ class UserRegistrationTestCase extends D
     $this->assertTrue($user->uid > 0, t('User has valid user id.'));
 
     // Check user fields.
-    $this->assertEqual($user->name, $name, t('Username matches.'));
+    $this->assertEqual($user->name, $name, t('User name matches.'));
+    $name = db_result(db_query("SELECT LOWER(name) FROM {users} WHERE uid = %d", $user->uid));
+    $username = db_result(db_query("SELECT username FROM {users} WHERE uid = :uid", array(':uid' => $user->uid)));
+    $this->assertEqual($name, $username, t('Username matches.'));
     $this->assertEqual($user->mail, $mail, t('E-mail address matches.'));
     $this->assertEqual($user->theme, '', t('Correct theme field.'));
     $this->assertEqual($user->signature, '', t('Correct signature field.'));
@@ -47,6 +52,10 @@ class UserRegistrationTestCase extends D
     $this->assertEqual($user->picture, '', t('Correct picture field.'));
     $this->assertEqual($user->init, $mail, t('Correct init field.'));
 
+    // Attempt to register with a duplicate username.
+    $this->drupalPost('user/register', $edit, t('Create new account'));
+    $this->assertRaw(t('The name %name is already taken.', array('%name' => $edit['name'])), t('Duplicate username registration prevented.'));
+
     // Attempt to login with incorrect password.
     $edit = array();
     $edit['name'] = $name;
@@ -749,6 +758,68 @@ class UserPermissionsTestCase extends Dr
 
 }
 
+class UserSearchTestCase extends DrupalWebTestCase {
+  /**
+   * Implementation of getInfo().
+   */
+  function getInfo() {
+    return array(
+      'name' => t('User search'),
+      'description' => t('Tests user integration with the search module.'),
+      'group' => t('User')
+    );
+  }
+
+  /**
+   * Implementation of setUp().
+   */
+ function setUp() {
+    parent::setUp('search');
+    // Create users.
+    $this->normal_user = $this->drupalCreateUser(array('access user profiles', 'search content'));
+    $this->admin_user = $this->drupalCreateUser(array('administer users', 'search content'));
+  }
+
+  function testUserSearch() {
+    // Register a new user for searching.
+    $edit = array();
+    $edit['name'] = $name = $this->randomName();
+    $edit['mail'] = $mail = $edit['name'] . '@example.com';
+    $this->drupalPost('user/register', $edit, t('Create new account'));
+    $this->assertText(t('Your password and further instructions have been sent to your e-mail address.'), t('User registered successfully.'));
+    $this->drupalLogin($this->admin_user);
+    $this->drupalGet('search/user');
+    $this->assertResponse(200, t('User search page exists.'));
+
+    // Search for a username with 'administer users' permission.
+    $edit = array();
+    $edit['keys'] = $name;
+    $this->drupalPost(NULL,  $edit, t('Search'));
+    $this->assertRaw($name, t('Username search successful.'));
+    // Email address search.
+    $this->drupalGet('search/user');
+    $edit = array();
+    $edit['keys'] = $mail;
+    $this->drupalPost(NULL,  $edit, t('Search'));
+    $this->assertRaw($name, t('Email search successful.'));
+    $this->drupalLogout();
+
+    // Search for a username with 'access user profiles' permission.
+    $this->drupalLogin($this->normal_user);
+    $this->drupalGet('search/user');
+    $this->assertResponse(200, t('User search page exists.'));
+    $edit = array();
+    $edit['keys'] = $name;
+    $this->drupalPost(NULL,  $edit, t('Search'));
+    $this->assertRaw($name, t('Username search successful'));
+    // E-mail address search.
+    $edit = array();
+    $edit['keys'] = $mail;
+    $this->drupalPost(NULL,  $edit, t('Search'));
+    $this->assertRaw($name, t('Email search does not return results without appropriate permission'));
+  }
+}
+
 class UserAdminTestCase extends DrupalWebTestCase {
   function getInfo() {
     return array(
