diff options
author | Ekaitz Zarraga <ekaitz@elenq.tech> | 2024-08-05 22:20:19 +0200 |
---|---|---|
committer | Ekaitz Zarraga <ekaitz@elenq.tech> | 2024-08-05 22:20:46 +0200 |
commit | 8efa7cb0013aae1708e2d815b7fa65f055062159 (patch) | |
tree | 4b815a71d8d09b00a29d9435f5b52369fa482081 | |
parent | 2c5149d5ca84b28d371dffa2c5ea3cae53c93a8e (diff) |
Results with proper conversion
-rw-r--r-- | src/duckdb/Column.zig | 204 | ||||
-rw-r--r-- | src/duckdb/Database.zig | 34 | ||||
-rw-r--r-- | src/duckdb/Result.zig | 18 | ||||
-rw-r--r-- | src/duckdb/Types.zig | 66 |
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") - }; -} |