summaryrefslogtreecommitdiff
path: root/sqlite.zig
diff options
context:
space:
mode:
Diffstat (limited to 'sqlite.zig')
-rw-r--r--sqlite.zig206
1 files changed, 124 insertions, 82 deletions
diff --git a/sqlite.zig b/sqlite.zig
index 817cff8..015dd64 100644
--- a/sqlite.zig
+++ b/sqlite.zig
@@ -180,6 +180,9 @@ pub fn Iterator(comptime Type: type) type {
180 180
181 stmt: *c.sqlite3_stmt, 181 stmt: *c.sqlite3_stmt,
182 182
183 // next scans the next row using the preapred statement.
184 //
185 // If it returns null iterating is done.
183 pub fn next(self: *Self, options: anytype) !?Type { 186 pub fn next(self: *Self, options: anytype) !?Type {
184 var result = c.sqlite3_step(self.stmt); 187 var result = c.sqlite3_step(self.stmt);
185 if (result == c.SQLITE_DONE) { 188 if (result == c.SQLITE_DONE) {
@@ -196,21 +199,15 @@ pub fn Iterator(comptime Type: type) type {
196 switch (Type) { 199 switch (Type) {
197 []const u8, []u8 => { 200 []const u8, []u8 => {
198 debug.assert(columns == 1); 201 debug.assert(columns == 1);
199 var ret: Type = undefined; 202 return try self.readBytes(Type, 0, .Text, options);
200 try self.readBytes(options, .Text, 0, &ret);
201 return ret;
202 }, 203 },
203 Blob => { 204 Blob => {
204 debug.assert(columns == 1); 205 debug.assert(columns == 1);
205 var ret: Type = undefined; 206 return try self.readBytes(Blob, 0, .Blob, options);
206 try self.readBytes(options, .Blob, 0, &ret.data);
207 return ret;
208 }, 207 },
209 Text => { 208 Text => {
210 debug.assert(columns == 1); 209 debug.assert(columns == 1);
211 var ret: Type = undefined; 210 return try self.readBytes(Text, 0, .Text, options);
212 try self.readBytes(options, .Text, 0, &ret.data);
213 return ret;
214 }, 211 },
215 else => {}, 212 else => {},
216 } 213 }
@@ -218,24 +215,22 @@ pub fn Iterator(comptime Type: type) type {
218 switch (TypeInfo) { 215 switch (TypeInfo) {
219 .Int => { 216 .Int => {
220 debug.assert(columns == 1); 217 debug.assert(columns == 1);
221 return try self.readInt(options); 218 return try self.readInt(Type, 0, options);
222 }, 219 },
223 .Float => { 220 .Float => {
224 debug.assert(columns == 1); 221 debug.assert(columns == 1);
225 return try self.readFloat(options); 222 return try self.readFloat(Type, 0, options);
226 }, 223 },
227 .Bool => { 224 .Bool => {
228 debug.assert(columns == 1); 225 debug.assert(columns == 1);
229 return try self.readBool(options); 226 return try self.readBool(0, options);
230 }, 227 },
231 .Void => { 228 .Void => {
232 debug.assert(columns == 1); 229 debug.assert(columns == 1);
233 }, 230 },
234 .Array => { 231 .Array => {
235 debug.assert(columns == 1); 232 debug.assert(columns == 1);
236 var ret: Type = undefined; 233 return try self.readArray(Type, 0);
237 try self.readArray(Type, 0, &ret);
238 return ret;
239 }, 234 },
240 .Struct => { 235 .Struct => {
241 std.debug.assert(columns == TypeInfo.Struct.fields.len); 236 std.debug.assert(columns == TypeInfo.Struct.fields.len);
@@ -245,10 +240,17 @@ pub fn Iterator(comptime Type: type) type {
245 } 240 }
246 } 241 }
247 242
248 fn readArray(self: *Self, comptime ArrayType: type, _i: usize, array: anytype) !void { 243 // readArray reads a sqlite BLOB or TEXT column into an array of u8.
244 //
245 // We also require the array to have a sentinel because otherwise we have no way
246 // of communicating the end of the data to the caller.
247 //
248 // If the array is too small for the data an error will be returned.
249 fn readArray(self: *Self, comptime ArrayType: type, _i: usize) error{ArrayTooSmall}!ArrayType {
249 const i = @intCast(c_int, _i); 250 const i = @intCast(c_int, _i);
250 const array_type_info = @typeInfo(ArrayType); 251 const array_type_info = @typeInfo(ArrayType);
251 252
253 var ret: ArrayType = undefined;
252 switch (array_type_info) { 254 switch (array_type_info) {
253 .Array => |arr| { 255 .Array => |arr| {
254 comptime if (arr.sentinel == null) { 256 comptime if (arr.sentinel == null) {
@@ -264,28 +266,32 @@ pub fn Iterator(comptime Type: type) type {
264 266
265 const ptr = @ptrCast([*c]const u8, data)[0..size]; 267 const ptr = @ptrCast([*c]const u8, data)[0..size];
266 268
267 mem.copy(u8, array[0..], ptr); 269 mem.copy(u8, ret[0..], ptr);
268 array[size] = arr.sentinel.?; 270 ret[size] = arr.sentinel.?;
269 }, 271 },
270 else => @compileError("cannot populate field " ++ field.name ++ " of type array of " ++ @typeName(arr.child)), 272 else => @compileError("cannot populate field " ++ field.name ++ " of type array of " ++ @typeName(arr.child)),
271 } 273 }
272 }, 274 },
273 else => @compileError("cannot populate field " ++ field.name ++ " of type array of " ++ @typeName(arr.child)), 275 else => @compileError("cannot populate field " ++ field.name ++ " of type array of " ++ @typeName(arr.child)),
274 } 276 }
277 return ret;
275 } 278 }
276 279
277 fn readInt(self: *Self, options: anytype) !Type { 280 // readInt reads a sqlite INTEGER column into an integer.
278 const n = c.sqlite3_column_int64(self.stmt, 0); 281 fn readInt(self: *Self, comptime IntType: type, i: usize, options: anytype) !IntType {
279 return @intCast(Type, n); 282 const n = c.sqlite3_column_int64(self.stmt, @intCast(c_int, i));
283 return @intCast(IntType, n);
280 } 284 }
281 285
282 fn readFloat(self: *Self, options: anytype) !Type { 286 // readFloat reads a sqlite REAL column into a float.
283 const d = c.sqlite3_column_double(self.stmt, 0); 287 fn readFloat(self: *Self, comptime FloatType: type, i: usize, options: anytype) !FloatType {
284 return @floatCast(Type, d); 288 const d = c.sqlite3_column_double(self.stmt, @intCast(c_int, i));
289 return @floatCast(FloatType, d);
285 } 290 }
286 291
287 fn readBool(self: *Self, options: anytype) !Type { 292 // readFloat reads a sqlite INTEGER column into a bool (true is anything > 0, false is anything <= 0).
288 const d = c.sqlite3_column_int64(self.stmt, 0); 293 fn readBool(self: *Self, i: usize, options: anytype) !bool {
294 const d = c.sqlite3_column_int64(self.stmt, @intCast(c_int, i));
289 return d > 0; 295 return d > 0;
290 } 296 }
291 297
@@ -294,34 +300,83 @@ pub fn Iterator(comptime Type: type) type {
294 Text, 300 Text,
295 }; 301 };
296 302
297 fn readBytes(self: *Self, options: anytype, mode: ReadBytesMode, _i: usize, ptr: *[]const u8) !void { 303 // readBytes reads a sqlite BLOB or TEXT column.
304 //
305 // The mode controls which sqlite function is used to retrieve the data:
306 // * .Blob uses sqlite3_column_blob
307 // * .Text uses sqlite3_column_text
308 //
309 // When using .Blob you can only read into either []const u8, []u8 or Blob.
310 // When using .Text you can only read into either []const u8, []u8 or Text.
311 //
312 // The options must contain an `allocator` field which will be used to create a copy of the data.
313 fn readBytes(self: *Self, comptime BytesType: type, _i: usize, comptime mode: ReadBytesMode, options: anytype) !BytesType {
298 const i = @intCast(c_int, _i); 314 const i = @intCast(c_int, _i);
315
316 var ret: BytesType = switch (BytesType) {
317 Text, Blob => .{ .data = "" },
318 else => "", // TODO(vincent): I think with a []u8 this will crash if the caller attempts to modify it...
319 };
320
299 switch (mode) { 321 switch (mode) {
300 .Blob => { 322 .Blob => {
301 const data = c.sqlite3_column_blob(self.stmt, i); 323 const data = c.sqlite3_column_blob(self.stmt, i);
302 if (data == null) ptr.* = ""; 324 if (data == null) return ret;
303 325
304 const size = @intCast(usize, c.sqlite3_column_bytes(self.stmt, i)); 326 const size = @intCast(usize, c.sqlite3_column_bytes(self.stmt, i));
305 327 const ptr = @ptrCast([*c]const u8, data)[0..size];
306 var tmp = try options.allocator.alloc(u8, size); 328
307 mem.copy(u8, tmp, @ptrCast([*c]const u8, data)[0..size]); 329 return switch (BytesType) {
308 330 []const u8, []u8 => try options.allocator.dupe(u8, ptr),
309 ptr.* = tmp; 331 Blob => blk: {
332 var tmp: Blob = undefined;
333 tmp.data = try options.allocator.dupe(u8, ptr);
334 break :blk tmp;
335 },
336 else => @compileError("cannot read blob into type " ++ @typeName(BytesType)),
337 };
310 }, 338 },
311 .Text => { 339 .Text => {
312 const data = c.sqlite3_column_text(self.stmt, i); 340 const data = c.sqlite3_column_text(self.stmt, i);
313 if (data == null) ptr.* = ""; 341 if (data == null) return ret;
314 342
315 const size = @intCast(usize, c.sqlite3_column_bytes(self.stmt, i)); 343 const size = @intCast(usize, c.sqlite3_column_bytes(self.stmt, i));
316 344 const ptr = @ptrCast([*c]const u8, data)[0..size];
317 var tmp = try options.allocator.alloc(u8, size); 345
318 mem.copy(u8, tmp, @ptrCast([*c]const u8, data)[0..size]); 346 return switch (BytesType) {
319 347 []const u8, []u8 => try options.allocator.dupe(u8, ptr),
320 ptr.* = tmp; 348 Text => blk: {
349 var tmp: Text = undefined;
350 tmp.data = try options.allocator.dupe(u8, ptr);
351 break :blk tmp;
352 },
353 else => @compileError("cannot read text into type " ++ @typeName(BytesType)),
354 };
321 }, 355 },
322 } 356 }
323 } 357 }
324 358
359 // readStruct reads an entire sqlite row into a struct.
360 //
361 // Each field correspond to a column; its position in the struct determines the column used for it.
362 // For example, given the following query:
363 //
364 // SELECT id, name, age FROM user
365 //
366 // The struct must have the following fields:
367 //
368 // struct {
369 // id: usize,
370 // name: []const u8,
371 // age: u16,
372 // }
373 //
374 // The field `id` will be associated with the column `id` and so on.
375 //
376 // This function relies on the fact that there are the same number of fields than columns and
377 // that the order is correct.
378 //
379 // TODO(vincent): add comptime checks for the fields/columns.
325 fn readStruct(self: *Self, options: anytype) !Type { 380 fn readStruct(self: *Self, options: anytype) !Type {
326 var value: Type = undefined; 381 var value: Type = undefined;
327 382
@@ -329,38 +384,21 @@ pub fn Iterator(comptime Type: type) type {
329 const i = @as(usize, _i); 384 const i = @as(usize, _i);
330 const field_type_info = @typeInfo(field.field_type); 385 const field_type_info = @typeInfo(field.field_type);
331 386
332 switch (field.field_type) { 387 const ret = switch (field.field_type) {
333 []const u8, []u8 => { 388 []const u8, []u8 => try self.readBytes(field.field_type, i, .Blob, options),
334 try self.readBytes(options, .Blob, i, &@field(value, field.name)); 389 Blob => try self.readBytes(Blob, i, .Blob, options),
335 }, 390 Text => try self.readBytes(Text, i, .Text, options),
336 Blob => {
337 try self.readBytes(options, .Blob, i, &@field(value, field.name).data);
338 },
339 Text => {
340 try self.readBytes(options, .Text, i, &@field(value, field.name).data);
341 },
342 else => switch (field_type_info) { 391 else => switch (field_type_info) {
343 .Int => { 392 .Int => try self.readInt(field.field_type, i, options),
344 const n = c.sqlite3_column_int64(self.stmt, i); 393 .Float => try self.readFloat(field.field_type, i, options),
345 @field(value, field.name) = @intCast(field.field_type, n); 394 .Bool => try self.readBool(i, options),
346 }, 395 .Void => {},
347 .Float => { 396 .Array => try self.readArray(field.field_type, i),
348 const f = c.sqlite3_column_double(self.stmt, i);
349 @field(value, field.name) = f;
350 },
351 .Bool => {
352 const n = c.sqlite3_column_int64(self.stmt, i);
353 @field(value, field.name) = n > 0;
354 },
355 .Void => {
356 @field(value, field.name) = {};
357 },
358 .Array => {
359 try self.readArray(field.field_type, i, &@field(value, field.name));
360 },
361 else => @compileError("cannot populate field " ++ field.name ++ " of type " ++ @typeName(field.field_type)), 397 else => @compileError("cannot populate field " ++ field.name ++ " of type " ++ @typeName(field.field_type)),
362 }, 398 },
363 } 399 };
400
401 @field(value, field.name) = ret;
364 } 402 }
365 403
366 return value; 404 return value;
@@ -647,12 +685,13 @@ const TestUser = struct {
647 id: usize, 685 id: usize,
648 name: []const u8, 686 name: []const u8,
649 age: usize, 687 age: usize,
688 weight: f32,
650}; 689};
651 690
652const test_users = &[_]TestUser{ 691const test_users = &[_]TestUser{
653 .{ .id = 20, .name = "Vincent", .age = 33 }, 692 .{ .id = 20, .name = "Vincent", .age = 33, .weight = 85.4 },
654 .{ .id = 40, .name = "Julien", .age = 35 }, 693 .{ .id = 40, .name = "Julien", .age = 35, .weight = 100.3 },
655 .{ .id = 60, .name = "José", .age = 40 }, 694 .{ .id = 60, .name = "José", .age = 40, .weight = 240.2 },
656}; 695};
657 696
658fn addTestData(db: *Db) !void { 697fn addTestData(db: *Db) !void {
@@ -660,7 +699,8 @@ fn addTestData(db: *Db) !void {
660 \\CREATE TABLE user( 699 \\CREATE TABLE user(
661 \\ id integer PRIMARY KEY, 700 \\ id integer PRIMARY KEY,
662 \\ name text, 701 \\ name text,
663 \\ age integer 702 \\ age integer,
703 \\ weight real
664 \\) 704 \\)
665 , 705 ,
666 \\CREATE TABLE article( 706 \\CREATE TABLE article(
@@ -678,7 +718,7 @@ fn addTestData(db: *Db) !void {
678 } 718 }
679 719
680 for (test_users) |user| { 720 for (test_users) |user| {
681 try db.exec("INSERT INTO user(id, name, age) VALUES(?{usize}, ?{[]const u8}, ?{usize})", user); 721 try db.exec("INSERT INTO user(id, name, age, weight) VALUES(?{usize}, ?{[]const u8}, ?{usize}, ?{f32})", user);
682 722
683 const rows_inserted = db.rowsAffected(); 723 const rows_inserted = db.rowsAffected();
684 testing.expectEqual(@as(usize, 1), rows_inserted); 724 testing.expectEqual(@as(usize, 1), rows_inserted);
@@ -755,7 +795,7 @@ test "sqlite: read a single user into a struct" {
755 try db.init(testing.allocator, .{ .mode = dbMode() }); 795 try db.init(testing.allocator, .{ .mode = dbMode() });
756 try addTestData(&db); 796 try addTestData(&db);
757 797
758 var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?{usize}"); 798 var stmt = try db.prepare("SELECT id, name, age, weight FROM user WHERE id = ?{usize}");
759 defer stmt.deinit(); 799 defer stmt.deinit();
760 800
761 var rows = try stmt.all( 801 var rows = try stmt.all(
@@ -778,7 +818,7 @@ test "sqlite: read all users into a struct" {
778 try db.init(testing.allocator, .{ .mode = dbMode() }); 818 try db.init(testing.allocator, .{ .mode = dbMode() });
779 try addTestData(&db); 819 try addTestData(&db);
780 820
781 var stmt = try db.prepare("SELECT id, name, age FROM user"); 821 var stmt = try db.prepare("SELECT id, name, age, weight FROM user");
782 defer stmt.deinit(); 822 defer stmt.deinit();
783 823
784 var rows = try stmt.all( 824 var rows = try stmt.all(
@@ -803,7 +843,7 @@ test "sqlite: read in an anonymous struct" {
803 try db.init(testing.allocator, .{ .mode = dbMode() }); 843 try db.init(testing.allocator, .{ .mode = dbMode() });
804 try addTestData(&db); 844 try addTestData(&db);
805 845
806 var stmt = try db.prepare("SELECT id, name, name, age, id FROM user WHERE id = ?{usize}"); 846 var stmt = try db.prepare("SELECT id, name, name, age, id, weight FROM user WHERE id = ?{usize}");
807 defer stmt.deinit(); 847 defer stmt.deinit();
808 848
809 var row = try stmt.one( 849 var row = try stmt.one(
@@ -813,6 +853,7 @@ test "sqlite: read in an anonymous struct" {
813 name_2: [200:0xAD]u8, 853 name_2: [200:0xAD]u8,
814 age: usize, 854 age: usize,
815 is_id: bool, 855 is_id: bool,
856 weight: f64,
816 }, 857 },
817 .{ .allocator = &arena.allocator }, 858 .{ .allocator = &arena.allocator },
818 .{ .id = @as(usize, 20) }, 859 .{ .id = @as(usize, 20) },
@@ -825,6 +866,7 @@ test "sqlite: read in an anonymous struct" {
825 testing.expectEqualStrings(exp.name, mem.spanZ(&row.?.name_2)); 866 testing.expectEqualStrings(exp.name, mem.spanZ(&row.?.name_2));
826 testing.expectEqual(exp.age, row.?.age); 867 testing.expectEqual(exp.age, row.?.age);
827 testing.expect(row.?.is_id); 868 testing.expect(row.?.is_id);
869 testing.expectEqual(exp.weight, @floatCast(f32, row.?.weight));
828} 870}
829 871
830test "sqlite: read in a Text struct" { 872test "sqlite: read in a Text struct" {
@@ -998,13 +1040,13 @@ test "sqlite: statement reset" {
998 1040
999 // Add data 1041 // Add data
1000 1042
1001 var stmt = try db.prepare("INSERT INTO user(id, name, age) VALUES(?{usize}, ?{[]const u8}, ?{usize})"); 1043 var stmt = try db.prepare("INSERT INTO user(id, name, age, weight) VALUES(?{usize}, ?{[]const u8}, ?{usize}, ?{f32})");
1002 defer stmt.deinit(); 1044 defer stmt.deinit();
1003 1045
1004 const users = &[_]TestUser{ 1046 const users = &[_]TestUser{
1005 .{ .id = 200, .name = "Vincent", .age = 33 }, 1047 .{ .id = 200, .name = "Vincent", .age = 33, .weight = 10.0 },
1006 .{ .id = 400, .name = "Julien", .age = 35 }, 1048 .{ .id = 400, .name = "Julien", .age = 35, .weight = 12.0 },
1007 .{ .id = 600, .name = "José", .age = 40 }, 1049 .{ .id = 600, .name = "José", .age = 40, .weight = 14.0 },
1008 }; 1050 };
1009 1051
1010 for (users) |user| { 1052 for (users) |user| {
@@ -1029,14 +1071,14 @@ test "sqlite: statement iterator" {
1029 try db.exec("DELETE FROM user", .{}); 1071 try db.exec("DELETE FROM user", .{});
1030 1072
1031 // Add data 1073 // Add data
1032 var stmt = try db.prepare("INSERT INTO user(id, name, age) VALUES(?{usize}, ?{[]const u8}, ?{usize})"); 1074 var stmt = try db.prepare("INSERT INTO user(id, name, age, weight) VALUES(?{usize}, ?{[]const u8}, ?{usize}, ?{f32})");
1033 defer stmt.deinit(); 1075 defer stmt.deinit();
1034 1076
1035 var expected_rows = std.ArrayList(TestUser).init(allocator); 1077 var expected_rows = std.ArrayList(TestUser).init(allocator);
1036 var i: usize = 0; 1078 var i: usize = 0;
1037 while (i < 20) : (i += 1) { 1079 while (i < 20) : (i += 1) {
1038 const name = try std.fmt.allocPrint(allocator, "Vincent {}", .{i}); 1080 const name = try std.fmt.allocPrint(allocator, "Vincent {}", .{i});
1039 const user = TestUser{ .id = i, .name = name, .age = i + 200 }; 1081 const user = TestUser{ .id = i, .name = name, .age = i + 200, .weight = @intToFloat(f32, i + 200) };
1040 1082
1041 try expected_rows.append(user); 1083 try expected_rows.append(user);
1042 1084