🦆🦆🦆DuckDBとSQLite・PostgreSQL連携🦆🦆
気にはなってるけど触ってないビッグデータ系のツール・サービスを触る Advent Calendar 2022の#21です。
DuckDBには他のデータベースと連携する機能があるので、いくつか試してみました。
tl;dr
- DuckDBからSQLiteとPostgreSQLのデータが読み込めるよ
- (多分)PostgreSQLからDuckDBのデータも読み込めるよ
準備
DuckDB(CLI)をダウンロードし、パスが通っている場所に置いておきます。
(私は~/.local/bin/duckdbに置きました)
Ubuntu 20.04 (Windows10のWSL2上)、DuckDBv0.6.1で試しました。
DuckDBとSQLite
SQLite Scannerを使うと、SQLiteのデータベースを読み込むことができます。
準備
まずは、SQLiteのデータベースを作成します。
sudo apt install sqlite
sqlite3 sqlite3.db
sqlite> CREATE TABLE t1(key varchar, not_key varchar);
sqlite> INSERT INTO t1 VALUES ('sqlite_key', 'made in sqlite3');
sqlite3.dbを作成したディレクトリでDuckDBを起動し、SQLite Scannerプラグインのインストール・有効化を行います。
duckdb hoge.db
D INSTALL sqlite;
D LOAD sqlite;
DuckDBからSQLiteのデータを読んでみる
SQLiteのデータベースを読んでみます。
D SELECT * FROM sqlite_scan('sqlite3.db', 't1');
┌────────────┬─────────────────┐
│ key │ not_key │
│ varchar │ varchar │
├────────────┼─────────────────┤
│ sqlite_key │ made in sqlite3 │
└────────────┴─────────────────┘
読めていますね。SQLiteのデータをDuckDBに保存したり、DuckDBのデータとSQLiteのデータを組み合わせたりも出来ます。
# SQLiteのデータをDucKDBに保存
D CREATE TABLE t1_from_sqlite AS SELECT * FROM sqlite_scan('sqlite3.db', 't1');
# SQLiteのデータとDuckDBのデータを同時に使う
D SELECT * FROM t1_from_sqlite UNION ALL SELECT * FROM sqlite_scan('sqlite3.db', 't1');
┌────────────┬─────────────────┐
│ key │ not_key │
│ varchar │ varchar │
├────────────┼─────────────────┤
│ sqlite_key │ made in sqlite3 │
│ sqlite_key │ made in sqlite3 │
└────────────┴─────────────────┘
トランザクション
SQLite側でトランザクション中のデータはDuckDB側からは見えません。
sqlite> BEGIN;
sqlite> INSERT INTO t1 VALUES ('sqlite_ke2', 'made in sqlite3');
sqlite>
D SELECT * FROM sqlite_scan('sqlite3.db', 't1');
┌────────────┬─────────────────┐
│ key │ not_key │
│ varchar │ varchar │
├────────────┼─────────────────┤
│ sqlite_key │ made in sqlite3 │
└────────────┴─────────────────┘
もちろんCommitすると見えるようになります。
sqlite> COMMIT;
sqlite> SELECT * FROM t1;
sqlite_key|made in sqlite3
sqlite_ke2|made in sqlite3
D SELECT * FROM sqlite_scan('sqlite3.db', 't1');
┌────────────┬─────────────────┐
│ key │ not_key │
│ varchar │ varchar │
├────────────┼─────────────────┤
│ sqlite_key │ made in sqlite3 │
│ sqlite_ke2 │ made in sqlite3 │
└────────────┴─────────────────┘
(多分)出来ないこと
以下の二つは試したらエラーになりました。多分対応していない?
- SQLite Scannerと名前にあることからも推測できますが、SQLiteのデータの読みとりのみで、データの変更はできないようです
- SQLite2系のファイルには対応していないようです
D UPDATE sqlite_scan('sqlite3.db', 't1') set not_key = 'hogehoge' WHERE key = 'sqlite_key';
Error: Parser Error: syntax error at or near "("
LINE 1: UPDATE sqlite_scan('sqlite3.db', 't1') set not_key = 'hog...
# sqlite3ではなく、sqlite2系のデータベースを作ってみる
sqlite sqlite.db
CREATE TABLE t1(key varchar, not_key varchar);
INSERT INTO t1 VALUES ('common_key','common');
# DuckDBからのアクセスが失敗
D SELECT * FROM sqlite_scan('sqlite.db', 't1');
Error: Invalid Error: file is not a database
DuckDBとPostgreSQL
SQLiteと同様に、Postgres Scannerを使うとPostgreSQLのテーブルを読み込むことができます。
準備
PostgreSQLのデータベースを用意します。今回はコンテナイメージを使いますので、docker-composeファイルを記載します。
version: '3.1'
services:
db:
image: postgres:15.1
restart: always
environment:
POSTGRES_PASSWORD: admin
ports:
- 5433:5432
volumes:
- ./postgresql:/var/lib/postgresql/data
command: ["postgres", "-c", "log_statement=all"]
# 動作確認用のツール。無くても動くはず
adminer:
image: adminer
restart: always
ports:
- 8080:8080
後々動作確認しやすくするために、log_statement=Trueしています。
コンテナを起動し、SQLiteと同様にテーブルを作成します。
docker-compose up -d
psql -h 127.0.0.1 -p 5433 -U postgres
postgres=# CREATE TABLE t1(key varchar, not_key varchar);
postgres=# INSERT INTO t1 VALUES ('common_key', 'common'), ('changed_key', 'changed'), ('inserted_key', 'inserted');
DuckDB側でPostgres Scannerプラグインのインストール・有効化を行っておきます。
D INSTALL postgres_scanner;
D LOAD postgres_scanner;
DuckDBからPostgreSQLのデータを読んでみる
PostgreSQLのデータベースを読んでみます。
D SELECT * FROM postgres_scan_pushdown('postgresql://postgres:admin@localhost:5433/postgres', 'public', 't1');
┌──────────────┬──────────┐
│ key │ not_key │
│ varchar │ varchar │
├──────────────┼──────────┤
│ common_key │ common │
│ changed_key │ changed │
│ inserted_key │ inserted │
└──────────────┴──────────┘
(docker logsで)PostgreSQLのログを見ると、リクエストが来ていることを確認できます。
2022-12-18 13:12:13.268 UTC [76] LOG: statement: BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY
2022-12-18 13:12:13.269 UTC [76] LOG: statement: SELECT pg_is_in_recovery()
2022-12-18 13:12:13.269 UTC [76] LOG: statement:
SELECT pg_class.oid, GREATEST(relpages, 1)
FROM pg_class JOIN pg_namespace ON relnamespace = pg_namespace.oid
WHERE nspname='public' AND relname='t1'
2022-12-18 13:12:13.270 UTC [76] LOG: statement:
SELECT
attname, atttypmod,
pg_type.typname, pg_type.typlen, pg_type.typtype, pg_type.typelem,
pg_type_elem.typname elem_typname, pg_type_elem.typlen elem_typlen, pg_type_elem.typtype elem_typtype
FROM pg_attribute
JOIN pg_type ON atttypid=pg_type.oid
LEFT JOIN pg_type pg_type_elem ON pg_type.typelem=pg_type_elem.oid
WHERE attrelid=16389 AND attnum > 0
ORDER BY attnum;
2022-12-18 13:12:13.276 UTC [77] LOG: statement: BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY
2022-12-18 13:12:13.276 UTC [77] LOG: statement:
COPY (SELECT "key", "not_key" FROM "public"."t1" WHERE ctid BETWEEN '(0,0)'::tid AND '(4294967295,0)'::tid ) TO STDOUT (FORMAT binary);
なお、DuckDBのPostgreSQL連携ではBinary Transfer Modeというのを使っているらしいです(PostgreSQLのCOPY)。詳しくDuckDBのブログを参照してください。
pushdown・projection
SELECT文の、カラム指定・WHERE文はDuckDB側ではなく、PostgreSQL側で動作させる(pushdown)ことが可能です。
D SELECT key FROM postgres_scan_pushdown('postgresql://postgres:admin@localhost:5433/postgres', 'public', 't1') WHERE key = 'common_key';
┌────────────┐
│ key │
│ varchar │
├────────────┤
│ common_key │
└────────────┘
COPY (SELECT "key", "not_key" FROM "public"."t1" WHERE ctid BETWEEN '(0,0)'::tid AND '(4294967295,0)'::tid AND ("key" = 'common_key' AND "key" IS NOT NULL)) TO STDOUT (FORMAT binary);
なお、WHEREのpushdownは行わない(=DuckDB側で絞り込み)も可能です。
D SELECT key FROM postgres_scan('postgresql://postgres:admin@localhost:5433/postgres', 'public', 't1') WHERE key = 'common_key';
┌────────────┐
│ key │
│ varchar │
├────────────┤
│ common_key │
└────────────┘
先ほどと違い、PostgreSQL側のログにWHERE文がありませんね。
COPY (SELECT "key" FROM "public"."t1" WHERE ctid BETWEEN '(0,0)'::tid AND '(4294967295,0)'::tid ) TO STDOUT (FORMAT binary);
PostgreSQLからDuckDB
duckdb_fdwというPostgreSQLの拡張で可能そうです。
(力尽きました)
Discussion