あまり詳しく調査はしていないが、clsqlでもMySQLでのPrepared Statementはそれほどうまく動作しないらしい。それで、cl-mysqlでもできないか調べてみた。

Prepared Statementには2種類の実装方法がある。動的、静的とそれぞれ呼ばれている。最近のMySQLは静的Prepared Statementを実装している。ここを参照。それでPrepared StatementでSQLを実行し、結果を返す関数は次のように実装できた。


(defun with-prepared-statement (st &rest arg)
  (cl-mysql:query (format nil "PREPARE stmt1 FROM '~a';" st))
  (let ((syms (loop for x from 1 to (length arg)
for sym = (gensym)
collect sym)))
    (loop for param in syms
for i from 0
       for set = (format nil "SET @~a = ~s;" param (nth i arg))
       do (cl-mysql:query set))
    (let ((result
  (cl-mysql:query (format nil "EXECUTE stmt1 USING ~{@~a~^, ~};" syms))))
      (cl-mysql:query "DEALLOCATE PREPARE stmt1;")
      result)))

あまりloop, formatの使い方に慣れていないので、とりあえず動いたというレベル。PREPARE, SET, EXECUTE, DEALLOCATEを順番に実行し、EXECUTEの結果を返す。

上記で確かにPrepared Statementとして動作はするのだが、自分の希望としては、これによってSQLインジェクション対策をしたかった。しかしながら、これでは完全な対策にはならなさそうである。これは単にPrepared Statementであって、何度も同じクエリをパラメタを変えて実行するには使えるが、パラメタ部分のサニタイズはされない。例えば、SET @hoge=(パラメタ)としているところで、"1; delete from tbl;"と入力されてしまうと、SETの次の文でtblからデータが消されてしまう。これを防ぐには、SETで文が意図せず終了されないように気をつけないといけない。

Comments