DuckDB-Wasm の HTMLファイルのみで試せる公式サンプルを書きかえたりもして試す

## はじめに

技術情報を色々見てる中で、ちょこちょこ以下の「[DuckDB](https://github.com/duckdb/duckdb)」をの情報を見かけます。

●DuckDB – An in-process SQL OLAP database management system
　https://duckdb.org/


また DuckDB関連では、以下の「[DuckDB-Wasm](https://github.com/duckdb/duckdb-wasm)」もわりと情報を見かける感じがして、こちらのほうがより気になっていました。

●DuckDB Wasm – DuckDB
　https://duckdb.org/docs/stable/clients/wasm/overview.html

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/6e9aa669-0f95-47f4-ad4d-1e75bf71173b.png)

今回、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ファイル

```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 からライブラリなどを読みこむ形になっています。具体的には以下の部分などです。

```javascript
      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ファイルの内容

最初に試す内容は、以下のとおりです。

```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>
```

#### ブラウザで得られる出力

上記の内容をブラウザで動かすと、以下のような出力が確認できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/acaa923f-57f1-4f17-8470-8f4330ffcf0f.png)

中を展開して途中の部分を見てみると、こんな感じになっていたりします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/88257626-adb3-4b88-bac0-3223238f471c.png)


どうやら以下の「Proxy」が使われているようです。

●Proxy - JavaScript | MDN
　https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Proxy



### お試し2

先ほどのログ出力の部分を少しだけ書きかえてみます。


#### 書きかえた部分とブラウザで得られた出力1

以下は書きかえた部分を取り出したものです。コメントアウトした部分が書きかえ前の内容で、その下が書きかえ後の内容です。

```javascript
        // console.log((await stmt.query(234)).toArray());
        const rows = (await stmt.query(234)).toArray();
        console.log(rows[0]);
```

先ほどの書きかえ後の内容をブラウザで開いたところ、以下の出力が得られました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/24a658dd-3454-4244-8e81-c0cfd96733e6.png)

この後、特定の部分を取り出してみます。


#### 書きかえた部分とブラウザで得られた出力2・3

コメントアウトした部分が書きかえ前の内容で、それ以外が書きかえ後の内容です。

```javascript
        const rows = (await stmt.query(234)).toArray();
        // console.log(rows[0]);
        console.log(JSON.stringify(rows.slice(0, 5), null, 2));
```

先ほどの書きかえ後の内容をブラウザで開いたところ、以下の出力が得られました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/c03090c5-1a42-4875-a9c0-86f1dd5d12ed.png)


以下のように変更した内容も試してみました。

```javascript
        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]));
```


出力は以下のとおりです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/465461b2-ebd7-4b6e-a6fa-39dfe64dc5a4.png)

部分的な内容を抽出もできました。


### お試し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


そうやって作った実装内容は、以下のとおりです。

```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();

        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>
```

出力は以下のとおりです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/a8ad724d-3e9a-47c1-9dd5-10979b707a5b.png)


出力結果を見ると、以下で用意していたデータに対して、age が 30以上のもののみ取り出すということができました。

```javascript
        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);
      `);
```

とりあえず、今回のお試しは以上になります。