diff options
| -rw-r--r-- | query.zig | 133 |
1 files changed, 115 insertions, 18 deletions
| @@ -7,9 +7,9 @@ const Blob = @import("sqlite.zig").Blob; | |||
| 7 | /// Text is used to represent a SQLite TEXT value when binding a parameter or reading a column. | 7 | /// Text is used to represent a SQLite TEXT value when binding a parameter or reading a column. |
| 8 | pub const Text = struct { data: []const u8 }; | 8 | pub const Text = struct { data: []const u8 }; |
| 9 | 9 | ||
| 10 | const BindMarker = union(enum) { | 10 | const BindMarker = struct { |
| 11 | Typed: type, | 11 | typed: ?type = null, // null == untyped |
| 12 | Untyped: void, | 12 | identifier: ?[]const u8 = null, |
| 13 | }; | 13 | }; |
| 14 | 14 | ||
| 15 | pub const ParsedQuery = struct { | 15 | pub const ParsedQuery = struct { |
| @@ -29,6 +29,9 @@ pub const ParsedQuery = struct { | |||
| 29 | comptime var current_bind_marker_type: [256]u8 = undefined; | 29 | comptime var current_bind_marker_type: [256]u8 = undefined; |
| 30 | comptime var current_bind_marker_type_pos = 0; | 30 | comptime var current_bind_marker_type_pos = 0; |
| 31 | 31 | ||
| 32 | comptime var current_bind_marker_id: [256]u8 = undefined; | ||
| 33 | comptime var current_bind_marker_id_pos = 0; | ||
| 34 | |||
| 32 | comptime var parsed_query: ParsedQuery = undefined; | 35 | comptime var parsed_query: ParsedQuery = undefined; |
| 33 | parsed_query.nb_bind_markers = 0; | 36 | parsed_query.nb_bind_markers = 0; |
| 34 | 37 | ||
| @@ -47,16 +50,48 @@ pub const ParsedQuery = struct { | |||
| 47 | }, | 50 | }, |
| 48 | .BindMarker => switch (c) { | 51 | .BindMarker => switch (c) { |
| 49 | '{' => { | 52 | '{' => { |
| 53 | parsed_query.bind_markers[parsed_query.nb_bind_markers] = BindMarker{}; | ||
| 50 | state = .BindMarkerType; | 54 | state = .BindMarkerType; |
| 51 | current_bind_marker_type_pos = 0; | 55 | current_bind_marker_type_pos = 0; |
| 52 | }, | 56 | }, |
| 53 | else => { | 57 | else => { |
| 54 | // This is a bind marker without a type. | 58 | if (std.ascii.isAlpha(c) or std.ascii.isDigit(c)){ |
| 55 | state = .Start; | 59 | parsed_query.bind_markers[parsed_query.nb_bind_markers] = BindMarker{}; |
| 56 | 60 | state = .BindMarkerIdentifier; | |
| 57 | parsed_query.bind_markers[parsed_query.nb_bind_markers] = BindMarker{ .Untyped = {} }; | 61 | current_bind_marker_id_pos = 0; |
| 58 | parsed_query.nb_bind_markers += 1; | 62 | current_bind_marker_id[current_bind_marker_id_pos] = c; |
| 63 | current_bind_marker_id_pos += 1; | ||
| 64 | } else { | ||
| 65 | // This is a bind marker without a type. | ||
| 66 | state = .Start; | ||
| 67 | |||
| 68 | parsed_query.bind_markers[parsed_query.nb_bind_markers] = BindMarker{}; | ||
| 69 | parsed_query.nb_bind_markers += 1; | ||
| 70 | } | ||
| 71 | buf[pos] = c; | ||
| 72 | pos += 1; | ||
| 73 | }, | ||
| 74 | }, | ||
| 75 | .BindMarkerIdentifier => switch (c) { | ||
| 76 | '{' => { | ||
| 77 | state = .BindMarkerType; | ||
| 78 | current_bind_marker_type_pos = 0; | ||
| 59 | 79 | ||
| 80 | // A bind marker with id and type: ?AAA{[]const u8}, we don't need move the pointer. | ||
| 81 | if (current_bind_marker_id_pos > 0){ | ||
| 82 | parsed_query.bind_markers[parsed_query.nb_bind_markers].identifier = std.fmt.comptimePrint("{s}", .{current_bind_marker_id[0..current_bind_marker_id_pos]}); | ||
| 83 | } | ||
| 84 | }, | ||
| 85 | else => { | ||
| 86 | if (std.ascii.isAlpha(c) or std.ascii.isDigit(c)){ | ||
| 87 | current_bind_marker_id[current_bind_marker_id_pos] = c; | ||
| 88 | current_bind_marker_id_pos += 1; | ||
| 89 | } else { | ||
| 90 | state = .Start; | ||
| 91 | if (current_bind_marker_id_pos > 0) { | ||
| 92 | parsed_query.bind_markers[parsed_query.nb_bind_markers].identifier = std.fmt.comptimePrint("{s}", .{current_bind_marker_id[0..current_bind_marker_id_pos]}); | ||
| 93 | } | ||
| 94 | } | ||
| 60 | buf[pos] = c; | 95 | buf[pos] = c; |
| 61 | pos += 1; | 96 | pos += 1; |
| 62 | }, | 97 | }, |
| @@ -67,7 +102,7 @@ pub const ParsedQuery = struct { | |||
| 67 | 102 | ||
| 68 | const typ = parseType(current_bind_marker_type[0..current_bind_marker_type_pos]); | 103 | const typ = parseType(current_bind_marker_type[0..current_bind_marker_type_pos]); |
| 69 | 104 | ||
| 70 | parsed_query.bind_markers[parsed_query.nb_bind_markers] = BindMarker{ .Typed = typ }; | 105 | parsed_query.bind_markers[parsed_query.nb_bind_markers].typed = typ; |
| 71 | parsed_query.nb_bind_markers += 1; | 106 | parsed_query.nb_bind_markers += 1; |
| 72 | }, | 107 | }, |
| 73 | else => { | 108 | else => { |
| @@ -83,7 +118,10 @@ pub const ParsedQuery = struct { | |||
| 83 | 118 | ||
| 84 | // The last character was ? so this must be an untyped bind marker. | 119 | // The last character was ? so this must be an untyped bind marker. |
| 85 | if (state == .BindMarker) { | 120 | if (state == .BindMarker) { |
| 86 | parsed_query.bind_markers[parsed_query.nb_bind_markers] = BindMarker{ .Untyped = {} }; | 121 | parsed_query.bind_markers[parsed_query.nb_bind_markers].typed = null; |
| 122 | parsed_query.nb_bind_markers += 1; | ||
| 123 | } else if (state == .BindMarkerIdentifier) { | ||
| 124 | parsed_query.bind_markers[parsed_query.nb_bind_markers].identifier = std.fmt.comptimePrint("{s}", .{current_bind_marker_id[0..current_bind_marker_id_pos]}); | ||
| 87 | parsed_query.nb_bind_markers += 1; | 125 | parsed_query.nb_bind_markers += 1; |
| 88 | } | 126 | } |
| 89 | 127 | ||
| @@ -175,19 +213,19 @@ test "parsed query: bind markers types" { | |||
| 175 | const testCases = &[_]testCase{ | 213 | const testCases = &[_]testCase{ |
| 176 | .{ | 214 | .{ |
| 177 | .query = "foobar ?{usize}", | 215 | .query = "foobar ?{usize}", |
| 178 | .expected_marker = .{ .Typed = usize }, | 216 | .expected_marker = .{ .typed = usize }, |
| 179 | }, | 217 | }, |
| 180 | .{ | 218 | .{ |
| 181 | .query = "foobar ?{text}", | 219 | .query = "foobar ?{text}", |
| 182 | .expected_marker = .{ .Typed = Text }, | 220 | .expected_marker = .{ .typed = Text }, |
| 183 | }, | 221 | }, |
| 184 | .{ | 222 | .{ |
| 185 | .query = "foobar ?{blob}", | 223 | .query = "foobar ?{blob}", |
| 186 | .expected_marker = .{ .Typed = Blob }, | 224 | .expected_marker = .{ .typed = Blob }, |
| 187 | }, | 225 | }, |
| 188 | .{ | 226 | .{ |
| 189 | .query = "foobar ?", | 227 | .query = "foobar ?", |
| 190 | .expected_marker = .{ .Untyped = {} }, | 228 | .expected_marker = .{ .typed = null }, |
| 191 | }, | 229 | }, |
| 192 | }; | 230 | }; |
| 193 | 231 | ||
| @@ -197,9 +235,68 @@ test "parsed query: bind markers types" { | |||
| 197 | try testing.expectEqual(1, parsed_query.nb_bind_markers); | 235 | try testing.expectEqual(1, parsed_query.nb_bind_markers); |
| 198 | 236 | ||
| 199 | const bind_marker = parsed_query.bind_markers[0]; | 237 | const bind_marker = parsed_query.bind_markers[0]; |
| 200 | switch (tc.expected_marker) { | 238 | try testing.expectEqual(tc.expected_marker.typed, bind_marker.typed); |
| 201 | .Typed => |typ| try testing.expectEqual(typ, bind_marker.Typed), | 239 | } |
| 202 | .Untyped => |typ| try testing.expectEqual(typ, bind_marker.Untyped), | 240 | } |
| 203 | } | 241 | |
| 242 | test "parsed query: bind markers identifier" { | ||
| 243 | const testCase = struct { | ||
| 244 | query: []const u8, | ||
| 245 | expected_marker: BindMarker, | ||
| 246 | }; | ||
| 247 | |||
| 248 | const testCases = &[_]testCase{ | ||
| 249 | .{ | ||
| 250 | .query = "foobar ?ABC{usize}", | ||
| 251 | .expected_marker = .{ .identifier = "ABC" }, | ||
| 252 | }, | ||
| 253 | .{ | ||
| 254 | .query = "foobar ?123{text}", | ||
| 255 | .expected_marker = .{ .identifier = "123" }, | ||
| 256 | }, | ||
| 257 | .{ | ||
| 258 | .query = "foobar ?abc{blob}", | ||
| 259 | .expected_marker = .{ .identifier = "abc" }, | ||
| 260 | }, | ||
| 261 | .{ | ||
| 262 | .query = "foobar ?123", | ||
| 263 | .expected_marker = .{ .identifier = "123" }, | ||
| 264 | }, | ||
| 265 | }; | ||
| 266 | |||
| 267 | inline for (testCases) |tc| { | ||
| 268 | comptime var parsed_query = ParsedQuery.from(tc.query); | ||
| 269 | |||
| 270 | try testing.expectEqual(1, parsed_query.nb_bind_markers); | ||
| 271 | |||
| 272 | const bind_marker = parsed_query.bind_markers[0]; | ||
| 273 | try testing.expectEqualStrings(tc.expected_marker.identifier.?, bind_marker.identifier.?); | ||
| 274 | } | ||
| 275 | } | ||
| 276 | |||
| 277 | test "parsed query: query bind identifier" { | ||
| 278 | const testCase = struct { | ||
| 279 | query: []const u8, | ||
| 280 | expected_query: []const u8, | ||
| 281 | }; | ||
| 282 | |||
| 283 | const testCases = &[_]testCase{ | ||
| 284 | .{ | ||
| 285 | .query = "INSERT INTO user(id, name, age) VALUES(?id{usize}, ?name{[]const u8}, ?age{u32})", | ||
| 286 | .expected_query = "INSERT INTO user(id, name, age) VALUES(?id, ?name, ?age)", | ||
| 287 | }, | ||
| 288 | .{ | ||
| 289 | .query = "SELECT id, name, age FROM user WHER age > ?ageGT{u32} AND age < ?ageLT{u32}", | ||
| 290 | .expected_query = "SELECT id, name, age FROM user WHER age > ?ageGT AND age < ?ageLT", | ||
| 291 | }, | ||
| 292 | .{ | ||
| 293 | .query = "SELECT id, name, age FROM user WHER age > ?ageGT AND age < ?ageLT", | ||
| 294 | .expected_query = "SELECT id, name, age FROM user WHER age > ?ageGT AND age < ?ageLT", | ||
| 295 | }, | ||
| 296 | }; | ||
| 297 | |||
| 298 | inline for (testCases) |tc| { | ||
| 299 | comptime var parsed_query = ParsedQuery.from(tc.query); | ||
| 300 | try testing.expectEqualStrings(tc.expected_query, parsed_query.getQuery()); | ||
| 204 | } | 301 | } |
| 205 | } | 302 | } |