diff options
| author | 2021-10-07 13:54:20 +0200 | |
|---|---|---|
| committer | 2021-10-07 13:54:20 +0200 | |
| commit | 5ae36bbcece7ec84ce6124b76115432264c4427e (patch) | |
| tree | bb3f7ff8d9574bbd5248bec0d5282de7bf8922a8 /sqlite.zig | |
| parent | build: fix for latest zig (diff) | |
| parent | use `try` instead of `catch unreachable` (diff) | |
| download | zig-sqlite-5ae36bbcece7ec84ce6124b76115432264c4427e.tar.gz zig-sqlite-5ae36bbcece7ec84ce6124b76115432264c4427e.tar.xz zig-sqlite-5ae36bbcece7ec84ce6124b76115432264c4427e.zip | |
Merge pull request #52 from nektro/bind-alloc
Allow `bind` to accept options and add BaseType handling for structs
Diffstat (limited to 'sqlite.zig')
| -rw-r--r-- | sqlite.zig | 114 |
1 files changed, 103 insertions, 11 deletions
| @@ -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 | |||
| 2262 | const 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 | |||
| 2284 | test "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 | } | ||