diff options
| author | 2021-09-24 14:52:32 +0800 | |
|---|---|---|
| committer | 2021-10-13 10:03:07 +0800 | |
| commit | cab714e36e4827a84e6b19d298b9e664121a7a3a (patch) | |
| tree | f1e93f71c7e2a0aa1bc6d4be00948aa23c7a687c /sqlite.zig | |
| parent | test binding an optional value too (diff) | |
| download | zig-sqlite-cab714e36e4827a84e6b19d298b9e664121a7a3a.tar.gz zig-sqlite-cab714e36e4827a84e6b19d298b9e664121a7a3a.tar.xz zig-sqlite-cab714e36e4827a84e6b19d298b9e664121a7a3a.zip | |
DynamicStatment: introduce original sqlite3 statement.
Diffstat (limited to 'sqlite.zig')
| -rw-r--r-- | sqlite.zig | 564 |
1 files changed, 442 insertions, 122 deletions
| @@ -450,6 +450,13 @@ pub const Db = struct { | |||
| 450 | try stmt.exec(options, values); | 450 | try stmt.exec(options, values); |
| 451 | } | 451 | } |
| 452 | 452 | ||
| 453 | /// execDynamic is a convenience function which prepares a statement and executes it directly. | ||
| 454 | pub fn execDynamic(self: *Self, query: []const u8, options: QueryOptions, values: anytype) !void { | ||
| 455 | var stmt = try self.prepareDynamicWithDiags(query, options); | ||
| 456 | defer stmt.deinit(); | ||
| 457 | try stmt.exec(options, values); | ||
| 458 | } | ||
| 459 | |||
| 453 | /// one is a convenience function which prepares a statement and reads a single row from the result set. | 460 | /// one is a convenience function which prepares a statement and reads a single row from the result set. |
| 454 | pub fn one(self: *Self, comptime Type: type, comptime query: []const u8, options: QueryOptions, values: anytype) !?Type { | 461 | pub fn one(self: *Self, comptime Type: type, comptime query: []const u8, options: QueryOptions, values: anytype) !?Type { |
| 455 | var stmt = try self.prepareWithDiags(query, options); | 462 | var stmt = try self.prepareWithDiags(query, options); |
| @@ -457,6 +464,13 @@ pub const Db = struct { | |||
| 457 | return try stmt.one(Type, options, values); | 464 | return try stmt.one(Type, options, values); |
| 458 | } | 465 | } |
| 459 | 466 | ||
| 467 | /// oneDynamic is a convenience function which prepares a statement and reads a single row from the result set. | ||
| 468 | pub fn oneDynamic(self: *Self, comptime Type: type, query: []const u8, options: QueryOptions, values: anytype) !?Type { | ||
| 469 | var stmt = try self.prepareDynamicWithDiags(query, options); | ||
| 470 | defer stmt.deinit(); | ||
| 471 | return try stmt.one(Type, options, values); | ||
| 472 | } | ||
| 473 | |||
| 460 | /// oneAlloc is like `one` but can allocate memory. | 474 | /// oneAlloc is like `one` but can allocate memory. |
| 461 | pub fn oneAlloc(self: *Self, comptime Type: type, allocator: *mem.Allocator, comptime query: []const u8, options: QueryOptions, values: anytype) !?Type { | 475 | pub fn oneAlloc(self: *Self, comptime Type: type, allocator: *mem.Allocator, comptime query: []const u8, options: QueryOptions, values: anytype) !?Type { |
| 462 | var stmt = try self.prepareWithDiags(query, options); | 476 | var stmt = try self.prepareWithDiags(query, options); |
| @@ -464,6 +478,13 @@ pub const Db = struct { | |||
| 464 | return try stmt.oneAlloc(Type, allocator, options, values); | 478 | return try stmt.oneAlloc(Type, allocator, options, values); |
| 465 | } | 479 | } |
| 466 | 480 | ||
| 481 | /// oneDynamicAlloc is like `oneDynamic` but can allocate memory. | ||
| 482 | pub fn oneDynamicAlloc(self: *Self, comptime Type: type, allocator: *mem.Allocator, query: []const u8, options: QueryOptions, values: anytype) !?Type { | ||
| 483 | var stmt = try self.prepareDynamicWithDiags(query, options); | ||
| 484 | defer stmt.deinit(); | ||
| 485 | return try stmt.oneAlloc(Type, allocator, options, values); | ||
| 486 | } | ||
| 487 | |||
| 467 | /// prepareWithDiags is like `prepare` but takes an additional options argument. | 488 | /// prepareWithDiags is like `prepare` but takes an additional options argument. |
| 468 | pub fn prepareWithDiags(self: *Self, comptime query: []const u8, options: QueryOptions) !blk: { | 489 | pub fn prepareWithDiags(self: *Self, comptime query: []const u8, options: QueryOptions) !blk: { |
| 469 | @setEvalBranchQuota(100000); | 490 | @setEvalBranchQuota(100000); |
| @@ -474,6 +495,11 @@ pub const Db = struct { | |||
| 474 | return Statement(.{}, comptime parsed_query).prepare(self, options, 0); | 495 | return Statement(.{}, comptime parsed_query).prepare(self, options, 0); |
| 475 | } | 496 | } |
| 476 | 497 | ||
| 498 | /// prepareDynamicWithDiags is like `prepareDynamic` but takes an additional options argument. | ||
| 499 | pub fn prepareDynamicWithDiags(self: *Self, query: []const u8, options: QueryOptions) !DynamicStatement { | ||
| 500 | return try DynamicStatement.prepare(self, query, options, 0); | ||
| 501 | } | ||
| 502 | |||
| 477 | /// prepare prepares a statement for the `query` provided. | 503 | /// prepare prepares a statement for the `query` provided. |
| 478 | /// | 504 | /// |
| 479 | /// The query is analysed at comptime to search for bind markers. | 505 | /// The query is analysed at comptime to search for bind markers. |
| @@ -497,6 +523,16 @@ pub const Db = struct { | |||
| 497 | return Statement(.{}, comptime parsed_query).prepare(self, .{}, 0); | 523 | return Statement(.{}, comptime parsed_query).prepare(self, .{}, 0); |
| 498 | } | 524 | } |
| 499 | 525 | ||
| 526 | /// prepareDynamic prepares a dynamic statement for the `query` provided. | ||
| 527 | /// | ||
| 528 | /// The query will be directly sent to create statement without any analysing. | ||
| 529 | /// That means such statement does not support comptime type-checking. | ||
| 530 | /// | ||
| 531 | /// Dynamic statement supports host parameter names. See `DynamicStatement` | ||
| 532 | pub fn prepareDynamic(self: *Self, query: []const u8) !DynamicStatement { | ||
| 533 | return try self.prepareDynamicWithDiags(query, .{}); | ||
| 534 | } | ||
| 535 | |||
| 500 | /// rowsAffected returns the number of rows affected by the last statement executed. | 536 | /// rowsAffected returns the number of rows affected by the last statement executed. |
| 501 | pub fn rowsAffected(self: *Self) usize { | 537 | pub fn rowsAffected(self: *Self) usize { |
| 502 | return @intCast(usize, c.sqlite3_changes(self.db)); | 538 | return @intCast(usize, c.sqlite3_changes(self.db)); |
| @@ -994,6 +1030,351 @@ pub fn Iterator(comptime Type: type) type { | |||
| 994 | 1030 | ||
| 995 | pub const StatementOptions = struct {}; | 1031 | pub const StatementOptions = struct {}; |
| 996 | 1032 | ||
| 1033 | /// DynamicStatement represents a statement in sqlite3. It almost works like sqlite3_stmt. | ||
| 1034 | /// The difference to `Statement` is that this structure comes without addtional comptime type-checking. | ||
| 1035 | /// | ||
| 1036 | /// The structure supports "host parameter names", which used in query to identify bind marker: | ||
| 1037 | /// ```` | ||
| 1038 | /// SELECT email FROM users WHERE name = @name AND password = $password; | ||
| 1039 | /// ```` | ||
| 1040 | /// | ||
| 1041 | /// To use these names, pass a normal structure instead of a tuple. Set `stmt` is the related `DynamicStatement`: | ||
| 1042 | /// ```` | ||
| 1043 | /// try stmt.one(.{ | ||
| 1044 | /// .name = "Tankman", .password = "Passw0rd", | ||
| 1045 | /// }) | ||
| 1046 | /// ```` | ||
| 1047 | /// | ||
| 1048 | /// It doesn't matter "@", "$" or ":" is being used, the one will be automatically chosen, | ||
| 1049 | /// but it's not recommended to mix them up, because: sqlite3 thinks @A, $A and :A are | ||
| 1050 | /// different, but `DynamicStatement` will try :A, @A, $A in order when you passing an 'A' field. | ||
| 1051 | /// The ":A" will be binded while "@A", "$A" are left behind. | ||
| 1052 | /// TL;DR: don't use same name with different indicator ("@", "$", ":"). | ||
| 1053 | /// | ||
| 1054 | /// You can use unnamed markers with tuple: | ||
| 1055 | /// ```` | ||
| 1056 | /// SELECT email FROM users WHERE name = ? AND password = ?; | ||
| 1057 | /// ```` | ||
| 1058 | /// | ||
| 1059 | /// ```` | ||
| 1060 | /// try stmt.one(.{"Tankman", "Passw0rd"}); | ||
| 1061 | /// ```` | ||
| 1062 | /// | ||
| 1063 | /// Named and unnamed markers could not be mixed, functions might be failed in slient. | ||
| 1064 | /// (Just like sqlite3's sqlite3_stmt, the unbinded values will be treated as NULL.) | ||
| 1065 | pub const DynamicStatement = struct { | ||
| 1066 | db: *c.sqlite3, | ||
| 1067 | stmt: *c.sqlite3_stmt, | ||
| 1068 | |||
| 1069 | const Self = @This(); | ||
| 1070 | |||
| 1071 | fn prepare(db: *Db, queryStr: []const u8, options: QueryOptions, flags: c_uint) !Self { | ||
| 1072 | var dummy_diags = Diagnostics{}; | ||
| 1073 | var diags = options.diags orelse &dummy_diags; | ||
| 1074 | var stmt = blk: { | ||
| 1075 | var tmp: ?*c.sqlite3_stmt = undefined; | ||
| 1076 | const result = c.sqlite3_prepare_v3( | ||
| 1077 | db.db, | ||
| 1078 | queryStr.ptr, | ||
| 1079 | @intCast(c_int, queryStr.len), | ||
| 1080 | flags, | ||
| 1081 | &tmp, | ||
| 1082 | null, | ||
| 1083 | ); | ||
| 1084 | if (result != c.SQLITE_OK) { | ||
| 1085 | diags.err = getLastDetailedErrorFromDb(db.db); | ||
| 1086 | return errors.errorFromResultCode(result); | ||
| 1087 | } | ||
| 1088 | break :blk tmp.?; | ||
| 1089 | }; | ||
| 1090 | return Self{ | ||
| 1091 | .db = db.db, | ||
| 1092 | .stmt = stmt, | ||
| 1093 | }; | ||
| 1094 | } | ||
| 1095 | |||
| 1096 | /// deinit releases the prepared statement. | ||
| 1097 | /// | ||
| 1098 | /// After a call to `deinit` the statement must not be used. | ||
| 1099 | pub fn deinit(self: *Self) void { | ||
| 1100 | const result = c.sqlite3_finalize(self.stmt); | ||
| 1101 | if (result != c.SQLITE_OK) { | ||
| 1102 | const detailed_error = getLastDetailedErrorFromDb(self.db); | ||
| 1103 | logger.err("unable to finalize prepared statement, result: {}, detailed error: {}", .{ result, detailed_error }); | ||
| 1104 | } | ||
| 1105 | } | ||
| 1106 | |||
| 1107 | /// reset resets the prepared statement to make it reusable. | ||
| 1108 | pub fn reset(self: *Self) void { | ||
| 1109 | const result = c.sqlite3_clear_bindings(self.stmt); | ||
| 1110 | if (result != c.SQLITE_OK) { | ||
| 1111 | const detailed_error = getLastDetailedErrorFromDb(self.db); | ||
| 1112 | logger.err("unable to clear prepared statement bindings, result: {}, detailed error: {}", .{ result, detailed_error }); | ||
| 1113 | } | ||
| 1114 | const result2 = c.sqlite3_reset(self.stmt); | ||
| 1115 | if (result2 != c.SQLITE_OK) { | ||
| 1116 | const detailed_error = getLastDetailedErrorFromDb(self.db); | ||
| 1117 | logger.err("unable to reset prepared statement, result: {}, detailed error: {}", .{ result2, detailed_error }); | ||
| 1118 | } | ||
| 1119 | } | ||
| 1120 | |||
| 1121 | fn tanslateError(value: anytype) !void { | ||
| 1122 | if (@TypeOf(value) != void) { | ||
| 1123 | if (@typeInfo(@TypeOf(value)) == .ErrorUnion and @typeInfo(@TypeOf(value)).ErrorUnion.payload == void) { | ||
| 1124 | return value; | ||
| 1125 | } else if (@TypeOf(value) == c_int and value == c.SQLITE_OK){ | ||
| 1126 | return; | ||
| 1127 | } else { | ||
| 1128 | return errors.errorFromResultCode(value); | ||
| 1129 | } | ||
| 1130 | } else { | ||
| 1131 | return; | ||
| 1132 | } | ||
| 1133 | } | ||
| 1134 | |||
| 1135 | fn bindField(self: *Self, comptime FieldType: type, options: anytype, comptime field_name: []const u8, i: c_int, field: FieldType) !void { | ||
| 1136 | const field_type_info = @typeInfo(FieldType); | ||
| 1137 | const column = i + 1; | ||
| 1138 | |||
| 1139 | const val = switch (FieldType) { | ||
| 1140 | Text => c.sqlite3_bind_text(self.stmt, column, field.data.ptr, @intCast(c_int, field.data.len), null), | ||
| 1141 | Blob => c.sqlite3_bind_blob(self.stmt, column, field.data.ptr, @intCast(c_int, field.data.len), null), | ||
| 1142 | ZeroBlob => c.sqlite3_bind_zeroblob64(self.stmt, column, field.length), | ||
| 1143 | else => switch (field_type_info) { | ||
| 1144 | .Int, .ComptimeInt => c.sqlite3_bind_int64(self.stmt, column, @intCast(c_longlong, field)), | ||
| 1145 | .Float, .ComptimeFloat => c.sqlite3_bind_double(self.stmt, column, field), | ||
| 1146 | .Bool => c.sqlite3_bind_int64(self.stmt, column, @boolToInt(field)), | ||
| 1147 | .Pointer => |ptr| switch (ptr.size) { | ||
| 1148 | .One => self.bindField(ptr.child, options, field_name, i, field.*), | ||
| 1149 | .Slice => switch (ptr.child) { | ||
| 1150 | u8 => c.sqlite3_bind_text(self.stmt, column, field.ptr, @intCast(c_int, field.len), null), | ||
| 1151 | else => @compileError("cannot bind field " ++ field_name ++ " of type " ++ @typeName(FieldType)), | ||
| 1152 | }, | ||
| 1153 | else => @compileError("cannot bind field " ++ field_name ++ " of type " ++ @typeName(FieldType)), | ||
| 1154 | }, | ||
| 1155 | .Array => |arr| switch (arr.child) { | ||
| 1156 | u8 => u8arr: { | ||
| 1157 | const data: []const u8 = field[0..field.len]; | ||
| 1158 | |||
| 1159 | break :u8arr c.sqlite3_bind_text(self.stmt, column, data.ptr, @intCast(c_int, data.len), null); | ||
| 1160 | }, | ||
| 1161 | else => @compileError("cannot bind field " ++ field_name ++ " of type array of " ++ @typeName(arr.child)), | ||
| 1162 | }, | ||
| 1163 | .Optional => |opt| if (field) |non_null_field| { | ||
| 1164 | try self.bindField(opt.child, options, field_name, i, non_null_field); | ||
| 1165 | } else optional_null: { | ||
| 1166 | break :optional_null c.sqlite3_bind_null(self.stmt, column); | ||
| 1167 | }, | ||
| 1168 | .Null => c.sqlite3_bind_null(self.stmt, column), | ||
| 1169 | .Enum => { | ||
| 1170 | if (comptime std.meta.trait.isZigString(FieldType.BaseType)) { | ||
| 1171 | return try self.bindField(FieldType.BaseType, options, field_name, i, @tagName(field)); | ||
| 1172 | } else if (@typeInfo(FieldType.BaseType) == .Int) { | ||
| 1173 | return try self.bindField(FieldType.BaseType, options, field_name, i, @enumToInt(field)); | ||
| 1174 | } | ||
| 1175 | // Above if-else tree should have return to bypass @compileError below. | ||
| 1176 | @compileError("enum column " ++ @typeName(FieldType) ++ " must have a BaseType of either string or int to bind"); | ||
| 1177 | }, | ||
| 1178 | .Struct => { | ||
| 1179 | return try self.bindField(FieldType.BaseType, options, field_name, i, try field.bindField(options.allocator)); | ||
| 1180 | }, | ||
| 1181 | else => @compileError("cannot bind field " ++ field_name ++ " of type " ++ @typeName(FieldType)), | ||
| 1182 | }, | ||
| 1183 | }; | ||
| 1184 | |||
| 1185 | return tanslateError(val); | ||
| 1186 | } | ||
| 1187 | |||
| 1188 | fn bind(self: *Self, options: anytype, values: anytype) !void { | ||
| 1189 | const StructType = @TypeOf(values); | ||
| 1190 | const StructTypeInfo = @typeInfo(StructType).Struct; | ||
| 1191 | |||
| 1192 | inline for (StructTypeInfo.fields) |struct_field, i| { | ||
| 1193 | const field_value = @field(values, struct_field.name); | ||
| 1194 | try self.bindField(struct_field.field_type, options, struct_field.name, i, field_value); | ||
| 1195 | } | ||
| 1196 | } | ||
| 1197 | |||
| 1198 | fn sqlite3BindParameterIndex(stmt: *c.sqlite3_stmt, comptime name: []const u8) c_int { | ||
| 1199 | inline for (.{":", "@", "$"}) |prefix| { | ||
| 1200 | const id = std.fmt.comptimePrint(prefix++"{s}", .{name}); | ||
| 1201 | const i = c.sqlite3_bind_parameter_index(stmt, id); | ||
| 1202 | if (i > 0) return i-1; // .bindField uses 0-based while sqlite3 uses 1-based index. | ||
| 1203 | } | ||
| 1204 | return -1; | ||
| 1205 | } | ||
| 1206 | |||
| 1207 | /// bind named structure | ||
| 1208 | fn bindNamedStruct(self: *Self, options: anytype, values: anytype) !void { | ||
| 1209 | const StructType = @TypeOf(values); | ||
| 1210 | const StructTypeInfo = @typeInfo(StructType).Struct; | ||
| 1211 | |||
| 1212 | inline for (StructTypeInfo.fields) |struct_field| { | ||
| 1213 | const i = sqlite3BindParameterIndex(self.stmt, struct_field.name); | ||
| 1214 | if (i >= 0) { | ||
| 1215 | try self.bindField( | ||
| 1216 | struct_field.field_type, | ||
| 1217 | options, | ||
| 1218 | struct_field.name, | ||
| 1219 | i, | ||
| 1220 | @field(values, struct_field.name)); | ||
| 1221 | } else if (i == -1) { | ||
| 1222 | return errors.SQLiteError.SQLiteNotFound; | ||
| 1223 | // bug: do not put into a else block. reproduced in 0.8.1 and 0.9.0+dev.1193 | ||
| 1224 | // title: broken LLVM module found: Operand is null. | ||
| 1225 | // TODO: fire an issue to ziglang/zig and place address here | ||
| 1226 | } | ||
| 1227 | } | ||
| 1228 | } | ||
| 1229 | |||
| 1230 | fn smartBind(self: *Self, options: anytype, values: anytype) !void { | ||
| 1231 | if (std.meta.fieldNames(@TypeOf(values)).len == 0){ | ||
| 1232 | return; | ||
| 1233 | }else if (std.meta.trait.isTuple(@TypeOf(values))){ | ||
| 1234 | try self.bind(options, values); | ||
| 1235 | } else { | ||
| 1236 | try self.bindNamedStruct(options, values); | ||
| 1237 | } | ||
| 1238 | } | ||
| 1239 | |||
| 1240 | /// exec executes a statement which does not return data. | ||
| 1241 | /// | ||
| 1242 | /// The `options` tuple is used to provide additional state in some cases. | ||
| 1243 | /// | ||
| 1244 | /// The `values` variable is used for the bind parameters. It must have as many fields as there are bind markers | ||
| 1245 | /// in the input query string. | ||
| 1246 | /// The values will be binded depends on the numberic name when it's a tuple, or the | ||
| 1247 | /// string name when it's a normal structure. | ||
| 1248 | /// | ||
| 1249 | /// Possible errors: | ||
| 1250 | /// - SQLiteError.SQLiteNotFound if some fields not found | ||
| 1251 | pub fn exec(self: *Self, options: QueryOptions, values: anytype) !void { | ||
| 1252 | try self.smartBind(.{}, values); | ||
| 1253 | |||
| 1254 | var dummy_diags = Diagnostics{}; | ||
| 1255 | var diags = options.diags orelse &dummy_diags; | ||
| 1256 | |||
| 1257 | const result = c.sqlite3_step(self.stmt); | ||
| 1258 | switch (result) { | ||
| 1259 | c.SQLITE_DONE => {}, | ||
| 1260 | else => { | ||
| 1261 | diags.err = getLastDetailedErrorFromDb(self.db); | ||
| 1262 | return errors.errorFromResultCode(result); | ||
| 1263 | }, | ||
| 1264 | } | ||
| 1265 | } | ||
| 1266 | |||
| 1267 | /// iterator returns an iterator to read data from the result set, one row at a time. | ||
| 1268 | /// | ||
| 1269 | /// The data in the row is used to populate a value of the type `Type`. | ||
| 1270 | /// This means that `Type` must have as many fields as is returned in the query | ||
| 1271 | /// executed by this statement. | ||
| 1272 | /// This also means that the type of each field must be compatible with the SQLite type. | ||
| 1273 | /// | ||
| 1274 | /// Here is an example of how to use the iterator: | ||
| 1275 | /// | ||
| 1276 | /// var iter = try stmt.iterator(usize, .{}); | ||
| 1277 | /// while (try iter.next(.{})) |row| { | ||
| 1278 | /// ... | ||
| 1279 | /// } | ||
| 1280 | /// | ||
| 1281 | /// The `values` tuple is used for the bind parameters. It must have as many fields as there are bind markers | ||
| 1282 | /// in the input query string. | ||
| 1283 | /// The values will be binded depends on the numberic name when it's a tuple, or the | ||
| 1284 | /// string name when it's a normal structure. | ||
| 1285 | /// | ||
| 1286 | /// The iterator _must not_ outlive the statement. | ||
| 1287 | /// | ||
| 1288 | /// Possible errors: | ||
| 1289 | /// - SQLiteError.SQLiteNotFound if some fields not found | ||
| 1290 | pub fn iterator(self: *Self, comptime Type: type, values: anytype) !Iterator(Type) { | ||
| 1291 | try self.smartBind(values); | ||
| 1292 | |||
| 1293 | var res: Iterator(Type) = undefined; | ||
| 1294 | res.db = self.db; | ||
| 1295 | res.stmt = self.stmt; | ||
| 1296 | |||
| 1297 | return res; | ||
| 1298 | } | ||
| 1299 | |||
| 1300 | /// one reads a single row from the result set of this statement. | ||
| 1301 | /// | ||
| 1302 | /// The data in the row is used to populate a value of the type `Type`. | ||
| 1303 | /// This means that `Type` must have as many fields as is returned in the query | ||
| 1304 | /// executed by this statement. | ||
| 1305 | /// This also means that the type of each field must be compatible with the SQLite type. | ||
| 1306 | /// | ||
| 1307 | /// Here is an example of how to use an anonymous struct type: | ||
| 1308 | /// | ||
| 1309 | /// const row = try stmt.one( | ||
| 1310 | /// struct { | ||
| 1311 | /// id: usize, | ||
| 1312 | /// name: [400]u8, | ||
| 1313 | /// age: usize, | ||
| 1314 | /// }, | ||
| 1315 | /// .{}, | ||
| 1316 | /// .{ .foo = "bar", .age = 500 }, | ||
| 1317 | /// ); | ||
| 1318 | /// | ||
| 1319 | /// The `options` tuple is used to provide additional state in some cases. | ||
| 1320 | /// | ||
| 1321 | /// The `values` tuple is used for the bind parameters. It must have as many fields as there are bind markers | ||
| 1322 | /// in the input query string. | ||
| 1323 | /// | ||
| 1324 | /// This cannot allocate memory. If you need to read TEXT or BLOB columns you need to use arrays or alternatively call `oneAlloc`. | ||
| 1325 | pub fn one(self: *Self, comptime Type: type, options: QueryOptions, values: anytype) !?Type { | ||
| 1326 | var iter = try self.iterator(Type, values); | ||
| 1327 | |||
| 1328 | const row = (try iter.next(options)) orelse return null; | ||
| 1329 | return row; | ||
| 1330 | } | ||
| 1331 | |||
| 1332 | /// oneAlloc is like `one` but can allocate memory. | ||
| 1333 | pub fn oneAlloc(self: *Self, comptime Type: type, allocator: *mem.Allocator, options: QueryOptions, values: anytype) !?Type { | ||
| 1334 | var iter = try self.iterator(Type, values); | ||
| 1335 | |||
| 1336 | const row = (try iter.nextAlloc(allocator, options)) orelse return null; | ||
| 1337 | return row; | ||
| 1338 | } | ||
| 1339 | |||
| 1340 | /// all reads all rows from the result set of this statement. | ||
| 1341 | /// | ||
| 1342 | /// The data in each row is used to populate a value of the type `Type`. | ||
| 1343 | /// This means that `Type` must have as many fields as is returned in the query | ||
| 1344 | /// executed by this statement. | ||
| 1345 | /// This also means that the type of each field must be compatible with the SQLite type. | ||
| 1346 | /// | ||
| 1347 | /// Here is an example of how to use an anonymous struct type: | ||
| 1348 | /// | ||
| 1349 | /// const rows = try stmt.all( | ||
| 1350 | /// struct { | ||
| 1351 | /// id: usize, | ||
| 1352 | /// name: []const u8, | ||
| 1353 | /// age: usize, | ||
| 1354 | /// }, | ||
| 1355 | /// allocator, | ||
| 1356 | /// .{}, | ||
| 1357 | /// .{ .foo = "bar", .age = 500 }, | ||
| 1358 | /// ); | ||
| 1359 | /// | ||
| 1360 | /// The `options` tuple is used to provide additional state in some cases. | ||
| 1361 | /// | ||
| 1362 | /// The `values` tuple is used for the bind parameters. It must have as many fields as there are bind markers | ||
| 1363 | /// in the input query string. | ||
| 1364 | /// | ||
| 1365 | /// Note that this allocates all rows into a single slice: if you read a lot of data this can use a lot of memory. | ||
| 1366 | pub fn all(self: *Self, comptime Type: type, allocator: *mem.Allocator, options: QueryOptions, values: anytype) ![]Type { | ||
| 1367 | var iter = try self.iterator(Type, values); | ||
| 1368 | |||
| 1369 | var rows = std.ArrayList(Type).init(allocator); | ||
| 1370 | while (try iter.nextAlloc(allocator, options)) |row| { | ||
| 1371 | try rows.append(row); | ||
| 1372 | } | ||
| 1373 | |||
| 1374 | return rows.toOwnedSlice(); | ||
| 1375 | } | ||
| 1376 | }; | ||
| 1377 | |||
| 997 | /// Statement is a wrapper around a SQLite statement, providing high-level functions to execute | 1378 | /// Statement is a wrapper around a SQLite statement, providing high-level functions to execute |
| 998 | /// a statement and retrieve rows for SELECT queries. | 1379 | /// a statement and retrieve rows for SELECT queries. |
| 999 | /// | 1380 | /// |
| @@ -1033,62 +1414,28 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t | |||
| 1033 | return struct { | 1414 | return struct { |
| 1034 | const Self = @This(); | 1415 | const Self = @This(); |
| 1035 | 1416 | ||
| 1036 | db: *c.sqlite3, | 1417 | dynamicStmt: DynamicStatement, |
| 1037 | stmt: *c.sqlite3_stmt, | ||
| 1038 | 1418 | ||
| 1039 | fn prepare(db: *Db, options: QueryOptions, flags: c_uint) !Self { | 1419 | fn prepare(db: *Db, options: QueryOptions, flags: c_uint) !Self { |
| 1040 | var dummy_diags = Diagnostics{}; | 1420 | return Self { |
| 1041 | var diags = options.diags orelse &dummy_diags; | 1421 | .dynamicStmt = try DynamicStatement.prepare(db, query.getQuery(), options, flags), |
| 1042 | |||
| 1043 | var stmt = blk: { | ||
| 1044 | const real_query = query.getQuery(); | ||
| 1045 | |||
| 1046 | var tmp: ?*c.sqlite3_stmt = undefined; | ||
| 1047 | const result = c.sqlite3_prepare_v3( | ||
| 1048 | db.db, | ||
| 1049 | real_query.ptr, | ||
| 1050 | @intCast(c_int, real_query.len), | ||
| 1051 | flags, | ||
| 1052 | &tmp, | ||
| 1053 | null, | ||
| 1054 | ); | ||
| 1055 | if (result != c.SQLITE_OK) { | ||
| 1056 | diags.err = getLastDetailedErrorFromDb(db.db); | ||
| 1057 | return errors.errorFromResultCode(result); | ||
| 1058 | } | ||
| 1059 | break :blk tmp.?; | ||
| 1060 | }; | 1422 | }; |
| 1423 | } | ||
| 1061 | 1424 | ||
| 1062 | return Self{ | 1425 | pub fn dynamic(self: *Self) *DynamicStatement { |
| 1063 | .db = db.db, | 1426 | return &self.dynamicStmt; |
| 1064 | .stmt = stmt, | ||
| 1065 | }; | ||
| 1066 | } | 1427 | } |
| 1067 | 1428 | ||
| 1068 | /// deinit releases the prepared statement. | 1429 | /// deinit releases the prepared statement. |
| 1069 | /// | 1430 | /// |
| 1070 | /// After a call to `deinit` the statement must not be used. | 1431 | /// After a call to `deinit` the statement must not be used. |
| 1071 | pub fn deinit(self: *Self) void { | 1432 | pub fn deinit(self: *Self) void { |
| 1072 | const result = c.sqlite3_finalize(self.stmt); | 1433 | self.dynamic().deinit(); |
| 1073 | if (result != c.SQLITE_OK) { | ||
| 1074 | const detailed_error = getLastDetailedErrorFromDb(self.db); | ||
| 1075 | logger.err("unable to finalize prepared statement, result: {}, detailed error: {}", .{ result, detailed_error }); | ||
| 1076 | } | ||
| 1077 | } | 1434 | } |
| 1078 | 1435 | ||
| 1079 | /// reset resets the prepared statement to make it reusable. | 1436 | /// reset resets the prepared statement to make it reusable. |
| 1080 | pub fn reset(self: *Self) void { | 1437 | pub fn reset(self: *Self) void { |
| 1081 | const result = c.sqlite3_clear_bindings(self.stmt); | 1438 | self.dynamic().reset(); |
| 1082 | if (result != c.SQLITE_OK) { | ||
| 1083 | const detailed_error = getLastDetailedErrorFromDb(self.db); | ||
| 1084 | logger.err("unable to clear prepared statement bindings, result: {}, detailed error: {}", .{ result, detailed_error }); | ||
| 1085 | } | ||
| 1086 | |||
| 1087 | const result2 = c.sqlite3_reset(self.stmt); | ||
| 1088 | if (result2 != c.SQLITE_OK) { | ||
| 1089 | const detailed_error = getLastDetailedErrorFromDb(self.db); | ||
| 1090 | logger.err("unable to reset prepared statement, result: {}, detailed error: {}", .{ result2, detailed_error }); | ||
| 1091 | } | ||
| 1092 | } | 1439 | } |
| 1093 | 1440 | ||
| 1094 | /// bind binds values to every bind marker in the prepared statement. | 1441 | /// bind binds values to every bind marker in the prepared statement. |
| @@ -1128,13 +1475,15 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t | |||
| 1128 | else => comptime assertMarkerType(struct_field.field_type, typ), | 1475 | else => comptime assertMarkerType(struct_field.field_type, typ), |
| 1129 | } | 1476 | } |
| 1130 | }, | 1477 | }, |
| 1478 | |||
| 1131 | .Untyped => {}, | 1479 | .Untyped => {}, |
| 1132 | } | 1480 | } |
| 1133 | |||
| 1134 | const field_value = @field(values, struct_field.name); | ||
| 1135 | |||
| 1136 | try self.bindField(struct_field.field_type, options, struct_field.name, _i, field_value); | ||
| 1137 | } | 1481 | } |
| 1482 | |||
| 1483 | return self.dynamic().bind(options, values) catch |e| switch (e) { | ||
| 1484 | errors.Error.SQLiteNotFound => unreachable, // impossible to have non-exists field | ||
| 1485 | else => e, | ||
| 1486 | }; | ||
| 1138 | } | 1487 | } |
| 1139 | 1488 | ||
| 1140 | fn assertMarkerType(comptime Actual: type, comptime Expected: type) void { | 1489 | fn assertMarkerType(comptime Actual: type, comptime Expected: type) void { |
| @@ -1143,57 +1492,19 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t | |||
| 1143 | } | 1492 | } |
| 1144 | } | 1493 | } |
| 1145 | 1494 | ||
| 1146 | fn bindField(self: *Self, comptime FieldType: type, options: anytype, comptime field_name: []const u8, i: c_int, field: FieldType) !void { | 1495 | /// execAlloc is like `exec` but can allocate memory. |
| 1147 | const field_type_info = @typeInfo(FieldType); | 1496 | pub fn execAlloc(self: *Self, allocator: *std.mem.Allocator, options: QueryOptions, values: anytype) !void { |
| 1148 | const column = i + 1; | 1497 | try self.bind(.{ .allocator = allocator }, values); |
| 1149 | 1498 | ||
| 1150 | switch (FieldType) { | 1499 | var dummy_diags = Diagnostics{}; |
| 1151 | Text => _ = c.sqlite3_bind_text(self.stmt, column, field.data.ptr, @intCast(c_int, field.data.len), null), | 1500 | var diags = options.diags orelse &dummy_diags; |
| 1152 | Blob => _ = c.sqlite3_bind_blob(self.stmt, column, field.data.ptr, @intCast(c_int, field.data.len), null), | 1501 | |
| 1153 | ZeroBlob => _ = c.sqlite3_bind_zeroblob64(self.stmt, column, field.length), | 1502 | const result = c.sqlite3_step(self.dynamic().stmt); |
| 1154 | else => switch (field_type_info) { | 1503 | switch (result) { |
| 1155 | .Int, .ComptimeInt => _ = c.sqlite3_bind_int64(self.stmt, column, @intCast(c_longlong, field)), | 1504 | c.SQLITE_DONE => {}, |
| 1156 | .Float, .ComptimeFloat => _ = c.sqlite3_bind_double(self.stmt, column, field), | 1505 | else => { |
| 1157 | .Bool => _ = c.sqlite3_bind_int64(self.stmt, column, @boolToInt(field)), | 1506 | diags.err = getLastDetailedErrorFromDb(self.dynamic().db); |
| 1158 | .Pointer => |ptr| switch (ptr.size) { | 1507 | return errors.errorFromResultCode(result); |
| 1159 | .One => try self.bindField(ptr.child, options, field_name, i, field.*), | ||
| 1160 | .Slice => switch (ptr.child) { | ||
| 1161 | u8 => { | ||
| 1162 | _ = c.sqlite3_bind_text(self.stmt, column, field.ptr, @intCast(c_int, field.len), null); | ||
| 1163 | }, | ||
| 1164 | else => @compileError("cannot bind field " ++ field_name ++ " of type " ++ @typeName(FieldType)), | ||
| 1165 | }, | ||
| 1166 | else => @compileError("cannot bind field " ++ field_name ++ " of type " ++ @typeName(FieldType)), | ||
| 1167 | }, | ||
| 1168 | .Array => |arr| { | ||
| 1169 | switch (arr.child) { | ||
| 1170 | u8 => { | ||
| 1171 | const data: []const u8 = field[0..field.len]; | ||
| 1172 | |||
| 1173 | _ = c.sqlite3_bind_text(self.stmt, column, data.ptr, @intCast(c_int, data.len), null); | ||
| 1174 | }, | ||
| 1175 | else => @compileError("cannot bind field " ++ field_name ++ " of type array of " ++ @typeName(arr.child)), | ||
| 1176 | } | ||
| 1177 | }, | ||
| 1178 | .Optional => |opt| if (field) |non_null_field| { | ||
| 1179 | try self.bindField(opt.child, options, field_name, i, non_null_field); | ||
| 1180 | } else { | ||
| 1181 | _ = c.sqlite3_bind_null(self.stmt, column); | ||
| 1182 | }, | ||
| 1183 | .Null => _ = c.sqlite3_bind_null(self.stmt, column), | ||
| 1184 | .Enum => { | ||
| 1185 | if (comptime std.meta.trait.isZigString(FieldType.BaseType)) { | ||
| 1186 | return try self.bindField(FieldType.BaseType, options, field_name, i, @tagName(field)); | ||
| 1187 | } | ||
| 1188 | if (@typeInfo(FieldType.BaseType) == .Int) { | ||
| 1189 | return try self.bindField(FieldType.BaseType, options, field_name, i, @enumToInt(field)); | ||
| 1190 | } | ||
| 1191 | @compileError("enum column " ++ @typeName(FieldType) ++ " must have a BaseType of either string or int to bind"); | ||
| 1192 | }, | ||
| 1193 | .Struct => { | ||
| 1194 | return try self.bindField(FieldType.BaseType, options, field_name, i, try field.bindField(options.allocator)); | ||
| 1195 | }, | ||
| 1196 | else => @compileError("cannot bind field " ++ field_name ++ " of type " ++ @typeName(FieldType)), | ||
| 1197 | }, | 1508 | }, |
| 1198 | } | 1509 | } |
| 1199 | } | 1510 | } |
| @@ -1206,33 +1517,16 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t | |||
| 1206 | /// in the input query string. | 1517 | /// in the input query string. |
| 1207 | /// | 1518 | /// |
| 1208 | pub fn exec(self: *Self, options: QueryOptions, values: anytype) !void { | 1519 | pub fn exec(self: *Self, options: QueryOptions, values: anytype) !void { |
| 1209 | try self.bind({}, values); | 1520 | try self.bind(.{}, values); |
| 1210 | 1521 | ||
| 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); | ||
| 1227 | |||
| 1228 | var dummy_diags = Diagnostics{}; | 1522 | var dummy_diags = Diagnostics{}; |
| 1229 | var diags = options.diags orelse &dummy_diags; | 1523 | var diags = options.diags orelse &dummy_diags; |
| 1230 | 1524 | ||
| 1231 | const result = c.sqlite3_step(self.stmt); | 1525 | const result = c.sqlite3_step(self.dynamic().stmt); |
| 1232 | switch (result) { | 1526 | switch (result) { |
| 1233 | c.SQLITE_DONE => {}, | 1527 | c.SQLITE_DONE => {}, |
| 1234 | else => { | 1528 | else => { |
| 1235 | diags.err = getLastDetailedErrorFromDb(self.db); | 1529 | diags.err = getLastDetailedErrorFromDb(self.dynamic().db); |
| 1236 | return errors.errorFromResultCode(result); | 1530 | return errors.errorFromResultCode(result); |
| 1237 | }, | 1531 | }, |
| 1238 | } | 1532 | } |
| @@ -1257,11 +1551,11 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t | |||
| 1257 | /// | 1551 | /// |
| 1258 | /// The iterator _must not_ outlive the statement. | 1552 | /// The iterator _must not_ outlive the statement. |
| 1259 | pub fn iterator(self: *Self, comptime Type: type, values: anytype) !Iterator(Type) { | 1553 | pub fn iterator(self: *Self, comptime Type: type, values: anytype) !Iterator(Type) { |
| 1260 | try self.bind({}, values); | 1554 | try self.bind(.{}, values); |
| 1261 | 1555 | ||
| 1262 | var res: Iterator(Type) = undefined; | 1556 | var res: Iterator(Type) = undefined; |
| 1263 | res.db = self.db; | 1557 | res.db = self.dynamic().db; |
| 1264 | res.stmt = self.stmt; | 1558 | res.stmt = self.dynamic().stmt; |
| 1265 | 1559 | ||
| 1266 | return res; | 1560 | return res; |
| 1267 | } | 1561 | } |
| @@ -1271,8 +1565,8 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: ParsedQuery) t | |||
| 1271 | try self.bind(.{ .allocator = allocator }, values); | 1565 | try self.bind(.{ .allocator = allocator }, values); |
| 1272 | 1566 | ||
| 1273 | var res: Iterator(Type) = undefined; | 1567 | var res: Iterator(Type) = undefined; |
| 1274 | res.db = self.db; | 1568 | res.db = self.dynamic().db; |
| 1275 | res.stmt = self.stmt; | 1569 | res.stmt = self.dynamic().stmt; |
| 1276 | 1570 | ||
| 1277 | return res; | 1571 | return res; |
| 1278 | } | 1572 | } |
| @@ -1504,6 +1798,32 @@ test "sqlite: statement exec" { | |||
| 1504 | } | 1798 | } |
| 1505 | } | 1799 | } |
| 1506 | 1800 | ||
| 1801 | test "sqlite: statement execDynamic" { | ||
| 1802 | // It's a smoke test for DynamicStatment, because the DynamicStatment is almost a wrapper to sqlite3_stmt | ||
| 1803 | // , but it's not our task to test. This test is a simple test to check if the .bindNamedStruct working. | ||
| 1804 | // Because of the dependence of Statment to DynamicStatment, it's not required to test rest functions. | ||
| 1805 | var db = try getTestDb(); | ||
| 1806 | try addTestData(&db); | ||
| 1807 | |||
| 1808 | // Test with a Blob struct | ||
| 1809 | { | ||
| 1810 | try db.execDynamic("INSERT INTO user(id, name, age) VALUES(@id, @name, @age)", .{}, .{ | ||
| 1811 | .id = @as(usize, 200), | ||
| 1812 | .name = Blob{ .data = "hello" }, | ||
| 1813 | .age = @as(u32, 20), | ||
| 1814 | }); | ||
| 1815 | } | ||
| 1816 | |||
| 1817 | // Test with a Text struct | ||
| 1818 | { | ||
| 1819 | try db.execDynamic("INSERT INTO user(id, name, age) VALUES(@id, @name, @age)", .{}, .{ | ||
| 1820 | .id = @as(usize, 201), | ||
| 1821 | .name = Text{ .data = "hello" }, | ||
| 1822 | .age = @as(u32, 20), | ||
| 1823 | }); | ||
| 1824 | } | ||
| 1825 | } | ||
| 1826 | |||
| 1507 | test "sqlite: read a single user into a struct" { | 1827 | test "sqlite: read a single user into a struct" { |
| 1508 | var arena = std.heap.ArenaAllocator.init(testing.allocator); | 1828 | var arena = std.heap.ArenaAllocator.init(testing.allocator); |
| 1509 | defer arena.deinit(); | 1829 | defer arena.deinit(); |