ものがたり(旧)

atsushieno.hatenablog.com に続く

don't use GetSortKey() without StringSort

もしあなたがGetSortKey()を使っていて、'-'と'−'を正しく違う文字として比較したかったら、CompareOptions.StringSortを指定しなければならない。

StringSortによって影響を受ける文字はそれほど多くない。と思う。少なくとも僕が実験した範囲では以下の4種類だ:

これらは、StringSortが指定された時はprimary weightをもち、そうでない場合はprimary weightをもたない。で、primary weightがある場合は06 xx ... 00 という普通の文字と同じようなsortkeyになり、primary weightをもたない場合のsortkeyの値は、01 01 01 01 80 07 06 xx 00 というトリッキーなものになる。この時、nonprimaryなsortkeyのxxは1桁しか入っていない。

半角アポストロフィ'\''と全角アポストロフィ'’'のsortkeyは、StringSortを指定した時には以下のようになる:


06 80 01 01 01 01 00 : 27, OtherPunctuation
06 80 01 01 03 01 01 00 : FF07, OtherPunctuation

一方、StringSortを指定しなかった場合は


01 01 01 01 80 07 06 80 00 : 27, OtherPunctuation
01 01 01 01 80 07 06 80 00 : FF07, OtherPunctuation

後者ではこれらが同じ値を返していることに注意。もちろんIgnoreWidthは指定していない。

ほとんどの人はこの問題を心配する必要はない。これはGetSortKey()の問題であり、Compare()ではこれらは正しく違った文字として扱われる。GetSortKey()なんて、collationを使って文字列比較を最適化しまくっているSQL Serverみたいなもんでもない限り、普通は使わないだろう。って、SQL Server 2005はmanaged codeベースだっけ…? ガクブル.

…何でCompare()がおっけーでGetSortKey()がNGなのか、もうちょっと詳しく説明しよう(といっても推測なのだけど)。
StringSortを指定すると、一部の「意味がない文字」として扱われてきた文字(というか、上で列挙した文字)が「意味のある文字」になる。MSのsortkeyは、レベル1から順に、それぞれのレベルが01で終わりsortkey全体が00で終わる仕様になっていて、01 01 01 01というのは、レベル1から4まで、その文字は意味のある重みを持たないということだ。最後のレベルで80 07 06 80となっているのが、レベル5の重みになる。この06 80の部分は、StringSortを指定した時には06の直後に来ている。実際、他のsortkeyの値と比べてみると分かるが、こちらの数値の方が標準的なのだ*1
で、レベル5の値はどうやら4桁(あるいは80 07が固定プレフィックスだとしたら2桁)固定なので、StringSortであればレベル2やレベル3に相当する重みの値が、sortkeyから消えてしまっているのだ。これは単に桁数の関係で切り捨てられているだけなので(という風に見える)、Compare()のロジックとは無関係な部分で発生している問題、つまり単なるバグなのだ(ろう)。

じゃあバグならMSは直すのかというと、けっこうビミョーだと思う。compatibility breakingな修正になると思うので。ていうかニッチだし。

*1:というかレベル5の重みを持つものはこれらの文字くらいしか無いと思う