lib/sql/src/statement/parse.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const sql = @import("../root.zig");
3 const access_mod = @import("access.zig");
4 const ast_mod = @import("ast.zig");
5 const catalog_mod = sql.catalog;
6 const trace = sql.trace;
7 const row = sql.row;
8
9 const Allocator = std.mem.Allocator;
10
11 pub fn parse(allocator: Allocator, source: []const u8) ast_mod.Error!ast_mod.Statement {
12 var parsed = try parseWithParameters(allocator, source);
13 errdefer parsed.deinit(allocator);
14 allocator.free(parsed.parameters);
15 return parsed.statement;
16 }
17
18 pub fn parseWithParameters(allocator: Allocator, source: []const u8) ast_mod.Error!ast_mod.Parsed {
19 const phase = trace.scope("statement.parse");
20 defer phase.end();
21
22 var parser = Parser{ .source = source };
23 defer parser.parameters.deinit(allocator);
24 const statement = try parser.statement(allocator);
25 errdefer {
26 var owned = statement;
27 owned.deinit(allocator);
28 }
29 try parser.finish();
30 return .{
31 .statement = statement,
32 .parameters = try parser.parameters.toOwnedSlice(allocator),
33 };
34 }
35
36 pub const Parser = struct {
37 source: []const u8,
38 offset: usize = 0,
39 parameters: std.ArrayList(ast_mod.Parameter) = .empty,
40
41 pub fn statement(self: *Parser, allocator: Allocator) ast_mod.Error!ast_mod.Statement {
42 self.skipSpace();
43 if (self.empty()) return error.EmptyStatement;
44 if (self.consumeKeyword("create")) return try self.create(allocator);
45 if (self.consumeKeyword("drop")) return .{ .drop_table = try self.drop() };
46 if (self.consumeKeyword("analyze")) return .{ .analyze = try self.analyze() };
47 if (self.consumeKeyword("insert")) return .{ .insert = try self.insert(allocator) };
48 if (self.consumeKeyword("select")) return .{ .select = try self.select(allocator) };
49 if (self.consumeKeyword("update")) return .{ .update = try self.update(allocator) };
50 if (self.consumeKeyword("delete")) return .{ .delete = try self.delete(allocator) };
51 return error.UnsupportedStatement;
52 }
53
54 pub fn create(self: *Parser, allocator: Allocator) ast_mod.Error!ast_mod.Statement {
55 if (self.consumeKeyword("table")) return .{ .create_table = try self.createTable(allocator) };
56 if (self.consumeKeyword("index")) return .{ .create_index = try self.createIndex(allocator) };
57 return error.ExpectedKeyword;
58 }
59
60 pub fn createTable(self: *Parser, allocator: Allocator) ast_mod.Error!ast_mod.CreateTable {
61 const table = try self.identifier();
62 var columns: std.ArrayList(catalog_mod.ColumnDefinition) = .empty;
63 errdefer columns.deinit(allocator);
64 var indexes: std.ArrayList(catalog_mod.IndexDefinition) = .empty;
65 errdefer {
66 for (indexes.items) |index| allocator.free(@constCast(index.fields));
67 indexes.deinit(allocator);
68 }
69 if (self.consumeByte('(')) {
70 if (!self.consumeByte(')')) {
71 while (true) {
72 if (self.consumeKeyword("index")) {
73 try indexes.append(allocator, try self.indexDefinition(allocator, columns.items));
74 } else {
75 try columns.append(allocator, try self.columnDefinition());
76 }
77 if (self.consumeByte(')')) break;
78 try self.expectByte(',');
79 }
80 }
81 }
82 const owned_columns = try columns.toOwnedSlice(allocator);
83 errdefer allocator.free(owned_columns);
84 return .{
85 .table = table,
86 .columns = owned_columns,
87 .indexes = try indexes.toOwnedSlice(allocator),
88 };
89 }
90
91 pub fn createIndex(self: *Parser, allocator: Allocator) ast_mod.Error!ast_mod.CreateIndex {
92 const name = try self.identifier();
93 try self.expectKeyword("on");
94 const table = try self.identifier();
95 return .{
96 .name = name,
97 .table = table,
98 .columns = try self.columnList(allocator),
99 };
100 }
101
102 pub fn columnDefinition(self: *Parser) ast_mod.Error!catalog_mod.ColumnDefinition {
103 const name = try self.identifier();
104 var column = row.Column{};
105 var default: row.Value = .nil;
106 while (true) {
107 if (self.consumeKeyword("default")) {
108 default = try self.literalValue();
109 continue;
110 }
111 if (self.consumeKeyword("collate")) {
112 if (self.consumeKeyword("binary")) {
113 column.collation = .binary;
114 continue;
115 }
116 if (self.consumeKeyword("nocase")) {
117 column.collation = .nocase;
118 continue;
119 }
120 if (self.consumeKeyword("rtrim")) {
121 column.collation = .rtrim;
122 continue;
123 }
124 return error.ExpectedKeyword;
125 }
126 break;
127 }
128 return .{
129 .name = name,
130 .column = column,
131 .default = default,
132 };
133 }
134
135 pub fn indexDefinition(self: *Parser, allocator: Allocator, columns: []const catalog_mod.ColumnDefinition) ast_mod.Error!catalog_mod.IndexDefinition {
136 const name = try self.identifier();
137 try self.expectByte('(');
138 const identifiers = try self.identifierList(allocator);
139 defer allocator.free(identifiers);
140 try self.expectByte(')');
141 const fields = try allocator.alloc(usize, identifiers.len);
142 errdefer allocator.free(fields);
143 for (identifiers, fields) |identifier_name, *field| {
144 field.* = access_mod.columnIndex(columns, identifier_name) orelse return error.ColumnNotFound;
145 }
146 return .{
147 .name = name,
148 .fields = fields,
149 };
150 }
151
152 pub fn drop(self: *Parser) ast_mod.Error!ast_mod.DropTable {
153 try self.expectKeyword("table");
154 return .{ .table = try self.identifier() };
155 }
156
157 pub fn analyze(self: *Parser) ast_mod.Error!ast_mod.Analyze {
158 _ = self.consumeKeyword("table");
159 return .{ .table = try self.identifier() };
160 }
161
162 pub fn insert(self: *Parser, allocator: Allocator) ast_mod.Error!ast_mod.Insert {
163 try self.expectKeyword("into");
164 const table = try self.identifier();
165 const columns = if (self.peekByte('(')) try self.columnList(allocator) else null;
166 errdefer if (columns) |owned| allocator.free(owned);
167 try self.expectKeyword("values");
168 return .{
169 .table = table,
170 .columns = columns,
171 .values = try self.valueList(allocator),
172 };
173 }
174
175 pub fn select(self: *Parser, allocator: Allocator) ast_mod.Error!ast_mod.Select {
176 const select_projection = try self.projection(allocator);
177 errdefer switch (select_projection) {
178 .all => {},
179 .columns => |columns| allocator.free(columns),
180 };
181 try self.expectKeyword("from");
182 const table = try self.identifier();
183 const predicates = if (self.consumeKeyword("where"))
184 try self.predicateList(allocator)
185 else
186 try allocator.alloc(ast_mod.Predicate, 0);
187 errdefer allocator.free(predicates);
188 var order: std.ArrayList(ast_mod.OrderKey) = .empty;
189 errdefer order.deinit(allocator);
190 if (self.consumeKeyword("order")) {
191 try self.expectKeyword("by");
192 try order.append(allocator, try self.orderKey());
193 while (self.consumeByte(',')) try order.append(allocator, try self.orderKey());
194 }
195 var limit: ?ast_mod.Expression = null;
196 var offset: ?ast_mod.Expression = null;
197 if (self.consumeKeyword("limit")) {
198 limit = try self.value(allocator);
199 if (self.consumeKeyword("offset")) offset = try self.value(allocator);
200 }
201 return .{
202 .table = table,
203 .projection = select_projection,
204 .predicates = predicates,
205 .order = try order.toOwnedSlice(allocator),
206 .limit = limit,
207 .offset = offset,
208 };
209 }
210
211 pub fn predicateList(self: *Parser, allocator: Allocator) ast_mod.Error![]ast_mod.Predicate {
212 var predicates: std.ArrayList(ast_mod.Predicate) = .empty;
213 errdefer predicates.deinit(allocator);
214 try predicates.append(allocator, try self.predicate(allocator));
215 while (self.consumeKeyword("and")) {
216 try predicates.append(allocator, try self.predicate(allocator));
217 }
218 return try predicates.toOwnedSlice(allocator);
219 }
220
221 pub fn orderKey(self: *Parser) ast_mod.Error!ast_mod.OrderKey {
222 const name = try self.identifier();
223 const descending = if (self.consumeKeyword("desc"))
224 true
225 else if (self.consumeKeyword("asc"))
226 false
227 else
228 false;
229 return .{
230 .column = if (std.ascii.eqlIgnoreCase(name, "rowid"))
231 .rowid
232 else
233 .{ .field = name },
234 .descending = descending,
235 };
236 }
237
238 pub fn predicate(self: *Parser, allocator: Allocator) ast_mod.Error!ast_mod.Predicate {
239 const predicate_column = try self.identifier();
240 const predicate_operator = try self.operator();
241 return .{
242 .column = if (std.ascii.eqlIgnoreCase(predicate_column, "rowid"))
243 .rowid
244 else
245 .{ .field = predicate_column },
246 .operator = predicate_operator,
247 .value = try self.value(allocator),
248 };
249 }
250
251 pub fn update(self: *Parser, allocator: Allocator) ast_mod.Error!ast_mod.Update {
252 const table = try self.identifier();
253 try self.expectKeyword("set");
254 var assignments: std.ArrayList(ast_mod.Assignment) = .empty;
255 errdefer assignments.deinit(allocator);
256 try assignments.append(allocator, try self.assignment(allocator));
257 while (self.consumeByte(',')) try assignments.append(allocator, try self.assignment(allocator));
258 try self.expectKeyword("where");
259 const predicates = try self.predicateList(allocator);
260 errdefer allocator.free(predicates);
261 return .{
262 .table = table,
263 .assignments = try assignments.toOwnedSlice(allocator),
264 .predicates = predicates,
265 };
266 }
267
268 pub fn assignment(self: *Parser, allocator: Allocator) ast_mod.Error!ast_mod.Assignment {
269 const column = try self.identifier();
270 try self.expectByte('=');
271 return .{
272 .column = column,
273 .value = try self.value(allocator),
274 };
275 }
276
277 pub fn delete(self: *Parser, allocator: Allocator) ast_mod.Error!ast_mod.Delete {
278 try self.expectKeyword("from");
279 const table = try self.identifier();
280 try self.expectKeyword("where");
281 return .{
282 .table = table,
283 .predicates = try self.predicateList(allocator),
284 };
285 }
286
287 pub fn projection(self: *Parser, allocator: Allocator) ast_mod.Error!ast_mod.Projection {
288 if (self.consumeByte('*')) return .all;
289 return .{ .columns = try self.identifierList(allocator) };
290 }
291
292 pub fn columnList(self: *Parser, allocator: Allocator) ast_mod.Error![][]const u8 {
293 try self.expectByte('(');
294 const columns = try self.identifierList(allocator);
295 errdefer allocator.free(columns);
296 try self.expectByte(')');
297 return columns;
298 }
299
300 pub fn identifierList(self: *Parser, allocator: Allocator) ast_mod.Error![][]const u8 {
301 var identifiers: std.ArrayList([]const u8) = .empty;
302 errdefer identifiers.deinit(allocator);
303 try identifiers.append(allocator, try self.identifier());
304 while (self.consumeByte(',')) try identifiers.append(allocator, try self.identifier());
305 return try identifiers.toOwnedSlice(allocator);
306 }
307
308 pub fn valueList(self: *Parser, allocator: Allocator) ast_mod.Error![]ast_mod.Expression {
309 try self.expectByte('(');
310 var values: std.ArrayList(ast_mod.Expression) = .empty;
311 errdefer values.deinit(allocator);
312 try values.append(allocator, try self.value(allocator));
313 while (self.consumeByte(',')) try values.append(allocator, try self.value(allocator));
314 try self.expectByte(')');
315 return try values.toOwnedSlice(allocator);
316 }
317
318 pub fn value(self: *Parser, allocator: Allocator) ast_mod.Error!ast_mod.Expression {
319 self.skipSpace();
320 if (self.consumeKeyword("null")) return .{ .literal = row.Value.nil };
321 if (self.peek() == '\'') return .{ .literal = .{ .text = try self.string() } };
322 if (self.parameterStart()) return .{ .parameter = try self.parameter(allocator) };
323 if (self.peek()) |byte| {
324 if (byte == '-' or byte == '+' or asciiDigit(byte)) return .{ .literal = .{ .integer = try self.integer() } };
325 }
326 return error.ExpectedValue;
327 }
328
329 pub fn literalValue(self: *Parser) ast_mod.Error!row.Value {
330 self.skipSpace();
331 if (self.consumeKeyword("null")) return .nil;
332 if (self.peek() == '\'') return .{ .text = try self.string() };
333 if (self.peek()) |byte| {
334 if (byte == '-' or byte == '+' or asciiDigit(byte)) return .{ .integer = try self.integer() };
335 }
336 return error.ExpectedValue;
337 }
338
339 pub fn operator(self: *Parser) ast_mod.Error!ast_mod.PredicateOperator {
340 self.skipSpace();
341 if (self.consumeRawByte('=')) return .eq;
342 if (self.consumeRawByte('<')) {
343 if (self.consumeRawByte('=')) return .lte;
344 return .lt;
345 }
346 if (self.consumeRawByte('>')) {
347 if (self.consumeRawByte('=')) return .gte;
348 return .gt;
349 }
350 return error.ExpectedToken;
351 }
352
353 pub fn parameterStart(self: *Parser) bool {
354 return switch (self.peek() orelse return false) {
355 '?', ':', '@', '$' => true,
356 else => false,
357 };
358 }
359
360 pub fn parameter(self: *Parser, allocator: Allocator) ast_mod.Error!usize {
361 self.skipSpace();
362 const start = self.offset;
363 const prefix = self.peek() orelse return error.InvalidParameter;
364 self.offset += 1;
365 return switch (prefix) {
366 '?' => if (self.peek()) |byte|
367 if (asciiDigit(byte))
368 try self.numberedParameter(allocator, start)
369 else
370 try self.anonymousParameter(allocator)
371 else
372 try self.anonymousParameter(allocator),
373 ':', '@', '$' => try self.namedParameter(allocator, start),
374 else => error.InvalidParameter,
375 };
376 }
377
378 pub fn anonymousParameter(self: *Parser, allocator: Allocator) ast_mod.Error!usize {
379 if (self.parameters.items.len >= ast_mod.max_parameters) return error.TooManyParameters;
380 try self.parameters.append(allocator, .{});
381 return self.parameters.items.len - 1;
382 }
383
384 pub fn numberedParameter(self: *Parser, allocator: Allocator, start: usize) ast_mod.Error!usize {
385 while (self.peek()) |byte| {
386 if (!asciiDigit(byte)) break;
387 self.offset += 1;
388 }
389 const name = self.source[start..self.offset];
390 const parsed = std.fmt.parseInt(usize, self.source[start + 1 .. self.offset], 10) catch return error.InvalidParameter;
391 if (parsed == 0 or parsed > ast_mod.max_parameters) return error.ParameterIndexOutOfBounds;
392 return try self.setParameter(allocator, parsed - 1, name);
393 }
394
395 pub fn namedParameter(self: *Parser, allocator: Allocator, start: usize) ast_mod.Error!usize {
396 if (self.peek()) |byte| {
397 if (!asciiIdentStart(byte)) return error.InvalidParameter;
398 self.offset += 1;
399 } else return error.InvalidParameter;
400 while (self.peek()) |byte| {
401 if (!asciiIdentContinue(byte)) break;
402 self.offset += 1;
403 }
404 const name = self.source[start..self.offset];
405 if (self.findParameter(name)) |index| return index;
406 if (self.parameters.items.len >= ast_mod.max_parameters) return error.TooManyParameters;
407 try self.parameters.append(allocator, .{ .name = name });
408 return self.parameters.items.len - 1;
409 }
410
411 pub fn setParameter(self: *Parser, allocator: Allocator, index: usize, name: []const u8) ast_mod.Error!usize {
412 if (index >= ast_mod.max_parameters) return error.ParameterIndexOutOfBounds;
413 while (self.parameters.items.len <= index) try self.parameters.append(allocator, .{});
414 if (self.parameters.items[index].name) |existing| {
415 if (!std.mem.eql(u8, existing, name)) return error.ParameterIndexConflict;
416 }
417 self.parameters.items[index].name = name;
418 return index;
419 }
420
421 pub fn findParameter(self: *const Parser, name: []const u8) ?usize {
422 for (self.parameters.items, 0..) |parameter_item, index| {
423 if (parameter_item.name) |parameter_name| {
424 if (std.mem.eql(u8, parameter_name, name)) return index;
425 }
426 }
427 return null;
428 }
429
430 pub fn integer(self: *Parser) ast_mod.Error!i64 {
431 self.skipSpace();
432 const start = self.offset;
433 if (self.peek()) |byte| {
434 if (byte == '-' or byte == '+') self.offset += 1;
435 }
436 while (self.peek()) |byte| {
437 if (!asciiDigit(byte)) break;
438 self.offset += 1;
439 }
440 if (self.offset == start) return error.ExpectedInteger;
441 if (self.offset == start + 1 and (self.source[start] == '-' or self.source[start] == '+')) return error.ExpectedInteger;
442 return std.fmt.parseInt(i64, self.source[start..self.offset], 10) catch error.InvalidInteger;
443 }
444
445 pub fn string(self: *Parser) ast_mod.Error![]const u8 {
446 self.skipSpace();
447 if (!self.consumeRawByte('\'')) return error.ExpectedString;
448 const start = self.offset;
449 while (self.peek()) |byte| {
450 if (byte == '\'') {
451 const text = self.source[start..self.offset];
452 self.offset += 1;
453 return text;
454 }
455 self.offset += 1;
456 }
457 return error.ExpectedString;
458 }
459
460 pub fn identifier(self: *Parser) ast_mod.Error![]const u8 {
461 self.skipSpace();
462 const start = self.offset;
463 if (self.peek()) |byte| {
464 if (!asciiIdentStart(byte)) return error.ExpectedIdentifier;
465 self.offset += 1;
466 } else return error.ExpectedIdentifier;
467 while (self.peek()) |byte| {
468 if (!asciiIdentContinue(byte)) break;
469 self.offset += 1;
470 }
471 return self.source[start..self.offset];
472 }
473
474 pub fn expectKeyword(self: *Parser, comptime keyword: []const u8) ast_mod.Error!void {
475 if (!self.consumeKeyword(keyword)) return error.ExpectedKeyword;
476 }
477
478 pub fn consumeKeyword(self: *Parser, comptime keyword: []const u8) bool {
479 self.skipSpace();
480 if (self.offset + keyword.len > self.source.len) return false;
481 const bytes = self.source[self.offset..][0..keyword.len];
482 if (!asciiEqlIgnoreCase(bytes, keyword)) return false;
483 const end = self.offset + keyword.len;
484 if (end < self.source.len and asciiIdentContinue(self.source[end])) return false;
485 self.offset = end;
486 return true;
487 }
488
489 pub fn expectByte(self: *Parser, byte: u8) ast_mod.Error!void {
490 if (!self.consumeByte(byte)) return error.ExpectedToken;
491 }
492
493 pub fn consumeByte(self: *Parser, byte: u8) bool {
494 self.skipSpace();
495 return self.consumeRawByte(byte);
496 }
497
498 pub fn peekByte(self: *Parser, byte: u8) bool {
499 self.skipSpace();
500 return self.peek() == byte;
501 }
502
503 pub fn consumeRawByte(self: *Parser, byte: u8) bool {
504 if (self.peek() != byte) return false;
505 self.offset += 1;
506 return true;
507 }
508
509 pub fn finish(self: *Parser) ast_mod.Error!void {
510 self.skipSpace();
511 _ = self.consumeByte(';');
512 self.skipSpace();
513 if (!self.empty()) return error.TrailingInput;
514 }
515
516 pub fn skipSpace(self: *Parser) void {
517 while (self.peek()) |byte| {
518 switch (byte) {
519 ' ', '\n', '\r', '\t' => self.offset += 1,
520 else => return,
521 }
522 }
523 }
524
525 pub fn empty(self: *const Parser) bool {
526 return self.offset >= self.source.len;
527 }
528
529 pub fn peek(self: *const Parser) ?u8 {
530 if (self.offset >= self.source.len) return null;
531 return self.source[self.offset];
532 }
533 };
534
535 pub fn asciiEqlIgnoreCase(left: []const u8, right: []const u8) bool {
536 if (left.len != right.len) return false;
537 for (left, right) |l, r| {
538 if (std.ascii.toLower(l) != std.ascii.toLower(r)) return false;
539 }
540 return true;
541 }
542
543 pub fn asciiIdentStart(byte: u8) bool {
544 return std.ascii.isAlphabetic(byte) or byte == '_';
545 }
546
547 pub fn asciiIdentContinue(byte: u8) bool {
548 return asciiIdentStart(byte) or asciiDigit(byte);
549 }
550
551 pub fn asciiDigit(byte: u8) bool {
552 return byte >= '0' and byte <= '9';
553 }
554
555 test "statement parser accepts rowid insert select and delete forms" {
556 var insert = try parse(std.testing.allocator, "insert into items values (7, 11, 'alpha', null);");
557 defer insert.deinit(std.testing.allocator);
558 switch (insert) {
559 .insert => |parsed| {
560 try std.testing.expectEqualStrings("items", parsed.table);
561 try std.testing.expect(parsed.columns == null);
562 try std.testing.expectEqual(@as(usize, 4), parsed.values.len);
563 try std.testing.expectEqual(@as(i64, 7), parsed.values[0].literal.integer);
564 try std.testing.expectEqual(@as(i64, 11), parsed.values[1].literal.integer);
565 try std.testing.expectEqualStrings("alpha", parsed.values[2].literal.text);
566 try std.testing.expectEqual(row.Value.nil, parsed.values[3].literal);
567 },
568 else => return error.UnsupportedStatement,
569 }
570
571 var named_insert = try parse(std.testing.allocator, "INSERT INTO items (rowid, name, score) VALUES (8, 'ada', 99)");
572 defer named_insert.deinit(std.testing.allocator);
573 switch (named_insert) {
574 .insert => |parsed| {
575 const columns = parsed.columns.?;
576 try std.testing.expectEqual(@as(usize, 3), columns.len);
577 try std.testing.expectEqualStrings("rowid", columns[0]);
578 try std.testing.expectEqualStrings("name", columns[1]);
579 try std.testing.expectEqualStrings("score", columns[2]);
580 try std.testing.expectEqual(@as(i64, 8), parsed.values[0].literal.integer);
581 try std.testing.expectEqualStrings("ada", parsed.values[1].literal.text);
582 try std.testing.expectEqual(@as(i64, 99), parsed.values[2].literal.integer);
583 },
584 else => return error.UnsupportedStatement,
585 }
586
587 var create = try parse(std.testing.allocator, "CREATE TABLE items (name DEFAULT 'unknown' COLLATE nocase, score, INDEX items_score (score))");
588 defer create.deinit(std.testing.allocator);
589 switch (create) {
590 .create_table => |parsed| {
591 try std.testing.expectEqualStrings("items", parsed.table);
592 try std.testing.expectEqual(@as(usize, 2), parsed.columns.len);
593 try std.testing.expectEqualStrings("name", parsed.columns[0].name);
594 try std.testing.expectEqual(row.Collation.nocase, parsed.columns[0].column.collation);
595 try std.testing.expectEqualStrings("unknown", parsed.columns[0].default.text);
596 try std.testing.expectEqualStrings("score", parsed.columns[1].name);
597 try std.testing.expectEqual(@as(usize, 1), parsed.indexes.len);
598 try std.testing.expectEqualStrings("items_score", parsed.indexes[0].name);
599 try std.testing.expectEqual(@as(usize, 1), parsed.indexes[0].fields[0]);
600 },
601 else => return error.UnsupportedStatement,
602 }
603
604 var create_index = try parse(std.testing.allocator, "CREATE INDEX items_score_name ON items (score, name)");
605 defer create_index.deinit(std.testing.allocator);
606 switch (create_index) {
607 .create_index => |parsed| {
608 try std.testing.expectEqualStrings("items_score_name", parsed.name);
609 try std.testing.expectEqualStrings("items", parsed.table);
610 try std.testing.expectEqual(@as(usize, 2), parsed.columns.len);
611 try std.testing.expectEqualStrings("score", parsed.columns[0]);
612 try std.testing.expectEqualStrings("name", parsed.columns[1]);
613 },
614 else => return error.UnsupportedStatement,
615 }
616
617 var analyze = try parse(std.testing.allocator, "ANALYZE TABLE items");
618 defer analyze.deinit(std.testing.allocator);
619 switch (analyze) {
620 .analyze => |parsed| try std.testing.expectEqualStrings("items", parsed.table),
621 else => return error.UnsupportedStatement,
622 }
623
624 var drop = try parse(std.testing.allocator, "DROP TABLE items;");
625 defer drop.deinit(std.testing.allocator);
626 switch (drop) {
627 .drop_table => |parsed| try std.testing.expectEqualStrings("items", parsed.table),
628 else => return error.UnsupportedStatement,
629 }
630 try std.testing.expectError(error.ExpectedKeyword, parse(std.testing.allocator, "DROP items"));
631 try std.testing.expectError(error.ExpectedIdentifier, parse(std.testing.allocator, "DROP TABLE"));
632
633 var select = try parse(std.testing.allocator, "SELECT * FROM items WHERE rowid = -3");
634 defer select.deinit(std.testing.allocator);
635 switch (select) {
636 .select => |parsed| {
637 try std.testing.expectEqualStrings("items", parsed.table);
638 switch (parsed.projection) {
639 .all => {},
640 .columns => return error.UnsupportedStatement,
641 }
642 try std.testing.expectEqual(@as(usize, 1), parsed.predicates.len);
643 try std.testing.expectEqual(@as(i64, -3), parsed.predicates[0].value.literal.integer);
644 switch (parsed.predicates[0].column) {
645 .rowid => {},
646 .field => return error.UnsupportedStatement,
647 }
648 try std.testing.expectEqual(ast_mod.PredicateOperator.eq, parsed.predicates[0].operator);
649 },
650 else => return error.UnsupportedStatement,
651 }
652
653 var projected_select = try parse(std.testing.allocator, "SELECT name, rowid FROM items WHERE rowid = 4");
654 defer projected_select.deinit(std.testing.allocator);
655 switch (projected_select) {
656 .select => |parsed| {
657 const columns = parsed.projection.columns;
658 try std.testing.expectEqual(@as(usize, 2), columns.len);
659 try std.testing.expectEqualStrings("name", columns[0]);
660 try std.testing.expectEqualStrings("rowid", columns[1]);
661 try std.testing.expectEqual(@as(i64, 4), parsed.predicates[0].value.literal.integer);
662 },
663 else => return error.UnsupportedStatement,
664 }
665
666 var indexed_select = try parse(std.testing.allocator, "SELECT name FROM items WHERE score = ?1");
667 defer indexed_select.deinit(std.testing.allocator);
668 switch (indexed_select) {
669 .select => |parsed| {
670 const columns = parsed.projection.columns;
671 try std.testing.expectEqual(@as(usize, 1), columns.len);
672 try std.testing.expectEqualStrings("name", columns[0]);
673 try std.testing.expectEqualStrings("score", parsed.predicates[0].column.field);
674 try std.testing.expectEqual(ast_mod.PredicateOperator.eq, parsed.predicates[0].operator);
675 try std.testing.expectEqual(@as(usize, 0), parsed.predicates[0].value.parameter);
676 },
677 else => return error.UnsupportedStatement,
678 }
679
680 var range_select = try parse(std.testing.allocator, "SELECT name FROM items WHERE score >= ?1");
681 defer range_select.deinit(std.testing.allocator);
682 switch (range_select) {
683 .select => |parsed| {
684 const columns = parsed.projection.columns;
685 try std.testing.expectEqual(@as(usize, 1), columns.len);
686 try std.testing.expectEqualStrings("name", columns[0]);
687 try std.testing.expectEqualStrings("score", parsed.predicates[0].column.field);
688 try std.testing.expectEqual(ast_mod.PredicateOperator.gte, parsed.predicates[0].operator);
689 try std.testing.expectEqual(@as(usize, 0), parsed.predicates[0].value.parameter);
690 },
691 else => return error.UnsupportedStatement,
692 }
693
694 var and_select = try parse(std.testing.allocator, "SELECT name FROM items WHERE score = ?1 AND note = :note");
695 defer and_select.deinit(std.testing.allocator);
696 switch (and_select) {
697 .select => |parsed| {
698 try std.testing.expectEqual(@as(usize, 2), parsed.predicates.len);
699 try std.testing.expectEqualStrings("score", parsed.predicates[0].column.field);
700 try std.testing.expectEqual(ast_mod.PredicateOperator.eq, parsed.predicates[0].operator);
701 try std.testing.expectEqual(@as(usize, 0), parsed.predicates[0].value.parameter);
702 try std.testing.expectEqualStrings("note", parsed.predicates[1].column.field);
703 try std.testing.expectEqual(ast_mod.PredicateOperator.eq, parsed.predicates[1].operator);
704 try std.testing.expectEqual(@as(usize, 1), parsed.predicates[1].value.parameter);
705 },
706 else => return error.UnsupportedStatement,
707 }
708
709 var delete = try parse(std.testing.allocator, "DELETE FROM items WHERE rowid = +9");
710 defer delete.deinit(std.testing.allocator);
711 switch (delete) {
712 .delete => |parsed| {
713 try std.testing.expectEqualStrings("items", parsed.table);
714 try std.testing.expectEqual(@as(usize, 1), parsed.predicates.len);
715 switch (parsed.predicates[0].column) {
716 .rowid => {},
717 .field => return error.UnsupportedStatement,
718 }
719 try std.testing.expectEqual(ast_mod.PredicateOperator.eq, parsed.predicates[0].operator);
720 try std.testing.expectEqual(@as(i64, 9), parsed.predicates[0].value.literal.integer);
721 },
722 else => return error.UnsupportedStatement,
723 }
724
725 var predicate_delete = try parse(std.testing.allocator, "DELETE FROM items WHERE score >= 7 AND note = 'hot'");
726 defer predicate_delete.deinit(std.testing.allocator);
727 switch (predicate_delete) {
728 .delete => |parsed| {
729 try std.testing.expectEqual(@as(usize, 2), parsed.predicates.len);
730 try std.testing.expectEqualStrings("score", parsed.predicates[0].column.field);
731 try std.testing.expectEqual(ast_mod.PredicateOperator.gte, parsed.predicates[0].operator);
732 try std.testing.expectEqual(@as(i64, 7), parsed.predicates[0].value.literal.integer);
733 try std.testing.expectEqualStrings("note", parsed.predicates[1].column.field);
734 try std.testing.expectEqualStrings("hot", parsed.predicates[1].value.literal.text);
735 },
736 else => return error.UnsupportedStatement,
737 }
738 }
739
740 test "statement parser accepts bare selects with limit and offset" {
741 var bare = try parse(std.testing.allocator, "SELECT * FROM items");
742 defer bare.deinit(std.testing.allocator);
743 switch (bare) {
744 .select => |parsed| {
745 try std.testing.expectEqualStrings("items", parsed.table);
746 try std.testing.expectEqual(@as(usize, 0), parsed.predicates.len);
747 try std.testing.expect(parsed.limit == null);
748 try std.testing.expect(parsed.offset == null);
749 },
750 else => return error.UnsupportedStatement,
751 }
752
753 var bounded = try parse(std.testing.allocator, "SELECT name FROM items LIMIT 20");
754 defer bounded.deinit(std.testing.allocator);
755 switch (bounded) {
756 .select => |parsed| {
757 try std.testing.expectEqual(@as(usize, 0), parsed.predicates.len);
758 try std.testing.expectEqual(@as(i64, 20), parsed.limit.?.literal.integer);
759 try std.testing.expect(parsed.offset == null);
760 },
761 else => return error.UnsupportedStatement,
762 }
763
764 var paged = try parse(std.testing.allocator, "SELECT name FROM items WHERE score >= 3 LIMIT ?1 OFFSET ?2");
765 defer paged.deinit(std.testing.allocator);
766 switch (paged) {
767 .select => |parsed| {
768 try std.testing.expectEqual(@as(usize, 1), parsed.predicates.len);
769 try std.testing.expectEqual(@as(usize, 0), parsed.limit.?.parameter);
770 try std.testing.expectEqual(@as(usize, 1), parsed.offset.?.parameter);
771 },
772 else => return error.UnsupportedStatement,
773 }
774
775 try std.testing.expectError(error.TrailingInput, parse(std.testing.allocator, "SELECT name FROM items OFFSET 2"));
776 }
777
778 test "statement parser accepts order by key lists" {
779 var ordered = try parse(std.testing.allocator, "SELECT name FROM items ORDER BY score DESC, name ASC LIMIT 3");
780 defer ordered.deinit(std.testing.allocator);
781 switch (ordered) {
782 .select => |parsed| {
783 try std.testing.expectEqual(@as(usize, 2), parsed.order.len);
784 try std.testing.expectEqualStrings("score", parsed.order[0].column.field);
785 try std.testing.expect(parsed.order[0].descending);
786 try std.testing.expectEqualStrings("name", parsed.order[1].column.field);
787 try std.testing.expect(!parsed.order[1].descending);
788 try std.testing.expectEqual(@as(i64, 3), parsed.limit.?.literal.integer);
789 },
790 else => return error.UnsupportedStatement,
791 }
792
793 var rowid_order = try parse(std.testing.allocator, "SELECT * FROM items WHERE score >= 1 ORDER BY rowid DESC");
794 defer rowid_order.deinit(std.testing.allocator);
795 switch (rowid_order) {
796 .select => |parsed| {
797 try std.testing.expectEqual(@as(usize, 1), parsed.order.len);
798 switch (parsed.order[0].column) {
799 .rowid => {},
800 .field => return error.UnsupportedStatement,
801 }
802 try std.testing.expect(parsed.order[0].descending);
803 },
804 else => return error.UnsupportedStatement,
805 }
806
807 try std.testing.expectError(error.ExpectedKeyword, parse(std.testing.allocator, "SELECT * FROM items ORDER score"));
808 }
809
810 test "statement parser accepts update set forms" {
811 var update = try parse(std.testing.allocator, "UPDATE items SET name = 'ada', score = ?1 WHERE rowid = :id");
812 defer update.deinit(std.testing.allocator);
813 switch (update) {
814 .update => |parsed| {
815 try std.testing.expectEqualStrings("items", parsed.table);
816 try std.testing.expectEqual(@as(usize, 2), parsed.assignments.len);
817 try std.testing.expectEqualStrings("name", parsed.assignments[0].column);
818 try std.testing.expectEqualStrings("ada", parsed.assignments[0].value.literal.text);
819 try std.testing.expectEqualStrings("score", parsed.assignments[1].column);
820 try std.testing.expectEqual(@as(usize, 0), parsed.assignments[1].value.parameter);
821 try std.testing.expectEqual(@as(usize, 1), parsed.predicates.len);
822 switch (parsed.predicates[0].column) {
823 .rowid => {},
824 .field => return error.UnsupportedStatement,
825 }
826 try std.testing.expectEqual(@as(usize, 1), parsed.predicates[0].value.parameter);
827 },
828 else => return error.UnsupportedStatement,
829 }
830
831 var single = try parse(std.testing.allocator, "update items set score = -4 where rowid = 9;");
832 defer single.deinit(std.testing.allocator);
833 switch (single) {
834 .update => |parsed| {
835 try std.testing.expectEqual(@as(usize, 1), parsed.assignments.len);
836 try std.testing.expectEqualStrings("score", parsed.assignments[0].column);
837 try std.testing.expectEqual(@as(i64, -4), parsed.assignments[0].value.literal.integer);
838 try std.testing.expectEqual(@as(i64, 9), parsed.predicates[0].value.literal.integer);
839 },
840 else => return error.UnsupportedStatement,
841 }
842
843 var filtered = try parse(std.testing.allocator, "UPDATE items SET score = 1 WHERE note = 'hot' AND score < 5");
844 defer filtered.deinit(std.testing.allocator);
845 switch (filtered) {
846 .update => |parsed| {
847 try std.testing.expectEqual(@as(usize, 2), parsed.predicates.len);
848 try std.testing.expectEqualStrings("note", parsed.predicates[0].column.field);
849 try std.testing.expectEqual(ast_mod.PredicateOperator.eq, parsed.predicates[0].operator);
850 try std.testing.expectEqualStrings("score", parsed.predicates[1].column.field);
851 try std.testing.expectEqual(ast_mod.PredicateOperator.lt, parsed.predicates[1].operator);
852 },
853 else => return error.UnsupportedStatement,
854 }
855
856 try std.testing.expectError(error.ExpectedKeyword, parse(std.testing.allocator, "UPDATE items name = 'ada' WHERE rowid = 1"));
857 try std.testing.expectError(error.ExpectedKeyword, parse(std.testing.allocator, "UPDATE items SET name = 'ada'"));
858 try std.testing.expectError(error.ExpectedValue, parse(std.testing.allocator, "UPDATE items SET name = WHERE rowid = 1"));
859 }
860
861 test "parse reaches allocation-free steady state under a reset-retained arena" {
862 const corpus = [_][]const u8{
863 "SELECT name, payload, rowid FROM items WHERE region = ?1 AND payload >= ?2",
864 "INSERT INTO items (rowid, name) VALUES (?1, :name)",
865 "CREATE TABLE items (region, payload, name, INDEX items_region (region), INDEX items_region_payload (region, payload))",
866 "UPDATE items SET payload = ?1 WHERE rowid = ?2",
867 "DELETE FROM items WHERE payload >= ?1 AND region = ?2",
868 "SELECT name FROM items WHERE payload = 0",
869 };
870 const parameters_per_round: usize = 8;
871
872 var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{});
873 var arena = std.heap.ArenaAllocator.init(failing.allocator());
874 defer arena.deinit();
875
876 for (0..4) |_| {
877 for (corpus) |source| {
878 var parsed = try parseWithParameters(arena.allocator(), source);
879 _ = &parsed;
880 _ = arena.reset(.retain_capacity);
881 }
882 }
883
884 failing.fail_index = failing.alloc_index;
885 failing.resize_fail_index = failing.resize_index;
886
887 var parameter_count: usize = 0;
888 for (0..8) |_| {
889 for (corpus) |source| {
890 const parsed = try parseWithParameters(arena.allocator(), source);
891 parameter_count += parsed.parameters.len;
892 _ = arena.reset(.retain_capacity);
893 }
894 }
895 try std.testing.expectEqual(parameters_per_round * 8, parameter_count);
896 try std.testing.expect(!failing.has_induced_failure);
897 }