summaryrefslogtreecommitdiff
path: root/sqlite.zig
diff options
context:
space:
mode:
Diffstat (limited to 'sqlite.zig')
-rw-r--r--sqlite.zig400
1 files changed, 252 insertions, 148 deletions
diff --git a/sqlite.zig b/sqlite.zig
index d08717b..3c2e924 100644
--- a/sqlite.zig
+++ b/sqlite.zig
@@ -120,6 +120,161 @@ pub const Db = struct {
120 } 120 }
121}; 121};
122 122
123/// Iterator allows iterating over a result set.
124///
125/// Each call to `next` returns the next row of the result set, or null if the result set is exhausted.
126/// Each row will have the type `Type` so the columns returned in the result set must be compatible with this type.
127///
128/// Here is an example of how to use the iterator:
129///
130/// const User = struct {
131/// name: Text,
132/// age: u16,
133/// };
134///
135/// var stmt = try db.prepare("SELECT name, age FROM user");
136/// defer stmt.deinit();
137///
138/// var iter = try stmt.iterator(User, .{});
139/// while (true) {
140/// const row: User = (try iter.next(.{})) orelse break;
141/// ...
142/// }
143///
144/// The iterator _must not_ outlive the statement.
145pub fn Iterator(comptime Type: type) type {
146 return struct {
147 const Self = @This();
148
149 const TypeInfo = @typeInfo(Type);
150
151 stmt: *c.sqlite3_stmt,
152
153 pub fn next(self: *Self, options: anytype) !?Type {
154 var result = c.sqlite3_step(self.stmt);
155 if (result == c.SQLITE_DONE) {
156 return null;
157 }
158
159 if (result != c.SQLITE_ROW) {
160 logger.err("unable to iterate, result: {}", .{result});
161 return error.SQLiteStepError;
162 }
163
164 const columns = c.sqlite3_column_count(self.stmt);
165
166 return switch (TypeInfo) {
167 .Int => blk: {
168 debug.assert(columns == 1);
169 break :blk try self.readInt(options);
170 },
171 .Float => blk: {
172 debug.assert(columns == 1);
173 break :blk try self.readFloat(options);
174 },
175 .Struct => blk: {
176 std.debug.assert(columns == TypeInfo.Struct.fields.len);
177 break :blk try self.readStruct(options);
178 },
179 else => @compileError("cannot read into type " ++ @typeName(Type)),
180 };
181 }
182
183 fn readInt(self: *Self, options: anytype) !Type {
184 const n = c.sqlite3_column_int64(self.stmt, 0);
185 return @intCast(Type, n);
186 }
187
188 fn readFloat(self: *Self, options: anytype) !Type {
189 const d = c.sqlite3_column_double(self.stmt, 0);
190 return @floatCast(Type, d);
191 }
192
193 const ReadBytesMode = enum {
194 Blob,
195 Text,
196 };
197
198 fn readBytes(self: *Self, options: anytype, mode: ReadBytesMode, _i: usize, ptr: *[]const u8) !void {
199 const i = @intCast(c_int, _i);
200 switch (mode) {
201 .Blob => {
202 const data = c.sqlite3_column_blob(self.stmt, i);
203 if (data == null) ptr.* = "";
204
205 const size = @intCast(usize, c.sqlite3_column_bytes(self.stmt, i));
206
207 var tmp = try options.allocator.alloc(u8, size);
208 mem.copy(u8, tmp, @ptrCast([*c]const u8, data)[0..size]);
209
210 ptr.* = tmp;
211 },
212 .Text => {
213 const data = c.sqlite3_column_text(self.stmt, i);
214 if (data == null) ptr.* = "";
215
216 const size = @intCast(usize, c.sqlite3_column_bytes(self.stmt, i));
217
218 var tmp = try options.allocator.alloc(u8, size);
219 mem.copy(u8, tmp, @ptrCast([*c]const u8, data)[0..size]);
220
221 ptr.* = tmp;
222 },
223 }
224 }
225
226 fn readStruct(self: *Self, options: anytype) !Type {
227 var value: Type = undefined;
228
229 inline for (@typeInfo(Type).Struct.fields) |field, _i| {
230 const i = @as(usize, _i);
231 const field_type_info = @typeInfo(field.field_type);
232
233 switch (field.field_type) {
234 []const u8, []u8 => {
235 try self.readBytes(options, .Blob, i, &@field(value, field.name));
236 },
237 Blob => {
238 try self.readBytes(options, .Blob, i, &@field(value, field.name).data);
239 },
240 Text => {
241 try self.readBytes(options, .Text, i, &@field(value, field.name).data);
242 },
243 else => switch (field_type_info) {
244 .Int => {
245 const n = c.sqlite3_column_int64(self.stmt, i);
246 @field(value, field.name) = @intCast(field.field_type, n);
247 },
248 .Float => {
249 const f = c.sqlite3_column_double(self.stmt, i);
250 @field(value, field.name) = f;
251 },
252 .Void => {
253 @field(value, field.name) = {};
254 },
255 .Array => |arr| {
256 switch (arr.child) {
257 u8 => {
258 const data = c.sqlite3_column_blob(self.stmt, i);
259 const size = @intCast(usize, c.sqlite3_column_bytes(self.stmt, i));
260
261 if (size > @as(usize, arr.len)) return error.ArrayTooSmall;
262
263 mem.copy(u8, @field(value, field.name)[0..], @ptrCast([*c]const u8, data)[0..size]);
264 },
265 else => @compileError("cannot populate field " ++ field.name ++ " of type array of " ++ @typeName(arr.child)),
266 }
267 },
268 else => @compileError("cannot populate field " ++ field.name ++ " of type " ++ @typeName(field.field_type)),
269 },
270 }
271 }
272
273 return value;
274 }
275 };
276}
277
123pub const StatementOptions = struct {}; 278pub const StatementOptions = struct {};
124 279
125/// Statement is a wrapper around a SQLite statement, providing high-level functions to execute 280/// Statement is a wrapper around a SQLite statement, providing high-level functions to execute
@@ -222,7 +377,7 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t
222 /// } 377 /// }
223 /// 378 ///
224 /// The types are checked at comptime. 379 /// The types are checked at comptime.
225 pub fn bind(self: *Self, values: anytype) void { 380 fn bind(self: *Self, values: anytype) void {
226 const StructType = @TypeOf(values); 381 const StructType = @TypeOf(values);
227 const StructTypeInfo = @typeInfo(StructType).Struct; 382 const StructTypeInfo = @typeInfo(StructType).Struct;
228 383
@@ -285,6 +440,34 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t
285 } 440 }
286 } 441 }
287 442
443 /// iterator returns an iterator to read data from the result set, one row at a time.
444 ///
445 /// The data in the row is used to populate a value of the type `Type`.
446 /// This means that `Type` must have as many fields as is returned in the query
447 /// executed by this statement.
448 /// This also means that the type of each field must be compatible with the SQLite type.
449 ///
450 /// Here is an example of how to use the iterator:
451 ///
452 /// var iter = try stmt.iterator(usize, .{});
453 /// while (true) {
454 /// const row = (try iter.next(.{})) orelse break;
455 /// ...
456 /// }
457 ///
458 /// The `values` tuple is used for the bind parameters. It must have as many fields as there are bind markers
459 /// in the input query string.
460 ///
461 /// The iterator _must not_ outlive the statement.
462 pub fn iterator(self: *Self, comptime Type: type, values: anytype) !Iterator(Type) {
463 self.bind(values);
464
465 var res: Iterator(Type) = undefined;
466 res.stmt = self.stmt;
467
468 return res;
469 }
470
288 /// one reads a single row from the result set of this statement. 471 /// one reads a single row from the result set of this statement.
289 /// 472 ///
290 /// The data in the row is used to populate a value of the type `Type`. 473 /// The data in the row is used to populate a value of the type `Type`.
@@ -312,32 +495,13 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t
312 /// 495 ///
313 pub fn one(self: *Self, comptime Type: type, options: anytype, values: anytype) !?Type { 496 pub fn one(self: *Self, comptime Type: type, options: anytype, values: anytype) !?Type {
314 if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) { 497 if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) {
315 @compileError("options passed to all must be a struct"); 498 @compileError("options passed to iterator must be a struct");
316 } 499 }
317 const TypeInfo = @typeInfo(Type);
318
319 self.bind(values);
320 500
321 var result = c.sqlite3_step(self.stmt); 501 var iter = try self.iterator(Type, values);
322 502
323 switch (TypeInfo) { 503 const row = (try iter.next(options)) orelse return null;
324 .Int => return switch (result) { 504 return row;
325 c.SQLITE_ROW => try self.readInt(Type, options),
326 c.SQLITE_DONE => null,
327 else => std.debug.panic("invalid result {}", .{result}),
328 },
329 .Float => return switch (result) {
330 c.SQLITE_ROW => try self.readFloat(Type, options),
331 c.SQLITE_DONE => null,
332 else => std.debug.panic("invalid result {}", .{result}),
333 },
334 .Struct => return switch (result) {
335 c.SQLITE_ROW => try self.readStruct(Type, options),
336 c.SQLITE_DONE => null,
337 else => std.debug.panic("invalid result {}", .{result}),
338 },
339 else => @compileError("cannot read into type " ++ @typeName(Type)),
340 }
341 } 505 }
342 506
343 /// all reads all rows from the result set of this statement. 507 /// all reads all rows from the result set of this statement.
@@ -370,137 +534,18 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t
370 /// 534 ///
371 pub fn all(self: *Self, comptime Type: type, options: anytype, values: anytype) ![]Type { 535 pub fn all(self: *Self, comptime Type: type, options: anytype, values: anytype) ![]Type {
372 if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) { 536 if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) {
373 @compileError("options passed to all must be a struct"); 537 @compileError("options passed to iterator must be a struct");
374 } 538 }
375 const TypeInfo = @typeInfo(Type); 539 var iter = try self.iterator(Type, values);
376
377 self.bind(values);
378 540
379 var rows = std.ArrayList(Type).init(options.allocator); 541 var rows = std.ArrayList(Type).init(options.allocator);
380 542 while (true) {
381 var result = c.sqlite3_step(self.stmt); 543 const row = (try iter.next(options)) orelse break;
382 while (result == c.SQLITE_ROW) : (result = c.sqlite3_step(self.stmt)) { 544 try rows.append(row);
383 const columns = c.sqlite3_column_count(self.stmt);
384
385 var value = switch (TypeInfo) {
386 .Int => blk: {
387 debug.assert(columns == 1);
388 break :blk try self.readInt(Type, options);
389 },
390 .Float => blk: {
391 debug.assert(columns == 1);
392 break :blk try self.readFloat(Type, options);
393 },
394 .Struct => blk: {
395 std.debug.assert(columns == @typeInfo(Type).Struct.fields.len);
396 break :blk try self.readStruct(Type, options);
397 },
398 else => @compileError("cannot read into type " ++ @typeName(Type)),
399 };
400
401 try rows.append(value);
402 }
403
404 if (result != c.SQLITE_DONE) {
405 logger.err("unable to iterate, result: {}", .{result});
406 return error.SQLiteStepError;
407 } 545 }
408 546
409 return rows.span(); 547 return rows.span();
410 } 548 }
411
412 fn readInt(self: *Self, comptime Type: type, options: anytype) !Type {
413 const n = c.sqlite3_column_int64(self.stmt, 0);
414 return @intCast(Type, n);
415 }
416
417 fn readFloat(self: *Self, comptime Type: type, options: anytype) !Type {
418 const d = c.sqlite3_column_double(self.stmt, 0);
419 return @floatCast(Type, d);
420 }
421
422 const ReadBytesMode = enum {
423 Blob,
424 Text,
425 };
426
427 fn readBytes(self: *Self, allocator: *mem.Allocator, mode: ReadBytesMode, _i: usize, ptr: *[]const u8) !void {
428 const i = @intCast(c_int, _i);
429 switch (mode) {
430 .Blob => {
431 const data = c.sqlite3_column_blob(self.stmt, i);
432 if (data == null) ptr.* = "";
433
434 const size = @intCast(usize, c.sqlite3_column_bytes(self.stmt, i));
435
436 var tmp = try allocator.alloc(u8, size);
437 mem.copy(u8, tmp, @ptrCast([*c]const u8, data)[0..size]);
438
439 ptr.* = tmp;
440 },
441 .Text => {
442 const data = c.sqlite3_column_text(self.stmt, i);
443 if (data == null) ptr.* = "";
444
445 const size = @intCast(usize, c.sqlite3_column_bytes(self.stmt, i));
446
447 var tmp = try allocator.alloc(u8, size);
448 mem.copy(u8, tmp, @ptrCast([*c]const u8, data)[0..size]);
449
450 ptr.* = tmp;
451 },
452 }
453 }
454
455 fn readStruct(self: *Self, comptime Type: type, options: anytype) !Type {
456 var value: Type = undefined;
457
458 inline for (@typeInfo(Type).Struct.fields) |field, _i| {
459 const i = @as(usize, _i);
460 const field_type_info = @typeInfo(field.field_type);
461
462 switch (field.field_type) {
463 []const u8, []u8 => {
464 try self.readBytes(options.allocator, .Blob, i, &@field(value, field.name));
465 },
466 Blob => {
467 try self.readBytes(options.allocator, .Blob, i, &@field(value, field.name).data);
468 },
469 Text => {
470 try self.readBytes(options.allocator, .Text, i, &@field(value, field.name).data);
471 },
472 else => switch (field_type_info) {
473 .Int => {
474 const n = c.sqlite3_column_int64(self.stmt, i);
475 @field(value, field.name) = @intCast(field.field_type, n);
476 },
477 .Float => {
478 const f = c.sqlite3_column_double(self.stmt, i);
479 @field(value, field.name) = f;
480 },
481 .Void => {
482 @field(value, field.name) = {};
483 },
484 .Array => |arr| {
485 switch (arr.child) {
486 u8 => {
487 const data = c.sqlite3_column_blob(self.stmt, i);
488 const size = @intCast(usize, c.sqlite3_column_bytes(self.stmt, i));
489
490 if (size > @as(usize, arr.len)) return error.ArrayTooSmall;
491
492 mem.copy(u8, @field(value, field.name)[0..], @ptrCast([*c]const u8, data)[0..size]);
493 },
494 else => @compileError("cannot populate field " ++ field.name ++ " of type array of " ++ @typeName(arr.child)),
495 }
496 },
497 else => @compileError("cannot populate field " ++ field.name ++ " of type " ++ @typeName(field.field_type)),
498 },
499 }
500 }
501
502 return value;
503 }
504 }; 549 };
505} 550}
506 551
@@ -715,6 +760,65 @@ test "sqlite: statement reset" {
715 } 760 }
716} 761}
717 762
763test "sqlite: statement iterator" {
764 var arena = std.heap.ArenaAllocator.init(testing.allocator);
765 defer arena.deinit();
766 var allocator = &arena.allocator;
767
768 var db: Db = undefined;
769 try db.init(testing.allocator, .{ .mode = dbMode() });
770
771 // Create the tables
772 inline for (AllDDL) |ddl| {
773 try db.exec(ddl, .{});
774 }
775
776 // Add data
777 var stmt = try db.prepare("INSERT INTO user(id, name, age) VALUES(?{usize}, ?{[]const u8}, ?{usize})");
778 defer stmt.deinit();
779
780 var expected_rows = std.ArrayList(TestUser).init(allocator);
781 var i: usize = 0;
782 while (i < 20) : (i += 1) {
783 const name = try std.fmt.allocPrint(allocator, "Vincent {}", .{i});
784 const user = TestUser{ .id = i, .name = name, .age = i + 200 };
785
786 try expected_rows.append(user);
787
788 stmt.reset();
789 try stmt.exec(user);
790
791 const rows_inserted = db.rowsAffected();
792 testing.expectEqual(@as(usize, 1), rows_inserted);
793 }
794
795 // Get the data with an iterator
796 var stmt2 = try db.prepare("SELECT name, age FROM user");
797 defer stmt2.deinit();
798
799 const Type = struct {
800 name: Text,
801 age: usize,
802 };
803
804 var iter = try stmt2.iterator(Type, .{});
805
806 var rows = std.ArrayList(Type).init(allocator);
807 while (true) {
808 const row = (try iter.next(.{ .allocator = allocator })) orelse break;
809 try rows.append(row);
810 }
811
812 // Check the data
813 testing.expectEqual(expected_rows.span().len, rows.span().len);
814
815 for (rows.span()) |row, j| {
816 const exp_row = expected_rows.span()[j];
817 testing.expectEqualStrings(exp_row.name, row.name.data);
818 testing.expectEqual(exp_row.age, row.age);
819 }
820}
821
718fn dbMode() Db.Mode { 822fn dbMode() Db.Mode {
719 return if (build_options.is_ci) blk: { 823 return if (build_options.is_ci) blk: {
720 break :blk .{ .Memory = {} }; 824 break :blk .{ .Memory = {} };