ALGOBIT

2011/09/06

clojure から DBMS を利用する

Filed under: 離散的な気まぐれ — タグ: , , , , , , , — Kohyama @ 14:11

準備

アクセスしたい DBMS 製品の JDBC ドライバを取得し,
JRE が標準でサーチする外部クラスライブラリの配置場所に加えます.

Oracle の場合は, このへん (Oracle 10g の場合) から,
MySQL の場合は, このへん から,
PostgreSQL の場合は, このへん から,
JRE (もしくは JDK) のバージョンおよび, DBMS 製品のバージョンに適合するバージョンのJDBCドライバファイル (大抵は zip か jar でアーカイブされた単一のファイル) を入手します.

Windows の場合は, C:\Program Files\Java\jre6\lib\ext (JRE 1.6 の場合) あたり,
Linux の場合は, /usr/lib/jvm/java-6-sun/jre/lib/ext/ (Ubuntu の場合) あたり,
MacOS X の場合は, /Library/Java/Extensions/ あたり,
に上記 zip または jar ファイルとして入手した JDBC ドライバをコピーします.

もちろん, DBMS がローカルにインストールされており, JDBCドライバが既に存在するのでコピーしたくないとか, 現在のプロジェクトを管理するフォルダ階層の下に置いておきたい,
などの理由で標準サーチパスに置きたくない場合は, クラスパスにドライバファイルへの絶対パスもしくは相対パスを追加しても構いません.

最小限

データベースアクセスに必要な最低限の記述を部品化せず全て展開した状態で書くと
以下のようになります.

(use 'clojure.contrib.sql)
(with-connection {:classname "oracle.jdbc.OracleDriver"
                  :subprotocol "oracle"
                  :subname "thin:@${HOST}:${PORT}:${SID}"
                  :user "${USER}"
                  :password "${PASSWORD}"}
  ; ここでの記述はデータベース ${HOST}, ${PORT}, ${SID}
  ; に対するトランザクションの中で実行される.
  (with-query-results rs [${QUERY}]
    ; ここでの記述はデータベースに対する ${QUERY} の実行結果
    ; を rs にシーケンスとして束縛した状態で行われる
    ))

${HOST}, ${PORT}, ${SID} にアクセスしたいデータベースに関する情報,
${USER}, ${PASSWORD} にそのデータベースアクセスするアカウントの情報,
${QUERY} には発行したい SQL 文,
でそれぞれを直接置き換えてください.

MySQL の場合は

    :classname “com.mysql.jdbc.Driver”
    :subprotocol “mysql”
    :subname “//${HOST}:${PORT}/${SID}”

とします.

PostgreSQL の場合は

    :classname "org.postgresql.Driver"
    :subprotocol "postgresql"
    :subname "//${HOST}:${PORT}/${SID}"

とします.

結果セットに対して, トランザクション内で作業するのではなく,
ベクタとして取り出してしまいたい場合は,
with-query-results の本体で (into [] rs) とすれば OK.

例えば

IP アドレス 192.168.1.1 で稼働する, ポート 1521 でアクセス可能な Oracle に,
パスワード PaSsWoRd の kohyama というユーザがあり,
kohyamadb というインスタンスへの select 権限があるとします.
JRE 1.6 (ともちろん clojure) のインストールされた Windows から, kohyamadb のテーブル一覧を見るには
上記サイトからダウンロードした classes12.jar を
C:\Program Files\Java\jre6\lib\ext\ にコピーし

(use 'clojure.contrib.sql)
(with-connection {:classname "oracle.jdbc.OracleDriver"
                  :subprotocol "oracle"
                  :subname "thin:@192.168.1.1:1521:kohyamadb"
                  :user "kohyama"
                  :password "PaSsWoRd"}
  (with-query-results rs ["select * from all_tables"]
    (into [] rs)))

とします.
簡単過ぎる...

部品化例

私の場合はこんな風にして使いました.

(use 'clojure.contrib.sql)
(defn db [& {:keys [dbms host port sid user password]}]
  (conj
    (cond
      (= dbms "oracle")
        {:classname "oracle.jdbc.OracleDriver"
         :subprotocol "oracle"
         :subname (str "thin:@" host ":" port ":" sid)}
      (= dbms "mysql")
        {:classname "com.mysql.jdbc.Driver"
         :subprotocol "mysql"
         :subname (str "//" host ":" port "/" sid)}
      (= dbms "postgresql")
        {:classname "org.postgresql.Driver"
         :subprotocol "postgresql"
         :subname (str "//" host ":" port "/" sid)})
    {:user user
     :password password}))

(def con
  (db :dbms "oracle" :host "192.168.1.1" :port 1521 :sid "kohyamadb"
    :user "kohyama" :password "PaSsWoRd"))

(def all_tables
  (with-connection con
    (with-query-results rs ["select * from all_tables"]
      (into [] rs))))

(print all_tables)

関数 db は, DBMS 製品の名称, ホスト名, ポート, インスタンス名, ユーザ名,
パスワードをキーワード引数で渡すと,
with-connection に渡すべきマップを作成します.
19〜21行目で特定のDBに対する特定のアカウントでの接続用のマップを作成します.
23〜26 行目でトランザクションを実行し結果を取り出します.

2011/04/07

scheme で TCP エコーサーバ

Filed under: 離散的な気まぐれ — タグ: , , , , — Kohyama @ 13:01

scheme で TCP のサーバを書く際の雛形としてエコーサーバとしてコードをまとめてみた.
ほとんど処理系依存, UNIX系OS + Gauche じゃないと動きません.
daemon のところは Gauche クックブック / デーモンプロセスを作成する をほぼそのまま使わせていただきました.
Gauche ユーザリファレンスC と Scheme の関数の対応 あたりを激しく参照しました.
chmod +x して実行すると, 制御端末を切り離し, 1111 番ポートの TCP 接続を待ちます.
telnet localhost 1111 などで接続すると, 改行の入力を待って, 改行までに入力されたテキストに改行を付けてそのまま返します.
止めるときは ps などで親の(一番若い) pid を調べて kill します.

#!/usr/bin/env gosh
(use gauche.net)
(set-signal-handler! SIGCHLD (lambda _
                               (guard (e ((<system-error> e)))
                                      (while (< 0 (sys-waitpid -1))))))
(set-signal-handler! SIGTERM (lambda _ (sys-kill 0 SIGTERM)))

(define (daemon)
  (when (positive? (sys-fork))
        (sys-exit 0))
  (sys-setsid)
  (sys-chdir "/")
  (sys-umask 0)
  (call-with-input-file "/dev/null"
                        (cut port-fd-dup! (standard-input-port) <> ))
  (call-with-output-file "/dev/null"
                         (lambda (out)
                           (port-fd-dup! (standard-output-port) out)
                           (port-fd-dup! (standard-error-port) out))))

(define (prog cs)
  (let ((iport (socket-input-port cs))
        (oport (socket-output-port cs)))
    (let loop ((line (read-line iport)))
      (cond
        ((eof-object? line) (socket-close cs))
        (else
          (display (string-append line "\r\n") oport)
          (loop (read-line iport)))))))

(define (main args)
  (daemon)
  (let1 s (make-server-socket 'inet 1111 :reuse-addr? #t)
        (let loop ((cs (socket-accept s)))
          (let ((pid (sys-fork)))
            (cond
              ((= pid 0)	; child
			   (set-signal-handler! SIGTERM #t) ; reset
               (prog cs)
               (sys-exit 0))
              (else			; parent
                (socket-close cs)
                (loop (socket-accept s))))))))

以下はほぼ等価な C のプログラムです.
というか, 過去に C で書いた TCP サーバから最低限のコードをエコーサーバとして下のように整理し, これを Gauche に置き換えたのが上のプログラムです.
UNIX 系の OS でないと動きません.
syslog は省いてます.

#include <sys/types.h>	/* read */
#include <sys/socket.h>	/* accept, bind, getsockname, listen, socket */
#include <sys/uio.h>	/* read */
#include <sys/wait.h>	/* waitpid */
#include <arpa/inet.h>	/* htons */
#include <errno.h>		/* errno */
#include <fcntl.h>		/* open */
#include <signal.h>		/* kill, sigaction */
#include <stdio.h>		/* BUFSIZ */
#include <stdlib.h>		/* daemon, exit */
#include <unistd.h>		/* _exit, close, dup, fork, read, write */

static void
reapchild(int signo)
{
	while (waitpid(-1, NULL, WNOHANG) > 0)
		;
}

static void
parent_exit(int signo)
{
	kill(0, SIGTERM);
	exit(0);
}

static int
prog(int s)
{
	int n;
	char buf[BUFSIZ];

	while (0 < (n = read(s, buf, BUFSIZ)))
		write(s, buf, n);
}

int
main(int argc, char *argv[])
{
	int addrlen, cs, pid, s;
	struct sockaddr_in addr;
	int port = 1111;
	struct sigaction sa;

	daemon(1, 1);
	chdir("/");
	close(0);
	close(1);
	close(2);
	open("/dev/null", O_RDWR);
	dup(0);
	dup(0);

	sa.sa_handler = reapchild;
	sigaction(SIGCHLD, &sa, NULL);
	sa.sa_handler = parent_exit;
	sigaction(SIGTERM, &sa, NULL);

	if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)
		exit(1);

	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = INADDR_ANY;
	addr.sin_port = htons((unsigned short)port);
	addrlen = sizeof(addr);
	if (bind(s, (struct sockaddr *)&addr, addrlen) < 0)
		exit(1);

	if (getsockname(s, (struct sockaddr *)&addr, &addrlen))
		exit(1);

	listen(s, 5);
	for (; ; ) {
		if ((cs = accept(s, (struct sockaddr *)&addr, &addrlen)) < 0) {
			if (errno == EINTR)
				continue;
			exit(1);
		}
		switch (fork()) {
		case -1:
			exit(1);
		case 0:
			sa.sa_handler = SIG_DFL;
			sigaction(SIGTERM, &sa, NULL);
			close(s);
			prog(cs);
			_exit(0);
		}
		close(cs);
	}
}

Kahuaプロジェクト
オライリージャパン
発売日:2008-03-14

デビッド・A. クリ
アスキー
発売日:1991-07

実際に役に立つプログラムは直接なり間接なり OS と対話しなければならない.
とくに UNIX オペレーティングシステムを記述するために生まれた C 言語は, より上位のプログラミング環境の下位で OS と直接対話する基盤である.
システムコールプログラミングへようこそ.

Copyright © 2010 Yoshinori Kohyama All Rights Reserved.