Index: includes/database/mysql/schema.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/database/mysql/schema.inc,v retrieving revision 1.12 diff -u -9 -p -r1.12 schema.inc --- includes/database/mysql/schema.inc 14 Mar 2009 20:34:17 -0000 1.12 +++ includes/database/mysql/schema.inc 30 Mar 2009 21:32:24 -0000 @@ -54,19 +54,20 @@ class DatabaseSchema_mysql extends Datab * @param $name * The name of the table to create. * @param $table * A Schema API table definition array. * @return * An array of SQL statements to create the table. */ protected function createTableSql($name, $table) { if (empty($table['mysql_suffix'])) { - $table['mysql_suffix'] = 'DEFAULT CHARACTER SET UTF8'; + // Store strings as UTF-8 and do case-sensitive comparision. + $table['mysql_suffix'] = 'DEFAULT CHARACTER SET UTF8 COLLATE utf8_bin'; } $sql = "CREATE TABLE {" . $name . "} (\n"; // Add the SQL statement for each field. foreach ($table['fields'] as $field_name => $field) { $sql .= $this->createFieldSql($field_name, $this->processField($field)) . ", \n"; } Index: includes/database/pgsql/database.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/database/pgsql/database.inc,v retrieving revision 1.19 diff -u -9 -p -r1.19 database.inc --- includes/database/pgsql/database.inc 26 Jan 2009 14:08:42 -0000 1.19 +++ includes/database/pgsql/database.inc 30 Mar 2009 21:32:24 -0000 @@ -91,25 +91,20 @@ class DatabaseConnection_pgsql extends D public function driver() { return 'pgsql'; } public function databaseType() { return 'pgsql'; } public function mapConditionOperator($operator) { - static $specials = array( - // In PostgreSQL, 'LIKE' is case-sensitive. For case-insensitive LIKE - // statements, we need to use ILIKE instead. - 'LIKE' => array('operator' => 'ILIKE'), - ); - - return isset($specials[$operator]) ? $specials[$operator] : NULL; + // We don't want to override any of the defaults. + return NULL; } /** * @todo Remove this as soon as db_rewrite_sql() has been exterminated. */ public function distinctField($table, $field, $query) { $field_to_select = 'DISTINCT(' . $table . '.' . $field . ')'; // (?transactionSupport = !isset($connection_options['transactions']) || $connection_options['transactions'] !== FALSE; parent::__construct('sqlite:'. $connection_options['database'], '', '', array( // Force column names to lower case. PDO::ATTR_CASE => PDO::CASE_LOWER, )); $this->exec('PRAGMA encoding="UTF-8"'); + $this->exec('PRAGMA case_sensitive_like=1'); // Create functions needed by SQLite. $this->sqliteCreateFunction('if', array($this, 'sqlFunctionIf')); $this->sqliteCreateFunction('greatest', array($this, 'sqlFunctionGreatest')); $this->sqliteCreateFunction('pow', 'pow', 2); $this->sqliteCreateFunction('length', 'strlen', 1); $this->sqliteCreateFunction('concat', array($this, 'sqlFunctionConcat')); $this->sqliteCreateFunction('substring', array($this, 'sqlFunctionSubstring'), 3); $this->sqliteCreateFunction('rand', array($this, 'sqlFunctionRand')); Index: modules/locale/locale.install =================================================================== RCS file: /cvs/drupal/drupal/modules/locale/locale.install,v retrieving revision 1.34 diff -u -9 -p -r1.34 locale.install --- modules/locale/locale.install 13 Feb 2009 00:45:18 -0000 1.34 +++ modules/locale/locale.install 30 Mar 2009 21:32:25 -0000 @@ -1,20 +1,16 @@ array( 'type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => 'default', 'description' => 'A module defined group of translations, see hook_locale().', ), 'source' => array( 'type' => 'text', - 'mysql_type' => 'blob', 'not null' => TRUE, 'description' => 'The original string in English.', ), 'version' => array( 'type' => 'varchar', 'length' => 20, 'not null' => TRUE, 'default' => 'none', 'description' => 'Version of Drupal, where the string was last used (for locales optimization).', @@ -377,19 +372,18 @@ function locale_schema() { 'fields' => array( 'lid' => array( 'type' => 'int', 'not null' => TRUE, 'default' => 0, 'description' => 'Source string ID. References {locales_source}.lid.', ), 'translation' => array( 'type' => 'text', - 'mysql_type' => 'blob', 'not null' => TRUE, 'description' => 'Translation string value in this language.', ), 'language' => array( 'type' => 'varchar', 'length' => 12, 'not null' => TRUE, 'default' => '', 'description' => 'Language code. References {languages}.language.', Index: modules/locale/locale.test =================================================================== RCS file: /cvs/drupal/drupal/modules/locale/locale.test,v retrieving revision 1.19 diff -u -9 -p -r1.19 locale.test --- modules/locale/locale.test 24 Feb 2009 16:46:52 -0000 1.19 +++ modules/locale/locale.test 30 Mar 2009 21:32:25 -0000 @@ -385,20 +385,21 @@ class LocaleTranslationFunctionalTest ex // The English name for the language. This will be translated. $name = $this->randomName(16); // The native name for the language. $native = $this->randomName(16); // The domain prefix. $prefix = $langcode; // This is the language indicator on the translation search screen for // untranslated strings. Copied straight from locale.inc. $language_indicator = "$langcode "; - // This will be the translation of $name. - $translation = $this->randomName(16); + // This will be the translation of $name. Make sure it contains at least + // one lower-case character in order to check case-sensitive search. + $translation = $this->randomName(16) . 'x'; // Add custom language. $this->drupalLogin($admin_user); $edit = array( 'langcode' => $langcode, 'name' => $name, 'native' => $native, 'prefix' => $prefix, 'direction' => '0', @@ -463,18 +464,28 @@ class LocaleTranslationFunctionalTest ex $search = array( 'string' => $translation, 'language' => 'all', 'translation' => 'translated', 'group' => 'all', ); $this->drupalPost('admin/build/translate/translate', $search, t('Filter')); $this->assertNoText(t('No strings found for your search.'), t('Search found the translation.')); + // Ensure string search is case-sensitive. + $search = array( + 'string' => drupal_strtoupper($translation), + 'language' => 'all', + 'translation' => 'translated', + 'group' => 'all', + ); + $this->drupalPost('admin/build/translate/translate', $search, t('Filter')); + $this->assertText(t('No strings found for your search.'), t("Search didn't find the translation.")); + // Ensure translated source string doesn't appear if searching on 'only // untranslated strings'. $search = array( 'string' => $name, 'language' => 'all', 'translation' => 'untranslated', 'group' => 'all', ); $this->drupalPost('admin/build/translate/translate', $search, t('Filter')); Index: modules/simpletest/tests/database_test.test =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/database_test.test,v retrieving revision 1.46 diff -u -9 -p -r1.46 database_test.test --- modules/simpletest/tests/database_test.test 14 Mar 2009 17:45:55 -0000 1.46 +++ modules/simpletest/tests/database_test.test 30 Mar 2009 21:32:25 -0000 @@ -1989,18 +1989,46 @@ class DatabaseRegressionTestCase extends } /** * Test the db_table_exists() function. */ function testDBTableExists() { $this->assertTrue(db_table_exists('node'), t('Returns true for existent table.')); $this->assertFalse(db_table_exists('nosuchtable'), t('Returns false for nonexistent table.')); } + + /** + * Test that string comparison is case-sensitive. + */ + function testCaseSensitiviteCompare() { + $num_matches = db_query("SELECT COUNT(*) FROM {test} WHERE name = :name", array(':name' => 'George'))->fetchField(); + $this->assertEqual($num_matches, 1, t('Correct number of records found with proper case.')); + $num_matches = db_query("SELECT COUNT(*) FROM {test} WHERE name = :name", array(':name' => 'GEORGE'))->fetchField(); + $this->assertEqual($num_matches, 0, t('Correct number of records found with wrong case.')); + + $num_matches = db_query("SELECT COUNT(*) FROM {test} WHERE name LIKE :name", array(':name' => 'Geo%'))->fetchField(); + $this->assertEqual($num_matches, 1, t('Correct number of records found with proper case.')); + $num_matches = db_query("SELECT COUNT(*) FROM {test} WHERE name LIKE :name", array(':name' => 'GEO%'))->fetchField(); + $this->assertEqual($num_matches, 0, t('Correct number of records found with wrong case.')); + + $num_matches = db_select('test') + ->condition('name', 'Geo%', 'LIKE') + ->countQuery() + ->execute() + ->fetchField(); + $this->assertEqual($num_matches, 1, t('Correct number of records found with proper case.')); + $num_matches = db_select('test') + ->condition('name', 'GEO%', 'LIKE') + ->countQuery() + ->execute() + ->fetchField(); + $this->assertEqual($num_matches, 0, t('Correct number of records found with wrong case.')); + } } /** * Query logging tests. */ class DatabaseLoggingTestCase extends DatabaseTestCase { function getInfo() { return array( Index: modules/statistics/statistics.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/statistics/statistics.admin.inc,v retrieving revision 1.19 diff -u -9 -p -r1.19 statistics.admin.inc --- modules/statistics/statistics.admin.inc 26 Feb 2009 07:30:27 -0000 1.19 +++ modules/statistics/statistics.admin.inc 30 Mar 2009 21:32:25 -0000 @@ -101,20 +101,20 @@ function statistics_top_visitors() { $output = theme('table', $header, $rows); $output .= theme('pager', NULL, 30, 0); return $output; } /** * Menu callback; presents the "referrer" page. */ function statistics_top_referrers() { - $query = "SELECT url, COUNT(url) AS hits, MAX(timestamp) AS last FROM {accesslog} WHERE url NOT LIKE :host AND url <> '' GROUP BY url"; - $query_cnt = "SELECT COUNT(DISTINCT(url)) FROM {accesslog} WHERE url <> '' AND url NOT LIKE :host"; + $query = "SELECT url, COUNT(url) AS hits, MAX(timestamp) AS last FROM {accesslog} WHERE LOWER(url) NOT LIKE :host AND url <> '' GROUP BY url"; + $query_cnt = "SELECT COUNT(DISTINCT(url)) FROM {accesslog} WHERE url <> '' AND LOWER(url) NOT LIKE :host"; drupal_set_title(t('Top referrers in the past %interval', array('%interval' => format_interval(variable_get('statistics_flush_accesslog_timer', 259200)))), PASS_THROUGH); $header = array( array('data' => t('Hits'), 'field' => 'hits', 'sort' => 'desc'), array('data' => t('Url'), 'field' => 'url'), array('data' => t('Last visit'), 'field' => 'last'), ); $query .= tablesort_sql($header); Index: modules/system/system.install =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.install,v retrieving revision 1.312 diff -u -9 -p -r1.312 system.install --- modules/system/system.install 17 Mar 2009 15:26:29 -0000 1.312 +++ modules/system/system.install 30 Mar 2009 21:32:25 -0000 @@ -3062,33 +3062,52 @@ function system_update_7011() { 'rid' => $rid, 'permission' => 'bypass node access', )); } $insert->execute(); return $ret; } /** + * Make tables case-sensitive on MySQL. + */ +function system_update_7012() { + $ret = array(); + if (db_driver() == 'mysql') { + // Table names as used in D6. Some have changed and are no longer reported + // by hook_schema(). The list is generated using the following command: + // grep -r "^ \+\$schema\['[a-z_]\+'\] =" modules | cut -d\' -f2 | sort -u | xargs -I '*' echo -n "'*', " + $tables = array('access', 'accesslog', 'actions', 'actions_aid', 'aggregator_category', 'aggregator_category_feed', 'aggregator_category_item', 'aggregator_feed', 'aggregator_item', 'authmap', 'batch', 'blocks', 'blocks_roles', 'blogapi_files', 'book', 'book_temp', 'boxes', 'cache', 'cache_block', 'cache_filter', 'cache_form', 'cache_menu', 'cache_page', 'cache_update', 'comments', 'contact', 'files', 'filter_formats', 'filters', 'flood', 'forum', 'history', 'languages', 'locales_source', 'locales_target', 'menu_custom', 'menu_links', 'menu_router', 'node', 'node_access', 'node_comment_statistics', 'node_counter', 'node_revisions', 'node_type', 'openid_association', 'permission', 'poll', 'poll_choices', 'poll_votes', 'profile_fields', 'profile_values', 'role', 'search_dataset', 'search_index', 'search_node_links', 'search_total', 'sessions', 'system', 'term_data', 'term_hierarchy', 'term_node', 'term_relation', 'term_synonym', 'trigger_assignments', 'upload', 'url_alias', 'users', 'users_roles', 'variable', 'vocabulary', 'vocabulary_node_types', 'watchdog'); + foreach ($tables as $table) { + if (db_table_exists($table)) { + $ret[] = update_sql('ALTER TABLE {' . $table . '} CONVERT TO CHARACTER SET utf8 COLLATE utf8_bin'); + } + } + } + return $ret; +} + +/** * Rename {blocks} table to {block}, {blocks_roles} to {block_role} and * {boxes} to {box}. */ -function system_update_7012() { +function system_update_7013() { $ret = array(); db_rename_table($ret, 'blocks', 'block'); db_rename_table($ret, 'blocks_roles', 'block_role'); db_rename_table($ret, 'boxes', 'box'); return $ret; } /** * Convert default time zone offset to default time zone name. */ -function system_update_7013() { +function system_update_7014() { $ret = array(); $timezone = NULL; $timezones = system_time_zones(); // If the contributed Date module set a default time zone name, use this // setting as the default time zone. if (($timezone_name = variable_get('date_default_timezone_name')) && isset($timezones[$timezone_name])) { $timezone = $timezone_name; } // If the contributed Event module has set a default site time zone, look up @@ -3117,38 +3136,38 @@ function system_update_7013() { } variable_set('date_default_timezone', $timezone); drupal_set_message('The default time zone has been set to ' . check_plain($timezone) . '. Please check the ' . l('date and time configuration page', 'admin/settings/regional-settings') . ' to configure it correctly.', 'warning'); return $ret; } /** * Drop the bootstrap column from the {system} table. */ -function system_update_7014() { +function system_update_7015() { $ret = array(); db_drop_field($ret, 'system', 'bootstrap'); return $ret; } /** * Change the user logout path. */ -function system_update_7015() { +function system_update_7016() { $ret = array(); $ret[] = update_sql("UPDATE {menu_links} SET link_path = 'user/logout' WHERE link_path = 'logout'"); $ret[] = update_sql("UPDATE {menu_links} SET router_path = 'user/logout' WHERE router_path = 'logout'"); return $ret; } /** * Remove custom datatype *_unsigned in PostgreSQL. */ -function system_update_7016() { +function system_update_7017() { $ret = array(); // Only run these queries if the driver used is pgsql. if (db_driver() == 'pgsql') { $result = db_query("SELECT c.relname AS table, a.attname AS field, pg_catalog.format_type(a.atttypid, a.atttypmod) AS type FROM pg_catalog.pg_attribute a LEFT JOIN pg_class c ON (c.oid = a.attrelid) WHERE pg_catalog.pg_table_is_visible(c.oid) AND c.relkind = 'r' AND pg_catalog.format_type(a.atttypid, a.atttypmod) LIKE '%unsigned%'"); @@ -3170,19 +3189,19 @@ function system_update_7016() { $ret[] = update_sql('DROP DOMAIN int_unsigned'); $ret[] = update_sql('DROP DOMAIN bigint_unsigned'); } return $ret; } /** * Change the theme setting 'toggle_node_info' into a per content type variable. */ -function system_update_7017() { +function system_update_7018() { $ret = array(); $types = node_get_types(); if (count($types)) { foreach ($types as $type) { $node_info = theme_get_setting('toggle_node_info_' . $type->type); if ($node_info !== NULL) { variable_set('node_submitted_' . $type->type, $node_info); $ret[] = array('success' => TRUE, 'query' => "variable_set('node_submitted_$type->type')"); } @@ -3199,42 +3218,42 @@ function system_update_7017() { variable_set('theme_settings', $theme_settings); $ret[] = array('success' => TRUE, 'query' => "variable_set('theme_settings')"); return $ret; } /** * Replace src index on the {url_alias} table with src, language. */ -function system_update_7018() { +function system_update_7019() { $ret = array(); db_add_index($ret, 'url_alias', 'src_language', array('src', 'language')); db_drop_index($ret, 'url_alias', 'src'); return $ret; } /** * Shorten the {system}.type column and add an index on type and name. */ -function system_update_7019() { +function system_update_7020() { $ret = array(); db_drop_index($ret, 'system', 'modules'); db_change_field($ret, 'system', 'type', 'type', array('type' => 'varchar', 'length' => 12, 'not null' => TRUE, 'default' => '')); db_add_index($ret, 'system', 'modules', array('type', 'status', 'weight', 'filename')); db_add_index($ret, 'system', 'type_name', array('type', 'name')); return $ret; } /** * Enable field module. */ -function system_update_7020() { +function system_update_7021() { $ret = array(); $module_list = array('field_sql_storage', 'field'); drupal_install_modules($module_list); module_enable($module_list); return $ret; } /** * @} End of "defgroup updates-6.x-to-7.x" Index: modules/user/user.module =================================================================== RCS file: /cvs/drupal/drupal/modules/user/user.module,v retrieving revision 1.972 diff -u -9 -p -r1.972 user.module --- modules/user/user.module 26 Mar 2009 13:31:28 -0000 1.972 +++ modules/user/user.module 30 Mar 2009 21:32:25 -0000 @@ -210,26 +210,24 @@ function user_load_multiple($uids = arra if ($uids || ($conditions && !$passed_uids)) { $query = db_select('users', 'u')->fields('u'); // If the $uids array is populated, add those to the query. if ($uids) { $query->condition('u.uid', $uids, 'IN'); } // If the conditions array is populated, add those to the query. if ($conditions) { - // TODO D7: Using LIKE() to get a case insensitive comparison because Crell - // and chx promise that dbtng will map it to ILIKE in postgres. if (isset($conditions['name'])) { - $query->condition('u.name', $conditions['name'], 'LIKE'); + $query->where('LOWER(u.name) = LOWER(:name)', array(':name' => $conditions['name'])); unset($conditions['name']); } if (isset($conditions['mail'])) { - $query->condition('u.mail', $conditions['mail'], 'LIKE'); + $query->where('LOWER(u.mail) = LOWER(:mail)', array(':mail' => $conditions['mail'])); unset($conditions['mail']); } foreach ($conditions as $field => $value) { $query->condition('u.' . $field, $value); } } $result = $query->execute(); $queried_users = array();