何が正しいのかを考える際は、正しさの基準が必要

 大垣さんの寄稿記事「第44回 セキュリティ対策が確実に実施されない2つの理由:なぜPHPアプリにセキュリティホールが多いのか?|gihyo.jp … 技術評論社」のまとめにて、『最後に「何が正しいのか?」常に考えるようにしてください』と書かれています。この部分は、私への反論のようですので、このエントリで返答したいと思います。

大垣さんの主張

 先にも述べたように、大垣さんはこのエントリの「まとめ」として以下のように書かれています。

最後に「何が正しいのか?」常に考えるようにしてください。

http://gihyo.jp/dev/serial/01/php-security/0044?page=2

 この主張自体には私も大賛成です。大垣さんの記事は以下のように続きます。

例えば,SQL文を作成する場合にリテラル(パラメータ)を文字列としてエスケープすると浮動小数点型のデータが正しく処理されないデータベースがあります。これは,SQLインジェクション対策の基本の1つである「すべてのリテラルを文字列として処理」することが間違っているのでしょうか? それとも「文字列として渡された浮動小数リテラルを正しく処理できない」処理系が間違っているのでしょうか?

http://gihyo.jp/dev/serial/01/php-security/0044?page=2

 私は、「(数値も含めて)すべてのリテラルを文字列として処理」に対して一貫して反対してきました。たとえば下記の記事です。そのため、大垣さんの記事は私への反論なのでしょう。

 1番目のエントリで私は、MySQLで文字列リテラルを数値に「暗黙の型変換」すると、結果は浮動小数点数になり、予期しない動作となる例を紹介しています。厳密に言うと、この動作と、大垣さんの指摘された「SQL文を作成する場合にリテラル(パラメータ)を文字列としてエスケープすると浮動小数点型のデータが正しく処理されないデータベースがあります」は、内容は異なります。私の主張は「浮動小数点数でないはずの数値まで浮動小数点数として扱われる」ことが問題だとしているからです。しかし、今は、細かい表現上の違いは気にしないことにしましょう。
 以下、大垣さんの主張の要点である「(数値も含めて)すべてのリテラルを文字列として処理」することの是非について、「何が正しいのか?」という視点から検討します。

議論の前提:数値を文字列リテラルとして扱うと「暗黙の型変換」が問題に

 大垣さんの主張、すなわち「(数値も含めて)すべてのリテラルを文字列として処理」すると、必然的に「暗黙の型変換」が出てきます。以下のSQL文を用いて説明します。

SELECT * FROM employee WHERE age = '30'

この例では、年齢を示す30を文字列リテラルとして与えています。数値列ageとの比較において、文字列'30'は「暗黙に」数値 30 に変換されることを期待したSQLです。私は、この「暗黙の型変換」が問題だと考えます。

正しいことの判断基準は何か

 ここで、「何が正しいのか」を検討するにあたり、「正しいとは何か」ということを考えてみたいと思います。正しいか、正しくないかを論じるためには、判断基準が必要です。
 現在議論しているのは、「SQL文を作成する場合」の話ですから、大前提として作成されたSQL文が正しくなければならないはずです。それでは、SQL文が正しいとはどういう状態でしょうか。
 これも色々な条件があるはずですが、正しいSQL文の基本条件の一つとして、SQL規格や実際に使うSQL処理系の文法に従っていることが必要と考えます。たとえば、PostgreSQLを使う場合は、以下が必要です。

  • 原則としてISO/JISの規格に従ったSQL文であること
  • その上で、使用するSQL処理系(この場合はPostgreSQL)の方言や制限にあわせるための調整をしていること

 この基準に従い、暗黙の型変換を伴うSQL文の「正しさ」について検討しましょう。

ISO/JIS規格のSQLでは、文字列から数値への暗黙の型変換は定義されていない

 ISO/JIS規格のSQLでは、文字列から数値への「暗黙の型変換」はどのように規定されているでしょうか。
 実は「規定されていない」が正解です。それをISO SQLのドキュメントから追ってみましょう。
 ISOのSQL規格には、SQL製品の規格準拠度を調べるためのチェックリストがあります。Annex F「SQL feature and package taxonomy」がそれです。PostgreSQLOracleなどSQL製品は、このリストへの準拠をチェックリストの形で公開しています。まず、以下は、Annex FのE011-06「数値データ型間の暗黙の型キャスト」の引用です。

 ISOの標準をお持ちの方は少ないと思いますが、幸いなことに、Annex Fは各SQL製品のリファレンスマニュアルに引用されています。以下に、PostgreSQL9.1の該当箇所を紹介します。

http://www.postgresql.jp/document/9.1/html/features-sql-standard.html

これを読むと、暗黙の型変換については以下の記述が見つかります。

  • E011-06 コア 数値データ型間の暗黙キャスト
  • E021-10 コア 文字列型間の暗黙的キャスト

 数値型同士、文字列型同士の「暗黙の型変換」については、ISO規格の要求があることが分かります。しかし、文字列型から数値データへの暗黙の型変換については、記載がありません。このことから、ISO SQLでは、文字列から数値への暗黙の型変換は規定されていないと考えられます。

製品の実装はどうか

 現実には多くのSQL製品で、文字列から数値への「暗黙の型変換」は実装されています。しかし、規格にない動作をどのような仕様で実装しているのでしょうか。私の調べた範囲では、以下の実装があります。

  • 文字列から数値への「暗黙の型変換」は許可せず、エラーにする(DB2 9.5まで)
  • 文字列から数値への「暗黙の型変換」の結果は浮動小数点数になる(MySQL)
  • 文字列から数値への「暗黙の型変換」は比較相手の型を見て「良きに計らう」(Oracle等)

 詳しくは「SQLの暗黙の型変換はワナがいっぱい | 徳丸浩の日記」を参照ください。
 このように、文字列から数値への「暗黙の型変換」は、多くの処理系で規格への拡張機能として提供されていますが、その仕様はバラバラです。規格に規定されていない機能を実装したことによる当然の結果と言えます。

「何が正しいのか」を考えるには基準が必要

 このように、文字列から数値への暗黙の型変換は規格による規定がないために、実装により仕様がまちまちです。極端な場合、同一製品でもバージョンにより挙動が変わる場合があります。DB2は前述のようにVer9.5までは文字列から数値への暗黙の型変換をエラーにしていましたが、Ver9.7にて、Oracleに近い仕様になりました。おそらくIBMは、SQLの本家としてのプライドを捨て、SQLの王者であるOracleの仕様にすりよったのでしょう。国際標準に規定されていないと言うことは、そのような大胆な仕様変更もあり得るということです。
 このように「暗黙の型変換」は規格化されていないことにより仕様が非常に不安定です。ある場合はエラーとなり、ある場合は浮動小数点数になるが故に不可解な動作をします。こういう不安定なものを選択することが、果たして「何が正しいのか」を考えた結果でしょうか。
 私はそうは考えません。正しい処理方法を考える上では、守るべき基準として、ISO/JIS/ANSI/ECMAの規格、RFCなどがあり、その上でプログラミング上の良い習慣などに従うべきだと考えます。
 このような根拠から、私は、「(数値も含めて)すべてのリテラルを文字列として処理」することは間違いだと考えます。たまたま特定の処理系(例えばPostgreSQL)でうまく動くからと言って、普遍的な真理であるかのように説明することはおかしい。『「何が正しいのか?」常に考えるようにしてください』という言葉は、そっくりそのまま大垣さんにお返しします。