GaucheでRDBプログラミング:Gaucheでメタプログラミング(3)(3/5 ページ)
Lispの一種であるScheme。いくつかある処理系の中でも気軽にスクリプトを書けるGaucheでLispの世界を体験してみよう(編集部)
GaucheのRDBプログラミング
GaucheでRDBを扱うプログラムを書くために、GaucheのDBI関数をEmacsのSchemeモードまたはgosh-rlを使い少しずつ試していきましょう。
1. 準備
DBIモジュールであるdbi、コレクションフレームワークであるgauche.collectionモジュールをロードします。
gosh> (use dbi) #<undef> gosh> (use gauche.collection) #<undef>
2. DBへの接続
まずは、JDBCやPerlのDBI同様にDBに接続します。ここではSQLite3を使うので、以下のようにSQLite3のDBファイルのパス名を指定しています。パス名は適宜変更してください。
gosh> (define conn (dbi-connect "dbi:sqlite3:/Users/yy/GaucheWork/alih.db")) conn
defineは前回も出てきましたが、関数定義のほかに広域変数の定義にも使います。変数connの値はRDBへの接続クラスのインスタンスです。
3. select文の実行
select文を実行してplayersテーブルの内容を取得してみましょう。dbi-doはシンプルなSQL文を実行する関数です。引数には、DBに接続、SQL文を渡します。関数値はselect文を実行した場合、結果セットが戻ります。
gosh> (define res (dbi-do conn "SELECT no, name, g FROM players ORDER BY g DESC")) res
ところで、このresとは何なのでしょうか。そこでresをdescribe関数で見ると<sqlite3-result>クラスのインスタンスだと分かります。また、スロットにはselectした結果のカラム名やデータが入っています。
gosh> (d res) #<<sqlite3-result> 0x71ff10> is an instance of class <sqlite3-result> slots: columns : #("no" "name" "g") rows : (#(18 "SUZUKI,Takahito" 13) #(16 "OBARA,Daisuke" 10) #(9 ....
ただし、このスロットの値はDBDのドライバに依存した値です。ほかのドライバではまったく違う構造をしていますので、直接アクセスするのは好ましくありません。ちなみに、MySQLではresは以下のようになっています。
gosh> (d res) #<<mysql-result-set> 0x2e16f98> is an instance of class <mysql-result-set> slots: %handle : #<<mysql-handle> 0x481100> %statement: #<<mysql-stmt> 0x2e0e5d0> %row-count: 25 %current-rowid: 0
4. 結果セットの表示
それでは、結果セットを表示してみましょう。DBIのユーザーリファレンスにあるようにselect文の結果はコレクションですので、for-eachやmapのようなイテレータを使って値を取得します。
また、結果セットはリレーション(<relation>)でもありますので、リレーションフレームワーク(util.relation)の関数を使いリレーションを操作するgetter/setterなどを取得できます。
gosh> (define getter (relation-accessor res)) getter gosh> (for-each (lambda(row) (format #t "~3d ~20a ~3d\n" (getter row "no")(getter row "name") (getter row "g"))) res) 18 SUZUKI,Takahito 13 16 OBARA,Daisuke 10 9 SATO,Sho 9 10 KAMINO,Toru 9 (中略) 6 YAMADA,Yuya 0 13 KASHINO,Yoshikazu 0
ここで新しく出てきたformat関数は、C言語のprintf関数のようなものです、詳細はユーザーリファレンスを参照してください。
これで、RDBを扱うための関数の説明は終わりにしますが、実用的なコードを書く場合には、RDB関連で例外が起きた場合の処理や、SQL文にパラメターを渡せかつSQLインジェクション対策になるdbi-prepare関数などを使う必要性があると思います。
ORマッパーを作る
最近のRDBプログラミングでは、JDBCのようなSQLを書き、直接データベースとやりとりするようなスタイルは主流ではなくなっています。
そのようなやり方に代わって、プログラム内のオブジェクトとRDBの対応を半自動的に行ってくれるHibernateのようなORマッパーが使われるようになってきています。
そこで、今回は簡単なORマッパーを作ってみましょう。
Gaucheのクラスの定義、使い方
その前に、Gaucheのオブジェクトシステムの続きを説明します。Gaucheのオブジェクトシステムは、CLOS(Common Lisp Object System)に似たオブジェクトシステムです。JavaやRubyなどのメジャーな言語のオブジェクトシステムとはかなり違っています。
まず、define-classでクラスを定義しますが、スロット(Javaでいうところのインスタンス変数)の定義と継承関係のみ行い、メソッドはクラスとは独立した総称関数(ジェネリック関数)として定義します。メソッドはクラスには所属していないのです。
(define-class <body> () ((height :init-keyword :height :accessor height-of) (weight :init-keyword :weight :accessor weight-of))) (define-method bmi ((body <body>)) (/. (weight-of body) (expt (/. (height-of body) 100) 2)))
メソッドの定義はdefine-methodで行います。一般の関数の定義とは違い、引数に仮引数とクラス名を指定し、クラスとの対応付けを行います。メソッドの場合、同じ関数名でも引数のクラスが違えば別の関数として定義できるのでポリモーフィズムを実現できます。また、同じ関数名のメソッドの集まりを総称関数(ジェネリック関数)と呼びます。
インスタンスの生成にはmakeメソッドを使います。makeの際にinit-keywordで指定したキーワード名で初期値を代入できます。また、インスタンス変数の参照はslot-ref、代入はslot-set!関数を使いますが、汎用のrefメソッドで参照したりset!で代入できたりします。
set!は、代入先を参照用の関数で指定して代入が行えるので、代入用関数名を覚える必要ありません。そのため、Lispではインスタンス変数以外、ベクターやハッシュの代入などで広く使われています。
また、クラス定義でaccessorを定義した場合は、その名前のメソッドで参照、代入が行えます。
gosh> (define yamada (make <body> :height 170 :weight 60)) yamada gosh> (slot-ref yamada 'weight) 60 gosh> (slot-set! yamada 'weight 62) #<undef> gosh> (slot-ref yamada 'weight) 62 gosh> (ref yamada 'weight) 62 gosh> (set! (ref yamada 'weight) 63) #<undef> gosh> (weight-of yamada) 63 gosh> (set! (weight-of yamada) 66) #<undef> gosh> (bmi yamada) 22.837370242214536
【編集部より】
上記の実行結果の一部に間違いがありましたので、訂正しました(2009/11/25)
誤:gosh> (ref yamada 'weight)
60
正:gosh> (ref yamada 'weight)
62
GaucheのオブジェクトシステムはSmalltalkなどと同様にクラスはメタクラスのインスタンスなので、インスタンスの生成やクラスの振る舞いを規定するようなメソッドをメタクラスに定義することで変更できるようになっています。
また、Javaのクラスメソッド相当のメソッドはメタクラスを引数で取るメソッドを定義することで作成できます。
Copyright © ITmedia, Inc. All Rights Reserved.