概要
inpureライブラリのdb_postgresモジュールを利用して、PostgreSqlのクライアントっぽいものを作ってみました。
- なんちゃって2Way-SQL対応
- パラメータも渡せるよ
2Way-SQL
SQLのクエリパラメータを指定する方法として、?とか:1とか使いますが、対応するDBライブラリによってまちまちです。
私の想定する2Way-SQLは、以下のようなもの
- そのままでもSQLが発行できること
- パラメータ指定をすれば、パラメータ部を?に変換する
- 外部から渡されたパラメータをパラメータにバインドする
例えばこんな感じです。
test.sql
select
*
from (
select 'abc' as name,'hello' as name2
union all
select 'def' as name,'hello' as name2
union all
select 'ghi' as name,'hello' as name2
) dummy
where 1=1
and name = /*name*/'abc'
or name in ( /*name2*/'def' , /*name3*/'ghi' )
上のSQLを実行時に、パラメータ部(/*XXX*/YY
)を?に変換します。
select
*
from (
select 'abc' as name,'hello' as name2
union all
select 'def' as name,'hello' as name2
union all
select 'ghi' as name,'hello' as name2
) dummy
where 1=1
and name = ?
or name in ( ? , ? )
nimで処理するなら、こんな感じですね。
for row in db.fastRows(SqlQuery(sql) , @["abc","def"]) :
echo row
作ったもの
pというモジュール名で、上記のSQLを実行した結果です。
-Pname,-Pname2というパラメータの「値」が、上記SQLの/*name*/'abc',/*name2*/'def'
にバインドされます。
# パラメータを2つ指定して実行
$ ./p -h=localhost -port=5432 -db=sample -u=postgres -p=postgres -i="test.sql" -Pname=abc -Pname2=ghi
abc hello
ghi hello
# パラメータを1つ指定して実行
$ ./p -h=localhost -port=5432 -db=sample -u=postgres -p=postgres -i="test.sql" -Pname=abc
abc hello
# パラメータなしで実行すると、SQLファイルの内容が実行されます
$ ./p -h=localhost -port=5432 -db=sample -u=postgres -p=postgres -i="test.sql"
abc hello
def hello
ghi hello
パラメータ仕様はこんな感じにしています。
パラメータ名 | 省略形 | 内容 | デフォルト値 |
---|---|---|---|
host | h | 接続先ホスト名 | localhost |
port | ポート番号 | 5432 | |
db | データベース名 | ||
user | u | ユーザー名 | |
password | p | パスワード | |
file | i | SQLファイル名へのパス | |
sql | c | SQL文を直接指定 | |
Pxxx | 大文字Pで始まるパラメータは、クエリーのバインドパラメータ |
fileおよびsqlオプションはあと勝ちとなります
ソース
p.nim
# nimによる2Way-SQL対応 PostgreSQLクライアント
# ex)
# ./p -h=localhost -port=5432 -db=sample -u=postgres -p=postgres -c="select 1 " -Pname=abc -Pname2=ghi
import os
import parseopt2
import db_postgres
import tables
import nre
import sequtils
import strutils
type
Sql2WayInfo = tuple[query: string, keys:seq[string]]
# 2way SQLを渡して、パラメータを?に変換かつ、パラメータ名を順番に抽出し、
# Sql2WayInfoに格納して返却する
# パラメータ変換対象は、以下のパターン
# /*PARAM_1*/''
# /*PARAM_2*/'ABC'
# /*PARAM_3*/0
# /*PARAM_4*/123
proc initSql2WayInfo( str:string , params: Table[string,string] ) : Sql2WayInfo =
let reParam = re"\/\*([\w_]+)\*\/('[^']*'|\d+)"
var q = str
var p: seq[string] = @[]
# パラメータが指定されていたら、変数名xxxを取り出し、/*XXX*/YYを?に変換する
# パラメータが指定されていないなら、SQLはそのまま
if params.len != 0 :
for x in str.findIter(reParam) :
# 変数名を取り出して格納する
p.add(x.captures[0])
# /*xxx*/yyを?に変換
q = str.replace(reParam,"?")
# 結果を設定
result = (query:q, keys:p)
# Sql2WayInfoのkeysの順番で、パラメータを構築する
proc p(info:Sql2WayInfo, params:Table[string,string]) : seq[string] =
info.keys.mapIt( if it in params : params[it] else: "" )
# メイン処理
proc mainProc(args:Table[string,string],params:Table[string,string]) : int =
result = 0
# 2WaySqlをパース
var qp = initSql2WayInfo(args["sql"],params)
# 接続文字列の組み立て
let conn = "host=" & args["host"] & " port=" & args["port"] & " dbname=" & args["db"]
# Postgresqlに接続
let db = open("", args["user"], args["password"], conn )
defer:
db.close
# パラメータを指定して実行
for row in db.fastRows( SqlQuery(qp.query) , qp.p(params)) :
echo row.join("\t")
# メインモジュールとして起動しているかチェック
if isMainModule :
var args = initTable[string,string]()
var prms = initTable[string,string]()
# デフォルト値
args["host"] = "localhost"
args["port"] = "5432"
# イテレータで取得
for kind, key, val in getopt() :
# (余談)kindは、enum型なので、case文ではすべてのenum値
# を網羅していないとコンパイルエラーとなります
case kind
of cmdEnd:
discard
of cmdArgument:
echo "無効な引数です"
quit(1)
of cmdLongOption, cmdShortOption:
var val2 = val
let key2 = case key
of "h","host": "host"
of "u","user": "user"
of "p","password": "password"
of "d","db" : "db"
of "c","sql": "sql"
of "port" : "port"
of "i","file":
var buff = @[""]
# ファイルを読み込む
for x in val.lines() :
buff.add(x)
val2 = buff.join("\n")
"sql"
else:
if key[0] == 'P' :
prms[ key.substr(1) ] = val
""
if key2 != "" :
args[key2] = val2
# メイン処理を呼び出し
quit(mainProc(args,prms))
作ってみた感想
普段は、Groovy/Scala/Java系を利用しているのですが、nimはコンパイルするだけあって、起動やら処理自体も早いので良いですね。
今回は、db_postgresモジュールを使いましたが、db_odbcモジュールを使えば、いろいろなDBへの接続できるクライアントが作れそうです。