2017年3月23日木曜日

SQLiteのクォートにまつわる奇妙な仕様

SQLiteでは、ISO SQL標準同様に、文字列リテラルはシングルクォートで囲み、識別子をクォートする場合は、ダブルクォートで囲むことになっています。

'foo' : 文字列リテラル
"foo" : 識別子(テーブル名、列名等)

しかし、マニュアルによると、SQLiteのクォーティングには例外があります。それを実例で紹介しましょぅ。まずは、実験の準備として、列 a だけを持つテーブル a を作成します。
$ sqlite3 test.db
sqlite> CREATE TABLE a(a integer);
sqlite> INSERT INTO a VALUES(1);
sqlite> SELECT * FROM a;
1
sqlite>
続いて、以下を実行します。実行結果はどうなるでしょうか?
sqlite> SELECT 'a', "a", [a], `a`, "aa" FROM 'a'
これ、FROM 'a' のところが文法違反に見えますよね。FROMの後には列名が続くはずです。また、"aa" は列名だとするとそのような列は存在しないので、こちらもエラーになるはずです。
しかし、SQLiteの場合、上記SQL文はエラーにならず、以下の結果となります。
sqlite> SELECT 'a', "a", [a], `a`, "aa" FROM 'a';
a|1|1|1|aa
sqlite>
これはどういうことでしょうか?その答えは、SQLiteのマニュアルにあります。
For resilience when confronted with historical SQL statements, SQLite will sometimes bend the quoting rules above:
  • If a keyword in single quotes (ex: 'key' or 'glob') is used in a context where an identifier is allowed but where a string literal is not allowed, then the token is understood to be an identifier instead of a string literal.
  • If a keyword in double quotes (ex: "key" or "glob") is used in a context where it cannot be resolved to an identifier but where a string literal is allowed, then the token is understood to be a string literal instead of an identifier.
SQLite Query Language: SQLite Keywords より引用
すなわち、シングルクォートで囲まれたキーワードが、文字列リテラルが許可されていない箇所に置かれている場合、識別子として認識されます。上記の FROM 'a' の 'a' がこれに該当します。
また、ダブルクォートで囲まれたキーワードであるのに、該当する列名等がない場合、文字列リテラルとして認識されます。上記の "aa" がこれに該当します。

SQLite はなんてすごいんだ! 僕達の気持ちをここまでくんでくれるなんて!!

というのはもちろん冗談で、こんなのは余計なお世話としか言いようがありません。開発者が間違った場合でもエラーにならず、見つけにくいバグの原因になりそうです。

ちなみに、SQLiteは、識別士を [] で囲む方式(MS SQL風)や、バッククォートで囲む方式(MySQL風)もサポートしていますが、これらの場合は、自動的に文字列リテラルとして認識されることはないようです。だったらこれらを使えば…という意見もありそうですが、ISO標準にはない書き方なので、移植性がそこなわれますよね。
ということで、SQLiteを使う場合、もしクォートする必要がなければ、識別子はクォートしない方が無難なのではないでしょうか? そうすれば、勝手に文字列リテラルとして認識されることもないし、移植性も損なわれません。

なお、この問題に起因するセキュリティ上の問題はないか考えてみましたが、思いつきませんでした。思いついた方があれば、ぜひ教えてください。

0 件のコメント:

コメントを投稿

フォロワー