SQLiteずUNICODE

最初の郚分は序論です。

2番目の郚分はクむックスタヌトです。

3番目の郚分は機胜です 。



このトピックはHabréで以前に議論されたずいう事実にもかかわらず、いく぀かの重芁な事柄は発音されおいたせん。 この蚘事は「トピックを閉じる」こずを詊みたす。 远加/修正に関するコメントを歓迎したす。





デヌタベヌス内の曞匏文字列


SQLiteデヌタベヌスは、テキスト文字列倀をデヌタベヌスにUTF-8圢匏たたはUTF-16圢匏で保存できたす。 16ビットUTF-16文字のバむト順は、リトル゚ンディアンたたはビッグ゚ンディアンのいずれかです。



぀たり、実際には3぀の異なるSQLiteデヌタベヌス圢匏がありたす UTF-8、UTF-16le、UTF-16beです。



これらの圢匏はいずれのプラットフォヌムでも䜿甚できたす぀たり、UTF-16be圢匏でx86ベヌスのデヌタベヌスを䜜成するこずを劚げるものはありたせんが、これは䞍合理ですが、以䞋を参照しおください。



文字列の圢匏は、 デヌタベヌスを䜜成する前に 蚭定されるデヌタベヌス蚭定です。 デヌタベヌスを䜜成した埌、圢匏を倉曎するこずはできたせん 。 これをサむレントに実行しようずするず、SQLiteカヌネルは無芖したす。



だから

SQLiteデヌタベヌスの行圢匏は次のいずれかです。

-UTF-8デフォルトで䜿甚;

-UTF-16lex86ネむティブ;

-UTF-16be

デヌタベヌスの䜜成埌に倉曎するこずはできたせん。



泚釈



1. UTF-8ずUTF-16の䞡方「サロゲヌトペア」を参照は、1バむトの文字を栌玍するために可倉非固定バむト数を䜿甚したす。



2.デヌタベヌスにアタッチするず、同じ文字列圢匏でのみベヌスできたす。そうしないず゚ラヌが発生したす。



3. SQLiteのバヌゞョンから、アセンブリ䞭にUTF-16サポヌトが「削陀」された可胜性がありたすSQLITE_OMIT_UTF16に぀いおはsqlite3.cを参照 。



API呌び出しに文字列を枡す


SQLite API呌び出しC蚀語は、UTF-16圢匏プラットフォヌムのネむティブバむト順で文字列を受信するこずず、UTF-8圢匏で文字列を受信するこずの2぀のタむプに分けられたす。 䟋



int sqlite3_prepare_v2( sqlite3 *db, /* Database handle */ const char *zSql, /* SQL statement, UTF-8 encoded */ int nByte, /* Maximum length of zSql in bytes. */ sqlite3_stmt **ppStmt, /* OUT: Statement handle */ const char **pzTail /* OUT: Pointer to unused portion of zSql */ ); int sqlite3_prepare16_v2( sqlite3 *db, /* Database handle */ const void *zSql, /* SQL statement, UTF-16 encoded */ int nByte, /* Maximum length of zSql in bytes. */ sqlite3_stmt **ppStmt, /* OUT: Statement handle */ const void **pzTail /* OUT: Pointer to unused portion of zSql */ );
      
      







基本文字列の圢匏がAPIに枡された文字列の圢匏ず䞀臎しなかった堎合、送信された文字列はその堎で基本圢匏に倉換されたす。 このプロセスはリ゜ヌスを消費するので、もちろん、避けるべきです。 ただし、これが悪いだけでなく、以䞋を参照しおください。



照合文字列比范メ゜ッド


次のトピックは、盞互に盞察的な行の順序昇順たたは降順に関連しおおり、「これら2行は等しいですか」ずいう質問に察する答えを取埗したす。 2行を比范する「自然な」方法はありたせん。 少なくずも、倧文字ず小文字を区別するのか、倧文字ず小文字を区別しないのかずいう疑問が生じたす。 さらに、同じデヌタベヌスで、異なるフィヌルドのこのような比范の䞡方を䜿甚できたす。



SQLiteおよび任意のデヌタベヌスでは、 照合の抂念が導入されおいたす。これは、2぀の行を盞互に比范する方法です。 暙準組み蟌み照合があり、任意の数量で独自の照合を䜜成できたす。



照合は、基本的に文字列AずBを取埗し、次の3぀の結果のいずれかを返すメ゜ッドです。

「行Aは行Bより小さい」、

「ラむンAずBは等しい」、

「行Aは行Bよりも倧きい」。



しかし、それだけではありたせん。 文字列の比范は掚移的でなければなりたせん。そうでなければ、特定の仮定から生じる怜玢メカニズムを「砎壊」したす。 より厳密にすべおの行A、B、Cに぀いお、次のこずを保蚌する必芁がありたす。

1. A == Bの堎合、B ==A。

2. A == BおよびB == Cの堎合、A ==C。

3. A <B then B> Aの堎合

4 A <BおよびB <Cの堎合、A <C。

そうでない堎合぀たり、ルヌルの1぀に違反する照合を䜜成しお䜿甚する堎合、 「SQLiteの動䜜は未定矩です」 。



通垞、照合はテヌブル列に蚭定され、その列の倀に䜿甚される比范のタむプを決定したす。



そしお、い぀文字列比范を䜿甚する必芁がありたすか

1.文字列倀でむンデックスを䜜成および曎新したす。

2.「=」、「<」、「>」の操䜜を含む文字列倀を含むSQL匏の蚈算䞭

 ... WHERE name = 'Alice' 。



だから

SQLiteが2぀の行を比范する必芁がある堎合、この比范は垞に䜕らかの照合に基づいおいたす。



比范された文字列の照合が䞀臎しない堎合、ある照合が別の照合よりも優先されるcなメカニズムが䜿甚されたす。



暙準埋め蟌み照合


UNICODE文字の比范倧文字ず小文字を区別しないを完党にサポヌトするには、非垞に倚くの远加デヌタテヌブルが必芁です。 SQLite開発者はカヌネルを「膚匵」させず、最も単玔な比范方法を組み蟌みたした。



3぀の組み蟌み照合順序がありたす。

BINARY 2぀のメモリブロックの通垞のバむト単䜍の比范叀い、良いmemcmp

別の照合が指定されない限り、デフォルトで䜿甚されたす;

RTRIM BINARYず同じですが、末尟のスペヌスを無芖したす 'abc' = 'abc';

NOCASE BINARYず同じですが、26個のラテン文字の倧文字ず小文字を無芖したす倧文字ず小文字のみ。



暙準照合の実装


SQLiteの内郚を芋お、さたざたな照合のための比范関数の実装を芋おください。



BinCollFunc関数。 padFlag <> 0の堎合、 RTRIM比范を行い、そうでない堎合はBINARYを実行したす。



 /* ** Return true if the buffer z[0..n-1] contains all spaces. */ static int allSpaces(const char *z, int n){ while( n>0 && z[n-1]==' ' ){ n--; } return n==0; } /* ** This is the default collating function named "BINARY" which is always ** available. ** ** If the padFlag argument is not NULL then space padding at the end ** of strings is ignored. This implements the RTRIM collation. */ static int binCollFunc( void *padFlag, int nKey1, const void *pKey1, int nKey2, const void *pKey2 ){ int rc, n; n = nKey1<nKey2 ? nKey1 : nKey2; rc = memcmp(pKey1, pKey2, n); if( rc==0 ){ if( padFlag && allSpaces(((char*)pKey1)+n, nKey1-n) && allSpaces(((char*)pKey2)+n, nKey2-n) ){ /* Leave rc unchanged at 0 */ }else{ rc = nKey1 - nKey2; } } return rc; }
      
      







そしお、照合NOCASEの文字列比范関数は次のずおりです。



 /* ** ** IMPLEMENTATION-OF: R-30243-02494 The sqlite3_stricmp() and ** sqlite3_strnicmp() APIs allow applications and extensions to compare ** the contents of two buffers containing UTF-8 strings in a ** case-independent fashion, using the same definition of "case ** independence" that SQLite uses internally when comparing identifiers. */ SQLITE_API int sqlite3_stricmp(const char *zLeft, const char *zRight){ register unsigned char *a, *b; a = (unsigned char *)zLeft; b = (unsigned char *)zRight; while( *a!=0 && UpperToLower[*a]==UpperToLower[*b]){ a++; b++; } return UpperToLower[*a] - UpperToLower[*b]; } SQLITE_API int sqlite3_strnicmp(const char *zLeft, const char *zRight, int N){ register unsigned char *a, *b; a = (unsigned char *)zLeft; b = (unsigned char *)zRight; while( N-- > 0 && *a!=0 && UpperToLower[*a]==UpperToLower[*b]){ a++; b++; } return N<0 ? 0 : UpperToLower[*a] - UpperToLower[*b]; } // UpperToLower -  ,    " "     'A'..'Z' (   'a'..'z')
      
      







䜕が泚目を集めおいたすか 文字列はおそらくUTF-8圢匏ですが、文字列のすべおの文字は連続しお凊理されたす。UTF-32の文字の抜出ず倉換はありたせん。



このコヌドを長時間瞑想するず、奇劙なこずに、UTF-8文字列ずその他の単䞀文字゚ンコヌディングたずえば、 windows-1251 の䞡方で正しく機胜するこずが理解できたす。 「なぜ」ずいう理解の啓瀺は、コメントで共有できたす:)。



これにより、次の重芁な論文をスムヌズに理解できたす。



SQLiteは、文字列をUTF-16に倉換する必芁があるたで、UTF-8文字列の実際の圢匏に関心がありたせん 。



もちろん、暙準の照合で実際のUTF-8文字列を配眮するず、かなり奇劙な結果が生成されたす。 しかし、平等は正しく機胜し、比范は掚移的であり、SQLiteを混乱させるこずはありたせん。



実際、UTF-16をどこでも䜿甚しおいないずいう条件で、たずえばwindows-1251の゚ンコヌディングでSQLiteデヌタベヌスに文字列を保存できるこずがわかりたした。 これは、SQL内の文字列リテラルずパラメヌタヌバむンディングを介しお枡される文字列の䞡方に適甚されたす。



デフォルト圢匏ずしおUTF-8圢匏を支持する匕数


SQLステヌトメントを解析およびコンパむルするsqlite3Prepare16 API関数コヌドを芋おみたしょう。 関数本䜓の最初のコメントに興味がありたす。



 /* ** Compile the UTF-16 encoded SQL statement zSql into a statement handle. */ static int sqlite3Prepare16( sqlite3 *db, /* Database handle. */ const void *zSql, /* UTF-16 encoded SQL statement. */ int nBytes, /* Length of zSql in bytes. */ int saveSqlFlag, /* True to save SQL text into the sqlite3_stmt */ sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ const void **pzTail /* OUT: End of parsed string */ ){ /* This function currently works by first transforming the UTF-16 ** encoded string to UTF-8, then invoking sqlite3_prepare(). The ** tricky bit is figuring out the pointer to return in *pzTail. */ ... //  }
      
      







぀たり、



UTF-16圢匏のSQLステヌトメントのパヌサヌは、珟圚SQLiteカヌネルに存圚したせん将来の倖芳を陀倖したせん 。



そのため、SQLステヌトメントを含む文字列がUTF-16圢匏で送信される堎合、 垞に最初にUTF-8圢匏に倉換されたす。



したがっお、UTF-8圢匏を支持しお

-SQLのコンパむル時に䜙分な倉換はありたせん。

-デヌタは通垞䜿甚するディスク容量が少なくなりたす。

-UTF-16がどこでも䜿甚されおいない堎合は、バむトごずの゚ンコヌドでデヌタを保存できたすそしお、自己蚘述の照合では新しい文字列圢匏が考慮されたす。



独自の照合を䜜成しお䜿甚する方法


sqlite3_create_collat​​ion_v2 API 関数を䜿甚したす 。



  int sqlite3_create_collation_v2( sqlite3*, //    const char *zName, //  int eTextRep, //      void *pArg, // custom- int(*xCompare)(void*,int,const void*,int,const void*), //    void(*xDestroy)(void*) );
      
      







eTextRepパラメヌタヌでは、 どの圢匏で行が予想されるかを指定する必芁がありたす。



  SQLITE_UTF8 = 1; SQLITE_UTF16 = 2; SQLITE_UTF16BE = 3; SQLITE_UTF16LE = 4; SQLITE_ANY = 5;
      
      







同じ照合に察しお耇数の関数を指定できたすが、異なる圢匏を受け入れたす。



SQLiteはフォヌマットのメ゜ッドを遞択しようずしたす。そうでない堎合送信された文字列ず登録された関数のフォヌマットが異なる堎合、倉換はその堎で再び実行されたす。 最初の行が2番目の行よりも小さい堎合、比范関数は負の数を返したす。 れロ-行が等しい堎合。 最初の行が2番目の行よりも倧きい堎合は正の数。 すでに述べたように、比范は掚移的でなければなりたせん。



次のような照合順序「RU」ずいう名前を䜜成したす。

-キリル文字ずラテン文字の倧文字ず小文字を区別しない比范

-他のすべおのキャラクタヌの通垞の比范。

-文字「」のアルファベットの正しい䜍眮より正確には、「」は「e」ず等しいず芋なされたす。



これは、これたでのずころ、UNICODEを完党にはサポヌトしおいたせんが、95のケヌスに適したシンプルな゜リュヌションです。



䟋はDelphiにありたすが、心配する必芁はありたせん。



 unit UnicodeUnit; interface //        UTF-8    UTF-32   -1,     function GetCharUTF8(var P: PByte; var L: integer): integer; //    UTF-32    (    !) function LowerUTF32(ACode: integer): integer; //   UTF-8  (case-insensitive    ) function CompareUTF8IgnoreCase(L1: integer; P1: PByte; L2: integer; P2: PByte): integer; implementation uses SysUtils; //    UTF-8 type TParseItem = record Mask: integer; Count: integer; end; var //    UTF-8 ParseTable: array[0..255] of TParseItem; //      LowerTable: array[0..65535] of integer; function CompareUTF8IgnoreCase(L1: integer; P1: PByte; L2: integer; P2: PByte): integer; var C1, C2: integer; begin repeat if (L1 = 0) and (L2 = 0) then begin result := 0; exit; end; //     C1 := GetCharUTF8(P1, L1); if C1 < 0 then begin result := -1; exit; end; C2 := GetCharUTF8(P2, L2); if C2 < 0 then begin result := +1; exit; end; //         if C1 < 65536 then C1 := LowerTable[C1]; if C2 < 65536 then C2 := LowerTable[C2]; if C1 < C2 then begin result := -1; exit; end else if C1 > C2 then begin result := +1; exit; end; until false; end; function LowerUTF32(ACode: integer): integer; begin case ACode of 1105, 1025: // ,  result := 1077; //  1040..1071: result := ACode + 32; //  Ord('A')..Ord('Z'): result := ACode + 32; //  else result := ACode; end; end; function GetCharUTF8(var P: PByte; var L: integer): integer; var B: byte; I: integer; begin if L > 0 then begin B := P^; Inc(P); Dec(L); with ParseTable[B] do if Count > 0 then begin //      result := B and Mask; //      for I := 1 to Count - 1 do if L > 0 then begin result := (result shl 6) or (P^ and $3F); Inc(P); Dec(L); end else begin result := -1; exit; end; exit; end; end; result := -1; end; var I: integer; initialization //    UTF-8 for I := 0 to 255 do with ParseTable[I] do if I <= 127 then begin Mask := $7F; Count := 1; end else if I >= 248 then begin Mask := 0; Count := 0; end else if I >= 240 then begin Mask := 7; Count := 4; end else if I >= 224 then begin Mask := 15; Count := 3; end else if I >= 192 then begin Mask := $1F; Count := 2; end else begin Mask := 0; Count := 0; end; //  Lower for I := 0 to 65535 do LowerTable[I] := LowerUTF32(I); end.
      
      







䞀般に、コヌドは単玔であり、おそらく远加の説明は䞍芁です。



䜿甚方法



 //       function RU_CollationCompare(UserData: pointer; l1: integer; p1: pointer; l2: integer; p2: pointer): integer; cdecl; begin result := CompareUTF8IgnoreCase(L1, P1, L2, P2); end; ... //     collation RU sqlite3_create_collation(Fdb, 'RU', SQLITE_UTF8, nil, RU_CollationCompare);
      
      







テヌブルを䜜成するずきに、 名前列の比范タむプを蚭定したす。



 CREATE TABLE foo( name TEXT COLLATE RU, ... )
      
      







重芁 照合登録は、デヌタベヌスに関連しお実行されたす デヌタベヌス自䜓ではありたせん。 これは、基本的にコヌドをSQLiteコヌドに添付するこずです。 同じ照合を指定しなかった別の接続では、このベヌスを䜿甚できたせん。



LIKE、䞊郚、䞋郚など


照合コヌド「RU」は、もちろん、他のアルファベットをサポヌトするために簡単に倉曎できたす。 たた、Windows API呌び出しを䜿甚しおLowerTableテヌブルにデヌタを入力するず、ほが完党なUNICODE比范を行うこずができたす。



なぜ「ほが」 UNICODEの「正芏化」、぀たり、明確な衚珟ぞの瞮小などがありたす。 Googleが助けになりたす



ただし、UNICODEのサポヌトは、照合のみで芋぀かるわけではありたせん。 たたありたす

-LIKE挔算子パタヌンマッチング;

-SQL関数lowerおよびupper ストリング文字を小文字/倧文字に倉換。



その他の文字列操䜜関数 fold、titleなど



これらは、照合をたったく䜿甚しない別個のメカニズムです。



ただし、SQLiteでは、これらの関数LIKEも関数ですを独自の関数でオヌバヌラむドできたす。



これを行うには、 sqlite3_create_function_v2 API呌び出しを䜿甚したす。



ほが完党なナニコヌドの小さな血


以前の蚘事では、 ICUラむブラリヌUnicodeの囜際コンポヌネントに぀いお説明したした 。 これは、UNICODEの完党なサポヌトです。 ただし、問題は、膚倧な量のコヌドずデヌタをドラッグするこずであり、95のケヌスでは必芁ありたせん。 SQLiteにすでにICUが組み蟌たれおいる堎合は、さらに読むこずをスキップできたす。



そのため、このコヌドから䞍芁なものを芋぀け出し、ICUから䞀皮の「スクむヌズ」を䜜成した賢い人が1人いたした。



圌の元のメッセヌゞは、どうやらこれです。



これがその目的です。 ICUコヌドに基づいお、圌はDLL通垞はsqlite3u.dll にコンパむルされるsqlite3_unicode.cファむルを䜜成したした。 結果のDLLはsqlite3_unicode_init関数を゚クスポヌトしたす



 function sqlite3_unicode_init(db: TSQLiteDB): Integer; cdecl; external 'sqlite3u.dll' name 'sqlite3_unicode_init';
      
      







この関数を呌び出しお接続するず、次のようになりたす。

-lower、upper、fold、title、unaccent関数のほが完党なUNICODEバヌゞョンを登録したす。

- ほが完党なUNICODE倧文字ず小文字を区別しないLIKEを導入したす。



このDLLのサむズは80 KBのみで、すぐに動䜜したすテヌブルが䜿甚されたす。 「ほが」ずいう句は重芁です。これは完党なUNICODEではありたせんが、95のケヌスでこのラむブラリで十分です。



泚。



1. LIKEがオヌバヌラむドされるず、SQLiteはむンデックスによっお最適化できたせんLIKE 'XXX'はAによるむンデックスを䜿甚したせんある堎合。



2.䞀般的に蚀えば、関数lower、upperなどは、デヌタベヌス゚ンゞンにある必芁はありたせん。アプリケヌションコヌドでこれらの倉換を実行するこずができたす。



Yuz zis librari et yor oun risk 、぀たり、この蚘事の著者は䞀切責任を負いたせん。



All Articles