diff options
| author | 2020-10-25 02:01:06 +0200 | |
|---|---|---|
| committer | 2020-10-25 02:01:06 +0200 | |
| commit | 28babe422b2c84ab623489c7a7924c30191fa751 (patch) | |
| tree | 05b2433b77997a68e37ba5db8755f860874980eb | |
| parent | Merge branch 'text' (diff) | |
| parent | refactor API (diff) | |
| download | zig-sqlite-28babe422b2c84ab623489c7a7924c30191fa751.tar.gz zig-sqlite-28babe422b2c84ab623489c7a7924c30191fa751.tar.xz zig-sqlite-28babe422b2c84ab623489c7a7924c30191fa751.zip | |
Merge branch 'refactor-api'
| -rw-r--r-- | sqlite.zig | 474 |
1 files changed, 246 insertions, 228 deletions
| @@ -88,9 +88,9 @@ pub const Db = struct { | |||
| 88 | 88 | ||
| 89 | /// exec is a convenience function which prepares a statement and executes it directly. | 89 | /// exec is a convenience function which prepares a statement and executes it directly. |
| 90 | pub fn exec(self: *Self, comptime query: []const u8, values: anytype) !void { | 90 | pub fn exec(self: *Self, comptime query: []const u8, values: anytype) !void { |
| 91 | var stmt = try self.prepare(query, values); | 91 | var stmt = try self.prepare(query); |
| 92 | defer stmt.deinit(); | 92 | defer stmt.deinit(); |
| 93 | try stmt.exec(); | 93 | try stmt.exec(values); |
| 94 | } | 94 | } |
| 95 | 95 | ||
| 96 | /// prepare prepares a statement for the `query` provided. | 96 | /// prepare prepares a statement for the `query` provided. |
| @@ -107,8 +107,8 @@ pub const Db = struct { | |||
| 107 | /// defer stmt.deinit(); | 107 | /// defer stmt.deinit(); |
| 108 | /// | 108 | /// |
| 109 | /// Note that the name of the fields in the tuple are irrelevant, only the types are. | 109 | /// Note that the name of the fields in the tuple are irrelevant, only the types are. |
| 110 | pub fn prepare(self: *Self, comptime query: []const u8, values: anytype) !Statement { | 110 | pub fn prepare(self: *Self, comptime query: []const u8) !Statement(StatementOptions.from(query)) { |
| 111 | return Statement.prepare(self, 0, query, values); | 111 | return Statement(comptime StatementOptions.from(query)).prepare(self, 0, query); |
| 112 | } | 112 | } |
| 113 | 113 | ||
| 114 | /// rowsAffected returns the number of rows affected by the last statement executed. | 114 | /// rowsAffected returns the number of rows affected by the last statement executed. |
| @@ -128,6 +128,18 @@ pub const Bytes = union(enum) { | |||
| 128 | Text: []const u8, | 128 | Text: []const u8, |
| 129 | }; | 129 | }; |
| 130 | 130 | ||
| 131 | pub const StatementOptions = struct { | ||
| 132 | const Self = @This(); | ||
| 133 | |||
| 134 | bind_markers: usize, | ||
| 135 | |||
| 136 | fn from(comptime query: []const u8) Self { | ||
| 137 | return Self{ | ||
| 138 | .bind_markers = std.mem.count(u8, query, "?"), | ||
| 139 | }; | ||
| 140 | } | ||
| 141 | }; | ||
| 142 | |||
| 131 | /// Statement is a wrapper around a SQLite statement, providing high-level functions to execute | 143 | /// Statement is a wrapper around a SQLite statement, providing high-level functions to execute |
| 132 | /// a statement and retrieve rows for SELECT queries. | 144 | /// a statement and retrieve rows for SELECT queries. |
| 133 | /// | 145 | /// |
| @@ -159,259 +171,264 @@ pub const Bytes = union(enum) { | |||
| 159 | /// | 171 | /// |
| 160 | /// Look at aach function for more complete documentation. | 172 | /// Look at aach function for more complete documentation. |
| 161 | /// | 173 | /// |
| 162 | pub const Statement = struct { | 174 | pub fn Statement(comptime opts: StatementOptions) type { |
| 163 | const Self = @This(); | 175 | return struct { |
| 176 | const Self = @This(); | ||
| 164 | 177 | ||
| 165 | stmt: *c.sqlite3_stmt, | 178 | stmt: *c.sqlite3_stmt, |
| 166 | 179 | ||
| 167 | const BytesType = enum { | 180 | const BytesType = enum { |
| 168 | Text, | 181 | Text, |
| 169 | Blob, | 182 | Blob, |
| 170 | }; | ||
| 171 | |||
| 172 | fn prepare(db: *Db, flags: c_uint, comptime query: []const u8, values: anytype) !Self { | ||
| 173 | const StructType = @TypeOf(values); | ||
| 174 | const StructTypeInfo = @typeInfo(StructType).Struct; | ||
| 175 | comptime { | ||
| 176 | const bind_parameter_count = std.mem.count(u8, query, "?"); | ||
| 177 | if (bind_parameter_count != StructTypeInfo.fields.len) { | ||
| 178 | @compileError("bind parameter count != number of fields in tuple/struct"); | ||
| 179 | } | ||
| 180 | } | ||
| 181 | |||
| 182 | // prepare | ||
| 183 | |||
| 184 | var stmt = blk: { | ||
| 185 | var tmp: ?*c.sqlite3_stmt = undefined; | ||
| 186 | const result = c.sqlite3_prepare_v3( | ||
| 187 | db.db, | ||
| 188 | query.ptr, | ||
| 189 | @intCast(c_int, query.len), | ||
| 190 | flags, | ||
| 191 | &tmp, | ||
| 192 | null, | ||
| 193 | ); | ||
| 194 | if (result != c.SQLITE_OK) { | ||
| 195 | logger.warn("unable to prepare statement, result: {}", .{result}); | ||
| 196 | return error.CannotPrepareStatement; | ||
| 197 | } | ||
| 198 | break :blk tmp.?; | ||
| 199 | }; | 183 | }; |
| 200 | 184 | ||
| 201 | // Bind | 185 | fn prepare(db: *Db, flags: c_uint, comptime query: []const u8) !Self { |
| 186 | // prepare | ||
| 187 | var stmt = blk: { | ||
| 188 | var tmp: ?*c.sqlite3_stmt = undefined; | ||
| 189 | const result = c.sqlite3_prepare_v3( | ||
| 190 | db.db, | ||
| 191 | query.ptr, | ||
| 192 | @intCast(c_int, query.len), | ||
| 193 | flags, | ||
| 194 | &tmp, | ||
| 195 | null, | ||
| 196 | ); | ||
| 197 | if (result != c.SQLITE_OK) { | ||
| 198 | logger.warn("unable to prepare statement, result: {}", .{result}); | ||
| 199 | return error.CannotPrepareStatement; | ||
| 200 | } | ||
| 201 | break :blk tmp.?; | ||
| 202 | }; | ||
| 202 | 203 | ||
| 203 | inline for (StructTypeInfo.fields) |struct_field, _i| { | 204 | return Self{ |
| 204 | const i = @as(usize, _i); | 205 | .stmt = stmt, |
| 205 | const field_type_info = @typeInfo(struct_field.field_type); | 206 | }; |
| 206 | const field_value = @field(values, struct_field.name); | 207 | } |
| 207 | const column = i + 1; | ||
| 208 | 208 | ||
| 209 | switch (struct_field.field_type) { | 209 | pub fn deinit(self: *Self) void { |
| 210 | []const u8, []u8 => { | 210 | const result = c.sqlite3_finalize(self.stmt); |
| 211 | _ = c.sqlite3_bind_text(stmt, column, field_value.ptr, @intCast(c_int, field_value.len), null); | 211 | if (result != c.SQLITE_OK) { |
| 212 | }, | 212 | logger.err("unable to finalize prepared statement, result: {}", .{result}); |
| 213 | Bytes => switch (field_value) { | ||
| 214 | .Text => |v| _ = c.sqlite3_bind_text(stmt, column, v.ptr, @intCast(c_int, v.len), null), | ||
| 215 | .Blob => |v| _ = c.sqlite3_bind_blob(stmt, column, v.ptr, @intCast(c_int, v.len), null), | ||
| 216 | }, | ||
| 217 | else => switch (field_type_info) { | ||
| 218 | .Int, .ComptimeInt => _ = c.sqlite3_bind_int64(stmt, column, @intCast(c_longlong, field_value)), | ||
| 219 | .Float, .ComptimeFloat => _ = c.sqlite3_bind_double(stmt, column, field_value), | ||
| 220 | .Array => |arr| { | ||
| 221 | switch (arr.child) { | ||
| 222 | u8 => { | ||
| 223 | const data: []const u8 = field_value[0..field_value.len]; | ||
| 224 | |||
| 225 | _ = c.sqlite3_bind_text(stmt, column, data.ptr, @intCast(c_int, data.len), null); | ||
| 226 | }, | ||
| 227 | else => @compileError("cannot populate field " ++ field.name ++ " of type array of " ++ @typeName(arr.child)), | ||
| 228 | } | ||
| 229 | }, | ||
| 230 | else => @compileError("cannot bind field " ++ struct_field.name ++ " of type " ++ @typeName(struct_field.field_type)), | ||
| 231 | }, | ||
| 232 | } | 213 | } |
| 233 | } | 214 | } |
| 234 | 215 | ||
| 235 | return Self{ | 216 | pub fn bind(self: *Self, values: anytype) void { |
| 236 | .stmt = stmt, | 217 | const StructType = @TypeOf(values); |
| 237 | }; | 218 | const StructTypeInfo = @typeInfo(StructType).Struct; |
| 238 | } | ||
| 239 | 219 | ||
| 240 | pub fn deinit(self: *Self) void { | 220 | if (comptime opts.bind_markers != StructTypeInfo.fields.len) { |
| 241 | const result = c.sqlite3_finalize(self.stmt); | 221 | @compileError("number of bind markers not equal to number of fields"); |
| 242 | if (result != c.SQLITE_OK) { | 222 | } |
| 243 | logger.err("unable to finalize prepared statement, result: {}", .{result}); | ||
| 244 | } | ||
| 245 | } | ||
| 246 | 223 | ||
| 247 | pub fn exec(self: *Self) !void { | 224 | inline for (StructTypeInfo.fields) |struct_field, _i| { |
| 248 | const result = c.sqlite3_step(self.stmt); | 225 | const i = @as(usize, _i); |
| 249 | switch (result) { | 226 | const field_type_info = @typeInfo(struct_field.field_type); |
| 250 | c.SQLITE_DONE => {}, | 227 | const field_value = @field(values, struct_field.name); |
| 251 | c.SQLITE_BUSY => return error.SQLiteBusy, | 228 | const column = i + 1; |
| 252 | else => std.debug.panic("invalid result {}", .{result}), | ||
| 253 | } | ||
| 254 | } | ||
| 255 | 229 | ||
| 256 | /// one reads a single row from the result set of this statement. | 230 | switch (struct_field.field_type) { |
| 257 | /// | 231 | []const u8, []u8 => { |
| 258 | /// The data in the row is used to populate a value of the type `Type`. | 232 | _ = c.sqlite3_bind_text(self.stmt, column, field_value.ptr, @intCast(c_int, field_value.len), null); |
| 259 | /// This means that `Type` must have as many fields as is returned in the query | 233 | }, |
| 260 | /// executed by this statement. | 234 | Bytes => switch (field_value) { |
| 261 | /// This also means that the type of each field must be compatible with the SQLite type. | 235 | .Text => |v| _ = c.sqlite3_bind_text(self.stmt, column, v.ptr, @intCast(c_int, v.len), null), |
| 262 | /// | 236 | .Blob => |v| _ = c.sqlite3_bind_blob(self.stmt, column, v.ptr, @intCast(c_int, v.len), null), |
| 263 | /// Here is an example of how to use an anonymous struct type: | 237 | }, |
| 264 | /// | 238 | else => switch (field_type_info) { |
| 265 | /// const row = try stmt.one( | 239 | .Int, .ComptimeInt => _ = c.sqlite3_bind_int64(self.stmt, column, @intCast(c_longlong, field_value)), |
| 266 | /// struct { | 240 | .Float, .ComptimeFloat => _ = c.sqlite3_bind_double(self.stmt, column, field_value), |
| 267 | /// id: usize, | 241 | .Array => |arr| { |
| 268 | /// name: []const u8, | 242 | switch (arr.child) { |
| 269 | /// age: usize, | 243 | u8 => { |
| 270 | /// }, | 244 | const data: []const u8 = field_value[0..field_value.len]; |
| 271 | /// .{ .allocator = allocator }, | 245 | |
| 272 | /// ); | 246 | _ = c.sqlite3_bind_text(self.stmt, column, data.ptr, @intCast(c_int, data.len), null); |
| 273 | /// | 247 | }, |
| 274 | /// The `options` tuple is used to provide additional state in some cases, for example | 248 | else => @compileError("cannot populate field " ++ field.name ++ " of type array of " ++ @typeName(arr.child)), |
| 275 | /// an allocator used to read text and blobs. | 249 | } |
| 276 | /// | 250 | }, |
| 277 | pub fn one(self: *Self, comptime Type: type, options: anytype) !?Type { | 251 | else => @compileError("cannot bind field " ++ struct_field.name ++ " of type " ++ @typeName(struct_field.field_type)), |
| 278 | if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) { | 252 | }, |
| 279 | @compileError("options passed to all must be a struct"); | 253 | } |
| 254 | } | ||
| 280 | } | 255 | } |
| 281 | const TypeInfo = @typeInfo(Type); | ||
| 282 | 256 | ||
| 283 | var result = c.sqlite3_step(self.stmt); | 257 | pub fn exec(self: *Self, values: anytype) !void { |
| 258 | self.bind(values); | ||
| 284 | 259 | ||
| 285 | switch (TypeInfo) { | 260 | const result = c.sqlite3_step(self.stmt); |
| 286 | .Int => return switch (result) { | 261 | switch (result) { |
| 287 | c.SQLITE_ROW => try self.readInt(Type, options), | 262 | c.SQLITE_DONE => {}, |
| 288 | c.SQLITE_DONE => null, | 263 | c.SQLITE_BUSY => return error.SQLiteBusy, |
| 289 | else => std.debug.panic("invalid result {}", .{result}), | ||
| 290 | }, | ||
| 291 | .Struct => return switch (result) { | ||
| 292 | c.SQLITE_ROW => try self.readStruct(Type, options), | ||
| 293 | c.SQLITE_DONE => null, | ||
| 294 | else => std.debug.panic("invalid result {}", .{result}), | 264 | else => std.debug.panic("invalid result {}", .{result}), |
| 295 | }, | 265 | } |
| 296 | else => @compileError("cannot read into type " ++ @typeName(Type)), | ||
| 297 | } | 266 | } |
| 298 | } | ||
| 299 | 267 | ||
| 300 | /// all reads all rows from the result set of this statement. | 268 | /// one reads a single row from the result set of this statement. |
| 301 | /// | 269 | /// |
| 302 | /// The data in each row is used to populate a value of the type `Type`. | 270 | /// The data in the row is used to populate a value of the type `Type`. |
| 303 | /// This means that `Type` must have as many fields as is returned in the query | 271 | /// This means that `Type` must have as many fields as is returned in the query |
| 304 | /// executed by this statement. | 272 | /// executed by this statement. |
| 305 | /// This also means that the type of each field must be compatible with the SQLite type. | 273 | /// This also means that the type of each field must be compatible with the SQLite type. |
| 306 | /// | 274 | /// |
| 307 | /// Here is an example of how to use an anonymous struct type: | 275 | /// Here is an example of how to use an anonymous struct type: |
| 308 | /// | 276 | /// |
| 309 | /// const rows = try stmt.all( | 277 | /// const row = try stmt.one( |
| 310 | /// struct { | 278 | /// struct { |
| 311 | /// id: usize, | 279 | /// id: usize, |
| 312 | /// name: []const u8, | 280 | /// name: []const u8, |
| 313 | /// age: usize, | 281 | /// age: usize, |
| 314 | /// }, | 282 | /// }, |
| 315 | /// .{ .allocator = allocator }, | 283 | /// .{ .allocator = allocator }, |
| 316 | /// ); | 284 | /// ); |
| 317 | /// | 285 | /// |
| 318 | /// The `options` tuple is used to provide additional state in some cases. | 286 | /// The `options` tuple is used to provide additional state in some cases, for example |
| 319 | /// Note that for this function the allocator is mandatory. | 287 | /// an allocator used to read text and blobs. |
| 320 | /// | 288 | /// |
| 321 | pub fn all(self: *Self, comptime Type: type, options: anytype) ![]Type { | 289 | pub fn one(self: *Self, comptime Type: type, options: anytype, values: anytype) !?Type { |
| 322 | if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) { | 290 | if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) { |
| 323 | @compileError("options passed to all must be a struct"); | 291 | @compileError("options passed to all must be a struct"); |
| 324 | } | 292 | } |
| 325 | const TypeInfo = @typeInfo(Type); | 293 | const TypeInfo = @typeInfo(Type); |
| 326 | 294 | ||
| 327 | var rows = std.ArrayList(Type).init(options.allocator); | 295 | self.bind(values); |
| 328 | 296 | ||
| 329 | var result = c.sqlite3_step(self.stmt); | 297 | var result = c.sqlite3_step(self.stmt); |
| 330 | while (result == c.SQLITE_ROW) : (result = c.sqlite3_step(self.stmt)) { | ||
| 331 | const columns = c.sqlite3_column_count(self.stmt); | ||
| 332 | 298 | ||
| 333 | var value = switch (TypeInfo) { | 299 | switch (TypeInfo) { |
| 334 | .Int => blk: { | 300 | .Int => return switch (result) { |
| 335 | debug.assert(columns == 1); | 301 | c.SQLITE_ROW => try self.readInt(Type, options), |
| 336 | break :blk try self.readInt(Type, options); | 302 | c.SQLITE_DONE => null, |
| 303 | else => std.debug.panic("invalid result {}", .{result}), | ||
| 337 | }, | 304 | }, |
| 338 | .Struct => blk: { | 305 | .Struct => return switch (result) { |
| 339 | std.debug.assert(columns == @typeInfo(Type).Struct.fields.len); | 306 | c.SQLITE_ROW => try self.readStruct(Type, options), |
| 340 | break :blk try self.readStruct(Type, options); | 307 | c.SQLITE_DONE => null, |
| 308 | else => std.debug.panic("invalid result {}", .{result}), | ||
| 341 | }, | 309 | }, |
| 342 | else => @compileError("cannot read into type " ++ @typeName(Type)), | 310 | else => @compileError("cannot read into type " ++ @typeName(Type)), |
| 343 | }; | 311 | } |
| 344 | |||
| 345 | try rows.append(value); | ||
| 346 | } | 312 | } |
| 347 | 313 | ||
| 348 | if (result != c.SQLITE_DONE) { | 314 | /// all reads all rows from the result set of this statement. |
| 349 | logger.err("unable to iterate, result: {}", .{result}); | 315 | /// |
| 350 | return error.SQLiteStepError; | 316 | /// The data in each row is used to populate a value of the type `Type`. |
| 351 | } | 317 | /// This means that `Type` must have as many fields as is returned in the query |
| 318 | /// executed by this statement. | ||
| 319 | /// This also means that the type of each field must be compatible with the SQLite type. | ||
| 320 | /// | ||
| 321 | /// Here is an example of how to use an anonymous struct type: | ||
| 322 | /// | ||
| 323 | /// const rows = try stmt.all( | ||
| 324 | /// struct { | ||
| 325 | /// id: usize, | ||
| 326 | /// name: []const u8, | ||
| 327 | /// age: usize, | ||
| 328 | /// }, | ||
| 329 | /// .{ .allocator = allocator }, | ||
| 330 | /// ); | ||
| 331 | /// | ||
| 332 | /// The `options` tuple is used to provide additional state in some cases. | ||
| 333 | /// Note that for this function the allocator is mandatory. | ||
| 334 | /// | ||
| 335 | pub fn all(self: *Self, comptime Type: type, options: anytype, values: anytype) ![]Type { | ||
| 336 | if (!comptime std.meta.trait.is(.Struct)(@TypeOf(options))) { | ||
| 337 | @compileError("options passed to all must be a struct"); | ||
| 338 | } | ||
| 339 | const TypeInfo = @typeInfo(Type); | ||
| 352 | 340 | ||
| 353 | return rows.span(); | 341 | self.bind(values); |
| 354 | } | ||
| 355 | 342 | ||
| 356 | fn readInt(self: *Self, comptime Type: type, options: anytype) !Type { | 343 | var rows = std.ArrayList(Type).init(options.allocator); |
| 357 | const n = c.sqlite3_column_int64(self.stmt, 0); | ||
| 358 | return @intCast(Type, n); | ||
| 359 | } | ||
| 360 | 344 | ||
| 361 | fn readStruct(self: *Self, comptime Type: type, options: anytype) !Type { | 345 | var result = c.sqlite3_step(self.stmt); |
| 362 | var value: Type = undefined; | 346 | while (result == c.SQLITE_ROW) : (result = c.sqlite3_step(self.stmt)) { |
| 347 | const columns = c.sqlite3_column_count(self.stmt); | ||
| 363 | 348 | ||
| 364 | inline for (@typeInfo(Type).Struct.fields) |field, _i| { | 349 | var value = switch (TypeInfo) { |
| 365 | const i = @as(usize, _i); | 350 | .Int => blk: { |
| 366 | const field_type_info = @typeInfo(field.field_type); | 351 | debug.assert(columns == 1); |
| 352 | break :blk try self.readInt(Type, options); | ||
| 353 | }, | ||
| 354 | .Struct => blk: { | ||
| 355 | std.debug.assert(columns == @typeInfo(Type).Struct.fields.len); | ||
| 356 | break :blk try self.readStruct(Type, options); | ||
| 357 | }, | ||
| 358 | else => @compileError("cannot read into type " ++ @typeName(Type)), | ||
| 359 | }; | ||
| 367 | 360 | ||
| 368 | switch (field.field_type) { | 361 | try rows.append(value); |
| 369 | []const u8, []u8 => { | 362 | } |
| 370 | const data = c.sqlite3_column_blob(self.stmt, i); | ||
| 371 | if (data == null) { | ||
| 372 | @field(value, field.name) = ""; | ||
| 373 | } else { | ||
| 374 | const size = @intCast(usize, c.sqlite3_column_bytes(self.stmt, i)); | ||
| 375 | 363 | ||
| 376 | var tmp = try options.allocator.alloc(u8, size); | 364 | if (result != c.SQLITE_DONE) { |
| 377 | mem.copy(u8, tmp, @ptrCast([*c]const u8, data)[0..size]); | 365 | logger.err("unable to iterate, result: {}", .{result}); |
| 366 | return error.SQLiteStepError; | ||
| 367 | } | ||
| 378 | 368 | ||
| 379 | @field(value, field.name) = tmp; | 369 | return rows.span(); |
| 380 | } | 370 | } |
| 381 | }, | 371 | |
| 382 | else => switch (field_type_info) { | 372 | fn readInt(self: *Self, comptime Type: type, options: anytype) !Type { |
| 383 | .Int => { | 373 | const n = c.sqlite3_column_int64(self.stmt, 0); |
| 384 | const n = c.sqlite3_column_int64(self.stmt, i); | 374 | return @intCast(Type, n); |
| 385 | @field(value, field.name) = @intCast(field.field_type, n); | 375 | } |
| 386 | }, | 376 | |
| 387 | .Float => { | 377 | fn readStruct(self: *Self, comptime Type: type, options: anytype) !Type { |
| 388 | const f = c.sqlite3_column_double(self.stmt, i); | 378 | var value: Type = undefined; |
| 389 | @field(value, field.name) = f; | 379 | |
| 390 | }, | 380 | inline for (@typeInfo(Type).Struct.fields) |field, _i| { |
| 391 | .Void => { | 381 | const i = @as(usize, _i); |
| 392 | @field(value, field.name) = {}; | 382 | const field_type_info = @typeInfo(field.field_type); |
| 393 | }, | 383 | |
| 394 | .Array => |arr| { | 384 | switch (field.field_type) { |
| 395 | switch (arr.child) { | 385 | []const u8, []u8 => { |
| 396 | u8 => { | 386 | const data = c.sqlite3_column_blob(self.stmt, i); |
| 397 | const data = c.sqlite3_column_blob(self.stmt, i); | 387 | if (data == null) { |
| 398 | const size = @intCast(usize, c.sqlite3_column_bytes(self.stmt, i)); | 388 | @field(value, field.name) = ""; |
| 389 | } else { | ||
| 390 | const size = @intCast(usize, c.sqlite3_column_bytes(self.stmt, i)); | ||
| 399 | 391 | ||
| 400 | if (size > @as(usize, arr.len)) return error.ArrayTooSmall; | 392 | var tmp = try options.allocator.alloc(u8, size); |
| 393 | mem.copy(u8, tmp, @ptrCast([*c]const u8, data)[0..size]); | ||
| 401 | 394 | ||
| 402 | mem.copy(u8, @field(value, field.name)[0..], @ptrCast([*c]const u8, data)[0..size]); | 395 | @field(value, field.name) = tmp; |
| 403 | }, | ||
| 404 | else => @compileError("cannot populate field " ++ field.name ++ " of type array of " ++ @typeName(arr.child)), | ||
| 405 | } | 396 | } |
| 406 | }, | 397 | }, |
| 407 | else => @compileError("cannot populate field " ++ field.name ++ " of type " ++ @typeName(field.field_type)), | 398 | else => switch (field_type_info) { |
| 408 | }, | 399 | .Int => { |
| 400 | const n = c.sqlite3_column_int64(self.stmt, i); | ||
| 401 | @field(value, field.name) = @intCast(field.field_type, n); | ||
| 402 | }, | ||
| 403 | .Float => { | ||
| 404 | const f = c.sqlite3_column_double(self.stmt, i); | ||
| 405 | @field(value, field.name) = f; | ||
| 406 | }, | ||
| 407 | .Void => { | ||
| 408 | @field(value, field.name) = {}; | ||
| 409 | }, | ||
| 410 | .Array => |arr| { | ||
| 411 | switch (arr.child) { | ||
| 412 | u8 => { | ||
| 413 | const data = c.sqlite3_column_blob(self.stmt, i); | ||
| 414 | const size = @intCast(usize, c.sqlite3_column_bytes(self.stmt, i)); | ||
| 415 | |||
| 416 | if (size > @as(usize, arr.len)) return error.ArrayTooSmall; | ||
| 417 | |||
| 418 | mem.copy(u8, @field(value, field.name)[0..], @ptrCast([*c]const u8, data)[0..size]); | ||
| 419 | }, | ||
| 420 | else => @compileError("cannot populate field " ++ field.name ++ " of type array of " ++ @typeName(arr.child)), | ||
| 421 | } | ||
| 422 | }, | ||
| 423 | else => @compileError("cannot populate field " ++ field.name ++ " of type " ++ @typeName(field.field_type)), | ||
| 424 | }, | ||
| 425 | } | ||
| 409 | } | 426 | } |
| 410 | } | ||
| 411 | 427 | ||
| 412 | return value; | 428 | return value; |
| 413 | } | 429 | } |
| 414 | }; | 430 | }; |
| 431 | } | ||
| 415 | 432 | ||
| 416 | test "sqlite: db init" { | 433 | test "sqlite: db init" { |
| 417 | var db: Db = undefined; | 434 | var db: Db = undefined; |
| @@ -444,9 +461,7 @@ test "sqlite: statement exec" { | |||
| 444 | \\) | 461 | \\) |
| 445 | }; | 462 | }; |
| 446 | inline for (all_ddl) |ddl| { | 463 | inline for (all_ddl) |ddl| { |
| 447 | var stmt = try db.prepare(ddl, .{}); | 464 | try db.exec(ddl, .{}); |
| 448 | defer stmt.deinit(); | ||
| 449 | try stmt.exec(); | ||
| 450 | } | 465 | } |
| 451 | 466 | ||
| 452 | // Add data | 467 | // Add data |
| @@ -473,10 +488,10 @@ test "sqlite: statement exec" { | |||
| 473 | // Read a single user | 488 | // Read a single user |
| 474 | 489 | ||
| 475 | { | 490 | { |
| 476 | var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?", .{ .id = 20 }); | 491 | var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?"); |
| 477 | defer stmt.deinit(); | 492 | defer stmt.deinit(); |
| 478 | 493 | ||
| 479 | var rows = try stmt.all(User, .{ .allocator = allocator }); | 494 | var rows = try stmt.all(User, .{ .allocator = allocator }, .{ .id = 20 }); |
| 480 | for (rows) |row| { | 495 | for (rows) |row| { |
| 481 | testing.expectEqual(users[0].id, row.id); | 496 | testing.expectEqual(users[0].id, row.id); |
| 482 | testing.expectEqualStrings(users[0].name, row.name); | 497 | testing.expectEqualStrings(users[0].name, row.name); |
| @@ -487,10 +502,10 @@ test "sqlite: statement exec" { | |||
| 487 | // Read all users | 502 | // Read all users |
| 488 | 503 | ||
| 489 | { | 504 | { |
| 490 | var stmt = try db.prepare("SELECT id, name, age FROM user", .{}); | 505 | var stmt = try db.prepare("SELECT id, name, age FROM user"); |
| 491 | defer stmt.deinit(); | 506 | defer stmt.deinit(); |
| 492 | 507 | ||
| 493 | var rows = try stmt.all(User, .{ .allocator = allocator }); | 508 | var rows = try stmt.all(User, .{ .allocator = allocator }, .{}); |
| 494 | testing.expectEqual(@as(usize, 3), rows.len); | 509 | testing.expectEqual(@as(usize, 3), rows.len); |
| 495 | for (rows) |row, i| { | 510 | for (rows) |row, i| { |
| 496 | const exp = users[i]; | 511 | const exp = users[i]; |
| @@ -503,7 +518,7 @@ test "sqlite: statement exec" { | |||
| 503 | // Test with anonymous structs | 518 | // Test with anonymous structs |
| 504 | 519 | ||
| 505 | { | 520 | { |
| 506 | var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?", .{ .id = 20 }); | 521 | var stmt = try db.prepare("SELECT id, name, age FROM user WHERE id = ?"); |
| 507 | defer stmt.deinit(); | 522 | defer stmt.deinit(); |
| 508 | 523 | ||
| 509 | var row = try stmt.one( | 524 | var row = try stmt.one( |
| @@ -513,6 +528,7 @@ test "sqlite: statement exec" { | |||
| 513 | age: usize, | 528 | age: usize, |
| 514 | }, | 529 | }, |
| 515 | .{ .allocator = allocator }, | 530 | .{ .allocator = allocator }, |
| 531 | .{ .id = 20 }, | ||
| 516 | ); | 532 | ); |
| 517 | testing.expect(row != null); | 533 | testing.expect(row != null); |
| 518 | 534 | ||
| @@ -525,10 +541,12 @@ test "sqlite: statement exec" { | |||
| 525 | // Test with a single integer | 541 | // Test with a single integer |
| 526 | 542 | ||
| 527 | { | 543 | { |
| 528 | var stmt = try db.prepare("SELECT age FROM user WHERE id = ?", .{ .id = 20 }); | 544 | const query = "SELECT age FROM user WHERE id = ?"; |
| 545 | |||
| 546 | var stmt: Statement(StatementOptions.from(query)) = try db.prepare(query); | ||
| 529 | defer stmt.deinit(); | 547 | defer stmt.deinit(); |
| 530 | 548 | ||
| 531 | var age = try stmt.one(usize, .{}); | 549 | var age = try stmt.one(usize, .{}, .{ .id = 20 }); |
| 532 | testing.expect(age != null); | 550 | testing.expect(age != null); |
| 533 | 551 | ||
| 534 | testing.expectEqual(@as(usize, 33), age.?); | 552 | testing.expectEqual(@as(usize, 33), age.?); |