Fix casing issues with non-ASCII characters in contact search.

SQLite's case-related stuff is ASCII-only. That means that even though LIKE is supposed to be case-insensitive, it fails when used on non-ASCII characters.

There appears to be no relief in SQLite itself, so I swapped our contact search to use GLOB instead of LIKE and wrote a little thing to convert query strings into a case-insensitive unicode-compatible patterns. Didn't see any noticeable performance difference.
master
Greyson Parrelli 2020-07-22 22:36:10 -04:00
parent 5f9c0c3204
commit 5bf15b0587
2 changed files with 41 additions and 16 deletions

View File

@ -43,6 +43,7 @@ import org.thoughtcrime.securesms.util.CursorUtil;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.SqlUtil;
import org.thoughtcrime.securesms.util.StringUtil;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.InvalidKeyException;
@ -1831,17 +1832,16 @@ public class RecipientDatabase extends Database {
}
public @Nullable Cursor querySignalContacts(@NonNull String query, boolean includeSelf) {
query = TextUtils.isEmpty(query) ? "*" : query;
query = "%" + query + "%";
query = buildGlobPattern(query);
String selection = BLOCKED + " = ? AND " +
REGISTERED + " = ? AND " +
GROUP_ID + " IS NULL AND " +
"(" + SYSTEM_DISPLAY_NAME + " NOT NULL OR " + PROFILE_SHARING + " = ?) AND " +
"(" +
PHONE + " LIKE ? OR " +
SORT_NAME + " LIKE ? OR " +
USERNAME + " LIKE ?" +
PHONE + " GLOB ? OR " +
SORT_NAME + " GLOB ? OR " +
USERNAME + " GLOB ?" +
")";
String[] args;
@ -1870,8 +1870,7 @@ public class RecipientDatabase extends Database {
}
public @Nullable Cursor queryNonSignalContacts(@NonNull String query) {
query = TextUtils.isEmpty(query) ? "*" : query;
query = "%" + query + "%";
query = buildGlobPattern(query);
String selection = BLOCKED + " = ? AND " +
REGISTERED + " != ? AND " +
@ -1879,9 +1878,9 @@ public class RecipientDatabase extends Database {
SYSTEM_DISPLAY_NAME + " NOT NULL AND " +
"(" + PHONE + " NOT NULL OR " + EMAIL + " NOT NULL) AND " +
"(" +
PHONE + " LIKE ? OR " +
EMAIL + " LIKE ? OR " +
SYSTEM_DISPLAY_NAME + " LIKE ?" +
PHONE + " GLOB ? OR " +
EMAIL + " GLOB ? OR " +
SYSTEM_DISPLAY_NAME + " GLOB ?" +
")";
String[] args = new String[] { "0", String.valueOf(RegisteredState.REGISTERED.getId()), query, query, query };
String orderBy = SYSTEM_DISPLAY_NAME + ", " + PHONE;
@ -1890,21 +1889,40 @@ public class RecipientDatabase extends Database {
}
public @Nullable Cursor queryAllContacts(@NonNull String query) {
query = TextUtils.isEmpty(query) ? "*" : query;
query = "%" + query + "%";
query = buildGlobPattern(query);
query = "*" + query + "*";
String selection = BLOCKED + " = ? AND " +
"(" +
SORT_NAME + " LIKE ? OR " +
USERNAME + " LIKE ? OR " +
PHONE + " LIKE ? OR " +
EMAIL + " LIKE ?" +
SORT_NAME + " GLOB ? OR " +
USERNAME + " GLOB ? OR " +
PHONE + " GLOB ? OR " +
EMAIL + " GLOB ?" +
")";
String[] args = new String[] { "0", query, query, query, query };
return databaseHelper.getReadableDatabase().query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, null);
}
private static String buildGlobPattern(@NonNull String query) {
if (TextUtils.isEmpty(query)) {
return "*";
}
StringBuilder pattern = new StringBuilder();
for (int i = 0, len = query.codePointCount(0, query.length()); i < len; i++) {
String point = StringUtil.codePointToString(query.codePointAt(i));
pattern.append("[");
pattern.append(point.toLowerCase());
pattern.append(point.toUpperCase());
pattern.append("]");
}
return "*" + pattern.toString() + "*";
}
public @NonNull List<Recipient> getRecipientsForMultiDeviceSync() {
String subquery = "SELECT " + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.RECIPIENT_ID + " FROM " + ThreadDatabase.TABLE_NAME;
String selection = REGISTERED + " = ? AND " +

View File

@ -61,4 +61,11 @@ public final class StringUtil {
public static boolean isVisuallyEmpty(char c) {
return Character.isWhitespace(c) || WHITESPACE.contains(c);
}
/**
* @return A string representation of the provided unicode code point.
*/
public static @NonNull String codePointToString(int codePoint) {
return new String(Character.toChars(codePoint));
}
}