SQLiteでの大文字と小文字を区別しないキリル文字検索の別の実装

こんにちは、ハブロフスク!



SQLiteでロシア語の文字を検索する問題は長い間言い訳になりました。その出現の理由については、 ここで詳しく説明します 。 ただし、この問題にはかなり一般的な解決策がありますが、最も一般的なのは、Unicodeで完全な検索を実装できるライブラリであるICUを接続することです。 しかし、結果が検索になるように、コードの観点でより短いソリューションが必要でした:





その結果、アイデアはSQLiteソースコードをより深く掘り下げ、少し修正するようになりました。 ツールキットは、SQLite Amalgamationソースコード(すべてのカーネルソースコードが単一のファイルに格納されている)、Visual Studio 2017、およびテストデータベースを含むSystem.Data.Sqliteアセンブリでした。



データベースエンジンの文字列を比較するために、patternCompare関数があります。 ご存知のように、UTF-8の最初の128文字についてのみ、大文字と小文字の比較を実装していません。UTF-8には、コードに対応するチェックがあります。 アイデアは、文字がキリル文字かどうかを確認する別の小さなコードブロックを記述することでした。 テーブルUTF-8を見ると、心に馴染みのある文字については、0x400から0x45Fまでの位置が強調表示されています。 次に、キリル文字と変換テーブルの大文字と小文字のチェックを作成します。 コードのビット。



#define IsCyrillic(A)(A >= 0x400 && A <= 0x45F) static const unsigned short CyrillicUpper[128] = { 1024, 1045, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1104, 1045, 1106, 1107, 1108, 1109, 1110, 1111, 1112, 1113, 1114, 1115, 1116, 1117, 1118, 1119, 1120, 1121, 1122, 1123, 1124, 1125, 1126, 1127, 1128, 1129, 1130, 1131, 1132, 1133, 1134, 1135, 1136, 1137, 1138, 1139, 1140, 1141, 1142, 1143, 1144, 1145, 1146, 1147, 1148, 1149, 1150, 1151 }; static const unsigned short CyrillicLower[128] = { 1024, 1177, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1177, 1106, 1107, 1108, 1109, 1110, 1111, 1112, 1113, 1114, 1115, 1116, 1117, 1118, 1119, 1120, 1121, 1122, 1123, 1124, 1125, 1126, 1127, 1128, 1129, 1130, 1131, 1132, 1133, 1134, 1135, 1136, 1137, 1138, 1139, 1140, 1141, 1142, 1143, 1144, 1145, 1146, 1147, 1148, 1149, 1150, 1151 }; #define CyrillicToUpper(ch)(CyrillicUpper[(unsigned char)(ch&~0x400)]) #define CyrillicToLower(ch)(CyrillicLower[(unsigned char)(ch&~0x400)])
      
      





問題は小さく、文字が0x80未満であるすべてのチェックを見て、キリル性のチェックを追加します。 これは、最初の文字と他のすべての文字をチェックするときに2つの場所で発生します。 patternCompareの機能全体を提供するのではなく、これら2つの場所のみを提供します(既に慎重に修正されています)。



コードの最初のブロック:



  if( c<=0x80 ){ u32 cx; int bMatch; if( noCase ){ cx = sqlite3Toupper(c); c = sqlite3Tolower(c); }else{ cx = c; } while( (c2 = *(zString++))!=0 ){ if( c2!=c && c2!=cx ) continue; bMatch = patternCompare(zPattern,zString,pInfo,matchOther); if( bMatch!=SQLITE_NOMATCH ) return bMatch; } } else if (noCase && IsCyrillic(c)) { u32 cx; int bMatch; if (noCase) { cx = CyrillicToUpper(c); c = CyrillicToLower(c); } else { cx = c; } while ((c2 = Utf8Read(zString)) != 0) { if (c2 != c && c2 != cx) continue; bMatch = patternCompare(zPattern, zString, pInfo, matchOther); if (bMatch != SQLITE_NOMATCH) return bMatch; } }
      
      





コードの2番目のブロック:



  if( noCase && sqlite3Tolower(c)==sqlite3Tolower(c2) && c<0x80 && c2<0x80 ){ continue; } if (noCase && CyrillicToLower(c) == CyrillicToLower(c2) && IsCyrillic(c) && IsCyrillic(c2)) { continue; }
      
      







ロシア語と英語のテキストの大文字と小文字を区別しない検索は、 WHERE LIKE '%xxx%'パターンでは正しく機能するようになりましたが、フィールドが標準のNOCASE関数によってインデックス付けされている場合、 WHERE LIKE 'xxx%'パターンでは機能しません。 したがって、インデックス検索も機能させるには、もう少し工夫する必要があります。 ライブラリコードを詳しく見て、sqlite3_strnicmp関数がc照合NOCASEインデックスの作成に関与していることを確認しましょう。 一般に、それらの2つがあります。1つはテキストの長さをチェックするパラメーターがあり、もう1つはありません。 最初のものが必要です。 ただし、ここでは、patternCompareとは異なり、テキストのみがUTF-8からデコードされないため、UTF-8でエンコードされたキリル文字の最初のバイトが高いことを確認し、UTF-からのキリル文字のデコードも実装します。 8小文字に変換します。



 static unsigned char IsCyrillcByte(unsigned char b) { return !((b >> 1) ^ 0x68); } static unsigned short ReadLowerCyrillic(const unsigned char* in) { unsigned char b1, b2; b1 = *in; in++; b2 = *in; return CyrillicLower[(unsigned char)(b1 << 6) | (b2 &~ 0x80)]; }
      
      







残念ながら、関数sqlite3_strnicmpは、ASCII向けに大幅に最適化されていたため、書き直さなければなりませんでした。現在は次のようになっています。



 SQLITE_API int sqlite3_strnicmp(const char *zLeft, const char *zRight, int N){ register unsigned char *a, *b; unsigned short cLeft, cRight; if( zLeft==0 ){ return zRight ? -1 : 0; }else if( zRight==0 ){ return 1; } a = (unsigned char *)zLeft; b = (unsigned char *)zRight; for(;N > 0; N--) { if (*a <= 0x80 && *b <= 0x80) { if (*a != 0 && UpperToLower[*a] == UpperToLower[*b]) { a++; b++; } else { return UpperToLower[*a] - UpperToLower[*b]; } } else if (IsCyrillcByte(*a) && IsCyrillcByte(*b)) { cLeft = ReadLowerCyrillic(a); cRight = ReadLowerCyrillic(b); if (cLeft == cRight) { a += 2; b += 2; } else { return cLeft - cRight; } } else if (*a == *b) { a++; b++; } else { return *a - *b; } } return 0; }
      
      







すべてをソース骨抜きにし、まとめて食べることができます。 最後に、 REINDEX NOCASEデータベースへの単純なクエリを実行する必要もあります すべての大文字と小文字を区別しないインデックスを再作成します。



All Articles