象と戯れ

 | 

2010-10-05

WIP: psqlsh(psqlでCGIを書く話)

01:32 | WIP: psqlsh(psqlでCGIを書く話) - 象と戯れ を含むブックマーク はてなブックマーク - WIP: psqlsh(psqlでCGIを書く話) - 象と戯れ

以前、"SQL is LL."というショートプレゼンを作りました。LL Tiger用に作ったのですが、そのときは発表する機会がなく、その後のUSP友の会・納涼船2010にてLTする機会を得ました。プレゼンの主旨は「SQLもLLだよね」ということに尽きるのですが、せっかくUSP友の会の人々に披露するのでと、その場で最後に"psql is a shell"というスライドを追加しました。

そのときから、psqlはほぼシェルといっていいのではないかと思うようになりました。実際に-cオプションでワンライナーが書けたり、オプション無しでインタラクティブモードに入る挙動などはpythonrubyと遜色ありません。ただ一つ設計ミスだったのは、

$ psql foo

と書くとデータベース"foo"に接続してしまうこと。本当はfooというファイルを実行すべきでした(DBなのでDB名の指定は当たり前なのですが)。何故そんなことにこだわるかというと、shebangが実行できないんですね。shebangというのはおなじみの#!/path/to/executableという一行目のアレです。あれができると、

$ chmod 755 foo; ./foo

とかできて、シェルっぽいわけです。

shebangがどのような仕組みで動作しているか、正確なところは理解しきれていませんが、どうやらexec系の関数がその根底にあるようでした。404 - エラー: 404参照。というわけで簡単なCでpsqlをラップしてみます。

int
main(int argc, char **argv)
{
	char   *new_argv[256];
	char   *filename;
	char	new_filename[256];
	int		i;

	memset(new_argv, 0, sizeof(char *) * 256);
	new_argv[0] = "psql";
	if (argc > 1)
	{
		for(i = 1; i < argc - 1; i++)
		{
			new_argv[i] = argv[i];
		}
		filename = argv[i];
		create_temp(filename, new_filename);

		new_argv[i++] = "-q";
		new_argv[i++] = "-f";
		new_argv[i++] = new_filename;
	}

	if(execvp("psql", new_argv) == -1)
	{
		fprintf(stderr, "exec %s failed:%s\n", "psql", strerror(errno));
	}

	return errno;
}

引数の付け替えだけで何とかなるかと思いきや、一行目の#!...がpsqlには読めないので、仕方なく一時ファイルを作って実体の2行目以降をコピーしています。execvp()でpsqlを呼び出しますが、めんどくさいので環境変数PATHを設定する前提で作ります。

下記のような.sqlを書いて、

$ cat foo.sql
#!/usr/local/bin/psqlsh

SELECT '1';

パーミッションを設定して、実行!

$ ./foo.sql 
 ?column? 
----------
 1
(1 row)

Time: 1.852 ms

おおおおおお。動きました。

これができるということは・・・

$ cat test.sql
#!/usr/local/bin/psqlsh postgres

\timing off
\t on
\a

DO $$
DECLARE
        cnt int;
BEGIN
        SELECT INTO cnt count(*) FROM pg_class WHERE relname = 't1';
        IF cnt = 0 THEN
                EXECUTE 'CREATE TABLE t1(id serial, name varchar)';
                INSERT INTO t1 (name) VALUES('Bob'),('Mary'),('Tom');
        END IF;
END;
$$;

INSERT INTO t1 (name) VALUES('hoge');

SELECT 'Content-type: text/html;';
SELECT '';
SELECT '<html><head><title>Hello psql CGI!</title>';
SELECT '<body>';
\html
SELECT * from t1;
\html
SELECT '</body></html>';

実行!!

f:id:umitanuki:20101006013131p:image

おおおおおおおお。CGIが実行できました。ちなみにshebangには引数が一つだけ書けるので、#!/usr/local/bin/psqlsh sampledb とか書いてターゲットDBを変えることもできます。


まとめ。

psqlCGIが書けます。とくに9.0からはDO文によりほとんど普通にスクリプト処理が書けますので、perlCGIと遜色ないかもしれません。興味のある方はgithubへどうぞ。

GitHub - umitanuki/psqlsh: A shell frontend of psql

めざせ、SQL on Rails!

 |