const std = @import("std"); const assert = std.debug.assert; const c = @cImport({ @cInclude("duckdb.h"); }); const Result = @import("Result.zig").Result; const Connection = struct { _conn: c.duckdb_connection, pub fn init(db: Database) !Connection { var conn: Connection = undefined; if( c.duckdb_connect(db._db, &conn._conn) == c.DuckDBError ){ return error.DuckDBError; } return conn; } pub fn deinit(self: *Connection) void { c.duckdb_disconnect(&self._conn); } /// Query returning a result. Caller needs to call result.deinit() pub fn query(self: *Connection, q: [:0]const u8, comptime res_type: type) !Result(res_type) { return try Result(res_type).init(self._conn, q); } /// Query with no results and autoclean pub fn run(self: *Connection, q: [:0]const u8) !void{ var x = try Result(void).init(self._conn, q); defer x.deinit(); } }; pub const Database = @This(); _db: c.duckdb_database, pub fn init(file: [*c]const u8) !Database{ var db : Database = undefined; if (c.duckdb_open(file, &db._db) == c.DuckDBError) { return error.DuckDBError; } return db; } pub fn deinit(self: *Database) void{ c.duckdb_close(&self._db); } pub fn connect(self: Database) !Connection { return Connection.init(self); } test "Open and connect" { var database = try Database.init(":memory:"); defer database.deinit(); var connection = try database.connect(); defer connection.deinit(); } test "Simple querying" { var database = try Database.init(":memory:"); defer database.deinit(); var connection = try database.connect(); defer connection.deinit(); const s : type = comptime struct { primer: i32, // This is safe because the first column is NOT NULL segund: ?i32 }; try connection.run("CREATE TABLE integers (i INTEGER NOT NULL, j INTEGER);"); try connection.run("INSERT INTO integers VALUES (3, 4), (5, 6), (7, NULL);"); var result = try connection.query("SELECT * FROM integers;", s); defer result.deinit(); try std.testing.expect(2 == result.getColumnCount()); var z = try result.next(); try std.testing.expect(z.primer == 3); try std.testing.expect(z.segund.? == 4); z = try result.next(); try std.testing.expect(z.primer == 5); try std.testing.expect(z.segund.? == 6); z = try result.next(); try std.testing.expect(z.primer == 7); try std.testing.expect(z.segund == null); } test "Checks if all fields are captured" { var database = try Database.init(":memory:"); defer database.deinit(); var connection = try database.connect(); defer connection.deinit(); const s : type = comptime struct { primer: ?i32, segund: ?i32, tercer: ?i32, }; try connection.run("CREATE TABLE integers (i INTEGER NOT NULL, j INTEGER);"); try connection.run("INSERT INTO integers VALUES (3, 4), (5, 6), (7, NULL);"); try std.testing.expectError(error.QueryColumnCountCapture, connection.query("SELECT * FROM integers;", s)); } test "String queries" { var database = try Database.init(":memory:"); defer database.deinit(); var connection = try database.connect(); defer connection.deinit(); const s : type = comptime struct { primer: []const u8 }; try connection.run("CREATE TABLE text (i VARCHAR NOT NULL );"); try connection.run("INSERT INTO text VALUES ('Inlined');"); try connection.run("INSERT INTO text VALUES ('A very long string that is not inlined');"); var result = try connection.query("SELECT * FROM text;", s); defer result.deinit(); try std.testing.expect(1 == result.getColumnCount()); var w = try result.next(); try std.testing.expect(std.mem.eql(u8, w.primer, "Inlined")); w = try result.next(); try std.testing.expect(std.mem.eql(u8, w.primer, "A very long string that is not inlined")); }