From c60c42fc64d9d95b707fc90b7bc08beffd5416af Mon Sep 17 00:00:00 2001 From: Ekaitz Zarraga Date: Sat, 3 Aug 2024 13:43:41 +0200 Subject: Reorganize duckdb --- src/duckdb/Connection.zig | 47 ++++++++++ src/duckdb/Database.zig | 145 +++++++++++++++++++++++++++++++ src/duckdb/PreparedStatement.zig | 4 + src/duckdb/db.zig | 183 --------------------------------------- 4 files changed, 196 insertions(+), 183 deletions(-) create mode 100644 src/duckdb/Connection.zig create mode 100644 src/duckdb/Database.zig delete mode 100644 src/duckdb/db.zig (limited to 'src/duckdb') diff --git a/src/duckdb/Connection.zig b/src/duckdb/Connection.zig new file mode 100644 index 0000000..e62b2db --- /dev/null +++ b/src/duckdb/Connection.zig @@ -0,0 +1,47 @@ +const c = @cImport({ + @cInclude("duckdb.h"); +}); +const Result = @import("Result.zig").Result; +const PreparedStatement = @import("PreparedStatement.zig"); + +_conn: c.duckdb_connection, + +const Self = @This(); + +pub fn init(conn: c.duckdb_connection) Self { + return .{ + ._conn = conn + }; +} + +pub fn deinit(self: *Self) void { + c.duckdb_disconnect(&self._conn); +} + +/// Query returning a result. Caller needs to call result.deinit() +pub fn query(self: *Self, q: [:0]const u8, comptime res_type: type) + !Result(res_type) { + var result: c.duckdb_result = undefined; + const state = c.duckdb_query(self._conn, q, &result); + if ( state == c.DuckDBError ){ + return error.DuckDBError; + } + return try Result(res_type).init(result); +} + +/// Query with no results and autoclean +pub fn run(self: *Self, q: [:0]const u8) !void{ + var x = try self.query(q, void); + defer x.deinit(); +} + +/// Make a prepared query. Caller needs to call prepared_query.deinit() +pub fn prepareStatement(self: *Self, q: [:0]const u8) + !PreparedStatement { + var stmt: c.duckdb_prepared_statement = undefined; + const state = c.duckdb_prepare(self._conn, q, &stmt); + if ( state == c.DuckDBError ){ + return error.DuckDBError; + } + return PreparedStatement.init(stmt); +} diff --git a/src/duckdb/Database.zig b/src/duckdb/Database.zig new file mode 100644 index 0000000..ca2a03a --- /dev/null +++ b/src/duckdb/Database.zig @@ -0,0 +1,145 @@ +const std = @import("std"); +const assert = std.debug.assert; +const c = @cImport({ + @cInclude("duckdb.h"); +}); +const Result = @import("Result.zig").Result; +const Connection = @import("Connection.zig"); +const PreparedStatement = @import("PreparedStatement.zig"); + +const Self = @This(); + +_db: c.duckdb_database, + +/// Creates (opens) a new database that needs to call .deinit() later +pub fn init(file: [*c]const u8) !Self{ + var db : Self = undefined; + if (c.duckdb_open(file, &db._db) == c.DuckDBError) { + return error.DuckDBError; + } + return db; +} + +pub fn deinit(self: *Self) void{ + c.duckdb_close(&self._db); +} + +/// Returns a new Connection that needs to call .deinit() later +pub fn connect(self: Self) !Connection { + var conn: c.duckdb_connection = undefined; + if( c.duckdb_connect(self._db, &conn) == c.DuckDBError ){ + return error.DuckDBError; + } + return Connection.init(conn); +} + +test "Open and connect" { + var database = try Self.init(":memory:"); + defer database.deinit(); + var connection = try database.connect(); + defer connection.deinit(); +} + +test "Simple querying" { + var database = try Self.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 Self.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 Self.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")); +} + +test "Prepared queries" { + var database = try Self.init(":memory:"); + defer database.deinit(); + var connection = try database.connect(); + defer connection.deinit(); + + try connection.run("CREATE TABLE ints (i INTEGER NOT NULL );"); + var prepared = try connection.prepareStatement("INSERT INTO ints VALUES (?), (?);"); + defer prepared.deinit(); + + const uno: i32 = 1; + const dos: i32 = 2; + try prepared.bindAll(.{uno, dos}); + var res = try prepared.exec(void); + res.deinit(); + + const s: type = struct { + primer: i32, + }; + var result = try connection.query("SELECT * FROM ints;", s); + defer result.deinit(); + + var r = try result.next(); + try std.testing.expect(r.primer == uno); + r = try result.next(); + try std.testing.expect(r.primer == dos); +} diff --git a/src/duckdb/PreparedStatement.zig b/src/duckdb/PreparedStatement.zig index e81ac4c..d6b60b2 100644 --- a/src/duckdb/PreparedStatement.zig +++ b/src/duckdb/PreparedStatement.zig @@ -136,6 +136,10 @@ pub fn exec(self: *Self, comptime T: type) !Result(T){ return try Result(T).init(result); } +pub fn clear(self: *Self) void { + c.duckdb_clear_bindings(self._q); +} + pub fn deinit(self: *Self) void{ c.duckdb_destroy_prepare(&self._q); } diff --git a/src/duckdb/db.zig b/src/duckdb/db.zig deleted file mode 100644 index 4fad7cb..0000000 --- a/src/duckdb/db.zig +++ /dev/null @@ -1,183 +0,0 @@ -const std = @import("std"); -const assert = std.debug.assert; -const c = @cImport({ - @cInclude("duckdb.h"); -}); -const Result = @import("Result.zig").Result; -const PreparedStatement = @import("PreparedStatement.zig"); - -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) { - var result: c.duckdb_result = undefined; - const state = c.duckdb_query(self._conn, q, &result); - if ( state == c.DuckDBError ){ - return error.DuckDBError; - } - return try Result(res_type).init(result); - } - - /// Query with no results and autoclean - pub fn run(self: *Connection, q: [:0]const u8) !void{ - var x = try self.query(q, void); - defer x.deinit(); - } - - /// Make a prepared query. Caller needs to call prepared_query.deinit() - pub fn prepareStatement(self: *Connection, q: [:0]const u8) - !PreparedStatement { - var stmt: c.duckdb_prepared_statement = undefined; - const state = c.duckdb_prepare(self._conn, q, &stmt); - if ( state == c.DuckDBError ){ - return error.DuckDBError; - } - return PreparedStatement.init(stmt); - } -}; - - -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")); -} - -test "Prepared queries" { - var database = try Database.init(":memory:"); - defer database.deinit(); - var connection = try database.connect(); - defer connection.deinit(); - - try connection.run("CREATE TABLE ints (i INTEGER NOT NULL );"); - var prepared = try connection.prepareStatement("INSERT INTO ints VALUES (?), (?);"); - defer prepared.deinit(); - - const uno: i32 = 1; - const dos: i32 = 2; - try prepared.bindAll(.{uno, dos}); - var res = try prepared.exec(void); - res.deinit(); - - const s: type = struct { - primer: i32, - }; - var result = try connection.query("SELECT * FROM ints;", s); - defer result.deinit(); - - var r = try result.next(); - try std.testing.expect(r.primer == uno); - r = try result.next(); - try std.testing.expect(r.primer == dos); -} -- cgit v1.2.3