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の豊富(だったと思う)な機能を全然活用していなかったり、とあまり良いコードではないが、開発時(特に初期)に使うことしか想定していないので問題無い。