SQL Scripting

awk4j (AWK for Java Platform)

SQL スクリプティング

awk4j SQL スクリプティングは、データベースの操作や定義を AWKスクリプトから実行するための拡張機能で、 ファイル名に データベース URL を指定することにより、 一般ファイルと同じインターフェイスで SQL文による RDBMS へのアクセスが可能。

入出力命令

特殊ファイル名

"jdbc:SUBPROTOCOL:SUBNAME [ ドライバパラメータ ... ] [ ;; SQL文 ]"
jdbc
この URL が データベース URL であることを表すスキーム名 (固定値)
SUBPROTOCOL
サブプロトコル (JDBCドライバを識別する文字列)
SUBNAME
接続先のホスト名、データベース名称 など ドライバ固有のパラメータ
;; SQL文
DB接続時に実行する SQL文 (オプション)

入出力命令

print expression ... |& URL
SQL文 を URL で指定した RDBMS で実行する ( |& は双方向パイプ指定)
エラー発生時は戻り値に、 0 (FALSE) および 組み込み変数 ERRNO を返す

printf format [ , expression ... ] |& URL
フォーマット変換機能以外は print文と同じ

getline [ var ] |& URL
SQLの実行結果を var (省略時は $0) に読み込む
読み込みデータは、欄 $1 から $NF に格納され、 $0に は 欄を FS(入力フィールドセパレータ) で結合した値を設定する
FSが ' '(空白)の場合は、 FSに '\t' を設定して TAB区切りとする
組込み変数 RS (入力レコードセパレータ) および RT (RS にマッチし入力文字列) は (無意味なため) 使用しない

close( URL )
RDBMS との接続を閉じる

Note: URL に双方向パイプ '|&' でアクセスすること以外、文法的に目新しい機能はない。

SQLの実行

SQL文を、 print[f] で データベース URL にリダイレクトすることにより SQL文を実行する。 実行結果は print[f] の復帰コ-ドおよび getline により取得する。

表の削除

make jdbc0

 ${AWK4J} -e ' \
    BEGIN { url = "jdbc:hsqldb:hsql://localhost/sampledb"; \
        if (!print "DROP TABLE COUNTRIES" |& url) \
            print ERRNO; \
        close(url); \
    }'

表の作成

make jdbc1

 ${AWK4J} -f JdbcCapitals.awk capitals

# JdbcCapitals.awk
BEGIN { FS = "\t"
    url = "jdbc:hsqldb:hsql://localhost/sampledb"
    print "CREATE TABLE COUNTRIES(" \
        "COUNTRY VARCHAR(30) NOT NULL, AREA INTEGER, POPULATION INTEGER, CONTINENT VARCHAR(30), " \
        "PRIMARY KEY ( COUNTRY ) )" |& url    # 表を作成
}
    {
    sql = "INSERT INTO COUNTRIES VALUES('" $1 "'," $2 "," $3 ",'" $4 "')"
    if (print sql |& url)    # 標準入力から読み込んだデータを表に追加
        print FNR, sql
}

表の問い合わせ

make jdbc2

 ${AWK4J} -e ' \
    BEGIN { url = "jdbc:hsqldb:hsql://localhost/sampledb"; \
        print "SELECT * FROM COUNTRIES ORDER BY COUNTRY" |& url; \
        while (0 < (url |& getline)) \
            print; \
    }'

I'll be back (帰ってきた一行野郎)

URL に SQL文を埋め込む

ファイル名を "URL;;SQL文" とすること により、指定した SQLの実行結果を標準入力ストリームから読み込むことが可能になる。

Note: 下記. -e ”1” の意味は、"世界最短 1文字スクリプトでホームページにアクセス" さ あ始めよう を参照。

make jdbc3

 ${AWK4J} -e '1' 'jdbc:hsqldb:hsql://localhost/sampledb;;SELECT * FROM COUNTRIES ORDER BY COUNTRY'

Note: SQLの実行結果より、 "Canada" から "Japan" にマッチするデータを抽出する。

 ${AWK4J} -e '/Canada/,/Japan/' 'jdbc:hsqldb:hsql://localhost/sampledb;;SELECT * FROM COUNTRIES ORDER BY COUNTRY'

これはどうか

"プログラミング言語AWK"(The AWK Programming Language) 4.3 関係データベース・システム AWKもどきの問い合わせ言語q (page.139) を、 SQLに対応させ、カラム属性の別名に日本語指定を可能とした例を下記に示す。 この問合せ言語は、AWK言語の欄指定 '$1 ... $NF' と '$属性' 形式で表のカラム指定を受け付けるため、AWK構文による SQLを意識しない 表への問合せが可能である。

make jdbcq

 ${AWK4J} -f Q.awk 'jdbc:hsqldb:hsql://localhost/sampledb' -

$ # アジア大陸の国名と人口を表示
$continent ~ /Asia/ { print $country, $population }

$ # 国の平均面積を表示 (組込み変数 NRは読み込みデータ件数)
$ { area += $area } END { print area/NR }

$ # カナダから日本にマッチする国のデータを表示
$ /Canada/,/Japan/ {
$   print $国, $大陸, $首都
$ }

 問合せのための表定義ファイル (辞書)
# relfile: 問合せ命令(SQL)と列名(属性の別名)を定義
# #   注釈
# TABLE識別 :
#     属性(英字で始まる英数字) [ 別名(英字で始まる英数字または日本語) ] ...
#     ! データ抽出の前に実行する shell コマンド
#     ? データを抽出する SQL文
countries:
    country    国    ...
    area       面積
    population 人口
    continent  大陸

    ?SELECT * FROM COUNTRIES ORDER BY COUNTRY
 ...

# 4.3 関係データベース・システム: AWKもどきの問い合わせ言語q: page.139
BEGIN { url = ARGV[1]; ARGV[1] = ""
    readrel("relfile")    # 表定義ファイルの読み込み
    RS = ""        # 空行までを一つの問合せスクリプトとする
}
/./ { doquery($0) }

function readrel(f  ,i) {
    while (getline <f > 0) {            # parse relfile
        if ($0 ~ /^[ \t]*#/) {          # comment
        } else if ($0 ~ /^[A-Za-z]+ *:/) { # name:
            gsub(/[^A-Za-z]+/, "", $0)  # remove all but name
            relname[++nrel] = $0
        } else if ($0 ~ /^[ \t]*!/) {   # !command...
            cmd[nrel, ++ncmd[nrel]] = substr($0,index($0,"!")+1)
        } else if ($0 ~ /^[ \t]*\?/) {  # ?query SQL...
            $0 = substr($0,index($0,"?")+1)
            sub(/^[ \t]+/, " ")
            querySql[nrel] = querySql[nrel] $0
        } else if ($0 ~ /^[ \t]*[A-Za-z][A-Za-z0-9]*[ \t]*/) {
            attr[nrel, $1] = ++nattr[nrel]  # 属性(英字で始まる英数字)
            for (i = 2; i <= NF; i++)
                if ("" != $(i))    # 属性の別名(英字で始まる英数字または日本語)
                    attr[nrel, $(i)] = nattr[nrel]

        } else if ($0 !~ /^[ \t]*$/) {    # not white space
            print "bad line in relfile:", $0
        }
    }
}
function doquery(s,  q, i, j) {
    q = s
    for (i in qattr)    # clean up for next query
        delete qattr[i]
    query = s           # put $names in query into qattr, without $

    # 属性(英字で始まる英数字または日本語、プラットフォーム非依存とするため Unicodeで指定 u3041:ぁ fa2d:鶴)
    while (match(s, /\$[A-Za-z\u3041-\ufa2d][A-Za-z0-9\u3041-\ufa2d]*/)) {
        qattr[substr(s, RSTART+1, RLENGTH-1)] = 1
        s = substr(s, RSTART+RLENGTH+1)
    }
    for (i = 1; i <= nrel && !subset(qattr, attr, i); )
        i++
    if (i > nrel)        # didn't find a table with all attributes
        missing(qattr)
    else {               # table i contains attributes in query
        for (j in qattr) # create awk program
            gsub("\\$" j, "$" attr[i,j], query)
        for (j = 1; j <= ncmd[i]; j++)  # create table i
            if (system(cmd[i, j]) != 0) {
                print "command failed, query skipped\n", cmd[i,j]
                return
            }
        awkcmd = sprintf("%s -e '%s' '\"\"' -F='\\t' -vOFS='\\t' '%s;;%s'",
            ENVIRON["AWK4SQL"], query, url, querySql[i]
)
        system(awkcmd)    # AWK4J で問合せ
    }
}
function subset(q, a, r,  i) {    # is q a subset of a[r]?
    for (i in q)
        if (!((r,i) in a))
            return 0
        return 1
}
function missing(x,  i) {
    print "no table contains all of the following attributes:"
    for (i in x)
        print i
}

Note: このスクリプトそのものには特に目新しい機能はない。 問合せのために system関数で起動動された awk4j がファイル名として指定された URLで RDBMSにアクセスするところがミソ。

RDBMS アクセス環境の設定

サンプル (sample/make) は、規定値として、 HSQLDB を使用するように設定している、 HSQLDB を使用する場合は、 http://hsqldb.org/ からダウンロードするか、 OpenOffice を使用しているなら、 "${OpenOffice}/program/classes/hsqldb.jar" または "/user/share/java/hsqldb-XXX.jar" を ${awk4j}/ext にコピーする。 (環境設定は不要)

SQLite を使用する場合は、 Pure Java版の JDBC Driver付き SQLite をダ ウンロードし、 "sqlitejdbc-XX-nested.jar" を ${awk4j}/ext にコピーする。 (環境設定は不要)

« フリーで入手可能な RDBMS一覧 »
RDBMS データベース URL (例) jar (lib に配置)
Apache Derby jdbc:derby:sampledb;create=true (組込みモード)
jdbc:derby://localhost/sampledb
derby.jar derbyclient.jar derbynet.jar derbytools.jar
HSQLDB jdbc:hsqldb:file:rdb/hsqldb/sampledb;shutdown=true (組込みモード)
jdbc:hsqldb:hsql://localhost/sampledb
hsqldb.jar
H2 jdbc:h2:file:rdb/h2/sampledb (組込みモード)
jdbc:h2:tcp://localhost/sampledb
h2.jar
MySQL jdbc:mysql://localhost/sampledb mysql-connector-java-XX-bin.jar
PostgreSQL jdbc:postgresql://localhost/sampledb postgresql-XX.jdbc3.jar
SQLite jdbc:sqlite:rdb/sqlite/sampledbe sqlitejdbc-XX-nested.jar
SQLite jdbc:sqlite:[${HOME}/databases/]filename;appId awk4j Android サポートに含む

awk4j 実行環境の設定 - awk4j.properties

lib/properties のパラメータをを利用する RDBMS 環境に合わせて設定する。

# jdbc データベース URLを "jdbc:?:path" で指定した場合の "?" サブプロトコルの規定値 (オプション)
 org.awk4j.io.JdbcSocket.subprotocol=sqlite

RDBMS 固有環境の設定 - subprotocol.properties

利用する RDBMS の接続パラメータを指定する。

# jdbc ドライバのクラス名 (必須)
 jdbc.driver=org.hsqldb.jdbcDriver

# jdbc データベース URLを "jdbc:subprotocol:?" とした場合の "?" の規定値 (オプション)
 jdbc.path=file:../sample/rdb/hsqldb/sampledb;shutdown=true

# username, password この行以降はドライバで解釈されるため、環境変数での指定は不可
 user=ユーザ名
 passwd=パスワード

# RDBMS 固有の接続パラメータ

Note: 上記の規定値の参照指定 "?" の利用により、 URLを "jdbc:?:?" とすることにより、スクリプトは RDBMS 非依存となる。 また、これらの規定値は環境変数で指定することも可能。
 ex. export org.awk4j.io.JdbcSocket.subprotocol=hsqldb
      export jdbc.path=file:../sample/rdb/hsqldb/sampledb;shutdown=true

RDB 関連リンク


SQL Scripting
  1. 入出力命令
    1. 特殊ファイル名
    2. 入出力命令
  2. SQLの実行
    1. 表の削除
    2. 表の作成
    3. 表の問い合わせ
  3. I'll be back (帰ってきた一行野郎)
    1. URL に SQL文を埋め込む
  4. これはどうか
    1. AWKもどきの問い合わせ言語 Q.awk
  5. RDB アクセス環境の設定
    1. awk4j 実行環境の設定
    2. RDBMS 固有環境の設定
  6. RDB 関連リンク