エントリを書くのは実に半年以上ぶりなのですが、今回は以前から試してみたかったdblinkネタをお届けします。
■なぜ今さら「dblink」?
PostgreSQLには、PostgreSQL、あるいは異種DBMSのデータベース連携を実現する手段として、dblinkとForeign Data Wrapper (FDW) が提供されています。
- https://www.postgresql.jp/document/10/html/dblink.html
- https://www.postgresql.jp/document/10/html/postgres-fdw.html
しかし、実際にデータベース連携を実現していく中で、FDWでは対応が困難なシーンがあります。
- FDWを使って外部テーブルを実装する際に設定すべき項目が多い。
- FDWは、VIEWのようにクエリを定義(固定)して使うため、アドホックな(もしくは動的に変わる)クエリのリモート実行ができない。
- FDWのAPIは年々複雑化しており、もはや普通の開発者が気軽に拡張できるレベルでない。
- そもそも、Cで開発できる or したい人はもうそんなにいない。
PostgreSQLの強みのひとつは「拡張性の高さ」です。そのため、その「拡張性の高さ」を最大限に活かす実装を目指します。
なお、このPL/Pythonによるdblinkの再実装を、本エントリでは便宜上「dblink/py」と表記します。
■dblink/pyの実装方針
dblink/pyは以下の方針で実装します。
- PostgreSQLのextensionとする。
- APIはcontribのdblinkのサブセットとする。
- 各DBMSへの接続、操作はPythonのDatabase APIに対応したドライバを活用する。
- 気軽に拡張を行えるようにシンプルな実装に留める。
- まずはPostgreSQLへのリモート接続機能を実装し、その後SQLiteへ拡張する。
■実装するdblink/pyのAPI
さて、dblink/pyで再実装するAPIを定義します。今回実装するAPIは以下の通りです。
以下は、リモートデータベースに接続してカーソルを操作するためのAPIです。
- dblink_connect
- dblink_disconnect
- dblink_get_connections
- dblink_open
- dblink_fetch
- dblink_close
- dblink
- dblink_exec
■dblink/pyの初期実装と動作確認
dblink/pyのコードは以下のレポジトリにあります。
初期実装(PostgreSQL接続のみサポートしたもの)は「release-pg」というブランチになっていますので、まずはこれをチェックアウトしてインストールします。
[snaga@devvm06 temp]$ git clone https://github.com/snaga/dblink_py.git Cloning into 'dblink_py'... remote: Enumerating objects: 78, done. remote: Counting objects: 100% (78/78), done. remote: Compressing objects: 100% (35/35), done. remote: Total 78 (delta 36), reused 64 (delta 25), pack-reused 0 Unpacking objects: 100% (78/78), done. [snaga@devvm06 temp]$ cd dblink_py/ [snaga@devvm06 dblink_py]$ git checkout release-pg Branch release-pg set up to track remote branch release-pg from origin. Switched to a new branch 'release-pg' [snaga@devvm06 dblink_py]$ sudo env USE_PGXS=1 PATH=/usr/pgsql-10/bin:$PATH make install /usr/bin/mkdir -p '/usr/pgsql-10/share/extension' /usr/bin/mkdir -p '/usr/pgsql-10/share/extension' /usr/bin/install -c -m 644 .//dblink_py.control '/usr/pgsql-10/share/extension/' /usr/bin/install -c -m 644 .//dblink_py--0.1.sql '/usr/pgsql-10/share/extension/' [snaga@devvm06 dblink_py]$
インストールが完了したら、動作確認として2つのデータベース「testdb」と「testdb2」を作成して、dblinkを使って「testdb」を介して「testdb2」のデータを読み書きしてみます。
まず、データベースを作成します。
[snaga@devvm06 dblink_py]$ createdb -U postgres testdb [snaga@devvm06 dblink_py]$ createdb -U postgres testdb2
次に「testdb」に接続して、dblink/pyをインストールします。
[snaga@devvm06 dblink_py]$ psql -U postgres testdb psql (10.6) Type "help" for help. testdb=# create language plpython2u; CREATE LANGUAGE testdb=# create extension dblink_py; CREATE EXTENSION
次に、「testdb」から「testdb2」に接続していることを確認します。
testdb=# select * from dblink('postgresql://localhost/testdb2', 'select current_database()', true) as (dbname text); dbname --------- testdb2 (1 row)
次に、CREATE TABLE ASを使ってtestdb2上にテーブルを作成し、そのテーブルの内容を読んでみます。
testdb=# select dblink_exec('postgresql://localhost/testdb2', 'create table temp as select current_database(),''ほげほげ''', true); dblink_exec ------------- SELECT 1 (1 row) testdb=# select * from dblink('postgresql://localhost/testdb2', 'select * from temp', true) as (db text, foo text); db | foo ---------+---------- testdb2 | ほげほげ (1 row)
最後に、このテーブルがtestdb上には作成されておらず、testdb2上に作成されていることを確認します。
testdb=# \d Did not find any relations. testdb=# \c testdb2 You are now connected to database "testdb2" as user "postgres". testdb2=# \d List of relations Schema | Name | Type | Owner --------+------+-------+---------- public | temp | table | postgres (1 row) testdb2=# select * from temp; current_database | ?column? ------------------+---------- testdb2 | ほげほげ (1 row)
以上で動作確認は完了です。(カーソルの動作確認はここではしてませんが、リグレッションテストには含まれていますので、 sql/dblink_py.sql と expected/dblink_py.out を確認してみてください。)
■dblink/pyのSQLiteへの拡張
さて、動作確認ができたら、次はSQLiteに対応するようにdblink/pyを拡張してみます。
SQLite対応の拡張をしたコードは「release-sqlite」ブランチにあります。
dblink/py本体のコード(つまりテストコードを除く)に関しては、PostgreSQLのみ対応のバージョン(release-pgブランチ)と比べると、17行追加、2行削除されていることが分かります。
[snaga@devvm06 dblink_py]$ git checkout release-sqlite Branch release-sqlite set up to track remote branch release-sqlite from origin. Switched to a new branch 'release-sqlite' [snaga@devvm06 dblink_py]$ git diff --stat release-pg dblink_py--0.1.sql dblink_py--0.1.sql | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) [snaga@devvm06 dblink_py]$
つまり、この「プラス17行、マイナス2行」だけでSQLite対応が完了したということです。詳細な差分は以下を参照してください。
さて、それではSQLiteのデータベースに読み書きしてみます。
まず、/tmp/testdb3.db というSQLiteデータベース(ファイル)を想定して、テーブルを作成します。
testdb=# select dblink_exec('sqlite:///tmp/testdb3.db', 'create table t ( uid integer, uname text)', true); dblink_exec ------------- OK (1 row)
次にそのテーブルが空であることを確認して、1件INSERT、再度SELECTしてレコードが挿入されたことを確認します。
testdb=# select * from dblink('sqlite:///tmp/testdb3.db', 'select * from t', true) as (uid int, uname text); uid | uname -----+------- (0 rows) testdb=# select dblink_exec('sqlite:///tmp/testdb3.db', 'insert into t values (1, ''aaa'')', true); dblink_exec ------------- OK (1 row) testdb=# select * from dblink('sqlite:///tmp/testdb3.db', 'select * from t', true) as (uid int, uname text); uid | uname -----+------- 1 | aaa (1 row)
最後に、testdbデータベースにはテーブルが「作成されていないこと」を確認して、dblink/pyを通して読み書きしていたSQLiteデータベース /tmp/testdb3.db の内容を確認します。
testdb=# \d Did not find any relations. testdb=# \q [snaga@devvm06 dblink_py]$ sudo -u postgres sqlite3 /tmp/testdb3.db SQLite version 3.7.17 2013-05-20 00:56:22 Enter ".help" for instructions Enter SQL statements terminated with a ";" sqlite> .tables t sqlite> select * from t; 1|aaa sqlite>
以上で、dblink/pyのSQLite対応の拡張とその動作確認は完了です。
■まとめ
というわけで、今回はdblinkの機能のサブセットをPL/Pythonで再実装し、それを他のDBMSに拡張する、ということを試してみました。
本家のcontribのdblinkはコードが3,000行以上ある大規模なモジュールですが、今回作ったdblink/pyのコードは200行以下です。SQLite対応の拡張に至っては、わずか20行以下のコードの変更で実現できています。PostgreSQLの拡張性をうまく活かすことで、これくらいの労力でさまざまな拡張ができる、ということがお分かりいただけたかと思います。
そんなこんなで今回いろいろやってきましたが、お伝えしたかったことは
- 「FDWが無くてもdblinkを使ってVIEWを定義してしまえば見た目はFDWとだいたい一緒や」
- 「シンプルに行こう。Less is more.」
PostgreSQL Advent Calendar 2018、明日の担当は・・・なんと、未定です(本エントリ執筆時点)。
Advent Calendarが初日で終わってしまうのか。それとも奇跡が起きて継続されるのか。ドキドキハラハラが止まらない。刮目せよ。
では。
0 件のコメント:
コメントを投稿