はじめに
技術情報を色々見てる中で、ちょこちょこ以下の「DuckDB」をの情報を見かけます。
●DuckDB – An in-process SQL OLAP database management system
https://duckdb.org/
また DuckDB関連では、以下の「DuckDB-Wasm」もわりと情報を見かける感じがして、こちらのほうがより気になっていました。
●DuckDB Wasm – DuckDB
https://duckdb.org/docs/stable/clients/wasm/overview.html
今回、DuckDB-Wasm のほうを試してみます。
DuckDB-Wasm が気になったきっかけ
DuckDB-Wasm の情報をいくつか見かける中、特に以下の記事に関するものが気になりました。
●ブラウザでオフライン日本語インスタント全文検索を実現する
https://voluntas.ghost.io/offline-japanese-full-text-search-in-browser/
●DuckDB-Wasmで簡単な地理空間情報分析アプリを作る - Qiita
https://qiita.com/northprint/items/0bb2113814a8878fdfef
●DuckDB-Wasm が OPFS に対応した
https://voluntas.ghost.io/duckdb-wasm-opfs/
今回の内容
今回、以下の HTMLファイルのみで試せる公式サンプルを動かしてみたり、少しだけ手を加えてみたりして「DuckDB-Wasm」を試していきます。
●duckdb-wasm/examples/plain-html/index.html at main · duckdb/duckdb-wasm
https://github.com/duckdb/duckdb-wasm/blob/main/examples/plain-html/index.html
サンプルの HTMLファイル
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
const getDb = async () => {
const duckdb = window.duckdbduckdbWasm;
// @ts-ignore
if (window._db) return window._db;
const JSDELIVR_BUNDLES = duckdb.getJsDelivrBundles();
// Select a bundle based on browser checks
const bundle = await duckdb.selectBundle(JSDELIVR_BUNDLES);
const worker_url = URL.createObjectURL(
new Blob([`importScripts("${bundle.mainWorker}");`], {
type: "text/javascript",
})
);
// Instantiate the asynchronus version of DuckDB-wasm
const worker = new Worker(worker_url);
// const logger = null //new duckdb.ConsoleLogger();
const logger = new duckdb.ConsoleLogger();
const db = new duckdb.AsyncDuckDB(logger, worker);
await db.instantiate(bundle.mainModule, bundle.pthreadWorker);
URL.revokeObjectURL(worker_url);
window._db = db;
return db;
};
</script>
<script type="module">
import * as duckdbduckdbWasm from "https://cdn.jsdelivr.net/npm/@duckdb/duckdb-wasm@1.28.1-dev106.0/+esm";
window.duckdbduckdbWasm = duckdbduckdbWasm;
getDb().then(async (db) => {
// Create a new connection
const conn = await db.connect();
// Prepare query
const stmt = await conn.prepare(
`SELECT v + ? FROM generate_series(0, 10000) AS t(v);`
);
// ... and run the query with materialized results
console.log((await stmt.query(234)).toArray());
});
</script>
</body>
</html>
さっそく試す
早速、試していきます。
お試し1
まずは公式サンプルをほぼそのまま動かしてみます。その際、少しだけ内容を書きかえます。
読みこむライブラリのバージョンを変える
内容を書きかえるのにあたり、少し内容を見てみます。
上で掲載した公式サンプルでは、CDN からライブラリなどを読みこむ形になっています。具体的には以下の部分などです。
import * as duckdbduckdbWasm from "https://cdn.jsdelivr.net/npm/@duckdb/duckdb-wasm@1.28.1-dev106.0/+esm";
以下の jsDelivr上のページを見ると、さらに新しいバージョンが出ているようです。
●@duckdb/duckdb-wasm CDN by jsDelivr - A CDN for npm and GitHub
https://www.jsdelivr.com/package/npm/@duckdb/duckdb-wasm
https://cdn.jsdelivr.net/npm/@duckdb/duckdb-wasm@1.29.1-dev132.0/+esm
読みこむライブラリのバージョンを、新しいものにしてみます。また、他もほんの少しだけ書きかえて、これを動かしてみます。
最初に試す HTMLファイルの内容
最初に試す内容は、以下のとおりです。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
const getDb = async () => {
const duckdb = window.duckdbduckdbWasm;
if (window._db) return window._db;
const JSDELIVR_BUNDLES = duckdb.getJsDelivrBundles();
const bundle = await duckdb.selectBundle(JSDELIVR_BUNDLES);
const worker_url = URL.createObjectURL(
new Blob([`importScripts("${bundle.mainWorker}");`], {
type: "text/javascript",
})
);
const worker = new Worker(worker_url);
const logger = new duckdb.ConsoleLogger();
const db = new duckdb.AsyncDuckDB(logger, worker);
await db.instantiate(bundle.mainModule, bundle.pthreadWorker);
URL.revokeObjectURL(worker_url);
window._db = db;
return db;
};
</script>
<script type="module">
import * as duckdbduckdbWasm from "https://cdn.jsdelivr.net/npm/@duckdb/duckdb-wasm@1.29.1-dev132.0/+esm";
window.duckdbduckdbWasm = duckdbduckdbWasm;
getDb().then(async (db) => {
const conn = await db.connect();
const stmt = await conn.prepare(
`SELECT v + ? FROM generate_series(0, 10000) AS t(v);`
);
// ... and run the query with materialized results
console.log((await stmt.query(234)).toArray());
});
</script>
</body>
</html>
ブラウザで得られる出力
上記の内容をブラウザで動かすと、以下のような出力が確認できました。
中を展開して途中の部分を見てみると、こんな感じになっていたりします。
どうやら以下の「Proxy」が使われているようです。
●Proxy - JavaScript | MDN
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Proxy
お試し2
先ほどのログ出力の部分を少しだけ書きかえてみます。
書きかえた部分とブラウザで得られた出力1
以下は書きかえた部分を取り出したものです。コメントアウトした部分が書きかえ前の内容で、その下が書きかえ後の内容です。
// console.log((await stmt.query(234)).toArray());
const rows = (await stmt.query(234)).toArray();
console.log(rows[0]);
先ほどの書きかえ後の内容をブラウザで開いたところ、以下の出力が得られました。
この後、特定の部分を取り出してみます。
書きかえた部分とブラウザで得られた出力2・3
コメントアウトした部分が書きかえ前の内容で、それ以外が書きかえ後の内容です。
const rows = (await stmt.query(234)).toArray();
// console.log(rows[0]);
console.log(JSON.stringify(rows.slice(0, 5), null, 2));
先ほどの書きかえ後の内容をブラウザで開いたところ、以下の出力が得られました。
以下のように変更した内容も試してみました。
const rows = (await stmt.query(234)).toArray();
// console.log(JSON.stringify(rows.slice(0, 5), null, 2));
console.log(JSON.stringify(rows[0], null, 2));
console.log(rows[0]["(v + $1)"]);
console.log(Object.keys(rows[0]));
console.log(Object.values(rows[0]));
出力は以下のとおりです。
部分的な内容を抽出もできました。
お試し3
先ほどまで試したものと、違ったデータの取り出しを試していきます。
その内容を準備するのにあたり、以下の内容や ChatGPT を使って進めました。
●DuckDB-Wasm: Efficient Analytical SQL in the Browser – DuckDB
https://duckdb.org/2021/10/29/duckdb-wasm.html
●DuckDB-wasm×wllamaでJSONを可視化&要約する軽量ダッシュボードを作った
https://zenn.dev/tesla/articles/e3ec9fe5e15c9a
そうやって作った実装内容は、以下のとおりです。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
const getDb = async () => {
const duckdb = window.duckdbduckdbWasm;
if (window._db) return window._db;
const JSDELIVR_BUNDLES = duckdb.getJsDelivrBundles();
const bundle = await duckdb.selectBundle(JSDELIVR_BUNDLES);
const worker_url = URL.createObjectURL(
new Blob([`importScripts("${bundle.mainWorker}");`], {
type: "text/javascript",
})
);
const worker = new Worker(worker_url);
const logger = new duckdb.ConsoleLogger();
const db = new duckdb.AsyncDuckDB(logger, worker);
await db.instantiate(bundle.mainModule, bundle.pthreadWorker);
URL.revokeObjectURL(worker_url);
window._db = db;
return db;
};
</script>
<script type="module">
import * as duckdbduckdbWasm from "https://cdn.jsdelivr.net/npm/@duckdb/duckdb-wasm@1.29.1-dev132.0/+esm";
window.duckdbduckdbWasm = duckdbduckdbWasm;
getDb().then(async (db) => {
const conn = await db.connect();
await conn.query(`
CREATE TABLE IF NOT EXISTS people(name VARCHAR, age INTEGER);
INSERT INTO people VALUES
('Alice', 30),
('Bob', 25),
('Charlie', 35),
('Dave', 28),
('Eve', 22);
`);
const sql = `SELECT * FROM people WHERE age >= 30 ORDER BY age;`;
const result = await conn.query(sql);
console.log(result);
console.log(result.toArray().length);
for (const r of result.toArray()) {
console.log(r.name, r.age);
}
});
</script>
</body>
</html>
出力は以下のとおりです。
出力結果を見ると、以下で用意していたデータに対して、age が 30以上のもののみ取り出すということができました。
await conn.query(`
CREATE TABLE IF NOT EXISTS people(name VARCHAR, age INTEGER);
INSERT INTO people VALUES
('Alice', 30),
('Bob', 25),
('Charlie', 35),
('Dave', 28),
('Eve', 22);
`);
とりあえず、今回のお試しは以上になります。