From 45d1c67a0ba9b041331252bc590e46439eede6f0 Mon Sep 17 00:00:00 2001 From: albertlast Date: Mon, 31 Aug 2026 15:37:23 +0200 Subject: [PATCH 1/3] Adds a {ci:} query type for case insensitive column comparisons Whether a string comparison folds case is decided by the database engine: MySQL folds it in the column's collation, PostgreSQL compares exactly. Callers handled that themselves by reading Db::$db->case_sensitive and wrapping the column in LOWER(), which put the decision at every call site and left it out wherever somebody did not think to add it. {ci:column} moves it into the query string, where the substitution layer already lives. It expands to the bare column on MySQL and to LOWER(column) on PostgreSQL. {ci_string:key} is the matching value type, for the places that were folding the value in SQL rather than in PHP. The column is named inline rather than through $db_values, so that a comparison shows in the query text which column it folds. Only a column name, optionally qualified by a table alias, is accepted. Memberlist keeps its own LOWER() loop, because it folds expressions such as COALESCE(group_name, '') as well as plain columns, and those are not what {ci:} accepts. Co-Authored-By: Claude Opus 5 Signed-off-by: albertlast --- Sources/Actions/Admin/Members.php | 6 +----- Sources/Actions/AutoSuggest.php | 6 ++---- Sources/Actions/Memberlist.php | 4 +++- Sources/Actions/Register2.php | 5 ++--- Sources/Actions/RequestMembers.php | 3 +-- Sources/Db/APIs/MySQL.php | 13 +++++++++++++ Sources/Db/APIs/PostgreSQL.php | 15 +++++++++++++++ Sources/PersonalMessage/PM.php | 2 +- Sources/PersonalMessage/Search.php | 9 ++------- Sources/Profile.php | 3 +-- Sources/User.php | 19 +++++++++---------- 11 files changed, 50 insertions(+), 35 deletions(-) diff --git a/Sources/Actions/Admin/Members.php b/Sources/Actions/Admin/Members.php index e022b0e9e72..ec4d4e10960 100644 --- a/Sources/Actions/Admin/Members.php +++ b/Sources/Actions/Admin/Members.php @@ -455,11 +455,7 @@ public function view(): void // Replace the wildcard characters ('*' and '?') into MySQL ones. $parameter = strtolower(strtr(Utils::htmlspecialchars($search_params[$param_name], ENT_QUOTES), ['%' => '\\%', '_' => '\\_', '*' => '%', '?' => '_'])); - if (Db::$db->case_sensitive) { - $query_parts[] = '(LOWER(' . implode(') LIKE {string:' . $param_name . '_normal} OR LOWER(', $param_info['db_fields']) . ') LIKE {string:' . $param_name . '_normal})'; - } else { - $query_parts[] = '(' . implode(' LIKE {string:' . $param_name . '_normal} OR ', $param_info['db_fields']) . ' LIKE {string:' . $param_name . '_normal})'; - } + $query_parts[] = '({ci:' . implode('} LIKE {string:' . $param_name . '_normal} OR {ci:', $param_info['db_fields']) . '} LIKE {string:' . $param_name . '_normal})'; $where_params[$param_name . '_normal'] = '%' . $parameter . '%'; } diff --git a/Sources/Actions/AutoSuggest.php b/Sources/Actions/AutoSuggest.php index 3bf58e94dce..a1a521bd8f9 100644 --- a/Sources/Actions/AutoSuggest.php +++ b/Sources/Actions/AutoSuggest.php @@ -146,12 +146,11 @@ public function member(): array $request = Db::$db->query( 'SELECT id_member, real_name FROM {db_prefix}members - WHERE {raw:real_name} LIKE {string:search}' . (!empty($this->search_param['buddies']) ? ' + WHERE {ci:real_name} LIKE {string:search}' . (!empty($this->search_param['buddies']) ? ' AND id_member IN ({array_int:buddy_list})' : '') . ' AND is_activated IN ({array_int:activated}) LIMIT ' . (Utils::entityStrlen($this->search) <= 2 ? '100' : '800'), [ - 'real_name' => Db::$db->case_sensitive ? 'LOWER(real_name)' : 'real_name', 'buddy_list' => User::$me->buddies, 'search' => $this->search, 'activated' => [User::ACTIVATED, User::ACTIVATED_BANNED], @@ -195,12 +194,11 @@ public function membergroups(): array $request = Db::$db->query( 'SELECT id_group, group_name FROM {db_prefix}membergroups - WHERE {raw:group_name} LIKE {string:search} + WHERE {ci:group_name} LIKE {string:search} AND min_posts = {int:min_posts} AND id_group NOT IN ({array_int:invalid_groups}) AND hidden != {int:hidden}', [ - 'group_name' => Db::$db->case_sensitive ? 'LOWER(group_name)' : 'group_name', 'min_posts' => -1, 'invalid_groups' => [1, 3], 'hidden' => 2, diff --git a/Sources/Actions/Memberlist.php b/Sources/Actions/Memberlist.php index c8843d90a4f..e7029978c53 100644 --- a/Sources/Actions/Memberlist.php +++ b/Sources/Actions/Memberlist.php @@ -521,6 +521,8 @@ public function search(): void $search_fields[] = 'email'; } + // These are expressions as well as plain columns, so they are + // folded here rather than through the {ci:} type. if (Db::$db->case_sensitive) { foreach ($fields as $key => $field) { $fields[$key] = 'LOWER(' . $field . ')'; @@ -545,7 +547,7 @@ public function search(): void ErrorHandler::fatalLang('invalid_search_string', false); } - $query = $_POST['search'] == '' ? '= {string:blank_string}' : (Db::$db->case_sensitive ? 'LIKE LOWER({string:search})' : 'LIKE {string:search}'); + $query = $_POST['search'] == '' ? '= {string:blank_string}' : 'LIKE {ci_string:search}'; $request = Db::$db->query( 'SELECT COUNT(*) diff --git a/Sources/Actions/Register2.php b/Sources/Actions/Register2.php index a650e23cd9a..4a971ff96f9 100644 --- a/Sources/Actions/Register2.php +++ b/Sources/Actions/Register2.php @@ -536,11 +536,10 @@ public static function registerMember(array &$reg_options, bool $return_errors = $request = Db::$db->query( 'SELECT id_member FROM {db_prefix}members - WHERE {raw:email_address_field} = {string:email_address} - OR {raw:email_address_field} = {string:username} + WHERE {ci:email_address} = {string:email_address} + OR {ci:email_address} = {string:username} LIMIT 1', [ - 'email_address_field' => Db::$db->case_sensitive ? 'LOWER(email_address)' : 'email_address', 'email_address' => $reg_options['email'], 'username' => $reg_options['username'], ], diff --git a/Sources/Actions/RequestMembers.php b/Sources/Actions/RequestMembers.php index 094f35c49c2..8a47435310f 100644 --- a/Sources/Actions/RequestMembers.php +++ b/Sources/Actions/RequestMembers.php @@ -74,12 +74,11 @@ public function execute(): void $request = Db::$db->query( 'SELECT real_name FROM {db_prefix}members - WHERE {raw:real_name} LIKE {string:search}' . (isset($_REQUEST['buddies']) ? ' + WHERE {ci:real_name} LIKE {string:search}' . (isset($_REQUEST['buddies']) ? ' AND id_member IN ({array_int:buddy_list})' : '') . ' AND is_activated IN ({array_int:activated}) LIMIT {int:limit}', [ - 'real_name' => Db::$db->case_sensitive ? 'LOWER(real_name)' : 'real_name', 'buddy_list' => User::$me->buddies, 'search' => $this->search, 'activated' => [User::ACTIVATED, User::ACTIVATED_BANNED], diff --git a/Sources/Db/APIs/MySQL.php b/Sources/Db/APIs/MySQL.php index 96542f47297..a97238e8944 100644 --- a/Sources/Db/APIs/MySQL.php +++ b/Sources/Db/APIs/MySQL.php @@ -282,6 +282,11 @@ public function quote(string $db_string, array $db_values, ?object $connection = [ // The literal type can have arbitrary content. '~{(literal):([^}]*)}~', + // The ci type names a column inline rather than by key, so + // that the column a comparison is case folding is visible in + // the query itself. Only a column name, optionally qualified + // by a table alias, is accepted. + '~{(ci):([a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)?)}~', // Everything else needs to be a key in $db_values. '~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', ], @@ -2753,6 +2758,12 @@ protected function replacement__callback(array $matches, array $db_values, objec return '\'' . mysqli_real_escape_string($connection, $matches[2]) . '\''; } + // MySQL folds case in the column's collation, so a case insensitive + // comparison is what a bare column already does. + if ($matches[1] === 'ci') { + return $matches[2]; + } + if (!\array_key_exists($matches[2], $db_values)) { $this->error_backtrace('The database value you\'re trying to insert does not exist: ' . Utils::htmlspecialchars($matches[2]), '', E_USER_ERROR, __FILE__, __LINE__); } @@ -2777,6 +2788,8 @@ protected function replacement__callback(array $matches, array $db_values, objec case 'string': case 'text': + // MySQL folds the case of the value in the collation as well. + case 'ci_string': return \sprintf('\'%1$s\'', mysqli_real_escape_string($connection, $this->fix_mb4((string) $replacement))); case 'array_int': diff --git a/Sources/Db/APIs/PostgreSQL.php b/Sources/Db/APIs/PostgreSQL.php index c0ba1e0668a..28054bd4c37 100644 --- a/Sources/Db/APIs/PostgreSQL.php +++ b/Sources/Db/APIs/PostgreSQL.php @@ -343,6 +343,11 @@ public function quote(string $db_string, array $db_values, ?object $connection = [ // The literal type can have arbitrary content. '~{(literal):([^}]*)}~', + // The ci type names a column inline rather than by key, so + // that the column a comparison is case folding is visible in + // the query itself. Only a column name, optionally qualified + // by a table alias, is accepted. + '~{(ci):([a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)?)}~', // Everything else needs to be a key in $db_values. '~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', ], @@ -2684,6 +2689,12 @@ protected function replacement__callback(array $matches, array $db_values, objec return '\'' . pg_escape_string($this->connection, $matches[2]) . '\''; } + // PostgreSQL compares strings exactly, so a case insensitive comparison + // folds the column and pairs it with a value folded the same way. + if ($matches[1] === 'ci') { + return 'LOWER(' . $matches[2] . ')'; + } + if (!\array_key_exists($matches[2], $db_values)) { $this->error_backtrace('The database value you\'re trying to insert does not exist: ' . Utils::htmlspecialchars($matches[2]), '', E_USER_ERROR, __FILE__, __LINE__); } @@ -2710,6 +2721,10 @@ protected function replacement__callback(array $matches, array $db_values, objec case 'text': return \sprintf('\'%1$s\'', pg_escape_string($this->connection, (string) $replacement)); + // Folded to match a column that {ci:} has folded. + case 'ci_string': + return \sprintf('LOWER(\'%1$s\')', pg_escape_string($this->connection, (string) $replacement)); + case 'array_int': if (\is_array($replacement)) { if (empty($replacement)) { diff --git a/Sources/PersonalMessage/PM.php b/Sources/PersonalMessage/PM.php index b2f3dcb1c0d..de6e53a2060 100644 --- a/Sources/PersonalMessage/PM.php +++ b/Sources/PersonalMessage/PM.php @@ -1210,7 +1210,7 @@ public static function send(array $recipients, string $subject, string $message, $request = Db::$db->query( 'SELECT id_member, member_name FROM {db_prefix}members - WHERE ' . (Db::$db->case_sensitive ? 'LOWER(member_name)' : 'member_name') . ' IN ({array_string:usernames})', + WHERE {ci:member_name} IN ({array_string:usernames})', [ 'usernames' => array_keys($usernames), ], diff --git a/Sources/PersonalMessage/Search.php b/Sources/PersonalMessage/Search.php index fb3cf24d9a3..a8da23f265b 100644 --- a/Sources/PersonalMessage/Search.php +++ b/Sources/PersonalMessage/Search.php @@ -478,11 +478,7 @@ protected function setUserQuery(): void foreach ($possible_users as $k => $v) { $where_params['name_' . $k] = $v; - $where_clause[] = '{raw:real_name} LIKE {string:name_' . $k . '}'; - - if (!isset($where_params['real_name'])) { - $where_params['real_name'] = Db::$db->case_sensitive ? 'LOWER(real_name)' : 'real_name'; - } + $where_clause[] = '{ci:real_name} LIKE {string:name_' . $k . '}'; } // Who matches those criteria? @@ -498,12 +494,11 @@ protected function setUserQuery(): void if (Db::$db->num_rows($request) > $this->max_members_to_search) { $this->user_query = ''; } else { - $this->searchq_parameters['real_name'] = Db::$db->case_sensitive ? 'LOWER(pm.from_name)' : 'pm.from_name'; $clauses = []; foreach ($possible_users as $k => $v) { $this->searchq_parameters['name_' . $k] = $v; - $clauses[] = '{raw:real_name} LIKE {string:name_' . $k . '}'; + $clauses[] = '{ci:pm.from_name} LIKE {string:name_' . $k . '}'; } if (Db::$db->num_rows($request) == 0) { diff --git a/Sources/Profile.php b/Sources/Profile.php index 1e156a88697..8568ce2db03 100644 --- a/Sources/Profile.php +++ b/Sources/Profile.php @@ -1649,10 +1649,9 @@ public function validateEmail(string $email): bool|string 'SELECT id_member FROM {db_prefix}members WHERE id_member != {int:selected_member} - AND {raw:email_address_field} = {string:email_address} + AND {ci:email_address} = {string:email_address} LIMIT 1', [ - 'email_address_field' => Db::$db->case_sensitive ? 'LOWER(email_address)' : 'email_address', 'selected_member' => $this->id, 'email_address' => $email, ], diff --git a/Sources/User.php b/Sources/User.php index d8399a01a80..22c80efee14 100644 --- a/Sources/User.php +++ b/Sources/User.php @@ -3800,9 +3800,10 @@ public static function find(string|array $names, bool $use_wildcards = false, bo $email_condition = ''; } - // Get the case of the columns right - but only if we need to as things like MySQL will go slow needlessly otherwise. - $member_name = Db::$db->case_sensitive ? 'LOWER(member_name)' : 'member_name'; - $real_name = Db::$db->case_sensitive ? 'LOWER(real_name)' : 'real_name'; + // The {ci:} type folds the column for the engines that need it and + // leaves it alone for the ones that do not. + $member_name = '{ci:member_name}'; + $real_name = '{ci:real_name}'; // Searches. $member_name_search = $member_name . ' ' . $comparison . ' ' . implode(' OR ' . $member_name . ' ' . $comparison . ' ', $names_list); @@ -5424,13 +5425,11 @@ protected static function addQueryCustomizationsForLoadType(array &$query_custom break; case self::LOAD_BY_NAME: - if (Db::$db->case_sensitive) { - $query_customizations['where'][] = 'LOWER(mem.member_name) IN ({array_string:users})'; - $query_customizations['params']['users'] = array_map('strtolower', $users); - } else { - $query_customizations['where'][] = 'mem.member_name IN ({array_string:users})'; - $query_customizations['params']['users'] = $users; - } + $query_customizations['where'][] = '{ci:mem.member_name} IN ({array_string:users})'; + + // An array of values has no {ci:} of its own, so the names are + // folded here for the engines that compare them exactly. + $query_customizations['params']['users'] = Db::$db->case_sensitive ? array_map('strtolower', $users) : $users; break; From 6fb2bde8925dbf6dcec29b5c21f8132306918d89 Mon Sep 17 00:00:00 2001 From: albertlast Date: Wed, 2 Sep 2026 08:05:24 +0200 Subject: [PATCH 2/3] Names the case folding value types by suffix, and adds the array one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The array_ modifier is already a prefix on the types that take a list, so a ci prefix would leave the list form as {array_ci_string} or {ci_array_string}. As a suffix it composes with what is there: {string_ci} beside {string}, and {array_string_ci} beside {array_string}. {array_string_ci} folds each value in the list the way {string_ci} folds one, which lets User::addQueryCustomizationsForLoadType() hand the names over as they came instead of folding them itself. That last one is a behaviour change, and the only one in this branch. Folding the names in PHP with strtolower() left them compared against a column folded by SQL LOWER(), and the two disagree outside ASCII: a member named ÄNNA gives 'änna' on the column and 'Änna' from strtolower(), which never match on PostgreSQL. Both sides now fold the same way. Co-Authored-By: Claude Opus 5 Signed-off-by: albertlast --- Sources/Actions/Memberlist.php | 2 +- Sources/Db/APIs/MySQL.php | 4 +++- Sources/Db/APIs/PostgreSQL.php | 20 +++++++++++++++++++- Sources/User.php | 7 ++----- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/Sources/Actions/Memberlist.php b/Sources/Actions/Memberlist.php index e7029978c53..c33ae72582a 100644 --- a/Sources/Actions/Memberlist.php +++ b/Sources/Actions/Memberlist.php @@ -547,7 +547,7 @@ public function search(): void ErrorHandler::fatalLang('invalid_search_string', false); } - $query = $_POST['search'] == '' ? '= {string:blank_string}' : 'LIKE {ci_string:search}'; + $query = $_POST['search'] == '' ? '= {string:blank_string}' : 'LIKE {string_ci:search}'; $request = Db::$db->query( 'SELECT COUNT(*) diff --git a/Sources/Db/APIs/MySQL.php b/Sources/Db/APIs/MySQL.php index a97238e8944..f09faad958b 100644 --- a/Sources/Db/APIs/MySQL.php +++ b/Sources/Db/APIs/MySQL.php @@ -2789,7 +2789,7 @@ protected function replacement__callback(array $matches, array $db_values, objec case 'string': case 'text': // MySQL folds the case of the value in the collation as well. - case 'ci_string': + case 'string_ci': return \sprintf('\'%1$s\'', mysqli_real_escape_string($connection, $this->fix_mb4((string) $replacement))); case 'array_int': @@ -2814,6 +2814,8 @@ protected function replacement__callback(array $matches, array $db_values, objec break; case 'array_string': + // As above, the collation folds each of these too. + case 'array_string_ci': if (\is_array($replacement)) { if (empty($replacement)) { $this->error_backtrace('Database error, given array of string values is empty. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__); diff --git a/Sources/Db/APIs/PostgreSQL.php b/Sources/Db/APIs/PostgreSQL.php index 28054bd4c37..8df9846b306 100644 --- a/Sources/Db/APIs/PostgreSQL.php +++ b/Sources/Db/APIs/PostgreSQL.php @@ -2722,7 +2722,7 @@ protected function replacement__callback(array $matches, array $db_values, objec return \sprintf('\'%1$s\'', pg_escape_string($this->connection, (string) $replacement)); // Folded to match a column that {ci:} has folded. - case 'ci_string': + case 'string_ci': return \sprintf('LOWER(\'%1$s\')', pg_escape_string($this->connection, (string) $replacement)); case 'array_int': @@ -2763,6 +2763,24 @@ protected function replacement__callback(array $matches, array $db_values, objec break; + // Each of these folded to match a column that {ci:} has folded. + case 'array_string_ci': + if (\is_array($replacement)) { + if (empty($replacement)) { + $this->error_backtrace('Database error, given array of string values is empty. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__); + } + + foreach ($replacement as $key => $value) { + $replacement[$key] = \sprintf('LOWER(\'%1$s\')', pg_escape_string($this->connection, (string) $value)); + } + + return implode(', ', $replacement); + } + + $this->error_backtrace('Wrong value type sent to the database. Array of strings expected. (' . $matches[2] . ')', '', E_USER_ERROR, __FILE__, __LINE__); + + break; + case 'array_uuid': if (\is_array($replacement)) { if (empty($replacement)) { diff --git a/Sources/User.php b/Sources/User.php index 22c80efee14..75b632edaf6 100644 --- a/Sources/User.php +++ b/Sources/User.php @@ -5425,11 +5425,8 @@ protected static function addQueryCustomizationsForLoadType(array &$query_custom break; case self::LOAD_BY_NAME: - $query_customizations['where'][] = '{ci:mem.member_name} IN ({array_string:users})'; - - // An array of values has no {ci:} of its own, so the names are - // folded here for the engines that compare them exactly. - $query_customizations['params']['users'] = Db::$db->case_sensitive ? array_map('strtolower', $users) : $users; + $query_customizations['where'][] = '{ci:mem.member_name} IN ({array_string_ci:users})'; + $query_customizations['params']['users'] = $users; break; From 9f9f4792661467f1d79d3a302104888801ccd0da Mon Sep 17 00:00:00 2001 From: albertlast Date: Thu, 3 Sep 2026 16:04:30 +0200 Subject: [PATCH 3/3] Names the column type column_ci The token says what it takes. {ci:} left a reader to work out that the thing inside it was a column name rather than a value key, which is the opposite way round from every other type in the query language. Co-Authored-By: Claude Opus 5 Signed-off-by: albertlast --- Sources/Actions/Admin/Members.php | 2 +- Sources/Actions/AutoSuggest.php | 4 ++-- Sources/Actions/Memberlist.php | 2 +- Sources/Actions/Register2.php | 4 ++-- Sources/Actions/RequestMembers.php | 2 +- Sources/Db/APIs/MySQL.php | 12 ++++++------ Sources/Db/APIs/PostgreSQL.php | 16 ++++++++-------- Sources/PersonalMessage/PM.php | 2 +- Sources/PersonalMessage/Search.php | 4 ++-- Sources/Profile.php | 2 +- Sources/User.php | 8 ++++---- 11 files changed, 29 insertions(+), 29 deletions(-) diff --git a/Sources/Actions/Admin/Members.php b/Sources/Actions/Admin/Members.php index ec4d4e10960..77ab0e08064 100644 --- a/Sources/Actions/Admin/Members.php +++ b/Sources/Actions/Admin/Members.php @@ -455,7 +455,7 @@ public function view(): void // Replace the wildcard characters ('*' and '?') into MySQL ones. $parameter = strtolower(strtr(Utils::htmlspecialchars($search_params[$param_name], ENT_QUOTES), ['%' => '\\%', '_' => '\\_', '*' => '%', '?' => '_'])); - $query_parts[] = '({ci:' . implode('} LIKE {string:' . $param_name . '_normal} OR {ci:', $param_info['db_fields']) . '} LIKE {string:' . $param_name . '_normal})'; + $query_parts[] = '({column_ci:' . implode('} LIKE {string:' . $param_name . '_normal} OR {column_ci:', $param_info['db_fields']) . '} LIKE {string:' . $param_name . '_normal})'; $where_params[$param_name . '_normal'] = '%' . $parameter . '%'; } diff --git a/Sources/Actions/AutoSuggest.php b/Sources/Actions/AutoSuggest.php index a1a521bd8f9..0f293e45b47 100644 --- a/Sources/Actions/AutoSuggest.php +++ b/Sources/Actions/AutoSuggest.php @@ -146,7 +146,7 @@ public function member(): array $request = Db::$db->query( 'SELECT id_member, real_name FROM {db_prefix}members - WHERE {ci:real_name} LIKE {string:search}' . (!empty($this->search_param['buddies']) ? ' + WHERE {column_ci:real_name} LIKE {string:search}' . (!empty($this->search_param['buddies']) ? ' AND id_member IN ({array_int:buddy_list})' : '') . ' AND is_activated IN ({array_int:activated}) LIMIT ' . (Utils::entityStrlen($this->search) <= 2 ? '100' : '800'), @@ -194,7 +194,7 @@ public function membergroups(): array $request = Db::$db->query( 'SELECT id_group, group_name FROM {db_prefix}membergroups - WHERE {ci:group_name} LIKE {string:search} + WHERE {column_ci:group_name} LIKE {string:search} AND min_posts = {int:min_posts} AND id_group NOT IN ({array_int:invalid_groups}) AND hidden != {int:hidden}', diff --git a/Sources/Actions/Memberlist.php b/Sources/Actions/Memberlist.php index c33ae72582a..5e92dbc248c 100644 --- a/Sources/Actions/Memberlist.php +++ b/Sources/Actions/Memberlist.php @@ -522,7 +522,7 @@ public function search(): void } // These are expressions as well as plain columns, so they are - // folded here rather than through the {ci:} type. + // folded here rather than through the {column_ci:} type. if (Db::$db->case_sensitive) { foreach ($fields as $key => $field) { $fields[$key] = 'LOWER(' . $field . ')'; diff --git a/Sources/Actions/Register2.php b/Sources/Actions/Register2.php index 4a971ff96f9..d70f3a486a2 100644 --- a/Sources/Actions/Register2.php +++ b/Sources/Actions/Register2.php @@ -536,8 +536,8 @@ public static function registerMember(array &$reg_options, bool $return_errors = $request = Db::$db->query( 'SELECT id_member FROM {db_prefix}members - WHERE {ci:email_address} = {string:email_address} - OR {ci:email_address} = {string:username} + WHERE {column_ci:email_address} = {string:email_address} + OR {column_ci:email_address} = {string:username} LIMIT 1', [ 'email_address' => $reg_options['email'], diff --git a/Sources/Actions/RequestMembers.php b/Sources/Actions/RequestMembers.php index 8a47435310f..b8f8167e2a5 100644 --- a/Sources/Actions/RequestMembers.php +++ b/Sources/Actions/RequestMembers.php @@ -74,7 +74,7 @@ public function execute(): void $request = Db::$db->query( 'SELECT real_name FROM {db_prefix}members - WHERE {ci:real_name} LIKE {string:search}' . (isset($_REQUEST['buddies']) ? ' + WHERE {column_ci:real_name} LIKE {string:search}' . (isset($_REQUEST['buddies']) ? ' AND id_member IN ({array_int:buddy_list})' : '') . ' AND is_activated IN ({array_int:activated}) LIMIT {int:limit}', diff --git a/Sources/Db/APIs/MySQL.php b/Sources/Db/APIs/MySQL.php index f09faad958b..73a09be6fce 100644 --- a/Sources/Db/APIs/MySQL.php +++ b/Sources/Db/APIs/MySQL.php @@ -282,11 +282,11 @@ public function quote(string $db_string, array $db_values, ?object $connection = [ // The literal type can have arbitrary content. '~{(literal):([^}]*)}~', - // The ci type names a column inline rather than by key, so - // that the column a comparison is case folding is visible in - // the query itself. Only a column name, optionally qualified - // by a table alias, is accepted. - '~{(ci):([a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)?)}~', + // The column_ci type names a column inline rather than by key, + // so that the column a comparison is case folding is visible + // in the query itself. Only a column name, optionally + // qualified by a table alias, is accepted. + '~{(column_ci):([a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)?)}~', // Everything else needs to be a key in $db_values. '~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', ], @@ -2760,7 +2760,7 @@ protected function replacement__callback(array $matches, array $db_values, objec // MySQL folds case in the column's collation, so a case insensitive // comparison is what a bare column already does. - if ($matches[1] === 'ci') { + if ($matches[1] === 'column_ci') { return $matches[2]; } diff --git a/Sources/Db/APIs/PostgreSQL.php b/Sources/Db/APIs/PostgreSQL.php index 8df9846b306..e3c39760ab9 100644 --- a/Sources/Db/APIs/PostgreSQL.php +++ b/Sources/Db/APIs/PostgreSQL.php @@ -343,11 +343,11 @@ public function quote(string $db_string, array $db_values, ?object $connection = [ // The literal type can have arbitrary content. '~{(literal):([^}]*)}~', - // The ci type names a column inline rather than by key, so - // that the column a comparison is case folding is visible in - // the query itself. Only a column name, optionally qualified - // by a table alias, is accepted. - '~{(ci):([a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)?)}~', + // The column_ci type names a column inline rather than by key, + // so that the column a comparison is case folding is visible + // in the query itself. Only a column name, optionally + // qualified by a table alias, is accepted. + '~{(column_ci):([a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)?)}~', // Everything else needs to be a key in $db_values. '~{([a-z_]+)(?::([a-zA-Z0-9_-]+))?}~', ], @@ -2691,7 +2691,7 @@ protected function replacement__callback(array $matches, array $db_values, objec // PostgreSQL compares strings exactly, so a case insensitive comparison // folds the column and pairs it with a value folded the same way. - if ($matches[1] === 'ci') { + if ($matches[1] === 'column_ci') { return 'LOWER(' . $matches[2] . ')'; } @@ -2721,7 +2721,7 @@ protected function replacement__callback(array $matches, array $db_values, objec case 'text': return \sprintf('\'%1$s\'', pg_escape_string($this->connection, (string) $replacement)); - // Folded to match a column that {ci:} has folded. + // Folded to match a column that {column_ci:} has folded. case 'string_ci': return \sprintf('LOWER(\'%1$s\')', pg_escape_string($this->connection, (string) $replacement)); @@ -2763,7 +2763,7 @@ protected function replacement__callback(array $matches, array $db_values, objec break; - // Each of these folded to match a column that {ci:} has folded. + // Each of these folded to match a column that {column_ci:} has folded. case 'array_string_ci': if (\is_array($replacement)) { if (empty($replacement)) { diff --git a/Sources/PersonalMessage/PM.php b/Sources/PersonalMessage/PM.php index de6e53a2060..a88fc98fa57 100644 --- a/Sources/PersonalMessage/PM.php +++ b/Sources/PersonalMessage/PM.php @@ -1210,7 +1210,7 @@ public static function send(array $recipients, string $subject, string $message, $request = Db::$db->query( 'SELECT id_member, member_name FROM {db_prefix}members - WHERE {ci:member_name} IN ({array_string:usernames})', + WHERE {column_ci:member_name} IN ({array_string:usernames})', [ 'usernames' => array_keys($usernames), ], diff --git a/Sources/PersonalMessage/Search.php b/Sources/PersonalMessage/Search.php index a8da23f265b..5433c07c669 100644 --- a/Sources/PersonalMessage/Search.php +++ b/Sources/PersonalMessage/Search.php @@ -478,7 +478,7 @@ protected function setUserQuery(): void foreach ($possible_users as $k => $v) { $where_params['name_' . $k] = $v; - $where_clause[] = '{ci:real_name} LIKE {string:name_' . $k . '}'; + $where_clause[] = '{column_ci:real_name} LIKE {string:name_' . $k . '}'; } // Who matches those criteria? @@ -498,7 +498,7 @@ protected function setUserQuery(): void foreach ($possible_users as $k => $v) { $this->searchq_parameters['name_' . $k] = $v; - $clauses[] = '{ci:pm.from_name} LIKE {string:name_' . $k . '}'; + $clauses[] = '{column_ci:pm.from_name} LIKE {string:name_' . $k . '}'; } if (Db::$db->num_rows($request) == 0) { diff --git a/Sources/Profile.php b/Sources/Profile.php index 8568ce2db03..0ddd3d12ceb 100644 --- a/Sources/Profile.php +++ b/Sources/Profile.php @@ -1649,7 +1649,7 @@ public function validateEmail(string $email): bool|string 'SELECT id_member FROM {db_prefix}members WHERE id_member != {int:selected_member} - AND {ci:email_address} = {string:email_address} + AND {column_ci:email_address} = {string:email_address} LIMIT 1', [ 'selected_member' => $this->id, diff --git a/Sources/User.php b/Sources/User.php index 75b632edaf6..c7567b271fe 100644 --- a/Sources/User.php +++ b/Sources/User.php @@ -3800,10 +3800,10 @@ public static function find(string|array $names, bool $use_wildcards = false, bo $email_condition = ''; } - // The {ci:} type folds the column for the engines that need it and + // The {column_ci:} type folds the column for the engines that need it and // leaves it alone for the ones that do not. - $member_name = '{ci:member_name}'; - $real_name = '{ci:real_name}'; + $member_name = '{column_ci:member_name}'; + $real_name = '{column_ci:real_name}'; // Searches. $member_name_search = $member_name . ' ' . $comparison . ' ' . implode(' OR ' . $member_name . ' ' . $comparison . ' ', $names_list); @@ -5425,7 +5425,7 @@ protected static function addQueryCustomizationsForLoadType(array &$query_custom break; case self::LOAD_BY_NAME: - $query_customizations['where'][] = '{ci:mem.member_name} IN ({array_string_ci:users})'; + $query_customizations['where'][] = '{column_ci:mem.member_name} IN ({array_string_ci:users})'; $query_customizations['params']['users'] = $users; break;