summaryrefslogtreecommitdiff
path: root/sqlite.zig
diff options
context:
space:
mode:
Diffstat (limited to 'sqlite.zig')
-rw-r--r--sqlite.zig100
1 files changed, 73 insertions, 27 deletions
diff --git a/sqlite.zig b/sqlite.zig
index 015dd64..dedd480 100644
--- a/sqlite.zig
+++ b/sqlite.zig
@@ -232,6 +232,10 @@ pub fn Iterator(comptime Type: type) type {
232 debug.assert(columns == 1); 232 debug.assert(columns == 1);
233 return try self.readArray(Type, 0); 233 return try self.readArray(Type, 0);
234 }, 234 },
235 .Pointer => {
236 debug.assert(columns == 1);
237 return try self.readPointer(Type, 0, options);
238 },
235 .Struct => { 239 .Struct => {
236 std.debug.assert(columns == TypeInfo.Struct.fields.len); 240 std.debug.assert(columns == TypeInfo.Struct.fields.len);
237 return try self.readStruct(options); 241 return try self.readStruct(options);
@@ -248,10 +252,10 @@ pub fn Iterator(comptime Type: type) type {
248 // If the array is too small for the data an error will be returned. 252 // 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 { 253 fn readArray(self: *Self, comptime ArrayType: type, _i: usize) error{ArrayTooSmall}!ArrayType {
250 const i = @intCast(c_int, _i); 254 const i = @intCast(c_int, _i);
251 const array_type_info = @typeInfo(ArrayType); 255 const type_info = @typeInfo(ArrayType);
252 256
253 var ret: ArrayType = undefined; 257 var ret: ArrayType = undefined;
254 switch (array_type_info) { 258 switch (type_info) {
255 .Array => |arr| { 259 .Array => |arr| {
256 comptime if (arr.sentinel == null) { 260 comptime if (arr.sentinel == null) {
257 @compileError("cannot populate array of " ++ @typeName(arr.child) ++ ", arrays must have a sentinel"); 261 @compileError("cannot populate array of " ++ @typeName(arr.child) ++ ", arrays must have a sentinel");
@@ -300,6 +304,25 @@ pub fn Iterator(comptime Type: type) type {
300 Text, 304 Text,
301 }; 305 };
302 306
307 // dupeWithSentinel is like dupe/dupeZ but allows for any sentinel value.
308 fn dupeWithSentinel(comptime SliceType: type, allocator: *mem.Allocator, data: []const u8) !SliceType {
309 const type_info = @typeInfo(SliceType);
310 switch (type_info) {
311 .Pointer => |ptr_info| {
312 if (ptr_info.sentinel) |sentinel| {
313 const slice = try allocator.alloc(u8, data.len + 1);
314 mem.copy(u8, slice, data);
315 slice[data.len] = sentinel;
316
317 return slice[0..data.len :sentinel];
318 } else {
319 return try allocator.dupe(u8, data);
320 }
321 },
322 else => @compileError("cannot dupe type " ++ @typeName(SliceType)),
323 }
324 }
325
303 // readBytes reads a sqlite BLOB or TEXT column. 326 // readBytes reads a sqlite BLOB or TEXT column.
304 // 327 //
305 // The mode controls which sqlite function is used to retrieve the data: 328 // The mode controls which sqlite function is used to retrieve the data:
@@ -312,48 +335,70 @@ pub fn Iterator(comptime Type: type) type {
312 // The options must contain an `allocator` field which will be used to create a copy of the data. 335 // 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 { 336 fn readBytes(self: *Self, comptime BytesType: type, _i: usize, comptime mode: ReadBytesMode, options: anytype) !BytesType {
314 const i = @intCast(c_int, _i); 337 const i = @intCast(c_int, _i);
338 const type_info = @typeInfo(BytesType);
315 339
316 var ret: BytesType = switch (BytesType) { 340 var ret: BytesType = switch (BytesType) {
317 Text, Blob => .{ .data = "" }, 341 Text, Blob => .{ .data = "" },
318 else => "", // TODO(vincent): I think with a []u8 this will crash if the caller attempts to modify it... 342 else => try dupeWithSentinel(BytesType, options.allocator, ""),
319 }; 343 };
320 344
321 switch (mode) { 345 switch (mode) {
322 .Blob => { 346 .Blob => {
323 const data = c.sqlite3_column_blob(self.stmt, i); 347 const data = c.sqlite3_column_blob(self.stmt, i);
324 if (data == null) return ret; 348 if (data == null) {
349 return switch (BytesType) {
350 Text, Blob => .{ .data = try options.allocator.dupe(u8, "") },
351 else => try dupeWithSentinel(BytesType, options.allocator, ""),
352 };
353 }
325 354
326 const size = @intCast(usize, c.sqlite3_column_bytes(self.stmt, i)); 355 const size = @intCast(usize, c.sqlite3_column_bytes(self.stmt, i));
327 const ptr = @ptrCast([*c]const u8, data)[0..size]; 356 const ptr = @ptrCast([*c]const u8, data)[0..size];
328 357
329 return switch (BytesType) { 358 if (BytesType == Blob) {
330 []const u8, []u8 => try options.allocator.dupe(u8, ptr), 359 return Blob{ .data = try options.allocator.dupe(u8, ptr) };
331 Blob => blk: { 360 }
332 var tmp: Blob = undefined; 361 return try dupeWithSentinel(BytesType, options.allocator, ptr);
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 };
338 }, 362 },
339 .Text => { 363 .Text => {
340 const data = c.sqlite3_column_text(self.stmt, i); 364 const data = c.sqlite3_column_text(self.stmt, i);
341 if (data == null) return ret; 365 if (data == null) {
366 return switch (BytesType) {
367 Text, Blob => .{ .data = try options.allocator.dupe(u8, "") },
368 else => try dupeWithSentinel(BytesType, options.allocator, ""),
369 };
370 }
342 371
343 const size = @intCast(usize, c.sqlite3_column_bytes(self.stmt, i)); 372 const size = @intCast(usize, c.sqlite3_column_bytes(self.stmt, i));
344 const ptr = @ptrCast([*c]const u8, data)[0..size]; 373 const ptr = @ptrCast([*c]const u8, data)[0..size];
345 374
346 return switch (BytesType) { 375 if (BytesType == Text) {
347 []const u8, []u8 => try options.allocator.dupe(u8, ptr), 376 return Text{ .data = try options.allocator.dupe(u8, ptr) };
348 Text => blk: { 377 }
349 var tmp: Text = undefined; 378 return try dupeWithSentinel(BytesType, options.allocator, ptr);
350 tmp.data = try options.allocator.dupe(u8, ptr); 379 },
351 break :blk tmp; 380 }
381 }
382
383 fn readPointer(self: *Self, comptime PointerType: type, i: usize, options: anytype) !PointerType {
384 const type_info = @typeInfo(PointerType);
385
386 var ret: PointerType = undefined;
387 switch (type_info) {
388 .Pointer => |ptr| {
389 switch (ptr.size) {
390 .One => unreachable,
391 .Slice => switch (ptr.child) {
392 u8 => ret = try self.readBytes(PointerType, i, .Text, options),
393 else => @compileError("cannot read pointer of type " ++ @typeName(PointerType)),
352 }, 394 },
353 else => @compileError("cannot read text into type " ++ @typeName(BytesType)), 395 else => @compileError("cannot read pointer of type " ++ @typeName(PointerType)),
354 }; 396 }
355 }, 397 },
398 else => @compileError("cannot read pointer of type " ++ @typeName(PointerType)),
356 } 399 }
400
401 return ret;
357 } 402 }
358 403
359 // readStruct reads an entire sqlite row into a struct. 404 // readStruct reads an entire sqlite row into a struct.
@@ -385,7 +430,6 @@ pub fn Iterator(comptime Type: type) type {
385 const field_type_info = @typeInfo(field.field_type); 430 const field_type_info = @typeInfo(field.field_type);
386 431
387 const ret = switch (field.field_type) { 432 const ret = switch (field.field_type) {
388 []const u8, []u8 => try self.readBytes(field.field_type, i, .Blob, options),
389 Blob => try self.readBytes(Blob, i, .Blob, options), 433 Blob => try self.readBytes(Blob, i, .Blob, options),
390 Text => try self.readBytes(Text, i, .Text, options), 434 Text => try self.readBytes(Text, i, .Text, options),
391 else => switch (field_type_info) { 435 else => switch (field_type_info) {
@@ -394,6 +438,7 @@ pub fn Iterator(comptime Type: type) type {
394 .Bool => try self.readBool(i, options), 438 .Bool => try self.readBool(i, options),
395 .Void => {}, 439 .Void => {},
396 .Array => try self.readArray(field.field_type, i), 440 .Array => try self.readArray(field.field_type, i),
441 .Pointer => try self.readPointer(field.field_type, i, options),
397 else => @compileError("cannot populate field " ++ field.name ++ " of type " ++ @typeName(field.field_type)), 442 else => @compileError("cannot populate field " ++ field.name ++ " of type " ++ @typeName(field.field_type)),
398 }, 443 },
399 }; 444 };
@@ -905,16 +950,17 @@ test "sqlite: read a single text value" {
905 try db.init(testing.allocator, .{ .mode = dbMode() }); 950 try db.init(testing.allocator, .{ .mode = dbMode() });
906 try addTestData(&db); 951 try addTestData(&db);
907 952
908 // TODO(vincent): implement the following
909 // [:0]const u8
910 // [:0]u8
911
912 const types = &[_]type{ 953 const types = &[_]type{
913 // Slices 954 // Slices
914 []const u8, 955 []const u8,
915 []u8, 956 []u8,
957 [:0]const u8,
958 [:0]u8,
959 [:0xAD]const u8,
960 [:0xAD]u8,
916 // Array 961 // Array
917 [8:0]u8, 962 [8:0]u8,
963 [8:0xAD]u8,
918 // Specific text or blob 964 // Specific text or blob
919 Text, 965 Text,
920 Blob, 966 Blob,