Open3

DuckDBクライアントAPIをZig対応にしてみる

PG_WalkerPG_Walker

方針

  • 使用する関数や構造体などはexternで明示し@cImport@cIncludeは使わない
  • [*c]やオプションはできるだけ使わない

リンクと実行(Linux環境の場合)

  1. libduckdb-linux-amd64.zip をDuckDB 1.0.0 "Nivis"からダウンロード
  2. libduckdb.soをリンクしてコンパイル
libduckdb.soのリンクと実行(デバッグモード)
$ zig run -L(ライブラリのディレクトリ) -lduckdb -lc (ソースファイル名)
PG_WalkerPG_Walker

Open/Connect Reference

構造体と関数をZig対応にする

// DuckDBの構造体(libduckdb.soより)
const duckdb_database = extern struct {
    __db: *opaque{}
};
const duckdb_connection = extern struct {
    __conn: *opaque{}
};

// DuckDBの関数(libduckdb.soより)
extern fn duckdb_open(path: ?[*:0]const u8, out_data: *duckdb_database) duckdb_state;
extern fn duckdb_close(database: *const duckdb_database) void;
extern fn duckdb_connect(database: duckdb_database, out_connection: *duckdb_connection) duckdb_state;
extern fn duckdb_disconnect(connection: *const duckdb_connection) void;

C APIをそのまま使う場合

C APIをそのまま使う場合
var db: duckdb_database = undefined;
var con: duckdb_connection = undefined;

// エラーチェックは割愛
_ = duckdb_open(null, &db);
_ = duckdb_connect(db, &con);

// .....

duckdb_disconnect(&con);
duckdb_close(&db);

structでまとめる

structでまとめる
const DuckDBState = enum (u8) {
    Success = 0,
    Error   = 1
};

const DuckDB = struct {
    db: duckdb_database,
    con: duckdb_connection,

    const Self = @This();

    pub fn init(path: ?[*:0]const u8) !Self {
        var db: duckdb_database = undefined;
        var state: usize = duckdb_open(path, &db);
        if (state != @intFromEnum(DuckDBState.Success)) return error.DuckDBNotOpen;

        var con: duckdb_connection = undefined;
        state = duckdb_connect(db, &con);
        if (state != @intFromEnum(DuckDBState.Success)) return error.DuckDBNotConnect;

        return .{ .db = db, .con = con };
    }

    pub fn deinit(self: Self) void {
        duckdb_disconnect(&self.con);
        duckdb_close(&self.db);
    }
};

// 実行イメージ
const duckdb = try DuckDB.init(null);
defer duckdb.deinit();
PG_WalkerPG_Walker

Query (Reference) / Fetch (Reference)

  • 公式のサンプル main.cを参考にして、必要な関数をextern fnで宣言し、SQLを実行し、結果を取得するプログラムを作成してみる
関数の宣言
// open, connect関連は省略
const duckdb_result = extern struct {
    __internal_data: *opaque{}
};

extern fn duckdb_query(connection: duckdb_connection, query: [*:0]const u8, out_result: ?*duckdb_result) duckdb_state;
extern fn duckdb_destroy_result(result: *duckdb_result) void;
extern fn duckdb_column_count(result: *duckdb_result) idx_t;
extern fn duckdb_column_name(result: *duckdb_result, col: idx_t) [*:0]const u8;
extern fn duckdb_row_count(result: *duckdb_result) idx_t;
extern fn duckdb_value_is_null(result: *duckdb_result, col: idx_t, row: idx_t) bool;
extern fn duckdb_value_int32(result: *duckdb_result, col: idx_t, row: idx_t) i32;
SQLの実行と結果の取得
const std = @import("std");

var db: duckdb_database = undefined;
var con: duckdb_connection = undefined;
var result: duckdb_result = undefined;

// エラーチェックは省略
_ = duckdb_open(null, &db);
_ = duckdb_connect(db, &con);
_ = duckdb_query(con, "CREATE TABLE integers(i INTEGER, j INTEGER);", null);
_ = duckdb_query(con, "INSERT INTO integers VALUES (3, 4), (5, 6), (7, null);", null);
_ = duckdb_query(con, "SELECT * FROM integers", &result);

const row_count: idx_t = duckdb_row_count(&result);
const column_count: idx_t = duckdb_column_count(&result);
for(0..column_count) |i| {
    try stdout.print("{s} ", .{duckdb_column_name(&result, i)});
}
try stdout.print("\n", .{});

for(0..row_count) |row_idx| {
    for(0..column_count) |col_idx| {
        if (duckdb_value_is_null(&result, col_idx, row_idx)) {
            try stdout.print("NULL ", .{});
        }
        else {
            const val: i32 = duckdb_value_int32(&result, col_idx, row_idx);
            try stdout.print("{} ", .{val});
        }
    }
    try stdout.print("\n", .{});
}
duckdb_destroy_result(&result);

duckdb_disconnect(&con);
duckdb_close(&db);
結果
i j 
3 4 
5 6 
7 NULL