🦆

@duckdb/node-api で sqlite ファイルの内容を duckdb ファイルにまるまるコピーする

に公開

nodejs 用の新しいクライアント(@duckdb/node-api)を使うとこんな実装になった。

import { DuckDBInstance } from "@duckdb/node-api";

export async function convertSqliteToDuckDB(
  sqliteFilePath: string,
  duckdbFilePath: string,
) {
  const duckdb = await DuckDBInstance.create(duckdbFilePath);
  const conn = await duckdb.connect();
  try {
    await conn.run("INSTALL sqlite;");
    await conn.run("LOAD sqlite;");
    await conn.run(`ATTACH '${sqliteFilePath}' AS sqlite;`);
    await conn.run("BEGIN TRANSACTION;");
    const reader = await conn.runAndReadAll(
      "SELECT name FROM main.sqlite_master WHERE type='table';",
    );
    const result = await reader.getRowObjects();
    for (const tableName of result.map((row) => row.name)) {
      const sql = `CREATE TABLE "${tableName}" AS SELECT * FROM sqlite."${tableName}";`;
      await conn.run(sql);
    }
  } catch (err) {
    await conn.close();
    throw err;
  }
  await conn.run("COMMIT;");
  await conn.run("CHECKPOINT;");
  await conn.close();
}

最後の conn.run("CHECKPOINT") が重要で、これを呼ばないと書き込んだはずのデータがバッファリングされてしまい、直後にファイルコピーしてもきちんとデータが書き込まれたファイルが扱えなかった。

バックアップや復帰用のコマンドだとばかり思っていたのだけれども、CHECKPOINT にはバッファリングされた WAL を flush する命令としても立派に使われるらしい。

参考

@duckdb/node-api は新しくて LLM はまだ知らないみたいで古いクライアントをつかいたがりがち。

Discussion