summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorEkaitz Zarraga <ekaitz@elenq.tech>2024-08-05 22:20:19 +0200
committerEkaitz Zarraga <ekaitz@elenq.tech>2024-08-05 22:20:46 +0200
commit8efa7cb0013aae1708e2d815b7fa65f055062159 (patch)
tree4b815a71d8d09b00a29d9435f5b52369fa482081 /src
parent2c5149d5ca84b28d371dffa2c5ea3cae53c93a8e (diff)
Results with proper conversion
Diffstat (limited to 'src')
-rw-r--r--src/duckdb/Column.zig204
-rw-r--r--src/duckdb/Database.zig34
-rw-r--r--src/duckdb/Result.zig18
-rw-r--r--src/duckdb/Types.zig66
4 files changed, 244 insertions, 78 deletions
diff --git a/src/duckdb/Column.zig b/src/duckdb/Column.zig
new file mode 100644
index 0000000..fb505bb
--- /dev/null
+++ b/src/duckdb/Column.zig
@@ -0,0 +1,204 @@
+const std = @import("std");
+const c = @cImport({
+ @cInclude("duckdb.h");
+});
+
+
+const DuckdbType = enum {
+ BOOLEAN,
+ TINYINT,
+ SMALLINT,
+ INTEGER,
+ BIGINT,
+ UTINYINT,
+ USMALLINT,
+ UINTEGER,
+ UBIGINT,
+ FLOAT,
+ DOUBLE,
+ TIMESTAMP,
+ DATE,
+ TIME,
+ INTERVAL,
+ HUGEINT,
+ UHUGEINT,
+ VARCHAR,
+ BLOB,
+ TIMESTAMP_S,
+ TIMESTAMP_MS,
+ TIMESTAMP_NS,
+ UUID,
+ TIME_TZ,
+ TIMESTAMP_TZ,
+};
+
+pub const Column = union(DuckdbType) {
+ BOOLEAN: [*]bool,
+ TINYINT: [*]i8,
+ SMALLINT: [*]i16,
+ INTEGER: [*]i32,
+ BIGINT: [*]i64,
+ UTINYINT: [*]u8,
+ USMALLINT: [*]u16,
+ UINTEGER: [*]u32,
+ UBIGINT: [*]u64,
+ FLOAT: [*]f32,
+ DOUBLE: [*]f64,
+ TIMESTAMP: [*]c.duckdb_timestamp,
+ DATE: [*]c.duckdb_date,
+ TIME: [*]c.duckdb_time,
+ INTERVAL: [*]c.duckdb_interval,
+ HUGEINT: [*]c.duckdb_hugeint,
+ UHUGEINT: [*]c.duckdb_uhugeint,
+ VARCHAR: [*]c.duckdb_string_t,
+ BLOB: [*]c.duckdb_string_t,
+ TIMESTAMP_S: [*]c.duckdb_timestamp,
+ TIMESTAMP_MS: [*]c.duckdb_timestamp,
+ TIMESTAMP_NS: [*]c.duckdb_timestamp,
+ UUID: [*]c.duckdb_hugeint,
+ TIME_TZ: [*]c.duckdb_time_tz,
+ TIMESTAMP_TZ: [*]c.duckdb_timestamp,
+
+ const Self = @This();
+
+ pub fn fromType(column_type: c.DUCKDB_TYPE, column: *anyopaque) Column {
+ return switch (column_type) {
+ c.DUCKDB_TYPE_BOOLEAN => .{.BOOLEAN = @alignCast(@ptrCast(column))},
+ c.DUCKDB_TYPE_TINYINT => .{.TINYINT = @alignCast(@ptrCast(column))},
+ c.DUCKDB_TYPE_SMALLINT => .{.SMALLINT = @alignCast(@ptrCast(column))},
+ c.DUCKDB_TYPE_INTEGER => .{.INTEGER = @alignCast(@ptrCast(column))},
+ c.DUCKDB_TYPE_BIGINT => .{.BIGINT = @alignCast(@ptrCast(column))},
+ c.DUCKDB_TYPE_UTINYINT => .{.UTINYINT = @alignCast(@ptrCast(column))},
+ c.DUCKDB_TYPE_USMALLINT => .{.USMALLINT = @alignCast(@ptrCast(column))},
+ c.DUCKDB_TYPE_UINTEGER => .{.UINTEGER = @alignCast(@ptrCast(column))},
+ c.DUCKDB_TYPE_UBIGINT => .{.UBIGINT = @alignCast(@ptrCast(column))},
+ c.DUCKDB_TYPE_FLOAT => .{.FLOAT = @alignCast(@ptrCast(column))},
+ c.DUCKDB_TYPE_DOUBLE => .{.DOUBLE = @alignCast(@ptrCast(column))},
+ c.DUCKDB_TYPE_VARCHAR => .{.VARCHAR = @alignCast(@ptrCast(column))},
+ c.DUCKDB_TYPE_BLOB => .{.BLOB = @alignCast(@ptrCast(column))},
+ c.DUCKDB_TYPE_TIMESTAMP => .{.TIMESTAMP = @alignCast(@ptrCast(column))},
+ c.DUCKDB_TYPE_DATE => .{.DATE = @alignCast(@ptrCast(column))},
+ c.DUCKDB_TYPE_TIME => .{.TIME = @alignCast(@ptrCast(column))},
+ c.DUCKDB_TYPE_INTERVAL => .{.INTERVAL = @alignCast(@ptrCast(column))},
+ c.DUCKDB_TYPE_HUGEINT => .{.HUGEINT = @alignCast(@ptrCast(column))},
+ c.DUCKDB_TYPE_UHUGEINT => .{.UHUGEINT = @alignCast(@ptrCast(column))},
+ c.DUCKDB_TYPE_TIMESTAMP_S => .{.TIMESTAMP_S = @alignCast(@ptrCast(column))},
+ c.DUCKDB_TYPE_TIMESTAMP_MS => .{.TIMESTAMP_MS = @alignCast(@ptrCast(column))},
+ c.DUCKDB_TYPE_TIMESTAMP_NS => .{.TIMESTAMP_NS = @alignCast(@ptrCast(column))},
+ c.DUCKDB_TYPE_UUID => .{.UUID = @alignCast(@ptrCast(column))},
+ c.DUCKDB_TYPE_TIME_TZ => .{.TIME_TZ = @alignCast(@ptrCast(column))},
+ c.DUCKDB_TYPE_TIMESTAMP_TZ => .{.TIMESTAMP_TZ = @alignCast(@ptrCast(column))},
+ else => unreachable,
+ };
+ }
+
+ pub fn getAs(self: Self, i: usize, T: anytype) !T{
+ const ti : std.builtin.Type = @typeInfo(T);
+ switch (self){
+ .BOOLEAN
+ => |ptr| switch(ti) {
+ .Bool => return ptr[i],
+ .Int => return @intFromBool(ptr[i]),
+ else => return error.InvalidConversion,
+ },
+ .TINYINT
+ => |ptr| switch(ti) {
+ .Int => return @intCast(ptr[i]),
+ .Float => return @floatFromInt(ptr[i]),
+ else => return error.InvalidConversion,
+ },
+ .SMALLINT
+ => |ptr| switch(ti) {
+ .Int => return @intCast(ptr[i]),
+ .Float => return @floatFromInt(ptr[i]),
+ else => return error.InvalidConversion,
+ },
+ .INTEGER
+ => |ptr| switch(ti) {
+ .Int => return @intCast(ptr[i]),
+ .Float => return @floatFromInt(ptr[i]),
+ else => return error.InvalidConversion,
+ },
+ .BIGINT
+ => |ptr| switch(ti) {
+ .Int => return @intCast(ptr[i]),
+ .Float => return @floatFromInt(ptr[i]),
+ else => return error.InvalidConversion,
+ },
+ .UTINYINT
+ => |ptr| switch(ti) {
+ .Int => return @intCast(ptr[i]),
+ .Float => return @floatFromInt(ptr[i]),
+ else => return error.InvalidConversion,
+ },
+ .USMALLINT
+ => |ptr| switch(ti) {
+ .Int => return @intCast(ptr[i]),
+ .Float => return @floatFromInt(ptr[i]),
+ else => return error.InvalidConversion,
+ },
+ .UINTEGER
+ => |ptr| switch(ti) {
+ .Int => return @intCast(ptr[i]),
+ .Float => return @floatFromInt(ptr[i]),
+ else => return error.InvalidConversion,
+ },
+ .UBIGINT
+ => |ptr| switch(ti) {
+ .Int => return @intCast(ptr[i]),
+ .Float => return @floatFromInt(ptr[i]),
+ else => return error.InvalidConversion,
+ },
+ .FLOAT
+ => |ptr| switch(ti) {
+ .Int => return @intFromFloat(ptr[i]),
+ .Float => return @floatCast(ptr[i]),
+ else => return error.InvalidConversion,
+ },
+ .DOUBLE
+ => |ptr| switch(ti) {
+ .Int => return @intFromFloat(ptr[i]),
+ .Float => return @floatCast(ptr[i]),
+ else => return error.InvalidConversion,
+ },
+ .VARCHAR,
+ .BLOB
+ => |ptr| switch(ti){
+ .Pointer => |p| switch (p.size) {
+ .Slice => {
+ if ( p.child == u8 ) {
+ var result: T = undefined;
+ if (c.duckdb_string_is_inlined(ptr[i])){
+ result = &ptr[i].value.inlined.inlined;
+ result.len = ptr[i].value.inlined.length;
+ return result;
+ } else {
+ result.len = ptr[i].value.pointer.length;
+ result.ptr = ptr[i].value.pointer.ptr;
+ return result;
+ }
+ } else {
+ @compileError("Invalid type for output data: "
+ ++ @typeName(T));
+ }
+ },
+ else => return error.InvalidConversion,
+ },
+ else => return error.InvalidConversion,
+ },
+ .TIMESTAMP,
+ .DATE,
+ .TIME,
+ .INTERVAL,
+ .HUGEINT,
+ .UHUGEINT,
+ .TIMESTAMP_S,
+ .TIMESTAMP_MS,
+ .TIMESTAMP_NS,
+ .UUID,
+ .TIME_TZ,
+ .TIMESTAMP_TZ =>
+ return error.ConversionNotImplemented,
+ }
+ }
+};
diff --git a/src/duckdb/Database.zig b/src/duckdb/Database.zig
index ca2a03a..ffdd052 100644
--- a/src/duckdb/Database.zig
+++ b/src/duckdb/Database.zig
@@ -72,7 +72,39 @@ test "Simple querying" {
try std.testing.expect(z.segund == null);
}
-test "Checks if all fields are captured" {
+test "Simple querying with conversion" {
+ var database = try Self.init(":memory:");
+ defer database.deinit();
+ var connection = try database.connect();
+ defer connection.deinit();
+
+ const s : type = comptime struct {
+ primer: f64, // Converting to f64
+ 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.0);
+ try std.testing.expect(z.segund.? == 4);
+
+ z = try result.next();
+ try std.testing.expect(z.primer == 5.0);
+ try std.testing.expect(z.segund.? == 6);
+
+ z = try result.next();
+ try std.testing.expect(z.primer == 7.0);
+ try std.testing.expect(z.segund == null);
+}
+
+test "All fields are captured" {
var database = try Self.init(":memory:");
defer database.deinit();
var connection = try database.connect();
diff --git a/src/duckdb/Result.zig b/src/duckdb/Result.zig
index 61a30ce..87d9f6f 100644
--- a/src/duckdb/Result.zig
+++ b/src/duckdb/Result.zig
@@ -1,5 +1,5 @@
const std = @import("std");
-const types = @import("Types.zig");
+const Column = @import("Column.zig").Column;
const c = @cImport({
@cInclude("duckdb.h");
});
@@ -98,10 +98,7 @@ pub fn Result(comptime T: type) type{
inline for (fields, 0..) |field, i| {
// TODO: check compatibility between the column type and
// the struct provided as result container
- //
const column_type = c.duckdb_column_type(&self._res, i);
- // std.debug.print("tipo => {any}\n", .{column_type});
- // std.debug.print("{any}\n", .{self._data});
// Check validity
const entry_idx :usize = self._current_row / 64;
@@ -115,21 +112,20 @@ pub fn Result(comptime T: type) type{
// Store the column in current row
if (is_valid){
- // Unwrap the Optional
+ // Unwrap the output Optional
const t = switch (@typeInfo(field.type)){
.Optional => |t| t.child,
else => field.type
};
- // Unpack the C array of duckdb_type
- const col: [*]types.unpack_type(t) = @alignCast(@ptrCast(self._data[i]));
- std.debug.assert(types.valid_unpack(column_type, t));
+ // Obtain and convert column to something we can process
+ const column = Column.fromType(column_type, self._data[i].?);
- // Convert to Zig data type
- @field(result, field.name) = try types.cast(&col[self._current_row], t);
+ // Convert to Zig data type and store in output struct
+ @field(result, field.name) = try column.getAs(self._current_row, t);
} else {
// Got a NULL from the DB
if (@typeInfo(field.type) != .Optional){
- // Cannot return it because it's not optional
+ // Cannot return it because output it's not optional
return error.NullInNotOptional;
}
@field(result, field.name) = null;
diff --git a/src/duckdb/Types.zig b/src/duckdb/Types.zig
deleted file mode 100644
index 2a12cd2..0000000
--- a/src/duckdb/Types.zig
+++ /dev/null
@@ -1,66 +0,0 @@
-const std = @import("std");
-const c = @cImport({
- @cInclude("duckdb.h");
-});
-
-pub fn unpack_type(T: type) type {
- return switch(@typeInfo(T)) {
- .Bool, .Int, .Float => T,
- .Pointer => |p| switch (p.size) {
- .Slice => if (p.child == u8) c.duckdb_string_t else
- @compileError("Invalid type for output data"),
- else => @compileError("Invalid type for output data")
- },
- // .Array =>
- // .Struct =>
- // .Enum =>
- // .Union =>
- else => @compileError("Invalid type for output data")
- };
-}
-
-pub fn valid_unpack(column_type: c.DUCKDB_TYPE, T: type) bool {
- switch (column_type) {
- c.DUCKDB_TYPE_BOOLEAN => if(T == bool) return true else return false,
- c.DUCKDB_TYPE_TINYINT => if(T == i8 ) return true else return false,
- c.DUCKDB_TYPE_SMALLINT => if(T == i16 ) return true else return false,
- c.DUCKDB_TYPE_INTEGER => if(T == i32 ) return true else return false,
- c.DUCKDB_TYPE_BIGINT => if(T == i64 ) return true else return false,
- c.DUCKDB_TYPE_UTINYINT => if(T == u8 ) return true else return false,
- c.DUCKDB_TYPE_USMALLINT => if(T == u16 ) return true else return false,
- c.DUCKDB_TYPE_UINTEGER => if(T == u32 ) return true else return false,
- c.DUCKDB_TYPE_UBIGINT => if(T == u64 ) return true else return false,
- c.DUCKDB_TYPE_FLOAT => if(T == f32 ) return true else return false,
- c.DUCKDB_TYPE_DOUBLE => if(T == f64 ) return true else return false,
- c.DUCKDB_TYPE_VARCHAR => if(T == []const u8) return true else return false,
- c.DUCKDB_TYPE_BLOB => if(T == []const u8) return true else return false,
- else => return false,
- }
-}
-
-/// Receives a pointer of the element (!)
-pub fn cast(el: anytype, T: type) !T {
- return switch (@typeInfo(T)) {
- .Bool, .Int, .Float => el.*,
- .Pointer => |p| switch (p.size) {
- .Slice => blk: {
- if ( p.child == u8 ) {
- var result: T = undefined;
- if (c.duckdb_string_is_inlined(el.*)){
- result = &el.value.inlined.inlined;
- result.len = el.value.inlined.length;
- break :blk result;
- } else {
- result.len = el.value.pointer.length;
- result.ptr = el.value.pointer.ptr;
- break :blk result;
- }
- } else {
- @compileError("Invalid type for output data");
- }
- },
- else => @compileError("Invalid type for output data")
- },
- else => @compileError("Invalid type for output data")
- };
-}