summaryrefslogtreecommitdiff
path: root/query.zig
diff options
context:
space:
mode:
Diffstat (limited to 'query.zig')
-rw-r--r--query.zig329
1 files changed, 168 insertions, 161 deletions
diff --git a/query.zig b/query.zig
index 70ffde7..a63bd3d 100644
--- a/query.zig
+++ b/query.zig
@@ -19,186 +19,193 @@ fn isNamedIdentifierChar(c: u8) bool {
19 return std.ascii.isAlpha(c) or std.ascii.isDigit(c) or c == '_'; 19 return std.ascii.isAlpha(c) or std.ascii.isDigit(c) or c == '_';
20} 20}
21 21
22pub const ParsedQuery = struct { 22pub fn ParsedQuery(comptime query: []const u8) ParsedQueryState(query.len) {
23 const Self = @This(); 23 // This contains the final SQL query after parsing with our
24 24 // own typed bind markers removed.
25 bind_markers: [128]BindMarker, 25 comptime var buf: [query.len]u8 = undefined;
26 nb_bind_markers: usize, 26 comptime var pos = 0;
27 27 comptime var state = .start;
28 query: [1024]u8, 28
29 query_size: usize, 29 comptime var current_bind_marker_type: [256]u8 = undefined;
30 30 comptime var current_bind_marker_type_pos = 0;
31 pub fn from(comptime query: []const u8) Self { 31
32 // This contains the final SQL query after parsing with our 32 // becomes part of our result
33 // own typed bind markers removed. 33 comptime var bind_markers: [128]BindMarker = undefined;
34 comptime var buf: [query.len]u8 = undefined; 34 comptime var nb_bind_markers: usize = 0;
35 comptime var pos = 0; 35
36 comptime var state = .start; 36 inline for (query) |c| {
37 37 switch (state) {
38 comptime var current_bind_marker_type: [256]u8 = undefined; 38 .start => switch (c) {
39 comptime var current_bind_marker_type_pos = 0; 39 '?', ':', '@', '$' => {
40 40 bind_markers[nb_bind_markers] = BindMarker{};
41 comptime var parsed_query: ParsedQuery = undefined; 41 current_bind_marker_type_pos = 0;
42 parsed_query.nb_bind_markers = 0; 42 state = .bind_marker;
43 43 buf[pos] = c;
44 inline for (query) |c| { 44 pos += 1;
45 switch (state) {
46 .start => switch (c) {
47 '?', ':', '@', '$' => {
48 parsed_query.bind_markers[parsed_query.nb_bind_markers] = BindMarker{};
49 current_bind_marker_type_pos = 0;
50 state = .bind_marker;
51 buf[pos] = c;
52 pos += 1;
53 },
54 '\'', '"' => {
55 state = .inside_string;
56 buf[pos] = c;
57 pos += 1;
58 },
59 else => {
60 buf[pos] = c;
61 pos += 1;
62 },
63 }, 45 },
64 .inside_string => switch (c) { 46 '\'', '"' => {
65 '\'', '"' => { 47 state = .inside_string;
66 state = .start; 48 buf[pos] = c;
67 buf[pos] = c; 49 pos += 1;
68 pos += 1;
69 },
70 else => {
71 buf[pos] = c;
72 pos += 1;
73 },
74 }, 50 },
75 .bind_marker => switch (c) { 51 else => {
76 '?', ':', '@', '$' => @compileError("invalid multiple '?', ':', '$' or '@'."), 52 buf[pos] = c;
77 '{' => { 53 pos += 1;
78 state = .bind_marker_type;
79 },
80 else => {
81 if (isNamedIdentifierChar(c)) {
82 // This is the start of a named bind marker.
83 state = .bind_marker_identifier;
84 } else {
85 // This is a unnamed, untyped bind marker.
86 state = .start;
87
88 parsed_query.bind_markers[parsed_query.nb_bind_markers].typed = null;
89 parsed_query.nb_bind_markers += 1;
90 }
91 buf[pos] = c;
92 pos += 1;
93 },
94 }, 54 },
95 .bind_marker_identifier => switch (c) { 55 },
96 '?', ':', '@', '$' => @compileError("unregconised multiple '?', ':', '$' or '@'."), 56 .inside_string => switch (c) {
97 '{' => { 57 '\'', '"' => {
98 state = .bind_marker_type; 58 state = .start;
99 current_bind_marker_type_pos = 0; 59 buf[pos] = c;
100 }, 60 pos += 1;
101 else => { 61 },
102 if (!isNamedIdentifierChar(c)) { 62 else => {
103 // This marks the end of the named bind marker. 63 buf[pos] = c;
104 state = .start; 64 pos += 1;
105 parsed_query.nb_bind_markers += 1; 65 },
106 } 66 },
107 buf[pos] = c; 67 .bind_marker => switch (c) {
108 pos += 1; 68 '?', ':', '@', '$' => @compileError("invalid multiple '?', ':', '$' or '@'."),
109 }, 69 '{' => {
70 state = .bind_marker_type;
110 }, 71 },
111 .bind_marker_type => switch (c) { 72 else => {
112 '}' => { 73 if (isNamedIdentifierChar(c)) {
74 // This is the start of a named bind marker.
75 state = .bind_marker_identifier;
76 } else {
77 // This is a unnamed, untyped bind marker.
113 state = .start; 78 state = .start;
114 79
115 const type_info_string = current_bind_marker_type[0..current_bind_marker_type_pos]; 80 bind_markers[nb_bind_markers].typed = null;
116 // Handles optional types 81 nb_bind_markers += 1;
117 const typ = if (type_info_string[0] == '?') blk: { 82 }
118 const child_type = parseType(type_info_string[1..]); 83 buf[pos] = c;
119 break :blk @Type(std.builtin.TypeInfo{ 84 pos += 1;
120 .Optional = .{ 85 },
121 .child = child_type, 86 },
122 }, 87 .bind_marker_identifier => switch (c) {
123 }); 88 '?', ':', '@', '$' => @compileError("unregconised multiple '?', ':', '$' or '@'."),
124 } else blk: { 89 '{' => {
125 break :blk parseType(type_info_string); 90 state = .bind_marker_type;
126 }; 91 current_bind_marker_type_pos = 0;
127
128 parsed_query.bind_markers[parsed_query.nb_bind_markers].typed = typ;
129 parsed_query.nb_bind_markers += 1;
130 },
131 else => {
132 current_bind_marker_type[current_bind_marker_type_pos] = c;
133 current_bind_marker_type_pos += 1;
134 },
135 }, 92 },
136 else => { 93 else => {
137 @compileError("invalid state " ++ @tagName(state)); 94 if (!isNamedIdentifierChar(c)) {
95 // This marks the end of the named bind marker.
96 state = .start;
97 nb_bind_markers += 1;
98 }
99 buf[pos] = c;
100 pos += 1;
101 },
102 },
103 .bind_marker_type => switch (c) {
104 '}' => {
105 state = .start;
106
107 const type_info_string = current_bind_marker_type[0..current_bind_marker_type_pos];
108 // Handles optional types
109 const typ = if (type_info_string[0] == '?') blk: {
110 const child_type = ParseType(type_info_string[1..]);
111 break :blk @Type(std.builtin.TypeInfo{
112 .Optional = .{
113 .child = child_type,
114 },
115 });
116 } else blk: {
117 break :blk ParseType(type_info_string);
118 };
119
120 bind_markers[nb_bind_markers].typed = typ;
121 nb_bind_markers += 1;
122 },
123 else => {
124 current_bind_marker_type[current_bind_marker_type_pos] = c;
125 current_bind_marker_type_pos += 1;
138 }, 126 },
139 }
140 }
141
142 // The last character was a bind marker prefix so this must be an untyped bind marker.
143 switch (state) {
144 .bind_marker => {
145 parsed_query.bind_markers[parsed_query.nb_bind_markers].typed = null;
146 parsed_query.nb_bind_markers += 1;
147 }, 127 },
148 .bind_marker_identifier => { 128 else => {
149 parsed_query.nb_bind_markers += 1; 129 @compileError("invalid state " ++ @tagName(state));
150 }, 130 },
151 .start => {},
152 else => @compileError("invalid final state " ++ @tagName(state) ++ ", this means you wrote an incomplete bind marker type"),
153 } 131 }
154
155 mem.copy(u8, &parsed_query.query, &buf);
156 parsed_query.query_size = pos;
157
158 return parsed_query;
159 } 132 }
160 133
161 fn parseType(type_info: []const u8) type { 134 // The last character was a bind marker prefix so this must be an untyped bind marker.
162 if (type_info.len <= 0) @compileError("invalid type info " ++ type_info); 135 switch (state) {
136 .bind_marker => {
137 bind_markers[nb_bind_markers].typed = null;
138 nb_bind_markers += 1;
139 },
140 .bind_marker_identifier => {
141 nb_bind_markers += 1;
142 },
143 .start => {},
144 else => @compileError("invalid final state " ++ @tagName(state) ++ ", this means you wrote an incomplete bind marker type"),
145 }
163 146
164 // Integer 147 var parsed_state = ParsedQueryState(query.len){
165 if (mem.eql(u8, "usize", type_info)) return usize; 148 .bind_markers = bind_markers,
166 if (mem.eql(u8, "isize", type_info)) return isize; 149 .nb_bind_markers = nb_bind_markers,
150 .query = undefined,
151 .query_len = pos,
152 };
167 153
168 if (type_info[0] == 'u' or type_info[0] == 'i') { 154 std.mem.copy(u8, &parsed_state.query, &buf);
169 return @Type(std.builtin.TypeInfo{
170 .Int = std.builtin.TypeInfo.Int{
171 .signedness = if (type_info[0] == 'i') .signed else .unsigned,
172 .bits = std.fmt.parseInt(usize, type_info[1..type_info.len], 10) catch {
173 @compileError("invalid type info " ++ type_info);
174 },
175 },
176 });
177 }
178 155
179 // Float 156 return parsed_state;
180 if (mem.eql(u8, "f16", type_info)) return f16; 157}
181 if (mem.eql(u8, "f32", type_info)) return f32;
182 if (mem.eql(u8, "f64", type_info)) return f64;
183 if (mem.eql(u8, "f128", type_info)) return f128;
184 158
185 // Bool 159pub fn ParsedQueryState(comptime max_query_len: usize) type {
186 if (mem.eql(u8, "bool", type_info)) return bool; 160 return struct {
161 const Self = @This();
162 bind_markers: [128]BindMarker,
163 nb_bind_markers: usize,
164 query: [max_query_len]u8,
165 query_len: usize,
187 166
188 // Strings 167 pub fn getQuery(comptime self: *const Self) []const u8 {
189 if (mem.eql(u8, "[]const u8", type_info) or mem.eql(u8, "[]u8", type_info)) { 168 return self.query[0..self.query_len];
190 return []const u8;
191 } 169 }
192 if (mem.eql(u8, "text", type_info)) return Text; 170 };
193 if (mem.eql(u8, "blob", type_info)) return Blob; 171}
172
173fn ParseType(type_info: []const u8) type {
174 if (type_info.len <= 0) @compileError("invalid type info " ++ type_info);
175
176 // Integer
177 if (mem.eql(u8, "usize", type_info)) return usize;
178 if (mem.eql(u8, "isize", type_info)) return isize;
194 179
195 @compileError("invalid type info " ++ type_info); 180 if (type_info[0] == 'u' or type_info[0] == 'i') {
181 return @Type(std.builtin.TypeInfo{
182 .Int = std.builtin.TypeInfo.Int{
183 .signedness = if (type_info[0] == 'i') .signed else .unsigned,
184 .bits = std.fmt.parseInt(usize, type_info[1..type_info.len], 10) catch {
185 @compileError("invalid type info " ++ type_info);
186 },
187 },
188 });
196 } 189 }
197 190
198 pub fn getQuery(comptime self: *const Self) []const u8 { 191 // Float
199 return self.query[0..self.query_size]; 192 if (mem.eql(u8, "f16", type_info)) return f16;
193 if (mem.eql(u8, "f32", type_info)) return f32;
194 if (mem.eql(u8, "f64", type_info)) return f64;
195 if (mem.eql(u8, "f128", type_info)) return f128;
196
197 // Bool
198 if (mem.eql(u8, "bool", type_info)) return bool;
199
200 // Strings
201 if (mem.eql(u8, "[]const u8", type_info) or mem.eql(u8, "[]u8", type_info)) {
202 return []const u8;
200 } 203 }
201}; 204 if (mem.eql(u8, "text", type_info)) return Text;
205 if (mem.eql(u8, "blob", type_info)) return Blob;
206
207 @compileError("invalid type info " ++ type_info);
208}
202 209
203test "parsed query: query" { 210test "parsed query: query" {
204 const testCase = struct { 211 const testCase = struct {
@@ -223,7 +230,7 @@ test "parsed query: query" {
223 230
224 inline for (testCases) |tc| { 231 inline for (testCases) |tc| {
225 @setEvalBranchQuota(100000); 232 @setEvalBranchQuota(100000);
226 comptime var parsed_query = ParsedQuery.from(tc.query); 233 comptime var parsed_query = ParsedQuery(tc.query);
227 try testing.expectEqualStrings(tc.expected_query, parsed_query.getQuery()); 234 try testing.expectEqualStrings(tc.expected_query, parsed_query.getQuery());
228 } 235 }
229} 236}
@@ -271,7 +278,7 @@ test "parsed query: bind markers types" {
271 278
272 inline for (testCases) |tc| { 279 inline for (testCases) |tc| {
273 @setEvalBranchQuota(100000); 280 @setEvalBranchQuota(100000);
274 comptime var parsed_query = ParsedQuery.from(tc.query); 281 comptime var parsed_query = ParsedQuery(tc.query);
275 282
276 try testing.expectEqual(1, parsed_query.nb_bind_markers); 283 try testing.expectEqual(1, parsed_query.nb_bind_markers);
277 284
@@ -319,7 +326,7 @@ test "parsed query: bind markers identifier" {
319 }; 326 };
320 327
321 inline for (testCases) |tc| { 328 inline for (testCases) |tc| {
322 comptime var parsed_query = ParsedQuery.from(tc.query); 329 comptime var parsed_query = ParsedQuery(tc.query);
323 330
324 try testing.expectEqual(@as(usize, 1), parsed_query.nb_bind_markers); 331 try testing.expectEqual(@as(usize, 1), parsed_query.nb_bind_markers);
325 332
@@ -365,7 +372,7 @@ test "parsed query: query bind identifier" {
365 372
366 inline for (testCases) |tc| { 373 inline for (testCases) |tc| {
367 @setEvalBranchQuota(100000); 374 @setEvalBranchQuota(100000);
368 comptime var parsed_query = ParsedQuery.from(tc.query); 375 comptime var parsed_query = ParsedQuery(tc.query);
369 try testing.expectEqualStrings(tc.expected_query, parsed_query.getQuery()); 376 try testing.expectEqualStrings(tc.expected_query, parsed_query.getQuery());
370 try testing.expectEqual(tc.expected_nb_bind_markers, parsed_query.nb_bind_markers); 377 try testing.expectEqual(tc.expected_nb_bind_markers, parsed_query.nb_bind_markers);
371 } 378 }
@@ -393,7 +400,7 @@ test "parsed query: bind marker character inside string" {
393 400
394 inline for (testCases) |tc| { 401 inline for (testCases) |tc| {
395 @setEvalBranchQuota(100000); 402 @setEvalBranchQuota(100000);
396 comptime var parsed_query = ParsedQuery.from(tc.query); 403 comptime var parsed_query = ParsedQuery(tc.query);
397 404
398 try testing.expectEqual(@as(usize, tc.exp_bind_markers), parsed_query.nb_bind_markers); 405 try testing.expectEqual(@as(usize, tc.exp_bind_markers), parsed_query.nb_bind_markers);
399 try testing.expectEqualStrings(tc.exp, parsed_query.getQuery()); 406 try testing.expectEqualStrings(tc.exp, parsed_query.getQuery());