summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--sqlite.zig114
1 files changed, 103 insertions, 11 deletions
diff --git a/sqlite.zig b/sqlite.zig
index 04a3b24..ab35fa3 100644
--- a/sqlite.zig
+++ b/sqlite.zig
@@ -981,6 +981,10 @@ pub fn Iterator(comptime Type: type) type {
981 } 981 }
982 @compileError("enum column " ++ @typeName(FieldType) ++ " must have a BaseType of either string or int"); 982 @compileError("enum column " ++ @typeName(FieldType) ++ " must have a BaseType of either string or int");
983 }, 983 },
984 .Struct => {
985 const innervalue = try self.readField(FieldType.BaseType, options, i);
986 return try FieldType.readField(options.allocator, innervalue);
987 },
984 else => @compileError("cannot populate field of type " ++ @typeName(FieldType)), 988 else => @compileError("cannot populate field of type " ++ @typeName(FieldType)),
985 }, 989 },
986 }; 990 };
@@ -1100,7 +1104,7 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t
1100 /// } 1104 /// }
1101 /// 1105 ///
1102 /// The types are checked at comptime. 1106 /// The types are checked at comptime.
1103 fn bind(self: *Self, values: anytype) void { 1107 fn bind(self: *Self, options: anytype, values: anytype) !void {
1104 const StructType = @TypeOf(values); 1108 const StructType = @TypeOf(values);
1105 const StructTypeInfo = @typeInfo(StructType).Struct; 1109 const StructTypeInfo = @typeInfo(StructType).Struct;
1106 1110
@@ -1129,7 +1133,7 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t
1129 1133
1130 const field_value = @field(values, struct_field.name); 1134 const field_value = @field(values, struct_field.name);
1131 1135
1132 self.bindField(struct_field.field_type, struct_field.name, _i, field_value); 1136 try self.bindField(struct_field.field_type, options, struct_field.name, _i, field_value);
1133 } 1137 }
1134 } 1138 }
1135 1139
@@ -1139,7 +1143,7 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t
1139 } 1143 }
1140 } 1144 }
1141 1145
1142 fn bindField(self: *Self, comptime FieldType: type, comptime field_name: []const u8, i: c_int, field: FieldType) void { 1146 fn bindField(self: *Self, comptime FieldType: type, options: anytype, comptime field_name: []const u8, i: c_int, field: FieldType) !void {
1143 const field_type_info = @typeInfo(FieldType); 1147 const field_type_info = @typeInfo(FieldType);
1144 const column = i + 1; 1148 const column = i + 1;
1145 1149
@@ -1152,7 +1156,7 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t
1152 .Float, .ComptimeFloat => _ = c.sqlite3_bind_double(self.stmt, column, field), 1156 .Float, .ComptimeFloat => _ = c.sqlite3_bind_double(self.stmt, column, field),
1153 .Bool => _ = c.sqlite3_bind_int64(self.stmt, column, @boolToInt(field)), 1157 .Bool => _ = c.sqlite3_bind_int64(self.stmt, column, @boolToInt(field)),
1154 .Pointer => |ptr| switch (ptr.size) { 1158 .Pointer => |ptr| switch (ptr.size) {
1155 .One => self.bindField(ptr.child, field_name, i, field.*), 1159 .One => try self.bindField(ptr.child, options, field_name, i, field.*),
1156 .Slice => switch (ptr.child) { 1160 .Slice => switch (ptr.child) {
1157 u8 => { 1161 u8 => {
1158 _ = c.sqlite3_bind_text(self.stmt, column, field.ptr, @intCast(c_int, field.len), null); 1162 _ = c.sqlite3_bind_text(self.stmt, column, field.ptr, @intCast(c_int, field.len), null);
@@ -1172,20 +1176,23 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t
1172 } 1176 }
1173 }, 1177 },
1174 .Optional => |opt| if (field) |non_null_field| { 1178 .Optional => |opt| if (field) |non_null_field| {
1175 self.bindField(opt.child, field_name, i, non_null_field); 1179 try self.bindField(opt.child, options, field_name, i, non_null_field);
1176 } else { 1180 } else {
1177 _ = c.sqlite3_bind_null(self.stmt, column); 1181 _ = c.sqlite3_bind_null(self.stmt, column);
1178 }, 1182 },
1179 .Null => _ = c.sqlite3_bind_null(self.stmt, column), 1183 .Null => _ = c.sqlite3_bind_null(self.stmt, column),
1180 .Enum => { 1184 .Enum => {
1181 if (comptime std.meta.trait.isZigString(FieldType.BaseType)) { 1185 if (comptime std.meta.trait.isZigString(FieldType.BaseType)) {
1182 return self.bindField(FieldType.BaseType, field_name, i, @tagName(field)); 1186 return try self.bindField(FieldType.BaseType, options, field_name, i, @tagName(field));
1183 } 1187 }
1184 if (@typeInfo(FieldType.BaseType) == .Int) { 1188 if (@typeInfo(FieldType.BaseType) == .Int) {
1185 return self.bindField(FieldType.BaseType, field_name, i, @enumToInt(field)); 1189 return try self.bindField(FieldType.BaseType, options, field_name, i, @enumToInt(field));
1186 } 1190 }
1187 @compileError("enum column " ++ @typeName(FieldType) ++ " must have a BaseType of either string or int to bind"); 1191 @compileError("enum column " ++ @typeName(FieldType) ++ " must have a BaseType of either string or int to bind");
1188 }, 1192 },
1193 .Struct => {
1194 return try self.bindField(FieldType.BaseType, options, field_name, i, try field.bindField(options.allocator));
1195 },
1189 else => @compileError("cannot bind field " ++ field_name ++ " of type " ++ @typeName(FieldType)), 1196 else => @compileError("cannot bind field " ++ field_name ++ " of type " ++ @typeName(FieldType)),
1190 }, 1197 },
1191 } 1198 }
@@ -1199,7 +1206,24 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t
1199 /// in the input query string. 1206 /// in the input query string.
1200 /// 1207 ///
1201 pub fn exec(self: *Self, options: QueryOptions, values: anytype) !void { 1208 pub fn exec(self: *Self, options: QueryOptions, values: anytype) !void {
1202 self.bind(values); 1209 try self.bind({}, values);
1210
1211 var dummy_diags = Diagnostics{};
1212 var diags = options.diags orelse &dummy_diags;
1213
1214 const result = c.sqlite3_step(self.stmt);
1215 switch (result) {
1216 c.SQLITE_DONE => {},
1217 else => {
1218 diags.err = getLastDetailedErrorFromDb(self.db);
1219 return errors.errorFromResultCode(result);
1220 },
1221 }
1222 }
1223
1224 /// execAlloc is like `exec` but can allocate memory.
1225 pub fn execAlloc(self: *Self, allocator: *std.mem.Allocator, options: QueryOptions, values: anytype) !void {
1226 try self.bind(.{ .allocator = allocator }, values);
1203 1227
1204 var dummy_diags = Diagnostics{}; 1228 var dummy_diags = Diagnostics{};
1205 var diags = options.diags orelse &dummy_diags; 1229 var diags = options.diags orelse &dummy_diags;
@@ -1233,7 +1257,18 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t
1233 /// 1257 ///
1234 /// The iterator _must not_ outlive the statement. 1258 /// The iterator _must not_ outlive the statement.
1235 pub fn iterator(self: *Self, comptime Type: type, values: anytype) !Iterator(Type) { 1259 pub fn iterator(self: *Self, comptime Type: type, values: anytype) !Iterator(Type) {
1236 self.bind(values); 1260 try self.bind({}, values);
1261
1262 var res: Iterator(Type) = undefined;
1263 res.db = self.db;
1264 res.stmt = self.stmt;
1265
1266 return res;
1267 }
1268
1269 /// iteratorAlloc is like `iterator` but can allocate memory.
1270 pub fn iteratorAlloc(self: *Self, comptime Type: type, allocator: *std.mem.Allocator, values: anytype) !Iterator(Type) {
1271 try self.bind(.{ .allocator = allocator }, values);
1237 1272
1238 var res: Iterator(Type) = undefined; 1273 var res: Iterator(Type) = undefined;
1239 res.db = self.db; 1274 res.db = self.db;
@@ -1276,7 +1311,7 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t
1276 1311
1277 /// oneAlloc is like `one` but can allocate memory. 1312 /// oneAlloc is like `one` but can allocate memory.
1278 pub fn oneAlloc(self: *Self, comptime Type: type, allocator: *mem.Allocator, options: QueryOptions, values: anytype) !?Type { 1313 pub fn oneAlloc(self: *Self, comptime Type: type, allocator: *mem.Allocator, options: QueryOptions, values: anytype) !?Type {
1279 var iter = try self.iterator(Type, values); 1314 var iter = try self.iteratorAlloc(Type, allocator, values);
1280 1315
1281 const row = (try iter.nextAlloc(allocator, options)) orelse return null; 1316 const row = (try iter.nextAlloc(allocator, options)) orelse return null;
1282 return row; 1317 return row;
@@ -1309,7 +1344,7 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t
1309 /// 1344 ///
1310 /// Note that this allocates all rows into a single slice: if you read a lot of data this can use a lot of memory. 1345 /// Note that this allocates all rows into a single slice: if you read a lot of data this can use a lot of memory.
1311 pub fn all(self: *Self, comptime Type: type, allocator: *mem.Allocator, options: QueryOptions, values: anytype) ![]Type { 1346 pub fn all(self: *Self, comptime Type: type, allocator: *mem.Allocator, options: QueryOptions, values: anytype) ![]Type {
1312 var iter = try self.iterator(Type, values); 1347 var iter = try self.iteratorAlloc(Type, allocator, values);
1313 1348
1314 var rows = std.ArrayList(Type).init(allocator); 1349 var rows = std.ArrayList(Type).init(allocator);
1315 while (try iter.nextAlloc(allocator, options)) |row| { 1350 while (try iter.nextAlloc(allocator, options)) |row| {
@@ -2223,3 +2258,60 @@ fn dbMode(allocator: *mem.Allocator) Db.Mode {
2223 break :blk .{ .File = path }; 2258 break :blk .{ .File = path };
2224 }; 2259 };
2225} 2260}
2261
2262const MyData = struct {
2263 data: [16]u8,
2264
2265 const BaseType = []const u8;
2266
2267 pub fn bindField(self: MyData, allocator: *std.mem.Allocator) !BaseType {
2268 return try std.fmt.allocPrint(allocator, "{}", .{std.fmt.fmtSliceHexLower(&self.data)});
2269 }
2270
2271 pub fn readField(alloc: *std.mem.Allocator, value: BaseType) !MyData {
2272 _ = alloc;
2273
2274 var result = [_]u8{0} ** 16;
2275 var i: usize = 0;
2276 while (i < result.len) : (i += 1) {
2277 const j = i * 2;
2278 result[i] = try std.fmt.parseUnsigned(u8, value[j..][0..2], 16);
2279 }
2280 return MyData{ .data = result };
2281 }
2282};
2283
2284test "sqlite: bind custom type" {
2285 var arena = std.heap.ArenaAllocator.init(testing.allocator);
2286 defer arena.deinit();
2287
2288 var db = try getTestDb();
2289 try addTestData(&db);
2290
2291 const my_data = MyData{
2292 .data = [_]u8{'x'} ** 16,
2293 };
2294
2295 {
2296 // insertion
2297 var stmt = try db.prepare("INSERT INTO article(data) VALUES(?)");
2298 try stmt.execAlloc(&arena.allocator, .{}, .{my_data});
2299 }
2300 {
2301 // reading back
2302 var stmt = try db.prepare("SELECT * FROM article");
2303 defer stmt.deinit();
2304
2305 const Article = struct {
2306 id: u32,
2307 author_id: u32,
2308 data: MyData,
2309 is_published: bool,
2310 };
2311
2312 const row = try stmt.oneAlloc(Article, &arena.allocator, .{}, .{});
2313
2314 try testing.expect(row != null);
2315 try testing.expectEqualSlices(u8, &my_data.data, &row.?.data.data);
2316 }
2317}