DuckDB
DuckDB Connectorは、DuckDBのデータベースファイルにあるテーブルを読み書きし、その隣のファイルにコンテンツをコピーします。Connectorとしては、ローカルのデータに対してプログラムを動かす一番手軽な方法です。接続でファイルを指定して、問い合わせるだけです。標準の @actql/node、actql、CLIのパッケージに同梱されている同じDuckDBは、1つの読み取り元では済まない処理(たとえばSalesforceとSnowflakeをまたぐJOIN)のためにHostが使うエンジンでもあり、explain では実行されるSiteとは別にEngineの欄に表示されます。explain では、実行する場所(Site)とは別に、Engineの列に表示されます。
読み取り元
データベースファイルにあるすべてのテーブルとビューが読み取り元です。duckdb.orders、duckdb.customers のように参照します。列と型はファイルのカタログから取ります。テーブルのキーは、宣言されている PRIMARY KEY か UNIQUE 制約です。DuckDBはこれらの制約を強制するので、宣言されたキーはConnectorが頼りにできる事実です。制約のないテーブルも、接続側でキーになる列を宣言すれば書き込めます。その場合、Connectorは書き込みの前に一意性を検証します。
DuckDBが読めるParquetやCSVのファイルは、代わりにローカルファイルConnectorを通して公開されます。このConnectorが扱うのは、データベースファイルの中のテーブルです。
型
| DuckDB | ActQL | 備考 |
|---|---|---|
TINYINT、SMALLINT、INTEGER、BIGINT | int64 | |
HUGEINT、UHUGEINT、UBIGINT | decimal(39, 0) | int64 の範囲外の値はdecimalとして保つ |
DECIMAL(p, s) | decimal(p, s) | 精度とスケールを保つ |
FLOAT、DOUBLE | float64 | 有限の値だけ。NaN と無限大は読み取りを失敗させる |
BOOLEAN | bool | |
VARCHAR | string | |
BLOB | bytes | |
DATE | date | |
TIME | time | マイクロ秒の精度を保つ |
TIMESTAMP | datetime | タイムゾーンなし。解釈し直すことはない |
TIMESTAMP WITH TIME ZONE | timestamp | UTCの時点 |
INTERVAL | string | 読み取りのみ |
STRUCT | object | |
LIST、ARRAY | array | |
MAP | json | |
JSON | json | |
UUID | string | |
ENUM | string | enumにない値の書き込みは、その項目を失敗させる |
書き込みは同じ規則で逆方向に変換します。対象の列に収まらない値は、静的に分かるなら check で、そうでなければその項目が失敗します。黙って切り詰めることはありません。
Effectと操作
レコードのEffectはキーで書きます。
- insert —
EFFECT INSERT duckdb.customer_sentiment (col, …)。 - update —
EFFECT UPDATE duckdb.customer_sentiment ID customer_id SET (…)。 - upsert —
EFFECT UPSERT duckdb.customer_sentiment ID customer_id SET (…)。INSERT … ON CONFLICT DO UPDATEとして適用します。 - delete —
EFFECT DELETE duckdb.customer_sentiment ID customer_id。
ID で指定するキーは、ちょうど1レコードを識別しなければなりません。宣言された PRIMARY KEY か UNIQUE 制約があればそれが保証します。制約なしに接続側で宣言したキーについては、Connectorがプレビューを作るときに検証し、複数の行に一致するキーの項目は failed になります。同じキーに対して元のレコードが2つの異なる値を持つ更新は、check で拒否されます。
テーブルとコンテンツ:
- create table —
|> CREATE [OR REPLACE] TABLE duckdb.active_usageは、レコードをデータベースファイルの中のテーブル全体として書きます。 - store —
EFFECT STORE duckdb.files SET (path = 'exports/' || name, content = content)は、レコードのcontent(SERIALIZEまたはcontentsの読み取り元から来たもの)を、DuckDBのCOPYで接続のrootの下のファイルにコピーします。パスはrootの下で解決され、プログラムからその外のパスを指定することはできません。
DuckDBには操作はありません。DuckDBがすることはすべて、読み取り、書き込み、テーブルのどれかです。
セットアップ
"duckdb": {
"connector": "duckdb",
"path": "./data/sales.duckdb",
"root": "./data",
"read_only": false
}認証情報は不要です。パスは設定ファイルからの相対パスとして解決され、Hostが固定しているバージョンのDuckDBで開けるファイルである必要があります。互換性のないファイルは変換されず、拒否されます。読み取りだけの接続には read_only: true を指定すると、Effectの対象にできなくなります。root は、STORE がファイルを書いてよいディレクトリです。
ActQL Cloudでは、DuckDBの接続はCloudがあなたのために保持するデータベースファイルを指します。プログラムは同じです。
Capabilityとプッシュダウン
| 演算 | プッシュダウンされる条件 |
|---|---|
| 列の選択 | 常に。参照される列だけを読む |
WHERE | 述語がこの読み取り元の列を使うとき。比較、IN、LIKE、IS NULL、日付の演算について、DuckDBのSQLの意味はActQLと一致する。大文字小文字を区別する述語に、大文字小文字を区別しない照合順序は使わない |
GROUP BY とあらゆる集計 | 常に。DuckDBの正確なdecimalの演算はActQLと一致し、float64 の集計はそう宣言される |
ORDER BY、LIMIT | 常に。バイナリの文字列順で |
| ウィンドウ関数 | 常に。ActQLが受け付ける指定について |
JOIN | 両方の入力が同じデータベースファイルのテーブルのとき。別の読み取り元とのJOINはエンジンで実行する。同梱のDuckDBがエンジンなら、それもDuckDBで、Site host:local、Engine DuckDB になる |
explain は、プッシュダウンされたステップをSite connector:duckdb、Engine DuckDB で表示します。2つの行の違いはSiteにあり、Engineにはありません。
プレビューと突き合わせ
プレビュー。 更新、UPSERT、削除は full_diff でプレビューします。Connectorはトランザクションの中で対象の行をキーで読み、現在の値を記録し、プレビューには行ごとに現在の値と新しい値が出ます。適用時には、書き込みをトランザクションの中で実行し、行を読み直してプレビューした値と比べます。その間に変わった行は更新されません。その項目は failed(conflict)で終わり、続けるよう求められればRunは新しいプレビューを作ります。追加、テーブル作成、ファイルのコピーは targeted でプレビューします。何をどこに書くかを、件数またはコンテンツの識別子とバイト数とともに示し、CREATE OR REPLACE なら置き換えられる件数も示します。
突き合わせ。 書き込みはEffectのバッチごとに1つのトランザクションで実行し、Connectorは同じトランザクションの中で、Effectの項目のIDを同じデータベースファイルの中の台帳テーブル(actql_effects)に記録します。書き込みとその記録は、一緒にコミットされるか、どちらもされないかのどちらかです。コミット前にクラッシュすれば、何も適用されず何も記録されません。コミット後、Hostが応答を受け取る前にクラッシュした場合は、台帳テーブルから項目のIDを読んで突き合わせます。対象のテーブルが期待どおりの値を持っているかどうかで決めるのではありません。ファイルのコピーは COPY の前に同じ actql_effects の台帳に記録し、それで突き合わせます。ファイルのチェックサムだけでは証跡になりません。失われる可能性のあるネットワークの応答がないので、結果は applied か failed です。unknown はクラッシュからだけ生じ、台帳で確定します。
上限と費用
- データベースファイルはHostのプロセスが開きます。2つのHostが同じファイルに同時に書いてはいけません。ConnectorはDuckDBのファイルロックを取り、2つ目の書き手はファイルを壊すのではなく、接続を開けずに失敗します。
- 大きな結果は、プログラムがエンジン側で必要とするまでDuckDBの中にとどまります。その境界を越える量には、プログラムの件数とバイト数の上限が適用されます。
- 読み書きにかかるのはローカルのCPU、メモリ、ディスクだけです。DuckDBが保持できる量は、Hostのメモリの上限で抑えられます。
例
未処理の注文金額が多い顧客の上位です。
FROM duckdb.orders
|> WHERE status = 'open'
|> AGGREGATE COUNT(*) AS order_count, SUM(amount) AS total
GROUP BY customer_id
|> ORDER BY total DESC
|> LIMIT 20;LLMの判定をローカルのテーブルで最新に保ちます。
SELECT c.id AS customer_id, c.notes
FROM duckdb.customers AS c
|> INFER `model-task:sentiment.v1` INPUT (notes) AS s
|> WHERE s.outcome = 'ok'
|> SELECT customer_id, s.value.label AS sentiment
|> EFFECT UPSERT duckdb.customer_sentiment ID customer_id SET (sentiment = sentiment);結果をデータベースの隣にCSVファイルとして書き出します。
FROM duckdb.customer_sentiment
|> SERIALIZE FORMAT CSV ORDER BY customer_id
OPTIONS (name = 'sentiment.csv', header = TRUE, null_marker = '')
|> EFFECT STORE duckdb.files SET (path = 'exports/' || name, content = content);Source: actql/actql/docs/connectors/duckdb.md at d904416674e4 · Edit on GitHub