45
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Systemi(株式会社システムアイ)Advent Calendar 2024

Day 23

DuckDB Node Neo Client 試し撃ち

Last updated at Posted at 2024-12-22

TL;DR

コードの全量はGitHubを参照ください。
下記コマンドで試せます。

git clone https://github.com/mineco13/duckdb-node-neo
cd duckdb-node-neo
docker compose down&&docker compose up -d --build
open http://localhost:3000

はじめに

duckdbにNode.js clientがあるらしく試してみようと思ったのですが、従来のClientはDeprecatedとなっていたので新しいものを使いました。

古いクライアントも軽く試してみましたがcallback styleしか対応してなかったので書き味は少し悪かったです。
新しいクライアントはPromiseスタイルの書き方に対応するなど機能が追加されてますが、一部未機能の機能があったりと開発途中のようです。

今回はS3に見立てたMinIO上にあるcsvファイルをRemix上でテーブル形式にして表示してみます。

前提条件

下記インストール済みの想定です

  • Docker
  • Node.js

準備

表示用のcsvデータを用意

適当なcsvファイルを用意します。

csvs data/sample.csv
name,github_star,url
Material-UI (MUI),90500,https://mui.com/
Chakra UI,34700,https://chakra-ui.com/
Ant Design,87000,https://ant.design/
React Bootstrap,22000,https://react-bootstrap.github.io/
Semantic UI React,13100,https://react.semantic-ui.com/
Blueprint,20000,https://blueprintjs.com/docs/
Grommet,8100,https://v2.grommet.io/
Evergreen,12000,https://evergreen.segment.com/
Rebass,7900,https://rebassjs.org/
Fluent UI,15000,https://developer.microsoft.com/en-us/fluentui

※人気のReact Component libraryをGitHub star数とともにAIに出力してもらいました。

MinIOコンテナ(S3互換)を用意

先ほど作ったdata/sample.csvを自動で読み込むminioコンテナを、下記のようにcompose.yamlに定義します

yaml compose.yaml
services:
  minio:
    image: minio/minio:latest
    ports:
      - 9000:9000
      - 9001:9001
    environment:
      MINIO_ACCESS_KEY: minio
      MINIO_SECRET_KEY: minio123
    command: server /export --console-address ":9001"
  createbuckets:
    image: minio/mc
    depends_on:
      - minio
    entrypoint: >
      /bin/sh -c "
      until (/usr/bin/mc config host add myminio http://minio:9000 minio minio123) do echo '...waiting...' && sleep 1; done;
      /usr/bin/mc mb myminio/mybucket;
      /usr/bin/mc policy download myminio/mybucket;
      /usr/bin/mc cp /data/sample.csv myminio/mybucket/sample.csv;
      exit 0;
      "
    volumes:
      - ./data:/data

下記でMinIOにブラウザでアクセスして、compose.yaml内のMINIO_ACCESS_KEYMINIO_SECRET_KEYを入力してログインすることでバケットとその中身を確認できます

open http://localhost:9000

Remixテンプレート作成

画面表示用のテンプレートとしてRemixを使っていきます。(Next.jsみたいなものです)

npx create-remix@latest remix

localhost:5173にアクセスしてそれっぽい画面が表示されれば完了です

cd remix
npm run dev
## ACCESS http://localhost:5173

開発

npm i @duckdb/node-api

duckdbライブラリを用いて先ほど定義したMinIOからデータを読み出して、整形して返します。
columnNames()await result.getRows()など、結果に容易にアクセスできるネイティブのメソッドが用意されているのは嬉しいですね。

typescript remix/app/duckdb.ts
import { DuckDBInstance } from "@duckdb/node-api";

export const fetchCSV = async () => {
  const instance = await DuckDBInstance.create();
  const con = await instance.connect();

  await con.run(
    `
  INSTALL httpfs;
  LOAD httpfs;
  `
  );
  await con.run(
    `
  CREATE SECRET secret1 (
    TYPE S3,
    KEY_ID '${process.env.S3_ACCESS_KEY_ID}',
    SECRET '${process.env.S3_SECRET_ACCESS_KEY}',
    REGION 'us-east-1',
    ENDPOINT '${process.env.S3_HOST}:${process.env.S3_PORT}',
    USE_SSL false,
    URL_STYLE 'path'
  );
  `
  );

  const result = await con.run(
    "SELECT * FROM read_csv_auto('s3://mybucket/sample.csv');"
  );

  return { header: result.columnNames(), rows: await result.getRows() };
};

Remixからはloader(getServerSideProps的な)経由で呼び出してテーブル形式で出力します。
fetchCSVの結果をjsonにそのまま詰めて返したい所ですが、TypeError: Do not know how to serialize a BigIntを吐いて止まるので、bigintをstringに変換するロジックを挟んでいます。

typescript remix/app/routes/_index.ts
import { useLoaderData } from "@remix-run/react";
import { fetchCSV } from "~/duckdb";

...
export const loader = async () =>
  json(
    JSON.parse(
      JSON.stringify(await fetchCSV(), (key, value) =>
        typeof value === "bigint" ? value.toString() : value
      )
    )
  );
export default function Index() {
  const data = useLoaderData<typeof loader>();

  return (
...
          </div>

          <table>
            <caption>Data from Object Storage</caption>
            <tr>
              {data.header.map((header) => (
                <th scope="col">{header}</th>
              ))}
            </tr>
            {data.rows.map((row, index) => (
              <tr key={index}>
                {row.map((d) => (
                  <td scope="col">{d}</td>
                ))}
              </tr>
            ))}
          </table>

        </header>
...

下記スクリプトで起動してテーブルが出力されることを確認します。

S3_HOST=localhost S3_PORT="9000" S3_ACCESS_KEY_ID=minio S3_SECRET_ACCESS_KEY=minio123 npm run dev

これでひとまず完了です。

コンテナ化

蛇足ですが、下記のようなremix/Dockerfileを作成して

FROM node:20-slim

WORKDIR /remix

COPY package*json .
RUN npm install
COPY . .

RUN npm run build

CMD ["npm", "run", "start"]

compose.yamlに書き加えたらコンテナ化まで完了です。

yaml compose.yaml
services:
  remix:
    build:
      context: remix
    ports: [3000:3000]
    environment:
      S3_HOST: minio
      S3_PORT: "9000"
      S3_ACCESS_KEY_ID: minio
      S3_SECRET_ACCESS_KEY: minio123
    depends_on:
      - createbuckets
...

起動コマンドです。

docker compose down&&docker compose up -d --build
open http://localhost:3000

感想

様々な形式のデータにSQLインターフェースでアクセスできるのは楽しいです。
積極的に使っていこうと思います。

45
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
45
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?