diff options
Diffstat (limited to 'sqlite.zig')
| -rw-r--r-- | sqlite.zig | 64 |
1 files changed, 51 insertions, 13 deletions
| @@ -21,6 +21,7 @@ const getDetailedErrorFromResultCode = errors.getDetailedErrorFromResultCode; | |||
| 21 | 21 | ||
| 22 | const getTestDb = @import("test.zig").getTestDb; | 22 | const getTestDb = @import("test.zig").getTestDb; |
| 23 | pub const vtab = @import("vtab.zig"); | 23 | pub const vtab = @import("vtab.zig"); |
| 24 | |||
| 24 | const helpers = @import("helpers.zig"); | 25 | const helpers = @import("helpers.zig"); |
| 25 | 26 | ||
| 26 | test { | 27 | test { |
| @@ -29,6 +30,43 @@ test { | |||
| 29 | 30 | ||
| 30 | const logger = std.log.scoped(.sqlite); | 31 | const logger = std.log.scoped(.sqlite); |
| 31 | 32 | ||
| 33 | // Returns true if the passed type is a struct. | ||
| 34 | fn isStruct(comptime T: type) bool { | ||
| 35 | const type_info = @typeInfo(T); | ||
| 36 | return type_info == .Struct; | ||
| 37 | } | ||
| 38 | |||
| 39 | // Returns true if the passed type will coerce to []const u8. | ||
| 40 | // | ||
| 41 | // NOTE(vincent): this is straight from the Zig stdlib before it was removed. | ||
| 42 | fn isZigString(comptime T: type) bool { | ||
| 43 | return comptime blk: { | ||
| 44 | // Only pointer types can be strings, no optionals | ||
| 45 | const info = @typeInfo(T); | ||
| 46 | if (info != .Pointer) break :blk false; | ||
| 47 | |||
| 48 | const ptr = &info.Pointer; | ||
| 49 | // Check for CV qualifiers that would prevent coerction to []const u8 | ||
| 50 | if (ptr.is_volatile or ptr.is_allowzero) break :blk false; | ||
| 51 | |||
| 52 | // If it's already a slice, simple check. | ||
| 53 | if (ptr.size == .Slice) { | ||
| 54 | break :blk ptr.child == u8; | ||
| 55 | } | ||
| 56 | |||
| 57 | // Otherwise check if it's an array type that coerces to slice. | ||
| 58 | if (ptr.size == .One) { | ||
| 59 | const child = @typeInfo(ptr.child); | ||
| 60 | if (child == .Array) { | ||
| 61 | const arr = &child.Array; | ||
| 62 | break :blk arr.child == u8; | ||
| 63 | } | ||
| 64 | } | ||
| 65 | |||
| 66 | break :blk false; | ||
| 67 | }; | ||
| 68 | } | ||
| 69 | |||
| 32 | /// Text is used to represent a SQLite TEXT value when binding a parameter or reading a column. | 70 | /// Text is used to represent a SQLite TEXT value when binding a parameter or reading a column. |
| 33 | pub const Text = struct { data: []const u8 }; | 71 | pub const Text = struct { data: []const u8 }; |
| 34 | 72 | ||
| @@ -1062,7 +1100,7 @@ pub fn Iterator(comptime Type: type) type { | |||
| 1062 | .Enum => |TI| { | 1100 | .Enum => |TI| { |
| 1063 | debug.assert(columns == 1); | 1101 | debug.assert(columns == 1); |
| 1064 | 1102 | ||
| 1065 | if (comptime std.meta.trait.isZigString(Type.BaseType)) { | 1103 | if (comptime isZigString(Type.BaseType)) { |
| 1066 | @compileError("cannot read into type " ++ @typeName(Type) ++ " ; BaseType " ++ @typeName(Type.BaseType) ++ " requires allocation, use nextAlloc or oneAlloc"); | 1104 | @compileError("cannot read into type " ++ @typeName(Type) ++ " ; BaseType " ++ @typeName(Type.BaseType) ++ " requires allocation, use nextAlloc or oneAlloc"); |
| 1067 | } | 1105 | } |
| 1068 | 1106 | ||
| @@ -1144,7 +1182,7 @@ pub fn Iterator(comptime Type: type) type { | |||
| 1144 | 1182 | ||
| 1145 | const inner_value = try self.readField(Type.BaseType, .{ .allocator = allocator }, 0); | 1183 | const inner_value = try self.readField(Type.BaseType, .{ .allocator = allocator }, 0); |
| 1146 | 1184 | ||
| 1147 | if (comptime std.meta.trait.isZigString(Type.BaseType)) { | 1185 | if (comptime isZigString(Type.BaseType)) { |
| 1148 | // The inner value is never returned to the user, we must free it ourselves. | 1186 | // The inner value is never returned to the user, we must free it ourselves. |
| 1149 | defer allocator.free(inner_value); | 1187 | defer allocator.free(inner_value); |
| 1150 | 1188 | ||
| @@ -1308,10 +1346,10 @@ pub fn Iterator(comptime Type: type) type { | |||
| 1308 | } | 1346 | } |
| 1309 | 1347 | ||
| 1310 | fn readPointer(self: *Self, comptime PointerType: type, options: anytype, i: usize) !PointerType { | 1348 | fn readPointer(self: *Self, comptime PointerType: type, options: anytype, i: usize) !PointerType { |
| 1311 | if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) { | 1349 | if (!comptime isStruct(@TypeOf(options))) { |
| 1312 | @compileError("options passed to readPointer must be a struct"); | 1350 | @compileError("options passed to readPointer must be a struct"); |
| 1313 | } | 1351 | } |
| 1314 | if (!comptime std.meta.trait.hasField("allocator")(@TypeOf(options))) { | 1352 | if (!@hasField(@TypeOf(options), "allocator")) { |
| 1315 | @compileError("options passed to readPointer must have an allocator field"); | 1353 | @compileError("options passed to readPointer must have an allocator field"); |
| 1316 | } | 1354 | } |
| 1317 | 1355 | ||
| @@ -1339,7 +1377,7 @@ pub fn Iterator(comptime Type: type) type { | |||
| 1339 | } | 1377 | } |
| 1340 | 1378 | ||
| 1341 | fn readOptional(self: *Self, comptime OptionalType: type, options: anytype, _i: usize) !OptionalType { | 1379 | fn readOptional(self: *Self, comptime OptionalType: type, options: anytype, _i: usize) !OptionalType { |
| 1342 | if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) { | 1380 | if (!comptime isStruct(@TypeOf(options))) { |
| 1343 | @compileError("options passed to readOptional must be a struct"); | 1381 | @compileError("options passed to readOptional must be a struct"); |
| 1344 | } | 1382 | } |
| 1345 | 1383 | ||
| @@ -1384,7 +1422,7 @@ pub fn Iterator(comptime Type: type) type { | |||
| 1384 | // | 1422 | // |
| 1385 | // TODO(vincent): add comptime checks for the fields/columns. | 1423 | // TODO(vincent): add comptime checks for the fields/columns. |
| 1386 | fn readStruct(self: *Self, options: anytype) !Type { | 1424 | fn readStruct(self: *Self, options: anytype) !Type { |
| 1387 | if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) { | 1425 | if (!comptime isStruct(@TypeOf(options))) { |
| 1388 | @compileError("options passed to readStruct must be a struct"); | 1426 | @compileError("options passed to readStruct must be a struct"); |
| 1389 | } | 1427 | } |
| 1390 | 1428 | ||
| @@ -1402,7 +1440,7 @@ pub fn Iterator(comptime Type: type) type { | |||
| 1402 | } | 1440 | } |
| 1403 | 1441 | ||
| 1404 | fn readField(self: *Self, comptime FieldType: type, options: anytype, i: usize) !FieldType { | 1442 | fn readField(self: *Self, comptime FieldType: type, options: anytype, i: usize) !FieldType { |
| 1405 | if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) { | 1443 | if (!comptime isStruct(@TypeOf(options))) { |
| 1406 | @compileError("options passed to readField must be a struct"); | 1444 | @compileError("options passed to readField must be a struct"); |
| 1407 | } | 1445 | } |
| 1408 | 1446 | ||
| @@ -1410,13 +1448,13 @@ pub fn Iterator(comptime Type: type) type { | |||
| 1410 | 1448 | ||
| 1411 | return switch (FieldType) { | 1449 | return switch (FieldType) { |
| 1412 | Blob => blk: { | 1450 | Blob => blk: { |
| 1413 | if (!comptime std.meta.trait.hasField("allocator")(@TypeOf(options))) { | 1451 | if (!@hasField(@TypeOf(options), "allocator")) { |
| 1414 | @compileError("options passed to readPointer must have an allocator field when reading a Blob"); | 1452 | @compileError("options passed to readPointer must have an allocator field when reading a Blob"); |
| 1415 | } | 1453 | } |
| 1416 | break :blk try self.readBytes(Blob, options.allocator, i, .Blob); | 1454 | break :blk try self.readBytes(Blob, options.allocator, i, .Blob); |
| 1417 | }, | 1455 | }, |
| 1418 | Text => blk: { | 1456 | Text => blk: { |
| 1419 | if (!comptime std.meta.trait.hasField("allocator")(@TypeOf(options))) { | 1457 | if (!@hasField(@TypeOf(options), "allocator")) { |
| 1420 | @compileError("options passed to readField must have an allocator field when reading a Text"); | 1458 | @compileError("options passed to readField must have an allocator field when reading a Text"); |
| 1421 | } | 1459 | } |
| 1422 | break :blk try self.readBytes(Text, options.allocator, i, .Text); | 1460 | break :blk try self.readBytes(Text, options.allocator, i, .Text); |
| @@ -1432,7 +1470,7 @@ pub fn Iterator(comptime Type: type) type { | |||
| 1432 | .Enum => |TI| { | 1470 | .Enum => |TI| { |
| 1433 | const inner_value = try self.readField(FieldType.BaseType, options, i); | 1471 | const inner_value = try self.readField(FieldType.BaseType, options, i); |
| 1434 | 1472 | ||
| 1435 | if (comptime std.meta.trait.isZigString(FieldType.BaseType)) { | 1473 | if (comptime isZigString(FieldType.BaseType)) { |
| 1436 | // The inner value is never returned to the user, we must free it ourselves. | 1474 | // The inner value is never returned to the user, we must free it ourselves. |
| 1437 | defer options.allocator.free(inner_value); | 1475 | defer options.allocator.free(inner_value); |
| 1438 | 1476 | ||
| @@ -1642,7 +1680,7 @@ pub const DynamicStatement = struct { | |||
| 1642 | return convertResultToError(result); | 1680 | return convertResultToError(result); |
| 1643 | }, | 1681 | }, |
| 1644 | .Enum => { | 1682 | .Enum => { |
| 1645 | if (comptime std.meta.trait.isZigString(FieldType.BaseType)) { | 1683 | if (comptime isZigString(FieldType.BaseType)) { |
| 1646 | try self.bindField(FieldType.BaseType, options, field_name, i, @tagName(field)); | 1684 | try self.bindField(FieldType.BaseType, options, field_name, i, @tagName(field)); |
| 1647 | } else if (@typeInfo(FieldType.BaseType) == .Int) { | 1685 | } else if (@typeInfo(FieldType.BaseType) == .Int) { |
| 1648 | try self.bindField(FieldType.BaseType, options, field_name, i, @intFromEnum(field)); | 1686 | try self.bindField(FieldType.BaseType, options, field_name, i, @intFromEnum(field)); |
| @@ -1655,7 +1693,7 @@ pub const DynamicStatement = struct { | |||
| 1655 | try self.bindField(info.backing_integer.?, options, field_name, i, @as(info.backing_integer.?, @bitCast(field))); | 1693 | try self.bindField(info.backing_integer.?, options, field_name, i, @as(info.backing_integer.?, @bitCast(field))); |
| 1656 | return; | 1694 | return; |
| 1657 | } | 1695 | } |
| 1658 | if (!comptime std.meta.trait.hasFn("bindField")(FieldType)) { | 1696 | if (!comptime helpers.hasFn(FieldType, "bindField")) { |
| 1659 | @compileError("cannot bind field " ++ field_name ++ " of type " ++ @typeName(FieldType) ++ ", consider implementing the bindField() method"); | 1697 | @compileError("cannot bind field " ++ field_name ++ " of type " ++ @typeName(FieldType) ++ ", consider implementing the bindField() method"); |
| 1660 | } | 1698 | } |
| 1661 | 1699 | ||
| @@ -2013,7 +2051,7 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: anytype) type | |||
| 2013 | /// The types are checked at comptime. | 2051 | /// The types are checked at comptime. |
| 2014 | fn bind(self: *Self, options: anytype, values: anytype) !void { | 2052 | fn bind(self: *Self, options: anytype, values: anytype) !void { |
| 2015 | const StructType = @TypeOf(values); | 2053 | const StructType = @TypeOf(values); |
| 2016 | if (!comptime std.meta.trait.is(.Struct)(@TypeOf(values))) { | 2054 | if (!comptime isStruct(StructType)) { |
| 2017 | @compileError("options passed to Statement.bind must be a struct (DynamicStatement supports runtime slices)"); | 2055 | @compileError("options passed to Statement.bind must be a struct (DynamicStatement supports runtime slices)"); |
| 2018 | } | 2056 | } |
| 2019 | 2057 | ||