sql用リードマクロ-簡易クエリ

あまり好きなわけではないのだが、諸々の事情によりsql(特にmysql)を使わなければならないことが結構あるので、開発補助用のマクロを定義しておく。

(require :clsql)

;; DBに接続(DBの各種情報はハードコーディング)
(defmacro with-db (&body body)
  `(clsql:with-database (clsql:*default-database*
                         '("host" "database" "user" "password")
                         :database-type :mysql)
     (clsql:execute-command "SET names utf8")
     ,@body))

;; クエリを一回実行する
(defun one-query (str)
  (with-db
   (handler-case (clsql:query str)
     ;; デバッガに入る必要はないので、デフォルトのハンドラを設定
     (error (c) (format *error-output* "~&### ERROR ###~%~A" c)))))

;; sqlのクエリをstreamから読み込む
;; 文字列の読み込みの部分は(エスケープ処理が)若干ややこしいが、その他は終端(#\;)チェックをしているだけ
(defun read-sql-line (stream)
  (let ((buf (make-array 32 :fill-pointer 0 
                            :adjustable t 
                            :element-type 'character)))
    (labels
     ((push-back (ch) 
        (vector-push-extend ch buf))
             
      (read-quote (quote)
        (loop for ch = #1=(read-char stream) do
          (push-back ch)
          (cond ((char= ch #\\)
                 (push-back #1#))
                ((char= ch quote)
                 (if (char= (peek-char nil stream) quote)
                     (push-back #1#)
                   (return)))))) 

      (readline ()
        (loop for ch = #1# do
          (push-back ch)
          (case ch
            ((#\;)     (return (copy-seq buf)))
            ((#\' #\") (read-quote ch))))))

     (readline))))

;; read-macroを設定
(set-macro-character #\@
  (lambda (stream ig)
    (declare (ignore ig))
    `(one-query ,(if (alpha-char-p (peek-char nil stream))
                     (read-sql-line stream)
                   (read stream)))))

これでlisp上から、簡単にDBにクエリを発行できるようになる。


例:

;; 一文字目がアルファベットで始まる場合
;; 最初の(文字列の一部ではない)#\;までを、クエリとして実行する
> @select * from table;

;; それ以外の場合は、通常のread関数を呼び出して、返ってきた結果(文字列)を投げる
> @"select * from table"

> @(format nil "select * from ~A" "table")

接続先などをハードコーディングしていたり、クエリごとに毎回接続しなおしていたり、clsqlの豊富(だったと思う)な機能を全然活用していなかったり、とあまり良いコードではないが、開発時(特に初期)に使うことしか想定していないので問題無い。