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 }