summaryrefslogtreecommitdiff
path: root/sqlite.zig
diff options
context:
space:
mode:
Diffstat (limited to 'sqlite.zig')
-rw-r--r--sqlite.zig59
1 files changed, 58 insertions, 1 deletions
diff --git a/sqlite.zig b/sqlite.zig
index f68d13f..2600ee2 100644
--- a/sqlite.zig
+++ b/sqlite.zig
@@ -548,6 +548,29 @@ pub fn Iterator(comptime Type: type) type {
548 return ret; 548 return ret;
549 } 549 }
550 550
551 fn readOptional(self: *Self, comptime OptionalType: type, options: anytype, _i: usize) !OptionalType {
552 const i = @intCast(c_int, _i);
553 const type_info = @typeInfo(OptionalType);
554
555 var ret: OptionalType = undefined;
556 switch (type_info) {
557 .Optional => |opt| {
558 // Easy way to know if the column represents a null value.
559 const value = c.sqlite3_column_value(self.stmt, i);
560 const datatype = c.sqlite3_value_type(value);
561
562 if (datatype == c.SQLITE_NULL) {
563 return null;
564 } else {
565 const val = try self.readField(opt.child, _i, options);
566 ret = val;
567 return ret;
568 }
569 },
570 else => @compileError("cannot read optional of type " ++ @typeName(OptionalType)),
571 }
572 }
573
551 // readStruct reads an entire sqlite row into a struct. 574 // readStruct reads an entire sqlite row into a struct.
552 // 575 //
553 // Each field correspond to a column; its position in the struct determines the column used for it. 576 // Each field correspond to a column; its position in the struct determines the column used for it.
@@ -596,7 +619,8 @@ pub fn Iterator(comptime Type: type) type {
596 .Void => {}, 619 .Void => {},
597 .Array => try self.readArray(FieldType, i), 620 .Array => try self.readArray(FieldType, i),
598 .Pointer => try self.readPointer(FieldType, options.allocator, i), 621 .Pointer => try self.readPointer(FieldType, options.allocator, i),
599 else => @compileError("cannot populate field " ++ field.name ++ " of type " ++ @typeName(FieldType)), 622 .Optional => try self.readOptional(FieldType, options, i),
623 else => @compileError("cannot populate field of type " ++ @typeName(FieldType)),
600 }, 624 },
601 }; 625 };
602 } 626 }
@@ -758,6 +782,12 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t
758 else => @compileError("cannot bind field " ++ field_name ++ " of type array of " ++ @typeName(arr.child)), 782 else => @compileError("cannot bind field " ++ field_name ++ " of type array of " ++ @typeName(arr.child)),
759 } 783 }
760 }, 784 },
785 .Optional => |opt| if (field) |non_null_field| {
786 self.bindField(opt.child, field_name, i, non_null_field);
787 } else {
788 _ = c.sqlite3_bind_null(self.stmt, column);
789 },
790 .Null => _ = c.sqlite3_bind_null(self.stmt, column),
761 else => @compileError("cannot bind field " ++ field_name ++ " of type " ++ @typeName(FieldType)), 791 else => @compileError("cannot bind field " ++ field_name ++ " of type " ++ @typeName(FieldType)),
762 }, 792 },
763 } 793 }
@@ -1380,6 +1410,33 @@ test "sqlite: read pointers" {
1380 } 1410 }
1381} 1411}
1382 1412
1413test "sqlite: optional" {
1414 var arena = std.heap.ArenaAllocator.init(testing.allocator);
1415 defer arena.deinit();
1416
1417 var db: Db = undefined;
1418 try db.init(initOptions());
1419 try addTestData(&db);
1420
1421 try db.exec("INSERT INTO article(author_id, data, is_published) VALUES(?, ?, ?)", .{ 1, null, true });
1422
1423 var stmt = try db.prepare("SELECT data, is_published FROM article");
1424 defer stmt.deinit();
1425
1426 const row = try stmt.one(
1427 struct {
1428 data: ?[128:0]u8,
1429 is_published: ?bool,
1430 },
1431 .{},
1432 .{},
1433 );
1434
1435 testing.expect(row != null);
1436 testing.expect(row.?.data == null);
1437 testing.expectEqual(true, row.?.is_published.?);
1438}
1439
1383test "sqlite: statement reset" { 1440test "sqlite: statement reset" {
1384 var db: Db = undefined; 1441 var db: Db = undefined;
1385 try db.init(initOptions()); 1442 try db.init(initOptions());