リンク: [ホーム] [自己紹介] [リンク集] [アルバム] [ソフトウェア] [発表文献] [その他]

まさおのChangeLogメモ / 2008-06-19

01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

2008-06-19 Thu

* SQLite + LIKE + 「サービスラーニング」問題

昨年つくった某所の実験システムの検索機能は、安直に SQLite にデータ
を格納しておいて、LIKE オペレータをかましているだけという単純なも
のなのだけど、ここで「サービスラーニング」というフレーズで検索する
と11件がヒットするにも関わらず、「サービス and ラーニング」で検索
するとヒット件数1件のみに減ってしまう問題が判明した。

これは、使用しているSqliteエンジンの LIKE オペレータの照合アルゴリ
ズム内で utf-8 を仮定した読み込みが行われていることに起因する模様。
cf. src/func.c 中の patternCompare 関数。

SQLite内の文字列読み込み結果を確認するプログラム:

static const unsigned char sqlite3UtfTrans1[] = {
  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
  0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
  0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
  0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
  0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
  0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00,
};
int sqlite3Utf8Read(
  const unsigned char *z, /* First byte of UTF-8 character */
  const unsigned char *zTerm, /* Pretend this byte is 0x00 */
  const unsigned char **pzNext /* Write first byte past UTF-8 char here */
){
  int c = *(z++);
  if( c>=0xc0 ){
    c = sqlite3UtfTrans1[c-0xc0];
    while( z!=zTerm && (*z & 0xc0)==0x80 ){
      c = (c<<6) + (0x3f & *(z++));
    }
    if( c<0x80
        || (c&0xFFFFF800)==0xD800
        || (c&0xFFFFFFFE)==0xFFFE ){ c = 0xFFFD; }
  }
  *pzNext = z;
  return c;
}

int main(int argc, char** argv) {
    unsigned char *str = (unsigned char*) argv[1];
    unsigned int c, i;
    printf("ORIGINAL: %s\nORIGINAL: ", str);
    for(i = 0; i < strlen(str); i++) {
printf("\\x%02x", str[i]);
    }
    printf("\nConverted: ");
    while( *str ) {
c = sqlite3Utf8Read(str, 0, &str);
printf("\\x%02x", c);
    }
    printf("\n");
}

上記のutf-8読み込みコードにeuc-jpを食わせると、当然意図しない文字
化けをおこす。

% cc test_utf.c
% ./a.out "サービスラーニング"
ORIGINAL: サービスラーニング
ORIGINAL: \xa5\xb5\xa1\xbc\xa5\xd3\xa5\xb9\xa5\xe9\xa1\xbc\xa5\xcb\xa5\xf3\xa5\xb0
Converted: \xa5\xb5\xa1\xbc\xa5\x4e5e65\x261f25\x2e5\x3970

% ./a.out "サービス"
ORIGINAL: サービス
ORIGINAL: \xa5\xb5\xa1\xbc\xa5\xd3\xa5\xb9
Converted: \xa5\xb5\xa1\xbc\xa5\x13979

% ./a.out "ラーニング"
ORIGINAL: ラーニング
ORIGINAL: \xa5\xe9\xa1\xbc\xa5\xcb\xa5\xf3\xa5\xb0
Converted: \xa5\x261f25\x2e5\x3970

% ./a.out "「サービスラーニング」"
ORIGINAL: 「サービスラーニング」
ORIGINAL: \xa1\xd6\xa5\xb5\xa1\xbc\xa5\xd3\xa5\xb9\xa5\xe9\xa1\xbc\xa5\xcb\xa5\xf3\xa5\xb0\xa1\xd7
Converted: \xa1\xfffd\x4e5e65\x261f25\x2e5\xe5c21\xfffd

なにはともあれ、SQLite に utf-8 以外の文字コードでデータを格納して
はいけないというのが結論の模様。。。

SQLiteはバージョン2.xの頃から使っていたのだけど、以前は明示的に
utf8対応をenableしないかぎりは、euc-jp で格納しておいたとしたも、
文字コード変換などはされないという記憶があったのだけど。。。
気のせいだったのかも。。。

マニュアルには明示的に書かれていないので、注意が必要そう。
Referrer (Inside): [2008-06-20-1]