plume 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +506 -11
  3. data/lib/plume/analysis/resolver.rb +797 -0
  4. data/lib/plume/analysis.rb +211 -0
  5. data/lib/plume/ast/alter_table_statement.rb +40 -0
  6. data/lib/plume/ast/analyze_statement.rb +15 -0
  7. data/lib/plume/ast/assignment.rb +18 -0
  8. data/lib/plume/ast/attach_statement.rb +17 -0
  9. data/lib/plume/ast/begin_statement.rb +17 -0
  10. data/lib/plume/ast/binary_expression.rb +71 -0
  11. data/lib/plume/ast/case_condition.rb +29 -0
  12. data/lib/plume/ast/case_expression.rb +22 -0
  13. data/lib/plume/ast/cast_expression.rb +16 -0
  14. data/lib/plume/ast/check_column_constraint.rb +20 -0
  15. data/lib/plume/ast/check_table_constraint.rb +20 -0
  16. data/lib/plume/ast/collate_column_constraint.rb +19 -0
  17. data/lib/plume/ast/collation_expression.rb +26 -0
  18. data/lib/plume/ast/column_constraint.rb +7 -0
  19. data/lib/plume/ast/column_definition.rb +16 -0
  20. data/lib/plume/ast/column_name.rb +22 -0
  21. data/lib/plume/ast/column_type.rb +26 -0
  22. data/lib/plume/ast/commit_statement.rb +13 -0
  23. data/lib/plume/ast/common_table_expression.rb +31 -0
  24. data/lib/plume/ast/compound_select.rb +30 -0
  25. data/lib/plume/ast/conflict_clause.rb +22 -0
  26. data/lib/plume/ast/conflict_resolution.rb +20 -0
  27. data/lib/plume/ast/create_index_statement.rb +29 -0
  28. data/lib/plume/ast/create_table_statement.rb +47 -0
  29. data/lib/plume/ast/create_trigger_statement.rb +43 -0
  30. data/lib/plume/ast/create_view_statement.rb +30 -0
  31. data/lib/plume/ast/create_virtual_table_statement.rb +28 -0
  32. data/lib/plume/ast/default_column_constraint.rb +29 -0
  33. data/lib/plume/ast/deferrable_column_constraint.rb +20 -0
  34. data/lib/plume/ast/delete_statement.rb +28 -0
  35. data/lib/plume/ast/detach_statement.rb +13 -0
  36. data/lib/plume/ast/drop_statement.rb +21 -0
  37. data/lib/plume/ast/empty_function_argument.rb +6 -0
  38. data/lib/plume/ast/exists_expression.rb +15 -0
  39. data/lib/plume/ast/explain_statement.rb +15 -0
  40. data/lib/plume/ast/expression.rb +30 -0
  41. data/lib/plume/ast/foreign_key_action.rb +26 -0
  42. data/lib/plume/ast/foreign_key_clause.rb +50 -0
  43. data/lib/plume/ast/foreign_key_column_constraint.rb +16 -0
  44. data/lib/plume/ast/foreign_key_table_constraint.rb +20 -0
  45. data/lib/plume/ast/frame_boundary.rb +35 -0
  46. data/lib/plume/ast/frame_spec.rb +34 -0
  47. data/lib/plume/ast/function_arguments.rb +18 -0
  48. data/lib/plume/ast/function_reference.rb +29 -0
  49. data/lib/plume/ast/generated_as_column_constraint.rb +24 -0
  50. data/lib/plume/ast/indexed_column.rb +24 -0
  51. data/lib/plume/ast/insert_statement.rb +35 -0
  52. data/lib/plume/ast/join.rb +12 -0
  53. data/lib/plume/ast/join_clause.rb +15 -0
  54. data/lib/plume/ast/join_on_constraint.rb +12 -0
  55. data/lib/plume/ast/join_operator.rb +31 -0
  56. data/lib/plume/ast/join_using_constraint.rb +16 -0
  57. data/lib/plume/ast/like_expression.rb +19 -0
  58. data/lib/plume/ast/literal_expression.rb +29 -0
  59. data/lib/plume/ast/literal_false.rb +7 -0
  60. data/lib/plume/ast/literal_nil.rb +7 -0
  61. data/lib/plume/ast/literal_value.rb +18 -0
  62. data/lib/plume/ast/match_clause.rb +15 -0
  63. data/lib/plume/ast/node.rb +330 -0
  64. data/lib/plume/ast/noop_column_constraint.rb +15 -0
  65. data/lib/plume/ast/noop_table_constraint.rb +15 -0
  66. data/lib/plume/ast/not_like_expression.rb +7 -0
  67. data/lib/plume/ast/not_null_column_constraint.rb +17 -0
  68. data/lib/plume/ast/null_column_constraint.rb +17 -0
  69. data/lib/plume/ast/ordering_term.rb +32 -0
  70. data/lib/plume/ast/over_clause.rb +25 -0
  71. data/lib/plume/ast/parenthetical_expression.rb +37 -0
  72. data/lib/plume/ast/parenthetical_source.rb +19 -0
  73. data/lib/plume/ast/pragma_statement.rb +26 -0
  74. data/lib/plume/ast/primary_key_column_constraint.rb +23 -0
  75. data/lib/plume/ast/primary_key_table_constraint.rb +23 -0
  76. data/lib/plume/ast/raise_expression.rb +23 -0
  77. data/lib/plume/ast/reindex_statement.rb +15 -0
  78. data/lib/plume/ast/release_statement.rb +16 -0
  79. data/lib/plume/ast/result_column.rb +18 -0
  80. data/lib/plume/ast/returning_clause.rb +12 -0
  81. data/lib/plume/ast/rollback_statement.rb +19 -0
  82. data/lib/plume/ast/savepoint_statement.rb +16 -0
  83. data/lib/plume/ast/select_core.rb +27 -0
  84. data/lib/plume/ast/select_statement.rb +34 -0
  85. data/lib/plume/ast/star_function_argument.rb +6 -0
  86. data/lib/plume/ast/star_result_column.rb +16 -0
  87. data/lib/plume/ast/strict_table_option.rb +6 -0
  88. data/lib/plume/ast/subquery_expression.rb +14 -0
  89. data/lib/plume/ast/subquery_source.rb +19 -0
  90. data/lib/plume/ast/table_constraint.rb +7 -0
  91. data/lib/plume/ast/table_function.rb +28 -0
  92. data/lib/plume/ast/table_name.rb +28 -0
  93. data/lib/plume/ast/table_option.rb +7 -0
  94. data/lib/plume/ast/ternary_expression.rb +26 -0
  95. data/lib/plume/ast/token.rb +183 -0
  96. data/lib/plume/ast/unary_expression.rb +36 -0
  97. data/lib/plume/ast/unique_column_constraint.rb +17 -0
  98. data/lib/plume/ast/unique_table_constraint.rb +20 -0
  99. data/lib/plume/ast/update_statement.rb +35 -0
  100. data/lib/plume/ast/upsert_clause.rb +26 -0
  101. data/lib/plume/ast/vacuum_statement.rb +18 -0
  102. data/lib/plume/ast/values_clause.rb +12 -0
  103. data/lib/plume/ast/values_row.rb +13 -0
  104. data/lib/plume/ast/variable.rb +15 -0
  105. data/lib/plume/ast/window_definition.rb +26 -0
  106. data/lib/plume/ast/with_clause.rb +15 -0
  107. data/lib/plume/ast/without_rowid_table_option.rb +6 -0
  108. data/lib/plume/catalog.rb +276 -0
  109. data/lib/plume/comment.rb +73 -0
  110. data/lib/plume/dump.rb +143 -0
  111. data/lib/plume/fingerprint.rb +86 -0
  112. data/lib/plume/lexer.rb +640 -0
  113. data/lib/plume/literal_patches.rb +76 -0
  114. data/lib/plume/normalize.rb +65 -0
  115. data/lib/plume/parameters.rb +52 -0
  116. data/lib/plume/parser.rb +4064 -0
  117. data/lib/plume/pretty_printer.rb +202 -0
  118. data/lib/plume/printer.rb +525 -0
  119. data/lib/plume/sqlite_grammar.rb +86 -0
  120. data/lib/plume/truncate.rb +67 -0
  121. data/lib/plume/union.rb +28 -0
  122. data/lib/plume/version.rb +1 -1
  123. data/lib/plume.rb +274 -3
  124. metadata +176 -12
@@ -0,0 +1,4064 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plume
4
+ class Parser
5
+ # https://www.sqlite.org/syntaxdiagrams.html
6
+ # Thrown on every dead end, most of which `maybe` backtracks out of, so the
7
+ # message is only put together when someone actually reads it
8
+ class ErrorMessage
9
+ def initialize(sql, token, beg, fin, expected)
10
+ @sql, @token, @beg, @fin, @expected = sql, token, beg, fin, expected
11
+ end
12
+
13
+ def to_error
14
+ # At the end of input the span sits one byte past the SQL
15
+ location = [@beg, [@fin, @sql.bytesize].min].freeze
16
+ SyntaxError.new(msg, location:, token: @token, line:, expected: @expected.freeze)
17
+ end
18
+
19
+ def line = @sql.b.byteslice(0, @beg).count("\n") + 1
20
+
21
+ def msg
22
+ src = @sql.b # the SQL can be invalid UTF-8, which String#strip will not take
23
+ where = src.strip.include?("\n") ? " line #{line}" : ""
24
+ what = @token ? "token #{@token}[#{@sql.byteslice(@beg, @fin - @beg).inspect}]" : "end of input"
25
+ "Unexpected #{what} at#{where}:\n#{Plume.excerpt(@sql, @beg, @fin)}\n Expected one of: #{@expected.join(", ")}\n"
26
+ end
27
+ end
28
+
29
+ # see: https://github.com/sqlite/sqlite/blob/master/src/parse.y#L255-L283
30
+ # Literals in an expression, names anywhere else: SQLite has no TRUE or FALSE keyword, and lets
31
+ # the CURRENT_* ones (CTIME_KW) fall back to names
32
+ LITERAL_KEYWORDS = Set[:TRUE, :FALSE, :CURRENT_DATE, :CURRENT_TIME, :CURRENT_TIMESTAMP].freeze
33
+
34
+ TOKEN_FALLBACKS = Set[
35
+ *LITERAL_KEYWORDS,
36
+ :EXPLAIN,
37
+ :QUERY,
38
+ :PLAN,
39
+ :BEGIN,
40
+ :DEFERRED,
41
+ :IMMEDIATE,
42
+ :EXCLUSIVE,
43
+ :END,
44
+ :ROLLBACK,
45
+ :SAVEPOINT,
46
+ :RELEASE,
47
+ :IF,
48
+ :TEMP,
49
+ :TEMPORARY,
50
+ :WITHIN,
51
+ :WITHOUT,
52
+ :ABORT,
53
+ :ACTION,
54
+ :AFTER,
55
+ :ANALYZE,
56
+ :ASC,
57
+ :ATTACH,
58
+ :BEFORE,
59
+ :BY,
60
+ :CASCADE,
61
+ :CAST,
62
+ :CONFLICT,
63
+ :DATABASE,
64
+ :DESC,
65
+ :DETACH,
66
+ :EACH,
67
+ :FAIL,
68
+ :MATCH,
69
+ :REGEXP,
70
+ :GLOB,
71
+ :LIKE,
72
+ :ROWID,
73
+ :STORED,
74
+ :STRICT,
75
+ :COLUMN,
76
+ :DO,
77
+ :FOR,
78
+ :IGNORE,
79
+ :INITIALLY,
80
+ :INSTEAD,
81
+ :NO,
82
+ :KEY,
83
+ :OF,
84
+ :OFFSET,
85
+ :PRAGMA,
86
+ :RAISE,
87
+ :RECURSIVE,
88
+ :REPLACE,
89
+ :RESTRICT,
90
+ :ROW,
91
+ :ROWS,
92
+ :TRIGGER,
93
+ :VACUUM,
94
+ :VIEW,
95
+ :VIRTUAL,
96
+ :WITH,
97
+ :NULLS,
98
+ :FIRST,
99
+ :LAST,
100
+ :CURRENT,
101
+ :FOLLOWING,
102
+ :PARTITION,
103
+ :PRECEDING,
104
+ :RANGE,
105
+ :UNBOUNDED,
106
+ :EXCLUDE,
107
+ :GROUPS,
108
+ :OTHERS,
109
+ :TIES,
110
+ :GENERATED,
111
+ :ALWAYS,
112
+ :MATERIALIZED,
113
+ :REINDEX,
114
+ :RENAME,
115
+ :CTIME_KW,
116
+ ].freeze
117
+
118
+ OPERATOR_PRECEDENCE = {
119
+ BITNOT: 100,
120
+ UPLUS: 100,
121
+ UMINUS: 100,
122
+ COLLATE: 90,
123
+ CONCAT: 80,
124
+ PTR1: 80,
125
+ PTR2: 80,
126
+ STAR: 70,
127
+ SLASH: 70,
128
+ REM: 70,
129
+ PLUS: 60,
130
+ MINUS: 60,
131
+ BITAND: 55,
132
+ BITOR: 55,
133
+ LSHIFT: 55,
134
+ RSHIFT: 55,
135
+ NOT: 50,
136
+ LT: 40,
137
+ GT: 40,
138
+ LE: 40,
139
+ GE: 40,
140
+ EQ: 30,
141
+ NE: 30,
142
+ IS: 30,
143
+ ISNULL: 30,
144
+ NOTNULL: 30,
145
+ BETWEEN: 20,
146
+ IN: 20,
147
+ MATCH: 20,
148
+ LIKE: 20,
149
+ REGEXP: 20,
150
+ GLOB: 20,
151
+ AND: 10,
152
+ OR: 5,
153
+ }.freeze
154
+
155
+ def initialize(sql)
156
+ @lexer = Lexer.new(sql, skip_spaces: true)
157
+ @peek_buffer = []
158
+ end
159
+
160
+ def parse
161
+ result = catch(:ERROR) { sql_stmt_list }
162
+
163
+ if ErrorMessage === result
164
+ raise result.to_error
165
+ else
166
+ result
167
+ end
168
+ rescue *STACK_EXHAUSTED
169
+ # How deep is too deep depends on the stack the caller has left, so no fixture relies on it
170
+ raise SyntaxError.new("statement nested too deeply")
171
+ end
172
+
173
+ def sql_stmt_list
174
+ # Relevant `parse.y` grammar rules:
175
+ # input ::= cmdlist
176
+ # cmdlist ::= cmdlist ecmd | ecmd
177
+ # ecmd ::= SEMI | cmdx SEMI | explain cmdx SEMI
178
+ #
179
+ # Syntax diagram:
180
+ # ┌──────{ ; }◀─────┐
181
+ # ◯─┴┬─▶[ sql-stmt ]─┬┴─▶◯
182
+ # └───────▶───────┘
183
+ #
184
+ # Simplified grammar:
185
+ # SEMI* [sql_stmt SEMI]*
186
+ #
187
+ # Relevant SQLite documentation:
188
+ # - https://www.sqlite.org/lang.html
189
+ #
190
+ # A `sql_stmt_list` is zero or more statements,
191
+ # where a `;` is the statement separator,
192
+ # but an empty statement is allowed.
193
+ #
194
+ [].tap do |stmts|
195
+ until current_token.nil?
196
+ maybe :SEMI
197
+ stmts << sql_stmt if current_token
198
+ maybe :SEMI
199
+ end
200
+ end
201
+ end
202
+
203
+ def sql_stmt
204
+ #
205
+ # Relevant `parse.y` grammar rules:
206
+ # ecmd ::= SEMI | cmdx SEMI | explain cmdx SEMI
207
+ # explain ::= EXPLAIN | EXPLAIN QUERY PLAN
208
+ # cmdx ::= cmd
209
+ #
210
+ # Syntax diagram:
211
+ # ◯─┬─────────────┬▶─────────────────────┬─┬─▶[ alter-table-stmt ]──────────▶─┬─▶◯
212
+ # └─{ EXPLAIN }─┴─▶{ QUERY }─▶{ PLAN }─┘ ├─▶[ analyze-stmt ]──────────────▶─┤
213
+ # ├─▶[ attach-stmt ]───────────────▶─┤
214
+ # ├─▶[ begin-stmt ]────────────────▶─┤
215
+ # ├─▶[ commit-stmt ]───────────────▶─┤
216
+ # ├─▶[ create-index-stmt ]─────────▶─┤
217
+ # ├─▶[ create-table-stmt ]─────────▶─┤
218
+ # ├─▶[ create-trigger-stmt ]───────▶─┤
219
+ # ├─▶[ create-view-stmt ]──────────▶─┤
220
+ # ├─▶[ create-virtual-table-stmt ]─▶─┤
221
+ # ├─▶[ delete-stmt ]───────────────▶─┤
222
+ # ├─▶[ detach-stmt ]───────────────▶─┤
223
+ # ├─▶[ drop-index-stmt ]───────────▶─┤
224
+ # ├─▶[ drop-table-stmt ]───────────▶─┤
225
+ # ├─▶[ drop-trigger-stmt ]─────────▶─┤
226
+ # ├─▶[ drop-view-stmt ]────────────▶─┤
227
+ # ├─▶[ insert-stmt ]───────────────▶─┤
228
+ # ├─▶[ pragma-stmt ]───────────────▶─┤
229
+ # ├─▶[ reindex-stmt ]──────────────▶─┤
230
+ # ├─▶[ release-stmt ]──────────────▶─┤
231
+ # ├─▶[ rollback-stmt ]─────────────▶─┤
232
+ # ├─▶[ savepoint-stmt ]────────────▶─┤
233
+ # ├─▶[ select-stmt ]───────────────▶─┤
234
+ # ├─▶[ update-stmt ]───────────────▶─┤
235
+ # └─▶[ vacuum-stmt ]───────────────▶─┘
236
+ # Simplified grammar:
237
+ # [EXPLAIN [QUERY PLAN]] stmt
238
+ #
239
+ # Relevant SQLite documentation:
240
+ # - https://www.sqlite.org/lang_explain.html
241
+ # - https://www.sqlite.org/eqp.html
242
+ # - https://www.sqlite.org/opcode.html
243
+ #
244
+ # A `sql_stmt` is a single SQL statement, optionally preceded by one of two different
245
+ # explanation prefixes. A simple `EXPLAIN` prefix requests the bytecode instructions,
246
+ # while `EXPLAIN QUERY PLAN` requests the query plan.
247
+ #
248
+ # This method doesn't consume tokens associated with the statements so that each
249
+ # statement method can independently consume the tokens it needs.
250
+ #
251
+ if (explain_kw = maybe_one_of [:EXPLAIN, :QUERY, :PLAN].freeze, :EXPLAIN)
252
+ return ExplainStatement.concrete(
253
+ full_source: @lexer.sql,
254
+ explain_kw: Token::Keyword(explain_kw),
255
+ statement: cmd,
256
+ )
257
+ end
258
+
259
+ cmd
260
+ end
261
+
262
+ def cmd
263
+ case current_token
264
+ when :ALTER then alter_table_stmt
265
+ when :ANALYZE then analyze_stmt
266
+ when :ATTACH then attach_stmt
267
+ when :BEGIN then begin_stmt
268
+ when :COMMIT, :END then commit_stmt
269
+ when :CREATE
270
+ case peek
271
+ when :INDEX then create_index_stmt
272
+ when :TABLE then create_table_stmt
273
+ when :TRIGGER then create_trigger_stmt
274
+ when :VIEW then create_view_stmt
275
+ when :VIRTUAL then create_virtual_table_stmt
276
+ when :UNIQUE then create_index_stmt
277
+ when :TEMP, :TEMPORARY
278
+ case peek(2)
279
+ when [:TEMP, :TABLE] then create_table_stmt
280
+ when [:TEMP, :TRIGGER] then create_trigger_stmt
281
+ when [:TEMP, :VIEW] then create_view_stmt
282
+ when [:TEMPORARY, :TABLE] then create_table_stmt
283
+ when [:TEMPORARY, :TRIGGER] then create_trigger_stmt
284
+ when [:TEMPORARY, :VIEW] then create_view_stmt
285
+ else
286
+ expected! :TABLE, :TRIGGER, :VIEW
287
+ end
288
+ else
289
+ expected! :INDEX, :TABLE, :TRIGGER, :VIEW, :VIRTUAL, :UNIQUE, :TEMP, :TEMPORARY
290
+ end
291
+ when :DELETE then delete_stmt
292
+ when :DETACH then detach_stmt
293
+ when :DROP then drop_stmt
294
+ when :INSERT, :REPLACE then insert_stmt
295
+ when :PRAGMA then pragma_stmt
296
+ when :REINDEX then reindex_stmt
297
+ when :RELEASE then release_stmt
298
+ when :ROLLBACK then rollback_stmt
299
+ when :SAVEPOINT then savepoint_stmt
300
+ when :SELECT, :VALUES then select_stmt
301
+ when :UPDATE then update_stmt
302
+ when :VACUUM then vacuum_stmt
303
+ when :WITH then with_stmt
304
+ else
305
+ expected!(:ALTER, :ANALYZE, :ATTACH, :BEGIN, :COMMIT, :END, :CREATE, :DELETE, :DETACH, :DROP, :INSERT, :REPLACE, :PRAGMA, :REINDEX, :RELEASE, :ROLLBACK, :SAVEPOINT, :SELECT, :UPDATE, :VACUUM, :WITH)
306
+ end
307
+ end
308
+
309
+ # ---------- Statements ----------
310
+
311
+ def begin_stmt
312
+ #
313
+ # Relevant `parse.y` grammar rules:
314
+ # cmd ::= BEGIN transtype trans_opt
315
+ # transtype ::= . | DEFERRED | IMMEDIATE | EXCLUSIVE
316
+ # trans_opt ::= . | TRANSACTION | TRANSACTION nm
317
+ #
318
+ # Relevant SQLite documentation:
319
+ # - https://www.sqlite.org/lang_transaction.html
320
+ #
321
+ begin_kw = require :BEGIN
322
+ type_kw = maybe_one_of :DEFERRED, :IMMEDIATE, :EXCLUSIVE
323
+ transaction_kw, name_tk = transaction_name
324
+
325
+ BeginStatement.concrete(
326
+ full_source: @lexer.sql,
327
+ begin_kw: Token::Keyword(begin_kw),
328
+ type_kw: Token::Keyword(type_kw),
329
+ transaction_kw: Token::Keyword(transaction_kw),
330
+ name_tk: Token::Identifier(name_tk),
331
+ )
332
+ end
333
+
334
+ def commit_stmt
335
+ #
336
+ # Relevant `parse.y` grammar rules:
337
+ # cmd ::= COMMIT|END trans_opt
338
+ #
339
+ # Relevant SQLite documentation:
340
+ # - https://www.sqlite.org/lang_transaction.html
341
+ #
342
+ commit_kw = require_one_of :COMMIT, :END
343
+ transaction_kw, name_tk = transaction_name
344
+
345
+ CommitStatement.concrete(
346
+ full_source: @lexer.sql,
347
+ commit_kw: Token::Keyword(commit_kw),
348
+ transaction_kw: Token::Keyword(transaction_kw),
349
+ name_tk: Token::Identifier(name_tk),
350
+ )
351
+ end
352
+
353
+ def rollback_stmt
354
+ #
355
+ # Relevant `parse.y` grammar rules:
356
+ # cmd ::= ROLLBACK trans_opt
357
+ # cmd ::= ROLLBACK trans_opt TO savepoint_opt nm
358
+ # savepoint_opt ::= SAVEPOINT | .
359
+ #
360
+ # Relevant SQLite documentation:
361
+ # - https://www.sqlite.org/lang_transaction.html
362
+ # - https://www.sqlite.org/lang_savepoint.html
363
+ #
364
+ rollback_kw = require :ROLLBACK
365
+ transaction_kw, name_tk = transaction_name
366
+ if (to_kw = maybe_one_of [:TO, :SAVEPOINT].freeze, :TO)
367
+ savepoint_tk = name
368
+ end
369
+
370
+ RollbackStatement.concrete(
371
+ full_source: @lexer.sql,
372
+ rollback_kw: Token::Keyword(rollback_kw),
373
+ transaction_kw: Token::Keyword(transaction_kw),
374
+ name_tk: Token::Identifier(name_tk),
375
+ to_kw: Token::Keyword(to_kw),
376
+ savepoint_tk: Token::Identifier(savepoint_tk),
377
+ )
378
+ end
379
+
380
+ # `trans_opt ::= . | TRANSACTION | TRANSACTION nm` - the name is accepted and ignored by SQLite
381
+ def transaction_name
382
+ return [] unless (transaction_kw = maybe :TRANSACTION)
383
+
384
+ name_tk = maybe { name(except: [:TO]) } unless statement_end?
385
+ [transaction_kw, name_tk]
386
+ end
387
+
388
+ def savepoint_stmt
389
+ #
390
+ # Relevant `parse.y` grammar rules:
391
+ # cmd ::= SAVEPOINT nm
392
+ #
393
+ # Relevant SQLite documentation:
394
+ # - https://www.sqlite.org/lang_savepoint.html
395
+ #
396
+ savepoint_kw = require :SAVEPOINT
397
+
398
+ SavepointStatement.concrete(
399
+ full_source: @lexer.sql,
400
+ savepoint_kw: Token::Keyword(savepoint_kw),
401
+ name_tk: Token::Identifier(name),
402
+ )
403
+ end
404
+
405
+ def release_stmt
406
+ #
407
+ # Relevant `parse.y` grammar rules:
408
+ # cmd ::= RELEASE savepoint_opt nm
409
+ #
410
+ # Relevant SQLite documentation:
411
+ # - https://www.sqlite.org/lang_savepoint.html
412
+ #
413
+ release_kw = require_one_of [:RELEASE, :SAVEPOINT].freeze, :RELEASE
414
+
415
+ ReleaseStatement.concrete(
416
+ full_source: @lexer.sql,
417
+ release_kw: Token::Keyword(release_kw),
418
+ name_tk: Token::Identifier(name),
419
+ )
420
+ end
421
+
422
+ def drop_stmt
423
+ #
424
+ # Relevant `parse.y` grammar rules:
425
+ # cmd ::= DROP TABLE ifexists fullname
426
+ # cmd ::= DROP VIEW ifexists fullname
427
+ # cmd ::= DROP INDEX ifexists fullname
428
+ # cmd ::= DROP TRIGGER ifexists fullname
429
+ # ifexists ::= IF EXISTS | .
430
+ #
431
+ # Relevant SQLite documentation:
432
+ # - https://www.sqlite.org/lang_droptable.html
433
+ # - https://www.sqlite.org/lang_dropview.html
434
+ # - https://www.sqlite.org/lang_dropindex.html
435
+ # - https://www.sqlite.org/lang_droptrigger.html
436
+ #
437
+ drop_kw = require :DROP
438
+ type_kw = require_one_of :TABLE, :VIEW, :INDEX, :TRIGGER
439
+ if_exists_kw = maybe_all_of :IF, :EXISTS
440
+
441
+ DropStatement.concrete(
442
+ full_source: @lexer.sql,
443
+ drop_kw: Token::Keyword(drop_kw),
444
+ type_kw: Token::Keyword(type_kw),
445
+ if_exists_kw: Token::Keyword(if_exists_kw),
446
+ target: table_name,
447
+ )
448
+ end
449
+
450
+ def pragma_stmt
451
+ #
452
+ # Relevant `parse.y` grammar rules:
453
+ # cmd ::= PRAGMA nm dbnm
454
+ # cmd ::= PRAGMA nm dbnm EQ nmnum
455
+ # cmd ::= PRAGMA nm dbnm LP nmnum RP
456
+ # cmd ::= PRAGMA nm dbnm EQ minus_num
457
+ # cmd ::= PRAGMA nm dbnm LP minus_num RP
458
+ # nmnum ::= plus_num | nm | ON | DELETE | DEFAULT
459
+ #
460
+ # Syntax diagram:
461
+ # ◯─▶{ PRAGMA }┬▶{ schema-name }─▶{ . }┬▶{ pragma-name }┬─────────────────────────────┬─▶◯
462
+ # └───────────▶───────────┘ ├▶{ = }─▶[ pragma-value ]─────┤
463
+ # └▶{ ( }─▶[ pragma-value ]─▶{ ) }┘
464
+ #
465
+ # Relevant SQLite documentation:
466
+ # - https://www.sqlite.org/pragma.html
467
+ #
468
+ pragma_kw = require :PRAGMA
469
+ name_tk = name
470
+ if (dot_tk = maybe :DOT)
471
+ schema_tk = name_tk
472
+ name_tk = name
473
+ end
474
+ if (eq_tk = maybe :EQ)
475
+ value_tk = pragma_value
476
+ elsif (eq_tk = maybe :LP)
477
+ value_tk = pragma_value
478
+ value_rp = require :RP
479
+ end
480
+
481
+ PragmaStatement.concrete(
482
+ full_source: @lexer.sql,
483
+ pragma_kw: Token::Keyword(pragma_kw),
484
+ schema_tk: Token::Identifier(schema_tk),
485
+ dot_tk: Token::Punctuation(dot_tk),
486
+ name_tk: Token::Identifier(name_tk),
487
+ eq_tk: Token::Punctuation(eq_tk),
488
+ value_tk: value_tk,
489
+ value_rp: Token::Punctuation(value_rp),
490
+ )
491
+ end
492
+
493
+ def pragma_value
494
+ if current_token in :PLUS | :MINUS | :INTEGER | :FLOAT
495
+ Token::Numeric(signed_number)
496
+ elsif (keyword_tk = maybe_one_of :ON, :DELETE, :DEFAULT, :TRUE, :FALSE)
497
+ Token::Keyword(keyword_tk)
498
+ else
499
+ Token::Identifier(name)
500
+ end
501
+ end
502
+
503
+ def attach_stmt
504
+ #
505
+ # Relevant `parse.y` grammar rules:
506
+ # cmd ::= ATTACH database_kw_opt expr AS expr key_opt
507
+ # key_opt ::= . | KEY expr
508
+ # database_kw_opt ::= DATABASE | .
509
+ #
510
+ # Relevant SQLite documentation:
511
+ # - https://www.sqlite.org/lang_attach.html
512
+ #
513
+ attach_kw = require_one_of [:ATTACH, :DATABASE].freeze, :ATTACH
514
+ file = expression
515
+ as_kw = require :AS
516
+ schema = expression
517
+ if (key_kw = maybe :KEY)
518
+ key = expression
519
+ end
520
+
521
+ AttachStatement.concrete(
522
+ full_source: @lexer.sql,
523
+ attach_kw: Token::Keyword(attach_kw),
524
+ file: file,
525
+ as_kw: Token::Keyword(as_kw),
526
+ schema: schema,
527
+ key_kw: Token::Keyword(key_kw),
528
+ key: key,
529
+ )
530
+ end
531
+
532
+ def detach_stmt
533
+ #
534
+ # Relevant `parse.y` grammar rules:
535
+ # cmd ::= DETACH database_kw_opt expr
536
+ #
537
+ # Relevant SQLite documentation:
538
+ # - https://www.sqlite.org/lang_detach.html
539
+ #
540
+ detach_kw = require_one_of [:DETACH, :DATABASE].freeze, :DETACH
541
+
542
+ DetachStatement.concrete(
543
+ full_source: @lexer.sql,
544
+ detach_kw: Token::Keyword(detach_kw),
545
+ schema: expression,
546
+ )
547
+ end
548
+
549
+ def analyze_stmt
550
+ #
551
+ # Relevant `parse.y` grammar rules:
552
+ # cmd ::= ANALYZE | ANALYZE nm dbnm
553
+ #
554
+ # Relevant SQLite documentation:
555
+ # - https://www.sqlite.org/lang_analyze.html
556
+ #
557
+ analyze_kw = require :ANALYZE
558
+ target = table_name unless statement_end?
559
+
560
+ AnalyzeStatement.concrete(
561
+ full_source: @lexer.sql,
562
+ analyze_kw: Token::Keyword(analyze_kw),
563
+ target: target,
564
+ )
565
+ end
566
+
567
+ def reindex_stmt
568
+ #
569
+ # Relevant `parse.y` grammar rules:
570
+ # cmd ::= REINDEX | REINDEX nm dbnm
571
+ #
572
+ # Relevant SQLite documentation:
573
+ # - https://www.sqlite.org/lang_reindex.html
574
+ #
575
+ reindex_kw = require :REINDEX
576
+ target = table_name unless statement_end?
577
+
578
+ ReindexStatement.concrete(
579
+ full_source: @lexer.sql,
580
+ reindex_kw: Token::Keyword(reindex_kw),
581
+ target: target,
582
+ )
583
+ end
584
+
585
+ def vacuum_stmt
586
+ #
587
+ # Relevant `parse.y` grammar rules:
588
+ # cmd ::= VACUUM vinto | VACUUM nm vinto
589
+ # vinto ::= INTO expr | .
590
+ #
591
+ # Relevant SQLite documentation:
592
+ # - https://www.sqlite.org/lang_vacuum.html
593
+ #
594
+ vacuum_kw = require :VACUUM
595
+ schema_tk = maybe { name } unless current_token.nil? || current_token in :SEMI | :INTO
596
+ if (into_kw = maybe :INTO)
597
+ into = expression
598
+ end
599
+
600
+ VacuumStatement.concrete(
601
+ full_source: @lexer.sql,
602
+ vacuum_kw: Token::Keyword(vacuum_kw),
603
+ schema_tk: Token::Identifier(schema_tk),
604
+ into_kw: Token::Keyword(into_kw),
605
+ into: into,
606
+ )
607
+ end
608
+
609
+ def create_index_stmt
610
+ #
611
+ # Relevant `parse.y` grammar rules:
612
+ # cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt
613
+ # uniqueflag ::= UNIQUE | .
614
+ #
615
+ # Syntax diagram:
616
+ # ◯─▶{ CREATE }┬▶{ UNIQUE }┬▶{ INDEX }┬▶{ IF }─▶{ NOT }─▶{ EXISTS }┬┐
617
+ # └─────▶─────┘ └───────────────▶────────────┘│
618
+ # ┌──────────────────────────────────◀──────────────────────────────┘
619
+ # └┬▶{ schema-name }─▶{ . }┬▶{ index-name }─▶{ ON }─▶{ table-name }─▶{ ( }┐
620
+ # └──────────▶────────────┘ │
621
+ # ┌───────────────────────────────────◀───────────────────────────────────┘
622
+ # └┬▶[ indexed-column ]┬▶{ ) }┬────────────────────────┬─▶◯
623
+ # └──────{ , }◀───────┘ └▶{ WHERE }─▶[ expr ]────┘
624
+ #
625
+ # Relevant SQLite documentation:
626
+ # - https://www.sqlite.org/lang_createindex.html
627
+ # - https://www.sqlite.org/partialindex.html
628
+ # - https://www.sqlite.org/expridx.html
629
+ #
630
+ create_kw = require :CREATE
631
+ unique_kw = maybe :UNIQUE
632
+ index_kw = require :INDEX
633
+ if_not_exists_kw = maybe_all_of :IF, :NOT, :EXISTS
634
+ index = table_name
635
+ on_kw = require :ON
636
+ table_tk = name
637
+ columns_lp = require :LP
638
+ columns = require_some(trailing_sep: :COMMA) { ordering_term }
639
+ columns_rp = require :RP
640
+ if (where_kw = maybe :WHERE)
641
+ condition = expression
642
+ end
643
+
644
+ CreateIndexStatement.concrete(
645
+ full_source: @lexer.sql,
646
+ create_kw: Token::Keyword(create_kw),
647
+ unique_kw: Token::Keyword(unique_kw),
648
+ index_kw: Token::Keyword(index_kw),
649
+ if_not_exists_kw: Token::Keyword(if_not_exists_kw),
650
+ index: index,
651
+ on_kw: Token::Keyword(on_kw),
652
+ table_tk: Token::Identifier(table_tk),
653
+ columns_lp: Token::Punctuation(columns_lp),
654
+ columns: columns,
655
+ columns_rp: Token::Punctuation(columns_rp),
656
+ where_kw: Token::Keyword(where_kw),
657
+ condition: condition,
658
+ )
659
+ end
660
+
661
+ def alter_table_stmt
662
+ #
663
+ # Relevant `parse.y` grammar rules:
664
+ # cmd ::= ALTER TABLE fullname RENAME TO nm
665
+ # cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist
666
+ # cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm
667
+ # cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm
668
+ # cmd ::= ALTER TABLE fullname DROP CONSTRAINT nm
669
+ # cmd ::= ALTER TABLE fullname ALTER kwcolumn_opt nm DROP NOT NULL
670
+ # cmd ::= ALTER TABLE fullname ALTER kwcolumn_opt nm SET NOT NULL onconf
671
+ # cmd ::= ALTER TABLE fullname ADD CONSTRAINT nm CHECK LP expr RP onconf
672
+ # cmd ::= ALTER TABLE fullname ADD CHECK LP expr RP onconf
673
+ # kwcolumn_opt ::= . | COLUMNKW
674
+ #
675
+ # Relevant SQLite documentation:
676
+ # - https://www.sqlite.org/lang_altertable.html
677
+ #
678
+ alter_table_kw = require_all_of :ALTER, :TABLE
679
+ table = table_name
680
+
681
+ if current_token == :RENAME && peek == :TO
682
+ action = :RENAME_TABLE
683
+ action_kw = require :RENAME
684
+ to_kw = require :TO
685
+ new_name_tk = name
686
+ elsif (action_kw = maybe_one_of [:RENAME, :COLUMN].freeze, :RENAME)
687
+ action = :RENAME_COLUMN
688
+ column_tk = name
689
+ to_kw = require :TO
690
+ new_name_tk = name
691
+ elsif current_token == :ADD && (peek in :CONSTRAINT | :CHECK)
692
+ action = :ADD_CONSTRAINT
693
+ action_kw = require :ADD
694
+ constraint_name = name if (constraint_kw = maybe :CONSTRAINT)
695
+ constraint = check_table_constraint(constraint_kw, constraint_name)
696
+ elsif (action_kw = maybe_one_of [:ADD, :COLUMN].freeze, :ADD)
697
+ action = :ADD_COLUMN
698
+ column_definition = column_def
699
+ elsif current_token == :DROP && peek == :CONSTRAINT
700
+ action = :DROP_CONSTRAINT
701
+ action_kw = require_all_of :DROP, :CONSTRAINT
702
+ constraint_name_tk = name
703
+ elsif (action_kw = maybe_one_of [:DROP, :COLUMN].freeze, :DROP)
704
+ action = :DROP_COLUMN
705
+ column_tk = name
706
+ elsif (action_kw = maybe_one_of [:ALTER, :COLUMN].freeze, :ALTER)
707
+ column_tk = name
708
+ if current_token == :DROP
709
+ action = :DROP_NOT_NULL
710
+ not_null_kw = require_all_of :DROP, :NOT, :NULL
711
+ else
712
+ action = :SET_NOT_NULL
713
+ not_null_kw = require_all_of :SET, :NOT, :NULL
714
+ on_conflict = maybe { conflict_clause }
715
+ end
716
+ else
717
+ expected!(:RENAME, :ADD, :DROP, :ALTER)
718
+ end
719
+
720
+ AlterTableStatement.concrete(
721
+ full_source: @lexer.sql,
722
+ alter_table_kw: Token::Keyword(alter_table_kw),
723
+ table: table,
724
+ action: action,
725
+ action_kw: Token::Keyword(action_kw),
726
+ column_tk: Token::Identifier(column_tk),
727
+ to_kw: Token::Keyword(to_kw),
728
+ new_name_tk: Token::Identifier(new_name_tk),
729
+ column_definition: column_definition,
730
+ constraint: constraint,
731
+ constraint_name_tk: Token::Identifier(constraint_name_tk),
732
+ not_null_kw: Token::Keyword(not_null_kw),
733
+ conflict_clause: on_conflict,
734
+ )
735
+ end
736
+
737
+ # tcons ::= CHECK LP expr RP onconf, after the `CONSTRAINT nm` the caller may have read
738
+ def check_table_constraint(constraint_kw = nil, constraint_name = nil)
739
+ check_kw = require :CHECK
740
+ check_lp = require :LP
741
+ check = expression
742
+ check_rp = require :RP
743
+ on_conflict = maybe { conflict_clause }
744
+
745
+ CheckTableConstraint.concrete(
746
+ full_source: @lexer.sql,
747
+ constraint_kw: Token::Keyword(constraint_kw),
748
+ name_tk: Token::Identifier(constraint_name),
749
+ check_kw: Token::Keyword(check_kw),
750
+ check_lp: Token::Punctuation(check_lp),
751
+ expression: check,
752
+ check_rp: Token::Punctuation(check_rp),
753
+ conflict_clause: on_conflict,
754
+ )
755
+ end
756
+
757
+ def create_virtual_table_stmt
758
+ #
759
+ # Relevant `parse.y` grammar rules:
760
+ # create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm
761
+ # cmd ::= create_vtab | create_vtab LP vtabarglist RP
762
+ # vtabarglist ::= vtabarg | vtabarglist COMMA vtabarg
763
+ # vtabarg ::= . | vtabarg vtabargtoken
764
+ # vtabargtoken ::= ANY | lp anylist RP
765
+ #
766
+ # Relevant SQLite documentation:
767
+ # - https://www.sqlite.org/lang_createvtab.html
768
+ #
769
+ create_virtual_table_kw = require_all_of :CREATE, :VIRTUAL, :TABLE
770
+ if_not_exists_kw = maybe_all_of :IF, :NOT, :EXISTS
771
+ table = table_name
772
+ using_kw = require :USING
773
+ module_tk = name
774
+ if (arguments_lp = maybe :LP)
775
+ arguments, arguments_rp = module_arguments
776
+ end
777
+
778
+ CreateVirtualTableStatement.concrete(
779
+ full_source: @lexer.sql,
780
+ create_virtual_table_kw: Token::Keyword(create_virtual_table_kw),
781
+ if_not_exists_kw: Token::Keyword(if_not_exists_kw),
782
+ table: table,
783
+ using_kw: Token::Keyword(using_kw),
784
+ module_tk: Token::Identifier(module_tk),
785
+ arguments_lp: Token::Punctuation(arguments_lp),
786
+ arguments: arguments,
787
+ arguments_rp: Token::Punctuation(arguments_rp),
788
+ )
789
+ end
790
+
791
+ # Collects the text of each comma-separated argument up to the closing parenthesis,
792
+ # whatever tokens it is made of
793
+ def module_arguments
794
+ arguments = []
795
+ depth = 0
796
+ first = last = nil
797
+
798
+ loop do
799
+ expected!(:RP) if current_token.nil?
800
+
801
+ if depth.zero? && current_token in :COMMA | :RP
802
+ arguments << (first ? @lexer.sql.byteslice(first, last - first) : "")
803
+ first = last = nil
804
+ return [arguments, advance] if current_token == :RP
805
+
806
+ advance
807
+ next
808
+ end
809
+
810
+ depth += 1 if current_token == :LP
811
+ depth -= 1 if current_token == :RP
812
+ _tok, beg, fin = advance
813
+ first ||= beg
814
+ last = fin
815
+ end
816
+ end
817
+
818
+ def create_trigger_stmt
819
+ #
820
+ # Relevant `parse.y` grammar rules:
821
+ # cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END
822
+ # trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause
823
+ # trigger_time ::= BEFORE | AFTER | INSTEAD OF | .
824
+ # trigger_event ::= DELETE | INSERT | UPDATE | UPDATE OF idlist
825
+ # foreach_clause ::= . | FOR EACH ROW
826
+ # when_clause ::= . | WHEN expr
827
+ # trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI | trigger_cmd SEMI
828
+ #
829
+ # Relevant SQLite documentation:
830
+ # - https://www.sqlite.org/lang_createtrigger.html
831
+ #
832
+ create_kw = require :CREATE
833
+ temp_kw = maybe_one_of :TEMP, :TEMPORARY
834
+ trigger_kw = require :TRIGGER
835
+ if_not_exists_kw = maybe_all_of :IF, :NOT, :EXISTS
836
+ trigger = table_name
837
+ time_kw = maybe_one_of :BEFORE, :AFTER, [:INSTEAD, :OF].freeze
838
+ if (event_kw = maybe_all_of :UPDATE, :OF)
839
+ columns = require_some(trailing_sep: :COMMA) { column_reference }
840
+ else
841
+ event_kw = require_one_of :DELETE, :INSERT, :UPDATE
842
+ end
843
+ on_kw = require :ON
844
+ table = table_name
845
+ for_each_row_kw = maybe_all_of :FOR, :EACH, :ROW
846
+ if (when_kw = maybe :WHEN)
847
+ condition = expression
848
+ end
849
+ begin_kw = require :BEGIN
850
+ statements = require_some do
851
+ statement =
852
+ case current_token
853
+ when :UPDATE then update_stmt
854
+ when :INSERT, :REPLACE then insert_stmt
855
+ when :DELETE then delete_stmt
856
+ when :WITH then with_stmt
857
+ else select_stmt
858
+ end
859
+ require :SEMI
860
+ statement
861
+ end
862
+ end_kw = require :END
863
+
864
+ CreateTriggerStatement.concrete(
865
+ full_source: @lexer.sql,
866
+ create_kw: Token::Keyword(create_kw),
867
+ temp_kw: Token::Keyword(temp_kw),
868
+ trigger_kw: Token::Keyword(trigger_kw),
869
+ if_not_exists_kw: Token::Keyword(if_not_exists_kw),
870
+ trigger: trigger,
871
+ time_kw: Token::Keyword(time_kw),
872
+ event_kw: Token::Keyword(event_kw),
873
+ columns: columns,
874
+ on_kw: Token::Keyword(on_kw),
875
+ table: table,
876
+ for_each_row_kw: Token::Keyword(for_each_row_kw),
877
+ when_kw: Token::Keyword(when_kw),
878
+ condition: condition,
879
+ begin_kw: Token::Keyword(begin_kw),
880
+ statements: statements,
881
+ end_kw: Token::Keyword(end_kw),
882
+ )
883
+ end
884
+
885
+ def create_table_stmt
886
+ #
887
+ # Relevant `parse.y` grammar rules:
888
+ # cmd ::= create_table create_table_args
889
+ # create_table ::= createkw temp TABLE ifnotexists nm dbnm
890
+ # create_table_args ::= LP columnlist conslist_opt RP table_option_set | AS select
891
+ # createkw ::= CREATE
892
+ # temp ::= . | TEMP
893
+ # ifnotexists ::= . | IF NOT EXISTS
894
+ # columnlist ::= columnlist COMMA columnname carglist
895
+ # nm ::= idj | STRING
896
+ # dbnm ::= . | DOT nm
897
+ #
898
+ # Syntax diagram:
899
+ # ◯─▶{ CREATE }┬───────▶──────┬▶{ TABLE }┬▶{ IF }─▶{ NOT }─▶{ EXISTS }─┐
900
+ # ├▶{ TEMP }─────┤ │ │
901
+ # └▶{ TEMPORARY }┘ │ │
902
+ # ┌─────────────────────◀────────────────┴──────────────────◀──────────┘
903
+ # ├─▶{ schema-name }─▶{ . }─┬▶{ table-name }┬───────▶{ AS }─▶[ select-stmt ]───▶─────┐
904
+ # └────────▶────────────────┘ │ │
905
+ # ┌────────────────────◀────────────────────┘ ┌────────▶─────────┼─▶◯
906
+ # └▶{ ( }─┬▶[ column-def ]─┬▶┬─────────────────────────────┬▶{ ) }┴▶[ table-options ]┘
907
+ # └─────{ , }◀─────┘ └[ table-constraint ]◀─{ , }◀─┘
908
+ #
909
+ # Simplified grammar:
910
+ # CREATE [TEMP] TABLE [IF NOT EXISTS] name [DOT name] (
911
+ # LP column_def [, column_def]* [table_constraint [, table_constraint]*] RP [table_options]
912
+ # | AS select_stmt
913
+ # )
914
+ #
915
+ # Relevant SQLite documentation:
916
+ # - https://www.sqlite.org/lang_createtable.html
917
+ #
918
+ # A `create_table_stmt` creates a table.
919
+ #
920
+ create_kw = require :CREATE
921
+ temp_kw = maybe_one_of :TEMP, :TEMPORARY
922
+ table_kw = require :TABLE
923
+ if_not_exists_kw = maybe_all_of :IF, :NOT, :EXISTS
924
+ table_name = table_name()
925
+
926
+ if maybe :AS
927
+ as_select = select_stmt
928
+
929
+ CreateTableStatement.concrete(
930
+ full_source: @lexer.sql,
931
+ create_kw: Token::Keyword(create_kw),
932
+ temp_kw: Token::Keyword(temp_kw),
933
+ table_kw: Token::Keyword(table_kw),
934
+ if_not_exists_kw: Token::Keyword(if_not_exists_kw),
935
+ table: table_name,
936
+ select_statement: as_select
937
+ )
938
+ elsif (columns_lp = maybe :LP)
939
+ # The comma after the last column leads into the table constraints: `columnlist COMMA conslist`
940
+ columns = require_some(given: column_def, trailing_sep: :COMMA) do
941
+ column_def unless current_token in :CONSTRAINT | :PRIMARY | :UNIQUE | :CHECK | :FOREIGN
942
+ end
943
+ # inlined `table_constraint`, but for CHECK, which ALTER TABLE shares through check_table_constraint
944
+ constraints = maybe_some(trailing_sep: [:COMMA, nil].freeze) do
945
+ #
946
+ # Relevant `parse.y` grammar rules:
947
+ # conslist_opt ::= . | COMMA conslist.
948
+ # conslist ::= conslist tconscomma tcons | tcons
949
+ # tconscomma ::= . | COMMA
950
+ # tcons ::= CONSTRAINT nm |
951
+ # PRIMARY KEY LP sortlist autoinc RP onconf |
952
+ # UNIQUE LP sortlist RP onconf |
953
+ # CHECK LP expr RP onconf |
954
+ # FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt
955
+ #
956
+ # Syntax diagram:
957
+ # ◯─▶┬▶{ CONSTRAINT }─▶{ name }─┐
958
+ # ├─────────────◀────────────┘
959
+ # ├─▶{ PRIMARY }─▶{ KEY }───▶{ ( }┬▶[ indexed-column ]┬┬▶{ AUTOINCREMENT }┬▶{ ) }─▶[ conflict-clause ]───┬─▶◯
960
+ # │ └───────{ , }◀──────┘└────────▶─────────┘ │
961
+ # ├─▶{ UNIQUE }──▶{ ( }┬▶[ indexed-column ]┬▶{ ) }─▶[ conflict-clause ]────────────────────────────────▶─┤
962
+ # │ └───────{ , }◀──────┘ │
963
+ # ├─▶{ CHECK }─▶{ ( }─▶[ expr ]─▶{ ) }─▶[ conflict-clause ]────────────────────────────────────────────▶─┤
964
+ # └─▶{ FOREIGN }─▶{ KEY }─▶{ ( }┬▶[ indexed-column ]┬▶{ ) }─▶[ foreign-key-clause ]────────────────────▶─┘
965
+ # └──────{ , }◀───────┘
966
+ #
967
+ # Simplified grammar:
968
+ #
969
+ # Relevant SQLite documentation:
970
+ # - https://www.sqlite.org/syntax/table-constraint.html
971
+ #
972
+ # A set of SQL constraints for each table.
973
+ # SQLite supports UNIQUE, NOT NULL, CHECK and FOREIGN KEY constraints.
974
+ #
975
+ if (constraint_kw = maybe :CONSTRAINT)
976
+ constraint_name = name
977
+ end
978
+
979
+ if (unique_kw = maybe :UNIQUE)
980
+ unique_lp = require :LP
981
+ unique_columns = require_some(trailing_sep: :COMMA) { indexed_column(nulls: true) }
982
+ unique_rp = require :RP
983
+ on_conflict = maybe { conflict_clause }
984
+
985
+ UniqueTableConstraint.concrete(
986
+ full_source: @lexer.sql,
987
+ constraint_kw: Token::Keyword(constraint_kw),
988
+ name_tk: Token::Identifier(constraint_name),
989
+ unique_kw: Token::Keyword(unique_kw),
990
+ unique_lp: Token::Punctuation(unique_lp),
991
+ columns: unique_columns,
992
+ unique_rp: Token::Punctuation(unique_rp),
993
+ conflict_clause: on_conflict,
994
+ )
995
+ elsif current_token == :CHECK
996
+ check_table_constraint(constraint_kw, constraint_name)
997
+ elsif (primary_key_kw = maybe_all_of :PRIMARY, :KEY)
998
+ primary_key_lp = require :LP
999
+ primary_key_columns = require_some(trailing_sep: :COMMA) { indexed_column(nulls: true) }
1000
+ autoincrement_kw = maybe :AUTOINCREMENT
1001
+ primary_key_rp = require :RP
1002
+ on_conflict = maybe { conflict_clause }
1003
+
1004
+ PrimaryKeyTableConstraint.concrete(
1005
+ full_source: @lexer.sql,
1006
+ constraint_kw: Token::Keyword(constraint_kw),
1007
+ name_tk: Token::Identifier(constraint_name),
1008
+ primary_key_kw: Token::Keyword(primary_key_kw),
1009
+ primary_key_lp: Token::Punctuation(primary_key_lp),
1010
+ columns: primary_key_columns,
1011
+ autoincrement_kw: Token::Keyword(autoincrement_kw),
1012
+ primary_key_rp: Token::Punctuation(primary_key_rp),
1013
+ conflict_clause: on_conflict,
1014
+ )
1015
+ elsif (foreign_key_kw = maybe_all_of :FOREIGN, :KEY)
1016
+ foreign_key_lp = require :LP
1017
+ foreign_key_columns = require_some(trailing_sep: :COMMA) { indexed_column }
1018
+ foreign_key_rp = require :RP
1019
+ clause = foreign_key_clause
1020
+
1021
+ ForeignKeyTableConstraint.concrete(
1022
+ full_source: @lexer.sql,
1023
+ constraint_kw: Token::Keyword(constraint_kw),
1024
+ name_tk: Token::Identifier(constraint_name),
1025
+ foreign_key_kw: Token::Keyword(foreign_key_kw),
1026
+ foreign_key_lp: Token::Punctuation(foreign_key_lp),
1027
+ columns: foreign_key_columns,
1028
+ foreign_key_rp: Token::Punctuation(foreign_key_rp),
1029
+ foreign_key_clause: clause,
1030
+ )
1031
+ else
1032
+ if constraint_name
1033
+ NoOpTableConstraint.concrete(
1034
+ full_source: @lexer.sql,
1035
+ constraint_kw: Token::Keyword(constraint_kw),
1036
+ name_tk: Token::Identifier(constraint_name),
1037
+ )
1038
+ else
1039
+ expected!(:PRIMARY, :UNIQUE, :CHECK, :FOREIGN)
1040
+ end
1041
+ end
1042
+ end
1043
+ columns_rp = require :RP
1044
+ # inlined `table_options`
1045
+ options = maybe_some(trailing_sep: :COMMA) do
1046
+ #
1047
+ # Relevant `parse.y` grammar rules:
1048
+ # table_option_set ::= . | table_option | table_option_set COMMA table_option
1049
+ # table_option ::= WITHOUT nm // nm must be "ROWID"
1050
+ # table_option ::= nm // nm must be "STRICT"
1051
+ #
1052
+ # Syntax diagram:
1053
+ # ┌─▶{ WITHOUT }─▶{ ROWID }─┐
1054
+ # ◯─┬─┤ ├─┬─▶◯
1055
+ # │ └─▶{ STRICT }─────────────┘ │
1056
+ # └──────◀───────────────{ , }◀─┘
1057
+ #
1058
+ # Simplified grammar:
1059
+ # [WITHOUT ROWID | STRICT] [, [WITHOUT ROWID | STRICT]]*
1060
+ #
1061
+ # Relevant SQLite documentation:
1062
+ # - https://www.sqlite.org/syntax/table-options.html
1063
+ # - https://www.sqlite.org/withoutrowid.html
1064
+ # - https://www.sqlite.org/stricttables.html
1065
+ #
1066
+ # Whether the table is a WITHOUT ROWID table.
1067
+ # Whether the table is subject to STRICT type checking.
1068
+ #
1069
+ if maybe(:STRICT)
1070
+ StrictTableOption.new
1071
+ elsif maybe_all_of(:WITHOUT, :ROWID)
1072
+ WithoutRowidTableOption.new
1073
+ else
1074
+ expected!(:STRICT, :WITHOUT)
1075
+ end
1076
+ end
1077
+
1078
+ CreateTableStatement.concrete(
1079
+ full_source: @lexer.sql,
1080
+ create_kw: Token::Keyword(create_kw),
1081
+ temp_kw: Token::Keyword(temp_kw),
1082
+ table_kw: Token::Keyword(table_kw),
1083
+ if_not_exists_kw: Token::Keyword(if_not_exists_kw),
1084
+ table: table_name,
1085
+ columns_lp: Token::Punctuation(columns_lp),
1086
+ columns: columns,
1087
+ columns_rp: Token::Punctuation(columns_rp),
1088
+ options: options,
1089
+ constraints: constraints,
1090
+ )
1091
+ else
1092
+ expected!(:AS, :LP)
1093
+ end
1094
+ end
1095
+
1096
+ def create_view_stmt
1097
+ #
1098
+ # Relevant `parse.y` grammar rules:
1099
+ # cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select
1100
+ #
1101
+ # Syntax diagram:
1102
+ # ◯─▶{ CREATE }┬───────▶──────┬▶{ VIEW }┬▶{ IF }─▶{ NOT }─▶{ EXISTS }─┐
1103
+ # ├▶{ TEMP }─────┤ │ │
1104
+ # └▶{ TEMPORARY }┘ │ │
1105
+ # ┌─────────────────────◀───────────────┴──────────────────◀──────────┘
1106
+ # ├─▶{ schema-name }─▶{ . }─┬▶{ view-name }┬─────────────────────────────────┬▶{ AS }─▶[ select-stmt ]─▶◯
1107
+ # └────────▶────────────────┘ └▶{ ( }┬▶{ column-name }┬▶{ ) }─┘
1108
+ # └──────{ , }◀────┘
1109
+ #
1110
+ # Simplified grammar:
1111
+ # CREATE [TEMP] VIEW [IF NOT EXISTS] [schema .] name [(column [, column]*)] AS select_stmt
1112
+ #
1113
+ # Relevant SQLite documentation:
1114
+ # - https://www.sqlite.org/lang_createview.html
1115
+ #
1116
+ # A view is a named select statement that can be queried like a table.
1117
+ #
1118
+ create_kw = require :CREATE
1119
+ temp_kw = maybe_one_of :TEMP, :TEMPORARY
1120
+ view_kw = require :VIEW
1121
+ if_not_exists_kw = maybe_all_of :IF, :NOT, :EXISTS
1122
+ view = table_name
1123
+ if (columns_lp = maybe :LP)
1124
+ columns = require_some(trailing_sep: :COMMA) { indexed_column }
1125
+ columns_rp = require :RP
1126
+ end
1127
+ as_kw = require :AS
1128
+ select = select_stmt
1129
+
1130
+ CreateViewStatement.concrete(
1131
+ full_source: @lexer.sql,
1132
+ create_kw: Token::Keyword(create_kw),
1133
+ temp_kw: Token::Keyword(temp_kw),
1134
+ view_kw: Token::Keyword(view_kw),
1135
+ if_not_exists_kw: Token::Keyword(if_not_exists_kw),
1136
+ view: view,
1137
+ columns_lp: Token::Punctuation(columns_lp),
1138
+ columns: columns,
1139
+ columns_rp: Token::Punctuation(columns_rp),
1140
+ as_kw: Token::Keyword(as_kw),
1141
+ select: select,
1142
+ )
1143
+ end
1144
+
1145
+ def insert_stmt(with = nil)
1146
+ #
1147
+ # Relevant `parse.y` grammar rules:
1148
+ # cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert
1149
+ # cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES returning
1150
+ # insert_cmd ::= INSERT orconf | REPLACE
1151
+ # orconf ::= . | OR resolvetype
1152
+ # resolvetype ::= raisetype | IGNORE | REPLACE
1153
+ # idlist_opt ::= . | LP idlist RP
1154
+ # upsert ::= . | RETURNING selcollist |
1155
+ # ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt upsert |
1156
+ # ON CONFLICT LP sortlist RP where_opt DO NOTHING upsert |
1157
+ # ON CONFLICT DO NOTHING returning |
1158
+ # ON CONFLICT DO UPDATE SET setlist where_opt returning
1159
+ #
1160
+ # Syntax diagram:
1161
+ # ◯─▶┬▶[ with-clause ]─┐
1162
+ # ├───────◀─────────┘
1163
+ # ├─▶{ REPLACE }─────────────────────────┐
1164
+ # └─▶{ INSERT }┬────────────────────────┬┴▶{ INTO }─▶[ qualified-table-name ]┐
1165
+ # └▶{ OR }─▶{ resolution }─┘ │
1166
+ # ┌────────────────────────────────◀────────────────────────────────────────────┘
1167
+ # ├─▶{ ( }┬▶{ column-name }┬▶{ ) }┐
1168
+ # │ └──────{ , }◀────┘ │
1169
+ # ├───────────────◀───────────────┘
1170
+ # ├─▶[ select-stmt ]─┬───────────────────┬───────────┬─▶[ returning-clause ]─▶◯
1171
+ # │ └▶[ upsert-clause ]─┘ │
1172
+ # └─▶{ DEFAULT }─▶{ VALUES }─────────────────────────┘
1173
+ #
1174
+ # Simplified grammar:
1175
+ # [with_clause] (INSERT [OR resolution] | REPLACE) INTO table [AS alias] [(column [, column]*)]
1176
+ # (select_stmt [upsert_clause]* | DEFAULT VALUES) [returning_clause]
1177
+ #
1178
+ # Relevant SQLite documentation:
1179
+ # - https://www.sqlite.org/lang_insert.html
1180
+ # - https://www.sqlite.org/lang_upsert.html
1181
+ #
1182
+ # Adds rows to a table, from a VALUES list, a select, or the column defaults.
1183
+ #
1184
+ insert_kw = require_one_of(
1185
+ [:INSERT, :OR, :ROLLBACK].freeze, [:INSERT, :OR, :ABORT].freeze, [:INSERT, :OR, :FAIL].freeze,
1186
+ [:INSERT, :OR, :IGNORE].freeze, [:INSERT, :OR, :REPLACE].freeze, :INSERT, :REPLACE,
1187
+ )
1188
+ into_kw = require :INTO
1189
+ table = qualified_table_name(indexed: false)
1190
+ if (columns_lp = maybe :LP)
1191
+ columns = require_some(trailing_sep: :COMMA) { column_reference }
1192
+ columns_rp = require :RP
1193
+ end
1194
+
1195
+ unless (default_values_kw = maybe_all_of :DEFAULT, :VALUES)
1196
+ select = select_stmt
1197
+ upserts = maybe_some { upsert_clause }
1198
+ end
1199
+ returning = returning_clause
1200
+
1201
+ InsertStatement.concrete(
1202
+ full_source: @lexer.sql,
1203
+ with_clause: with,
1204
+ insert_kw: Token::Keyword(insert_kw),
1205
+ into_kw: Token::Keyword(into_kw),
1206
+ table: table,
1207
+ columns_lp: Token::Punctuation(columns_lp),
1208
+ columns: columns,
1209
+ columns_rp: Token::Punctuation(columns_rp),
1210
+ default_values_kw: Token::Keyword(default_values_kw),
1211
+ select: select,
1212
+ upserts: upserts,
1213
+ returning: returning,
1214
+ )
1215
+ end
1216
+
1217
+ def update_stmt(with = nil)
1218
+ #
1219
+ # Relevant `parse.y` grammar rules:
1220
+ # cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist from where_opt_ret orderby_opt limit_opt
1221
+ # where_opt_ret ::= . | WHERE expr | RETURNING selcollist | WHERE expr RETURNING selcollist
1222
+ #
1223
+ # Syntax diagram:
1224
+ # ◯─▶┬▶[ with-clause ]─┐
1225
+ # ├───────◀─────────┘
1226
+ # └─▶{ UPDATE }┬────────────────────────┬▶[ qualified-table-name ]─▶{ SET }─┐
1227
+ # └▶{ OR }─▶{ resolution }─┘ │
1228
+ # ┌─────────────────────────────────◀──────────────────────────────────────────┘
1229
+ # └┬▶{ column-name }──────────────────┬▶{ = }─▶[ expr ]┬┬───────────────────────────┬┐
1230
+ # ├▶{ ( }┬▶{ column-name }┬▶{ ) }────┘ │└▶{ FROM }─▶[ join-clause ]─┘│
1231
+ # │ └──────{ , }◀────┘ │ │
1232
+ # └───────────────────────{ , }◀──────────────────────┘ │
1233
+ # ┌──────────────────────────────────────◀───────────────────────────────────────────┘
1234
+ # └┬▶{ WHERE }─▶[ expr ]┬┬─▶[ returning-clause ]┬─▶[ ORDER BY … ]─▶[ LIMIT … ]─▶◯
1235
+ # └──────────▶─────────┘└──────────▶───────────┘
1236
+ #
1237
+ # Simplified grammar:
1238
+ # [with_clause] UPDATE [OR resolution] table [AS alias] [INDEXED BY index | NOT INDEXED]
1239
+ # SET assignment [, assignment]* [FROM join_clause] [WHERE expr] [returning_clause]
1240
+ # [ORDER BY ordering_term [, ordering_term]*] [LIMIT expr [(OFFSET | COMMA) expr]]
1241
+ #
1242
+ # Relevant SQLite documentation:
1243
+ # - https://www.sqlite.org/lang_update.html
1244
+ #
1245
+ # Changes columns of existing rows.
1246
+ #
1247
+ update_kw = require_one_of(
1248
+ [:UPDATE, :OR, :ROLLBACK].freeze, [:UPDATE, :OR, :ABORT].freeze, [:UPDATE, :OR, :FAIL].freeze,
1249
+ [:UPDATE, :OR, :IGNORE].freeze, [:UPDATE, :OR, :REPLACE].freeze, :UPDATE,
1250
+ )
1251
+ table = qualified_table_name(indexed: true)
1252
+ set_kw = require :SET
1253
+ assignments = set_list
1254
+ if (from_kw = maybe :FROM)
1255
+ source = join_clause
1256
+ end
1257
+ if (where_kw = maybe :WHERE)
1258
+ condition = expression
1259
+ end
1260
+ returning = returning_clause
1261
+ tail = order_and_limit
1262
+
1263
+ UpdateStatement.concrete(
1264
+ full_source: @lexer.sql,
1265
+ with_clause: with,
1266
+ update_kw: Token::Keyword(update_kw),
1267
+ table: table,
1268
+ set_kw: Token::Keyword(set_kw),
1269
+ assignments: assignments,
1270
+ from_kw: Token::Keyword(from_kw),
1271
+ source: source,
1272
+ where_kw: Token::Keyword(where_kw),
1273
+ condition: condition,
1274
+ returning: returning,
1275
+ **tail,
1276
+ )
1277
+ end
1278
+
1279
+ def delete_stmt(with = nil)
1280
+ #
1281
+ # Relevant `parse.y` grammar rules:
1282
+ # cmd ::= with DELETE FROM xfullname indexed_opt where_opt_ret orderby_opt limit_opt
1283
+ #
1284
+ # Syntax diagram:
1285
+ # ◯─▶┬▶[ with-clause ]─┐
1286
+ # ├───────◀─────────┘
1287
+ # └─▶{ DELETE }─▶{ FROM }─▶[ qualified-table-name ]┬▶{ WHERE }─▶[ expr ]┬┐
1288
+ # └──────────▶─────────┘│
1289
+ # ┌──────────────────────────────────────◀──────────────────────────────────┘
1290
+ # └┬─▶[ returning-clause ]┬─▶[ ORDER BY … ]─▶[ LIMIT … ]─▶◯
1291
+ # └──────────▶───────────┘
1292
+ #
1293
+ # Simplified grammar:
1294
+ # [with_clause] DELETE FROM table [AS alias] [INDEXED BY index | NOT INDEXED]
1295
+ # [WHERE expr] [returning_clause] [ORDER BY …] [LIMIT …]
1296
+ #
1297
+ # Relevant SQLite documentation:
1298
+ # - https://www.sqlite.org/lang_delete.html
1299
+ #
1300
+ # Removes rows from a table.
1301
+ #
1302
+ delete_kw = require :DELETE
1303
+ from_kw = require :FROM
1304
+ table = qualified_table_name(indexed: true)
1305
+ if (where_kw = maybe :WHERE)
1306
+ condition = expression
1307
+ end
1308
+ returning = returning_clause
1309
+ tail = order_and_limit
1310
+
1311
+ DeleteStatement.concrete(
1312
+ full_source: @lexer.sql,
1313
+ with_clause: with,
1314
+ delete_kw: Token::Keyword(delete_kw),
1315
+ from_kw: Token::Keyword(from_kw),
1316
+ table: table,
1317
+ where_kw: Token::Keyword(where_kw),
1318
+ condition: condition,
1319
+ returning: returning,
1320
+ **tail,
1321
+ )
1322
+ end
1323
+
1324
+ # A WITH clause can lead a SELECT, INSERT, UPDATE or DELETE - which one is only
1325
+ # known once the clause is behind us
1326
+ def with_stmt
1327
+ with = with_clause
1328
+
1329
+ case current_token
1330
+ when :SELECT, :VALUES then select_stmt(with)
1331
+ when :INSERT, :REPLACE then insert_stmt(with)
1332
+ when :UPDATE then update_stmt(with)
1333
+ when :DELETE then delete_stmt(with)
1334
+ else
1335
+ expected!(:SELECT, :VALUES, :INSERT, :REPLACE, :UPDATE, :DELETE)
1336
+ end
1337
+ end
1338
+
1339
+ def select_stmt(with = nil)
1340
+ #
1341
+ # Relevant `parse.y` grammar rules:
1342
+ # select ::= WITH wqlist selectnowith |
1343
+ # WITH RECURSIVE wqlist selectnowith |
1344
+ # selectnowith
1345
+ # selectnowith ::= oneselect |
1346
+ # selectnowith multiselect_op oneselect
1347
+ # multiselect_op ::= UNION | UNION ALL | EXCEPT | INTERSECT
1348
+ # orderby_opt ::= . | ORDER BY sortlist
1349
+ # limit_opt ::= . | LIMIT expr | LIMIT expr OFFSET expr | LIMIT expr COMMA expr
1350
+ #
1351
+ # Syntax diagram:
1352
+ # ◯─▶┬▶[ with-stmt ]─┐
1353
+ # ├───────◀───────┘
1354
+ # ┌──┴──────────────▶[ select-core ]───────────────────┐
1355
+ # └──────────◀────[ compound-operator ]◀───────────────┤
1356
+ # ┌──────────────────◀────────────────────────────────┘
1357
+ # ├─▶{ ORDER }─▶{ BY }┬▶[ ordering-term ]─┬┐
1358
+ # ▼ └───────{ , }◀──────┘│
1359
+ # ├────────────────◀───────────────────────┘
1360
+ # ├─▶{ LIMIT }─▶[ expr ]─▶┬───────────────────────▶─┐
1361
+ # │ ├─▶{ OFFSET }─▶[ expr ]─▶─┤
1362
+ # │ └─▶{ , }─▶[ expr ]──────▶─┤
1363
+ # └───────────────────────────────────────────────▶─┴───────▶◯
1364
+ #
1365
+ # Simplified grammar:
1366
+ # [with_clause] select_core [compound_operator select_core]*
1367
+ # [ORDER BY ordering_term [, ordering_term]*]
1368
+ # [LIMIT expr [(OFFSET | COMMA) expr]]
1369
+ #
1370
+ # Relevant SQLite documentation:
1371
+ # - https://www.sqlite.org/lang_select.html
1372
+ #
1373
+ # The SELECT statement is used to query the database.
1374
+ #
1375
+ with ||= with_clause if current_token == :WITH
1376
+ body = select_core
1377
+
1378
+ while (operator_tk = maybe_one_of [:UNION, :ALL].freeze, :UNION, :INTERSECT, :EXCEPT)
1379
+ body = CompoundSelect.concrete(
1380
+ full_source: @lexer.sql,
1381
+ operator_tk: Token::Keyword(operator_tk),
1382
+ left: body,
1383
+ right: select_core,
1384
+ )
1385
+ end
1386
+
1387
+ SelectStatement.concrete(
1388
+ full_source: @lexer.sql,
1389
+ with_clause: with,
1390
+ body: body,
1391
+ **order_and_limit,
1392
+ )
1393
+ end
1394
+
1395
+ def with_clause
1396
+ #
1397
+ # Relevant `parse.y` grammar rules:
1398
+ # with ::= WITH wqlist | WITH RECURSIVE wqlist
1399
+ # wqlist ::= wqitem | wqlist COMMA wqitem
1400
+ # wqitem ::= withnm eidlist_opt wqas LP select RP
1401
+ # wqas ::= AS | AS MATERIALIZED | AS NOT MATERIALIZED
1402
+ # eidlist_opt ::= . | LP eidlist RP
1403
+ #
1404
+ # Syntax diagram:
1405
+ # ◯─▶{ WITH }─┬──────────────────┬▶[ common-table-expression ]┬─▶◯
1406
+ # └─▶{ RECURSIVE }─▶─┘└───────────{ , }◀──────────┘
1407
+ #
1408
+ # ◯─▶{ table-name }┬────────────────────────────────────┬▶{ AS }┐
1409
+ # └▶{ ( }┬▶{ column-name }┬▶{ ) }──────┘ │
1410
+ # └──────{ , }◀────┘ │
1411
+ # ┌──────────────────────────────◀──────────────────────────────┘
1412
+ # ├────────────────▶────────────────┬▶{ ( }─▶[ select-stmt ]─▶{ ) }─▶◯
1413
+ # ├─▶{ NOT }─┬─▶{ MATERIALIZED }──▶─┘
1414
+ # └────▶─────┘
1415
+ #
1416
+ # Simplified grammar:
1417
+ # WITH [RECURSIVE] name [(column [, column]*)] AS [[NOT] MATERIALIZED] (select_stmt)
1418
+ # [, name [(column [, column]*)] AS [[NOT] MATERIALIZED] (select_stmt)]*
1419
+ #
1420
+ # Relevant SQLite documentation:
1421
+ # - https://www.sqlite.org/lang_with.html
1422
+ #
1423
+ # Common table expressions act like temporary views that exist only for a single statement.
1424
+ #
1425
+ with_kw = require :WITH
1426
+ recursive_kw = maybe :RECURSIVE
1427
+
1428
+ ctes = require_some(trailing_sep: :COMMA) do
1429
+ name_tk = name
1430
+ if (columns_lp = maybe :LP)
1431
+ columns = require_some(trailing_sep: :COMMA) { indexed_column }
1432
+ columns_rp = require :RP
1433
+ end
1434
+ as_kw = require :AS
1435
+ materialized_tk = maybe_one_of :MATERIALIZED, [:NOT, :MATERIALIZED].freeze
1436
+ select_lp = require :LP
1437
+ select = select_stmt
1438
+ select_rp = require :RP
1439
+
1440
+ CommonTableExpression.concrete(
1441
+ full_source: @lexer.sql,
1442
+ name_tk: Token::Identifier(name_tk),
1443
+ columns_lp: Token::Punctuation(columns_lp),
1444
+ columns: columns,
1445
+ columns_rp: Token::Punctuation(columns_rp),
1446
+ as_kw: Token::Keyword(as_kw),
1447
+ materialized_tk: Token::Keyword(materialized_tk),
1448
+ select_lp: Token::Punctuation(select_lp),
1449
+ select: select,
1450
+ select_rp: Token::Punctuation(select_rp),
1451
+ )
1452
+ end
1453
+
1454
+ WithClause.concrete(
1455
+ full_source: @lexer.sql,
1456
+ with_kw: Token::Keyword(with_kw),
1457
+ recursive_kw: Token::Keyword(recursive_kw),
1458
+ ctes: ctes,
1459
+ )
1460
+ end
1461
+
1462
+ # ---------- Clauses ----------
1463
+
1464
+ def expression(min_precedence = 0)
1465
+ #
1466
+ # Relevant `parse.y` grammar rules:
1467
+ #
1468
+ # Syntax diagram:
1469
+ # ◯┬─▶[ literal-value ]───────────────────────────────────────────────────────────────────────────────────┬─▶◯
1470
+ # ├─▶{ bind-parameter }────────────────────────────────────────────────────────────────────────────────▶─┤
1471
+ # ├────────────────────────┬──────────────────────┐ │
1472
+ # ├─▶{ schema-name }─▶{ . }┴▶{ table-name }─▶{ . }┴▶{ column-name }────────────────────────────────────▶─┤
1473
+ # ├─▶{ unary-operator }─▶[ expr ]──────────────────────────────────────────────────────────────────────▶─┤
1474
+ # ├─▶[ expr ]─▶{ binary-operator }─▶[ expr ]───────────────────────────────────────────────────────────▶─┤
1475
+ # ├─▶{ function-name }─▶{ ( }─▶[ function-arguments ]─▶{ ) }┬───────────────────┬┬─────────────────────▶─┤
1476
+ # │ ┌──{ , }◀─┐ └▶[ filter-clause ]─┘└▶[ over-clause ]─────▶─┤
1477
+ # ├─▶{ ( }┴▶[ expr ]┴▶{ ) }────────────────────────────────────────────────────────────────────────────▶─┤
1478
+ # ├─▶{ CAST }─▶{ ( }─▶[ expr ]─▶{ AS }─▶[ type-name ]─▶{ ) }───────────────────────────────────────────▶─┤
1479
+ # ├─▶[ expr ]─▶{ COLLATE }─▶{ collation-name }─────────────────────────────────────────────────────────▶─┤
1480
+ # ├─▶[ expr ]┬▶{ NOT }┬┬▶{ LIKE }─────▶[ expr ]─┬────▶────────────────┬▶┬──────────────────────────────▶─┤
1481
+ # │ └────▶───┘├▶{ GLOB }───┐ └▶{ ESCAPE }─▶[ expr ]┘ │ │
1482
+ # │ ├▶{ REGEXP }─┼─▶[ expr ]────────────▶────────────┘ │
1483
+ # │ └▶{ MATCH }──┘ │
1484
+ # ├─▶[ expr ]┬▶{ ISNULL }───────┬──────────────────────────────────────────────────────────────────────▶─┤
1485
+ # │ ├▶{ NOTNULL }──────┤ │
1486
+ # │ └▶{ NOT }─▶{ NULL }┘ │
1487
+ # ├─▶[ expr ]─▶{ IS }┬▶{ NOT }┬┬▶{ DISTINCT }─▶{ FROM }┬▶[ expr ]──────────────────────────────────────▶─┤
1488
+ # │ └────▶───┘└────────────▶──────────┘ │
1489
+ # ├─▶[ expr ]┬▶{ NOT }┬▶{ BETWEEN }─▶[ expr ]─▶{ AND }─▶[ expr ]───────────────────────────────────────▶─┤
1490
+ # │ └────▶───┘ ┌─────────────────┐ │
1491
+ # ├─▶[ expr ]┬▶{ NOT }┬▶{ IN }┬▶{ ( }─┼▶[ select-stmt ]─┼▶{ ) }────────────────────────────────────────▶─┤
1492
+ # │ └────▶───┘ │ └─┬──▶[ expr ]──┬─┘ │
1493
+ # │ │ └────{ , }◀───┘ ┌▶{ table-name }───────────────────────────────▶─┤
1494
+ # │ ├────────────────────────┐│ ┌──{ , }──┐ │
1495
+ # │ └─▶{ schema-name }─▶{ . }┴┴▶{ table-function }─▶{ ( }┬┴▶[ expr ]┴┬▶{ ) }─▶─┤
1496
+ # ├────▶────┬───────────┐ └─────▶─────┘ │
1497
+ # ├─▶{ NOT }┴▶{ EXISTS }┴▶{ ( }─▶[ select-stmt ]─▶{ ) }────────────────────────────────────────────────▶─┤
1498
+ # ├─▶{ CASE }┬▶[ expr ]┬┬▶{ WHEN }─▶[ expr ]─▶{ THEN }─▶[ expr ]┬┬▶{ ELSE }─▶[ expr ]┬▶{ END }─────────▶─┤
1499
+ # │ └─────▶───┘└────────────────◀──────────────────────┘└─────────▶─────────┘ │
1500
+ # └─▶[ raise-function ]────────────────────────────────────────────────────────────────────────────────▶─┘
1501
+ #
1502
+ # Simplified grammar:
1503
+ #
1504
+ # Relevant SQLite documentation:
1505
+ # -
1506
+ #
1507
+ # [description]
1508
+ #
1509
+
1510
+ left = basic_expression
1511
+
1512
+ while (operator = current_token)
1513
+ # ~ has a precedence for its operand's sake, but is no binary operator
1514
+ operator_precedence = ((operator != :BITNOT) && OPERATOR_PRECEDENCE[operator]) || -1
1515
+ break if operator_precedence < min_precedence
1516
+ next_precedence = operator_precedence + 1
1517
+
1518
+ negated = maybe :NOT
1519
+
1520
+ if (in_tk = maybe :IN)
1521
+ if (value_lp = maybe :LP)
1522
+ if (value_rp = maybe :RP)
1523
+ right = ParentheticalExpression.concrete(
1524
+ full_source: @lexer.sql,
1525
+ value_lp: Token::Punctuation(value_lp),
1526
+ value: nil,
1527
+ value_rp: Token::Punctuation(value_rp),
1528
+ )
1529
+ elsif subquery_start?
1530
+ select = select_stmt
1531
+ value_rp = require :RP
1532
+
1533
+ right = SubqueryExpression.concrete(
1534
+ full_source: @lexer.sql,
1535
+ select_lp: Token::Punctuation(value_lp),
1536
+ select: select,
1537
+ select_rp: Token::Punctuation(value_rp),
1538
+ )
1539
+ elsif (e = maybe { expression })
1540
+ exprs = require_some(given: e, trailing_sep: :COMMA) do
1541
+ expression
1542
+ end
1543
+ value_rp = require :RP
1544
+
1545
+ right = ParentheticalExpression.concrete(
1546
+ full_source: @lexer.sql,
1547
+ value_lp: Token::Punctuation(value_lp),
1548
+ value: exprs,
1549
+ value_rp: Token::Punctuation(value_rp),
1550
+ )
1551
+ else
1552
+ expected!(:RP, "select-stmt", "expr")
1553
+ end
1554
+ elsif (first_tk = maybe { name })
1555
+ # nm dbnm paren_exprlist: a table, or a table-valued function's call
1556
+ dot_tk = maybe :DOT
1557
+ schema_tk, object_tk = dot_tk ? [first_tk, name] : [nil, first_tk]
1558
+
1559
+ if (arguments_lp = maybe :LP)
1560
+ arguments = maybe_some(trailing_sep: :COMMA) { expression }
1561
+ arguments_rp = require :RP
1562
+
1563
+ right = FunctionReference.concrete(
1564
+ full_source: @lexer.sql,
1565
+ schema_tk: Token::Identifier(schema_tk),
1566
+ dot_tk: Token::Punctuation(dot_tk),
1567
+ function_tk: Token::Identifier(object_tk),
1568
+ arguments_lp: Token::Punctuation(arguments_lp),
1569
+ arguments: FunctionArguments.concrete(
1570
+ full_source: @lexer.sql,
1571
+ expressions: arguments
1572
+ ),
1573
+ arguments_rp: Token::Punctuation(arguments_rp),
1574
+ )
1575
+ else
1576
+ right = TableName.concrete(
1577
+ full_source: @lexer.sql,
1578
+ schema_tk: Token::Identifier(schema_tk),
1579
+ dot_tk: Token::Punctuation(dot_tk),
1580
+ table_tk: Token::Identifier(object_tk),
1581
+ )
1582
+ end
1583
+ else
1584
+ expected!(:LP, :STRING, "idj")
1585
+ end
1586
+
1587
+ left = BinaryExpression.concrete(
1588
+ full_source: @lexer.sql,
1589
+ operator_tk: Token::Punctuation(token_span_from(negated, in_tk)),
1590
+ left: left,
1591
+ right: right,
1592
+ )
1593
+ elsif (between_kw = maybe :BETWEEN)
1594
+ middle = expression(next_precedence)
1595
+ and_kw = require :AND
1596
+ right = expression(next_precedence)
1597
+ left = TernaryExpression.concrete(
1598
+ full_source: @lexer.sql,
1599
+ operator_tk: Token::Keyword(token_span_from(negated, between_kw)),
1600
+ left: left,
1601
+ middle: middle,
1602
+ and_kw: Token::Keyword(and_kw),
1603
+ right: right,
1604
+ )
1605
+ elsif (like_kw = maybe :LIKE)
1606
+ expression_node = negated ? NotLikeExpression : LikeExpression
1607
+ right = expression(next_precedence)
1608
+ if (escape_kw = maybe :ESCAPE)
1609
+ escape = expression(next_precedence)
1610
+ end
1611
+ left = expression_node.concrete(
1612
+ full_source: @lexer.sql,
1613
+ operator_tk: Token::Keyword(token_span_from(negated, like_kw)),
1614
+ left: left,
1615
+ right: right,
1616
+ escape_kw: Token::Keyword(escape_kw),
1617
+ escape: escape,
1618
+ )
1619
+ elsif (match_tk = maybe_one_of :GLOB, :REGEXP, :MATCH)
1620
+ operator_tk = token_span_from(negated, match_tk)
1621
+ right = expression(next_precedence)
1622
+
1623
+ left_attr = (Node === left) ? { left: left } : { left_tk: left }
1624
+ right_attr = (Node === right) ? { right: right } : { right_tk: right }
1625
+
1626
+ left = BinaryExpression.concrete(
1627
+ full_source: @lexer.sql,
1628
+ operator_tk: Token::Punctuation(operator_tk),
1629
+ **left_attr,
1630
+ **right_attr,
1631
+ )
1632
+ end
1633
+ next if in_tk || between_kw || like_kw || match_tk
1634
+
1635
+ if negated && (null_kw = maybe :NULL)
1636
+ left = UnaryExpression.concrete(
1637
+ full_source: @lexer.sql,
1638
+ operator_tk: Token::Punctuation(token_span_from(negated, null_kw)),
1639
+ operand: left,
1640
+ )
1641
+ next
1642
+ end
1643
+
1644
+ expected!(:IN, :BETWEEN, :LIKE, :GLOB, :REGEXP, :MATCH, :NULL) if negated
1645
+
1646
+ if (operator_tk = maybe :ISNULL)
1647
+ operand_attr = (Node === left) ? { operand: left } : { operand_tk: left }
1648
+
1649
+ left = UnaryExpression.concrete(
1650
+ full_source: @lexer.sql,
1651
+ operator_tk: Token::Punctuation(operator_tk),
1652
+ **operand_attr,
1653
+ )
1654
+ elsif (operator_tk = maybe :NOTNULL)
1655
+ operand_attr = (Node === left) ? { operand: left } : { operand_tk: left }
1656
+
1657
+ left = UnaryExpression.concrete(
1658
+ full_source: @lexer.sql,
1659
+ operator_tk: Token::Punctuation(operator_tk),
1660
+ **operand_attr,
1661
+ )
1662
+ elsif (is_tk = maybe :IS)
1663
+ negated_tk = maybe :NOT
1664
+ distinct_from_tk = maybe_all_of :DISTINCT, :FROM
1665
+
1666
+ operator_tk = token_span_from(is_tk, negated_tk, distinct_from_tk)
1667
+ right = expression(next_precedence)
1668
+
1669
+ left_attr = (Node === left) ? { left: left } : { left_tk: left }
1670
+ right_attr = (Node === right) ? { right: right } : { right_tk: right }
1671
+
1672
+ left = BinaryExpression.concrete(
1673
+ full_source: @lexer.sql,
1674
+ operator_tk: Token::Punctuation(operator_tk),
1675
+ **left_attr,
1676
+ **right_attr,
1677
+ )
1678
+ elsif (collate_kw = maybe :COLLATE)
1679
+ expression_attr = (Node === left) ? { expression: left } : { expression_tk: left }
1680
+ sequence = identifier_or_string
1681
+
1682
+ left = CollationExpression.concrete(
1683
+ full_source: @lexer.sql,
1684
+ collate_kw: Token::Keyword(collate_kw),
1685
+ **expression_attr,
1686
+ sequence_tk: Token::Identifier(sequence),
1687
+ )
1688
+ else
1689
+ operator_tk = require operator
1690
+ right = expression(next_precedence)
1691
+
1692
+ left_attr = (Node === left) ? { left: left } : { left_tk: left }
1693
+ right_attr = (Node === right) ? { right: right } : { right_tk: right }
1694
+
1695
+ left = BinaryExpression.concrete(
1696
+ full_source: @lexer.sql,
1697
+ operator_tk: Token::Punctuation(operator_tk),
1698
+ **left_attr,
1699
+ **right_attr,
1700
+ )
1701
+ end
1702
+ end
1703
+
1704
+ left
1705
+ end
1706
+
1707
+ def column_def
1708
+ #
1709
+ # Used by:
1710
+ # - alter_table_stmt
1711
+ # - create_table_stmt
1712
+ #
1713
+ # Relevant `parse.y` grammar rules:
1714
+ # columnlist ::= columnname carglist
1715
+ # columnname ::= nm typetoken
1716
+ # carglist ::= . | carglist ccons
1717
+ #
1718
+ # Syntax diagram:
1719
+ # ◯─▶{ column-name }─┬─▶[ type-name ]─┬▶┬─▶───────────────────▶──┬─▶◯
1720
+ # └────────▶───────┘ └─[ column-constraint ]◀─┘
1721
+ #
1722
+ # Simplified grammar:
1723
+ # name [type] [constraint [constraint]*]
1724
+ #
1725
+ # Relevant SQLite documentation:
1726
+ # - https://www.sqlite.org/lang_createtable.html#column_definitions
1727
+ # - https://www.sqlite.org/syntax/column-def.html
1728
+ #
1729
+ # Each column definition consists of the name of the column,
1730
+ # optionally followed by the declared type of the column,
1731
+ # then one or more optional column constraints.
1732
+ #
1733
+ column_name = name
1734
+ column_type = maybe { type_name }
1735
+ constraints = maybe_some(trailing_sep: nil) do
1736
+ #
1737
+ # Relevant `parse.y` grammar rules:
1738
+ # ccons ::= CONSTRAINT nm |
1739
+ # DEFAULT term |
1740
+ # DEFAULT LP expr RP |
1741
+ # DEFAULT PLUS term |
1742
+ # DEFAULT MINUS term |
1743
+ # DEFAULT id |
1744
+ # NULL onconf |
1745
+ # NOT NULL onconf |
1746
+ # PRIMARY KEY sortorder onconf autoinc |
1747
+ # UNIQUE onconf |
1748
+ # CHECK LP expr RP |
1749
+ # REFERENCES nm eidlist_opt refargs |
1750
+ # defer_subclause |
1751
+ # COLLATE ids |
1752
+ # GENERATED ALWAYS AS generated |
1753
+ # AS generated
1754
+ # term ::= NULL | FLOAT | BLOB | STRING | INTEGER | CTIME_KW | QNUMBER
1755
+ # id ::= ID | INDEXED
1756
+ # ids ::= ID | STRING
1757
+ # nm ::= idj | STRING
1758
+ # eidlist_opt ::= . | LP eidlist RP
1759
+ # eidlist ::= eidlist COMMA nm collate sortorder |
1760
+ # nm collate sortorder
1761
+ # onconf ::= . | ON CONFLICT resolvetype
1762
+ # sortorder ::= . | ASC | DESC
1763
+ # refargs ::= . | refargs refarg
1764
+ # refarg ::= MATCH nm |
1765
+ # ON INSERT refact |
1766
+ # ON DELETE refact |
1767
+ # ON UPDATE refact
1768
+ # generated ::= LP expr RP |
1769
+ # LP expr RP ID
1770
+ # autoinc ::= . | AUTOINCR.
1771
+ #
1772
+ # Syntax diagram:
1773
+ # ◯─▶┬▶{ CONSTRAINT }─▶{ name }─┬───────────────────────────────▶───────────────────────┐
1774
+ # ├─────────────◀────────────┘ │
1775
+ # ├─▶{ PRIMARY }─▶{ KEY }──┬─────▶──────┬─▶[ conflict-clause ]┬──────────▶───────────┼─▶◯
1776
+ # │ ├─▶{ ASC }──▶┤ └─▶{ AUTOINCREMENT }─▶─┤
1777
+ # ├─────▶───┐ └─▶{ DESC }─▶┘ │
1778
+ # ├─▶{ NOT }┴▶{ NULL }─▶[ conflict-clause ]────────────────────────────────────────▶─┤
1779
+ # ├─▶{ UNIQUE }─▶[ conflict-clause ]───────────────────────────────────────────────▶─┤
1780
+ # ├─▶{ CHECK }─▶{ ( }─▶[ expr ]─▶{ ) }─────────────────────────────────────────────▶─┤
1781
+ # ├─▶{ DEFAULT }─▶┬▶{ ( }─▶[ expr ]─▶{ ) }──┬──────────────────────────────────────▶─┤
1782
+ # │ ├─▶[ literal-value ]────▶─┤ │
1783
+ # │ └─▶[ signed-number ]────▶─┘ │
1784
+ # ├─▶{ COLLATE }─▶{ collation-name }───────────────────────────────────────────────▶─┤
1785
+ # ├─▶[ foreign-key-clause ]────────────────────────────────────────────────────────▶─┤
1786
+ # ├─▶{ GENERATED }─▶{ ALWAYS }┬▶{ AS }─▶{ ( }─▶[ expr ]─▶{ ) }┬────────────────────▶─┤
1787
+ # └──────────────▶────────────┘ ├─▶{ STORED }────────▶─┤
1788
+ # └─▶{ VIRTUAL }───────▶─┘
1789
+ #
1790
+ # Simplified grammar:
1791
+ # [CONSTRAINT name] (
1792
+ # [PRIMARY KEY [ASC|DESC] [conflict_clause] [AUTOINCREMENT]]
1793
+ # [NOT NULL [conflict_clause]]
1794
+ # [NULL [conflict_clause]]
1795
+ # [UNIQUE [conflict_clause]]
1796
+ # [CHECK LP expression RP [conflict_clause]]
1797
+ # [COLLATE collation-name]
1798
+ # [foreign-key-clause]
1799
+ # [GENERATED ALWAYS AS (expr) STORED | VIRTUAL]
1800
+ # )
1801
+ #
1802
+ # Relevant SQLite documentation:
1803
+ # - https://www.sqlite.org/lang_createtable.html#column_definitions
1804
+ # - https://www.sqlite.org/syntax/column-constraint.html
1805
+ #
1806
+ # Included in the definition of "column constraints" for the purposes of the previous statement
1807
+ # are the COLLATE and DEFAULT clauses, even though these are not really constraints in the sense
1808
+ # that they do not restrict the data that the table may contain. The other constraints -
1809
+ # NOT NULL, CHECK, UNIQUE, PRIMARY KEY and FOREIGN KEY constraints - impose restrictions on the table data.
1810
+ #
1811
+ if (constraint_kw = maybe :CONSTRAINT)
1812
+ constraint_name = name
1813
+ end
1814
+
1815
+ if (primary_key_kw = maybe_all_of :PRIMARY, :KEY)
1816
+ direction_tk = maybe_one_of :ASC, :DESC
1817
+ on_conflict = maybe { conflict_clause }
1818
+ autoincrement_kw = maybe :AUTOINCREMENT
1819
+
1820
+ PrimaryKeyColumnConstraint.concrete(
1821
+ full_source: @lexer.sql,
1822
+ constraint_kw: Token::Keyword(constraint_kw),
1823
+ name_tk: Token::Identifier(constraint_name),
1824
+ primary_key_kw: Token::Keyword(primary_key_kw),
1825
+ direction_tk: Token::Keyword(direction_tk),
1826
+ conflict_clause: on_conflict,
1827
+ autoincrement_kw: Token::Keyword(autoincrement_kw),
1828
+ )
1829
+ elsif (not_null_kw = maybe_all_of :NOT, :NULL)
1830
+ on_conflict = maybe { conflict_clause }
1831
+
1832
+ NotNullColumnConstraint.concrete(
1833
+ full_source: @lexer.sql,
1834
+ constraint_kw: Token::Keyword(constraint_kw),
1835
+ name_tk: Token::Identifier(constraint_name),
1836
+ not_null_kw: Token::Keyword(not_null_kw),
1837
+ conflict_clause: on_conflict,
1838
+ )
1839
+ elsif current_token == :DEFERRABLE || (current_token == :NOT && peek == :DEFERRABLE)
1840
+ DeferrableColumnConstraint.concrete(
1841
+ full_source: @lexer.sql,
1842
+ constraint_kw: Token::Keyword(constraint_kw),
1843
+ name_tk: Token::Identifier(constraint_name),
1844
+ deferrable_span: Token::Keyword(defer_subclause),
1845
+ )
1846
+ elsif (null_kw = maybe :NULL)
1847
+ on_conflict = maybe { conflict_clause }
1848
+
1849
+ NullColumnConstraint.concrete(
1850
+ full_source: @lexer.sql,
1851
+ constraint_kw: Token::Keyword(constraint_kw),
1852
+ name_tk: Token::Identifier(constraint_name),
1853
+ null_kw: Token::Keyword(null_kw),
1854
+ conflict_clause: on_conflict,
1855
+ )
1856
+ elsif (unique_kw = maybe :UNIQUE)
1857
+ on_conflict = maybe { conflict_clause }
1858
+
1859
+ UniqueColumnConstraint.concrete(
1860
+ full_source: @lexer.sql,
1861
+ constraint_kw: Token::Keyword(constraint_kw),
1862
+ name_tk: Token::Identifier(constraint_name),
1863
+ unique_kw: Token::Keyword(unique_kw),
1864
+ conflict_clause: on_conflict,
1865
+ )
1866
+ elsif (check_kw = maybe :CHECK)
1867
+ check_lp = require :LP
1868
+ check = expression
1869
+ check_rp = require :RP
1870
+
1871
+ CheckColumnConstraint.concrete(
1872
+ full_source: @lexer.sql,
1873
+ constraint_kw: Token::Keyword(constraint_kw),
1874
+ name_tk: Token::Identifier(constraint_name),
1875
+ check_kw: Token::Keyword(check_kw),
1876
+ check_lp: Token::Punctuation(check_lp),
1877
+ expression: check,
1878
+ check_rp: Token::Punctuation(check_rp),
1879
+ )
1880
+ elsif (default_kw = maybe :DEFAULT)
1881
+ if (default_lp = maybe :LP)
1882
+ value = expression
1883
+ default_rp = require :RP
1884
+
1885
+ value_attr = (Node === value) ? { value: value } : { value_tk: value }
1886
+
1887
+ DefaultColumnConstraint.concrete(
1888
+ full_source: @lexer.sql,
1889
+ constraint_kw: Token::Keyword(constraint_kw),
1890
+ name_tk: Token::Identifier(constraint_name),
1891
+ default_kw: Token::Keyword(default_kw),
1892
+ default_lp: Token::Punctuation(default_lp),
1893
+ default_rp: Token::Punctuation(default_rp),
1894
+ **value_attr,
1895
+ )
1896
+ elsif (number = maybe { signed_number })
1897
+ DefaultColumnConstraint.concrete(
1898
+ full_source: @lexer.sql,
1899
+ constraint_kw: Token::Keyword(constraint_kw),
1900
+ name_tk: Token::Identifier(constraint_name),
1901
+ default_kw: Token::Keyword(default_kw),
1902
+ value_tk: Token::Numeric(number),
1903
+ )
1904
+ elsif (value = maybe { literal_value })
1905
+ DefaultColumnConstraint.concrete(
1906
+ full_source: @lexer.sql,
1907
+ constraint_kw: Token::Keyword(constraint_kw),
1908
+ name_tk: Token::Identifier(constraint_name),
1909
+ default_kw: Token::Keyword(default_kw),
1910
+ value_tk: value,
1911
+ )
1912
+ # A sign on something that is not a number, like `DEFAULT -'hello'`
1913
+ elsif (current_token in :PLUS | :MINUS) && (value = maybe { basic_expression })
1914
+ DefaultColumnConstraint.concrete(
1915
+ full_source: @lexer.sql,
1916
+ constraint_kw: Token::Keyword(constraint_kw),
1917
+ name_tk: Token::Identifier(constraint_name),
1918
+ default_kw: Token::Keyword(default_kw),
1919
+ value: value,
1920
+ )
1921
+ elsif (id = maybe { identifier })
1922
+ DefaultColumnConstraint.concrete(
1923
+ full_source: @lexer.sql,
1924
+ constraint_kw: Token::Keyword(constraint_kw),
1925
+ name_tk: Token::Identifier(constraint_name),
1926
+ default_kw: Token::Keyword(default_kw),
1927
+ value_tk: Token::Identifier(id),
1928
+ )
1929
+ else
1930
+ expected! :LP, "literal-value", "signed-number"
1931
+ end
1932
+ elsif (collate_kw = maybe :COLLATE)
1933
+ collation = name
1934
+
1935
+ CollateColumnConstraint.concrete(
1936
+ full_source: @lexer.sql,
1937
+ constraint_kw: Token::Keyword(constraint_kw),
1938
+ name_tk: Token::Identifier(constraint_name),
1939
+ collate_kw: Token::Keyword(collate_kw),
1940
+ collation_tk: Token::Identifier(collation),
1941
+ )
1942
+ elsif :REFERENCES == current_token
1943
+ clause = foreign_key_clause
1944
+
1945
+ ForeignKeyColumnConstraint.concrete(
1946
+ full_source: @lexer.sql,
1947
+ constraint_kw: Token::Keyword(constraint_kw),
1948
+ name_tk: Token::Identifier(constraint_name),
1949
+ foreign_key_clause: clause,
1950
+ )
1951
+ elsif (as_kw = maybe_all_of(:GENERATED, :ALWAYS, :AS) || maybe(:AS))
1952
+ as_lp = require :LP
1953
+ default = expression
1954
+ as_rp = require :RP
1955
+ type_tk = maybe_one_of :STORED, :VIRTUAL
1956
+
1957
+ GeneratedAsColumnConstraint.concrete(
1958
+ full_source: @lexer.sql,
1959
+ constraint_kw: Token::Keyword(constraint_kw),
1960
+ name_tk: Token::Identifier(constraint_name),
1961
+ as_kw: Token::Keyword(as_kw),
1962
+ as_lp: Token::Punctuation(as_lp),
1963
+ expression: default,
1964
+ as_rp: Token::Punctuation(as_rp),
1965
+ type_tk: Token::Keyword(type_tk),
1966
+ )
1967
+ else
1968
+ if constraint_name
1969
+ NoOpColumnConstraint.concrete(
1970
+ full_source: @lexer.sql,
1971
+ constraint_kw: Token::Keyword(constraint_kw),
1972
+ name_tk: Token::Identifier(constraint_name),
1973
+ )
1974
+ else
1975
+ expected! :PRIMARY, :NOT, :NULL, :UNIQUE, :CHECK, :DEFAULT, :COLLATE, :REFERENCES, :GENERATED
1976
+ end
1977
+ end
1978
+ end
1979
+
1980
+ ColumnDefinition.concrete(
1981
+ full_source: @lexer.sql,
1982
+ name_tk: Token::Identifier(column_name),
1983
+ type: column_type,
1984
+ constraints: constraints,
1985
+ )
1986
+ end
1987
+
1988
+ def select_core
1989
+ #
1990
+ # Relevant `parse.y` grammar rules:
1991
+ # oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt |
1992
+ # SELECT distinct selcollist from where_opt groupby_opt having_opt window_clause orderby_opt limit_opt |
1993
+ # values
1994
+ # distinct ::= DISTINCT | ALL | .
1995
+ # from ::= . | FROM seltablist
1996
+ # where_opt ::= . | WHERE expr
1997
+ # groupby_opt ::= . | GROUP BY nexprlist
1998
+ # having_opt ::= . | HAVING expr
1999
+ # window_clause ::= WINDOW windowdefn_list
2000
+ # windowdefn_list ::= windowdefn | windowdefn_list COMMA windowdefn
2001
+ # windowdefn ::= nm AS LP window RP
2002
+ # values ::= VALUES LP nexprlist RP | values COMMA LP nexprlist RP
2003
+ #
2004
+ # Syntax diagram:
2005
+ # ◯─┬───▶{ SELECT }─┬───────▶──────┬─┬▶[ result-column ]─┐
2006
+ # │ ├▶{ DISTINCT }─┤ └─────{ , }◀────────┤
2007
+ # │ └▶{ ALL }─────▶┘ │
2008
+ # │ ┌──────────────────────────◀──────────────────────┘
2009
+ # │ ├─▶{ FROM }┬─┬▶[ table-or-subquery ]─┬─┬─▶───┐
2010
+ # │ │ │ └────────{ , }◀─────────┘ ▲ ▼
2011
+ # │ │ └──▶[ join-clause ]─────────┘ │
2012
+ # │ ├──────────────────────◀─────────────────────┘
2013
+ # │ ├─▶{ WHERE }─▶[ expr ]─┐
2014
+ # │ ├──────────◀───────────┘
2015
+ # │ ├────────────▶─────────────────┐
2016
+ # │ ├─▶{ GROUP }─▶{ BY }┬▶[ expr ]┬┴▶┬─▶{ HAVING }─▶[ expr ]─┬─┐
2017
+ # │ ▼ └──{ , }◀─┘ └────────▶──────────────┘ ▼
2018
+ # │ ├─────────────────────────────◀────────────────────────────┘
2019
+ # │ ├─▶{ WINDOW }┬▶{ window-name }─▶{ AS }─▶[ window-defn ]─┐
2020
+ # │ │ └───────────────────{ , }◀─────────────────┤
2021
+ # │ └──────────────────────▶─────────────────────────────▶──┴─┬───▶◯
2022
+ # │ ┌──{ , }◀─┐ │
2023
+ # └─▶{ VALUES }┬▶{ ( }─┴▶[ expr ]┴─▶{ ) }─┬─────────────────▶──┘
2024
+ # └──────────{ , }◀──────────┘
2025
+ #
2026
+ # Simplified grammar:
2027
+ # SELECT [DISTINCT | ALL] result_column [, result_column]*
2028
+ # [FROM join_clause] [WHERE expr] [GROUP BY expr [, expr]* [HAVING expr]]
2029
+ # [WINDOW name AS window_defn [, name AS window_defn]*]
2030
+ # | VALUES (expr [, expr]*) [, (expr [, expr]*)]*
2031
+ #
2032
+ # Relevant SQLite documentation:
2033
+ # - https://www.sqlite.org/lang_select.html#select_core
2034
+ #
2035
+ # A single SELECT or VALUES, which compound operators then glue together.
2036
+ #
2037
+ if (select_kw = maybe :SELECT)
2038
+ type_tk = maybe_one_of :DISTINCT, :ALL
2039
+ columns = require_some(trailing_sep: :COMMA) { result_column }
2040
+ if (from_kw = maybe :FROM)
2041
+ source = join_clause
2042
+ end
2043
+ if (where_kw = maybe :WHERE)
2044
+ condition = expression
2045
+ end
2046
+ if (group_by_kw = maybe_all_of :GROUP, :BY)
2047
+ groupings = require_some(trailing_sep: :COMMA) { expression }
2048
+ end
2049
+ # SQLite accepts HAVING without GROUP BY, treating the whole result as one group
2050
+ if (having_kw = maybe :HAVING)
2051
+ having = expression
2052
+ end
2053
+ if (window_kw = maybe :WINDOW)
2054
+ windows = require_some(trailing_sep: :COMMA) { window_definition }
2055
+ end
2056
+
2057
+ SelectCore.concrete(
2058
+ full_source: @lexer.sql,
2059
+ select_kw: Token::Keyword(select_kw),
2060
+ type_tk: Token::Keyword(type_tk),
2061
+ columns: columns,
2062
+ from_kw: Token::Keyword(from_kw),
2063
+ source: source,
2064
+ where_kw: Token::Keyword(where_kw),
2065
+ condition: condition,
2066
+ group_by_kw: Token::Keyword(group_by_kw),
2067
+ groupings: groupings,
2068
+ having_kw: Token::Keyword(having_kw),
2069
+ having: having,
2070
+ window_kw: Token::Keyword(window_kw),
2071
+ windows: windows,
2072
+ )
2073
+ elsif (values_kw = maybe :VALUES)
2074
+ rows = require_some(trailing_sep: :COMMA) do
2075
+ row_lp = require :LP
2076
+ values = require_some(trailing_sep: :COMMA) { expression }
2077
+ row_rp = require :RP
2078
+
2079
+ ValuesRow.concrete(
2080
+ full_source: @lexer.sql,
2081
+ row_lp: Token::Punctuation(row_lp),
2082
+ values: values,
2083
+ row_rp: Token::Punctuation(row_rp),
2084
+ )
2085
+ end
2086
+
2087
+ ValuesClause.concrete(
2088
+ full_source: @lexer.sql,
2089
+ values_kw: Token::Keyword(values_kw),
2090
+ rows: rows,
2091
+ )
2092
+ else
2093
+ expected!(:SELECT, :VALUES)
2094
+ end
2095
+ end
2096
+
2097
+ def window_definition
2098
+ #
2099
+ # Relevant `parse.y` grammar rules:
2100
+ # windowdefn ::= nm AS LP window RP
2101
+ #
2102
+ # Syntax diagram:
2103
+ # ◯─▶{ window-name }─▶{ AS }─▶[ window-defn ]─▶◯
2104
+ #
2105
+ # Relevant SQLite documentation:
2106
+ # - https://www.sqlite.org/windowfunctions.html#window_chaining
2107
+ #
2108
+ # A named window from the WINDOW clause, which OVER clauses may refer to or extend.
2109
+ #
2110
+ name_tk = name
2111
+ as_kw = require :AS
2112
+ window_lp = require :LP
2113
+ window = window_body
2114
+ window_rp = require :RP
2115
+
2116
+ WindowDefinition.concrete(
2117
+ full_source: @lexer.sql,
2118
+ name_tk: Token::Identifier(name_tk),
2119
+ as_kw: Token::Keyword(as_kw),
2120
+ window_lp: Token::Punctuation(window_lp),
2121
+ **window,
2122
+ window_rp: Token::Punctuation(window_rp),
2123
+ )
2124
+ end
2125
+
2126
+ # The inside of a window-defn parenthesis, shared by OVER (…) and WINDOW name AS (…)
2127
+ def window_body
2128
+ base_window_name_tk = maybe { name(except: [:PARTITION, :ORDER, :RANGE, :ROWS, :GROUPS]) }
2129
+ if (partition_by_kw = maybe_all_of(:PARTITION, :BY))
2130
+ partitions = require_some(trailing_sep: :COMMA) { expression }
2131
+ end
2132
+ if (order_by_kw = maybe_all_of(:ORDER, :BY))
2133
+ orderings = require_some(trailing_sep: :COMMA) { ordering_term }
2134
+ end
2135
+ if current_token in :RANGE | :ROWS | :GROUPS
2136
+ frame = frame_spec
2137
+ end
2138
+
2139
+ {
2140
+ base_window_name_tk: Token::Identifier(base_window_name_tk),
2141
+ partition_by_kw: Token::Keyword(partition_by_kw),
2142
+ partitions: partitions,
2143
+ order_by_kw: Token::Keyword(order_by_kw),
2144
+ orderings: orderings,
2145
+ frame: frame,
2146
+ }
2147
+ end
2148
+
2149
+ def filter_clause
2150
+ # Relevant `parse.y` grammar rules:
2151
+ #
2152
+ # Syntax diagram:
2153
+ # ◯─▶{ FILTER }─▶{ ( }─▶{ WHERE }─▶[ expr ]─▶{ ) }─▶◯
2154
+ #
2155
+ # Simplified grammar:
2156
+ #
2157
+ # Relevant SQLite documentation:
2158
+ # -
2159
+ #
2160
+ # [description]
2161
+ #
2162
+ require_all_of :FILTER, :LP, :WHERE
2163
+ condition = expression
2164
+ require :RP
2165
+
2166
+ condition
2167
+ end
2168
+
2169
+ def over_clause
2170
+ #
2171
+ # Relevant `parse.y` grammar rules:
2172
+ # over_clause ::= OVER LP window RP |
2173
+ # OVER nm
2174
+ # window ::= nm frame_opt |
2175
+ # frame_opt |
2176
+ # nm ORDER BY sortlist frame_opt |
2177
+ # ORDER BY sortlist frame_opt |
2178
+ # nm PARTITION BY nexprlist orderby_opt frame_opt |
2179
+ # PARTITION BY nexprlist orderby_opt frame_opt
2180
+ # frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt |
2181
+ # range_or_rows frame_bound_s frame_exclude_opt |
2182
+ # .
2183
+ # range_or_rows ::= RANGE | ROWS | GROUPS
2184
+ # frame_bound_s ::= frame_bound |
2185
+ # UNBOUNDED PRECEDING
2186
+ # frame_bound_e ::= frame_bound |
2187
+ # UNBOUNDED FOLLOWING
2188
+ # frame_exclude_opt ::= . |
2189
+ # EXCLUDE frame_exclude
2190
+ # frame_bound ::= CURRENT ROW |
2191
+ # expr PRECEDING | FOLLOWING
2192
+ # frame_exclude ::= NO OTHERS | CURRENT ROW | GROUP | TIES
2193
+ # filter_clause ::= FILTER LP WHERE expr RP
2194
+ #
2195
+ # Syntax diagram:
2196
+ # ◯─▶{ OVER }┬─▶{ window-name }───────────────────────────────┬─▶◯
2197
+ # └─▶{ ( }┬───────────▶────────────┐ │
2198
+ # └─▶{ base-window-name }─▶┤ │
2199
+ # ┌───────────────◀───────────┘ │
2200
+ # ├─▶{ PARTITION }─▶{ BY }┬[ expr ]┐ │
2201
+ # │ └─{ , }◀─┤ │
2202
+ # ├────────────◀───────────────────┘ │
2203
+ # ├─▶{ ORDER }─▶{ BY }┬[ ordering-term ]┐ │
2204
+ # │ └──────{ , }◀─────┤ │
2205
+ # ├─────────────◀───────────────────────┘ │
2206
+ # ├─▶[ frame-spec ]┬─▶──────────────────{ ) }─┘
2207
+ # └─────────▶──────┘
2208
+ #
2209
+ # Simplified grammar:
2210
+ #
2211
+ # Relevant SQLite documentation:
2212
+ # -
2213
+ #
2214
+ # [description]
2215
+ #
2216
+ over_kw = require :OVER
2217
+ if (window_lp = maybe :LP)
2218
+ window = window_body
2219
+ window_rp = require :RP
2220
+
2221
+ OverClause.concrete(
2222
+ full_source: @lexer.sql,
2223
+ over_kw: Token::Keyword(over_kw),
2224
+ window_lp: Token::Punctuation(window_lp),
2225
+ **window,
2226
+ window_rp: Token::Punctuation(window_rp),
2227
+ )
2228
+ else
2229
+ window_name_tk = name
2230
+
2231
+ OverClause.concrete(
2232
+ full_source: @lexer.sql,
2233
+ over_kw: Token::Keyword(over_kw),
2234
+ window_name_tk: Token::Identifier(window_name_tk),
2235
+ )
2236
+ end
2237
+ end
2238
+
2239
+ def foreign_key_clause
2240
+ #
2241
+ # Relevant `parse.y` grammar rules:
2242
+ # ccons ::= REFERENCES nm eidlist_opt refargs.
2243
+ # eidlist_opt ::= . | LP eidlist RP
2244
+ # eidlist ::= eidlist COMMA nm collate sortorder | nm collate sortorder
2245
+ # refargs ::= . | refargs refarg
2246
+ # refarg ::= MATCH nm |
2247
+ # ON INSERT refact |
2248
+ # ON DELETE refact |
2249
+ # ON UPDATE refact
2250
+ # refact ::= SET NULL |
2251
+ # SET DEFAULT |
2252
+ # CASCADE |
2253
+ # RESTRICT |
2254
+ # NO ACTION
2255
+ # defer_subclause_opt ::= . | defer_subclause
2256
+ # defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt |
2257
+ # DEFERRABLE init_deferred_pred_opt
2258
+ # init_deferred_pred_opt ::= . | INITIALLY DEFERRED | INITIALLY IMMEDIATE
2259
+ #
2260
+ # Syntax diagram:
2261
+ # ┌─────{ , }◀─────┐
2262
+ # ◯─▶{ REFERENCES }─▶{ foreign-table }─┬─▶{ ( }┴▶{ column-name }┴▶{ ) }──┐
2263
+ # ┌───────────────────────────────────┴──────────◀──┬───────────────────┘
2264
+ # ├─▶{ ON }┬─▶{ DELETE }─┬┬─▶{ SET }─▶{ NULL }────▶─┤
2265
+ # │ └─▶{ UPDATE }─┘├─▶{ SET }─▶{ DEFAULT }─▶─┤
2266
+ # │ ├─▶{ CASCADE }──────────▶─┤
2267
+ # │ ├─▶{ RESTRICT }─────────▶─┤
2268
+ # │ └─▶{ NO }─▶{ ACTION }───▶─┤
2269
+ # ├─▶{ MATCH }─▶{ name }────────────────────────────┘
2270
+ # ├────────────┬─▶{ DEFERRABLE }┬───────────────────────────────▶─┐
2271
+ # ├─{ NOT }──▶─┘ ├─▶{ INITIALLY }─▶{ DEFERRED }──▶─┤
2272
+ # │ └─▶{ INITIALLY }─▶{ IMMEDIATE }─▶─┤
2273
+ # └───────────────────────────────────────────────────────────────┴──────▶◯
2274
+ #
2275
+ # Simplified grammar:
2276
+ #
2277
+ # Relevant SQLite documentation:
2278
+ # - https://www.sqlite.org/syntax/foreign-key-clause.html
2279
+ # - https://www.sqlite.org/foreignkeys.html
2280
+ #
2281
+ # [description]
2282
+ #
2283
+ references_kw = require :REFERENCES
2284
+ foreign_table = name(except: [:ON, :MATCH, :DEFERRABLE, :NOT])
2285
+ columns = nil
2286
+ if (columns_lp = maybe :LP)
2287
+ columns = require_some(trailing_sep: :COMMA) { indexed_column }
2288
+ columns_rp = require :RP
2289
+ end
2290
+ # refargs ::= refargs refarg, so ON and MATCH clauses come in any order
2291
+ refargs = maybe_some(trailing_sep: nil) do
2292
+ next match_clause if current_token == :MATCH
2293
+
2294
+ on_kw = require :ON
2295
+ trigger_kw = require_one_of :DELETE, :UPDATE, :INSERT
2296
+ action_kw = require_one_of :CASCADE, :RESTRICT, [:SET, :NULL].freeze, [:SET, :DEFAULT].freeze, [:NO, :ACTION].freeze
2297
+ ForeignKeyAction.concrete(
2298
+ full_source: @lexer.sql,
2299
+ on_kw: Token::Keyword(on_kw),
2300
+ trigger_kw: Token::Keyword(trigger_kw),
2301
+ action_kw: Token::Keyword(action_kw),
2302
+ )
2303
+ end
2304
+ foreign_key_actions = refargs.grep(ForeignKeyAction)
2305
+ match_clauses = refargs.grep(MatchClause)
2306
+ deferrable_span = defer_subclause
2307
+
2308
+ ForeignKeyClause.concrete(
2309
+ full_source: @lexer.sql,
2310
+ references_kw: Token::Keyword(references_kw),
2311
+ table_tk: Token::Identifier(foreign_table),
2312
+ columns_lp: Token::Punctuation(columns_lp),
2313
+ columns: columns,
2314
+ columns_rp: Token::Punctuation(columns_rp),
2315
+ actions: foreign_key_actions,
2316
+ match_clauses: match_clauses,
2317
+ deferrable_span: Token::Keyword(deferrable_span),
2318
+ )
2319
+ end
2320
+
2321
+ def match_clause
2322
+ match_kw = require :MATCH
2323
+ type_tk = name(except: [:ON, :MATCH, :DEFERRABLE, :NOT])
2324
+ MatchClause.concrete(
2325
+ full_source: @lexer.sql,
2326
+ match_kw: Token::Keyword(match_kw),
2327
+ type_tk: Token::Identifier(type_tk),
2328
+ )
2329
+ end
2330
+
2331
+ # defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt | DEFERRABLE init_deferred_pred_opt
2332
+ def defer_subclause
2333
+ maybe_one_of(
2334
+ [:NOT, :DEFERRABLE, :INITIALLY, :IMMEDIATE].freeze,
2335
+ [:NOT, :DEFERRABLE, :INITIALLY, :DEFERRED].freeze,
2336
+ [:DEFERRABLE, :INITIALLY, :IMMEDIATE].freeze,
2337
+ [:DEFERRABLE, :INITIALLY, :DEFERRED].freeze,
2338
+ [:NOT, :DEFERRABLE].freeze,
2339
+ :DEFERRABLE,
2340
+ )
2341
+ end
2342
+
2343
+ def conflict_clause
2344
+ #
2345
+ # Used by:
2346
+ # - column_def
2347
+ #
2348
+ # Relevant `parse.y` grammar rules:
2349
+ # onconf ::= . | ON CONFLICT resolvetype.
2350
+ # orconf ::= . | OR resolvetype.
2351
+ # resolvetype ::= raisetype |
2352
+ # IGNORE |
2353
+ # REPLACE
2354
+ # raisetype ::= ROLLBACK |
2355
+ # ABORT |
2356
+ # FAIL
2357
+ #
2358
+ # Syntax diagram:
2359
+ # ◯─▶┬────────────────────────────────────────┬─────▶◯
2360
+ # └─▶{ ON }─▶{ CONFLICT }┬─▶{ ROLLBACK }─▶─┤
2361
+ # ├─▶{ ABORT }────▶─┤
2362
+ # ├─▶{ FAIL }─────▶─┤
2363
+ # ├─▶{ IGNORE }───▶─┤
2364
+ # └─▶{ REPLACE }──▶─┘
2365
+ #
2366
+ # Simplified grammar:
2367
+ # [ON CONFLICT [ROLLBACK | ABORT | FAIL | IGNORE | REPLACE]]
2368
+ #
2369
+ # Relevant SQLite documentation:
2370
+ # - https://www.sqlite.org/lang_conflict.html
2371
+ #
2372
+ # The ON CONFLICT clause is a non-standard extension specific to SQLite
2373
+ # that can appear in many other SQL commands. There are five conflict resolution
2374
+ # algorithm choices: ROLLBACK, ABORT, FAIL, IGNORE, and REPLACE.
2375
+ #
2376
+ return unless (on_conflict_kw = maybe_all_of :ON, :CONFLICT)
2377
+
2378
+ if (resolution_kw = maybe_one_of :ROLLBACK, :ABORT, :FAIL, :IGNORE, :REPLACE)
2379
+ ConflictClause.concrete(
2380
+ full_source: @lexer.sql,
2381
+ on_conflict_kw: Token::Keyword(on_conflict_kw),
2382
+ resolution_kw: Token::Keyword(resolution_kw),
2383
+ )
2384
+ else
2385
+ expected! :ROLLBACK, :ABORT, :FAIL, :IGNORE, :REPLACE
2386
+ end
2387
+ end
2388
+
2389
+ def basic_expression
2390
+ if (operator_tk = maybe :BITNOT)
2391
+ operand = expression(OPERATOR_PRECEDENCE[:BITNOT])
2392
+ operand_attr = (Node === operand) ? { operand: operand } : { operand_tk: operand }
2393
+
2394
+ UnaryExpression.concrete(
2395
+ full_source: @lexer.sql,
2396
+ operator_tk: Token::Punctuation(operator_tk),
2397
+ **operand_attr,
2398
+ )
2399
+ elsif (operator_tk = maybe :PLUS)
2400
+ operand = expression(OPERATOR_PRECEDENCE[:UPLUS])
2401
+ operand_attr = (Node === operand) ? { operand: operand } : { operand_tk: operand }
2402
+
2403
+ UnaryExpression.concrete(
2404
+ full_source: @lexer.sql,
2405
+ operator_tk: Token::Punctuation(operator_tk),
2406
+ **operand_attr,
2407
+ )
2408
+ elsif (operator_tk = maybe :MINUS)
2409
+ operand = expression(OPERATOR_PRECEDENCE[:UMINUS])
2410
+ operand_attr = (Node === operand) ? { operand: operand } : { operand_tk: operand }
2411
+
2412
+ UnaryExpression.concrete(
2413
+ full_source: @lexer.sql,
2414
+ operator_tk: Token::Punctuation(operator_tk),
2415
+ **operand_attr,
2416
+ )
2417
+ elsif (operator_tk = maybe :NOT)
2418
+ operand = expression(OPERATOR_PRECEDENCE[:NOT])
2419
+ operand_attr = (Node === operand) ? { operand: operand } : { operand_tk: operand }
2420
+
2421
+ UnaryExpression.concrete(
2422
+ full_source: @lexer.sql,
2423
+ operator_tk: Token::Punctuation(operator_tk),
2424
+ **operand_attr,
2425
+ )
2426
+ elsif (value_lp = maybe :LP)
2427
+ if subquery_start?
2428
+ select = select_stmt
2429
+ select_rp = require :RP
2430
+
2431
+ return SubqueryExpression.concrete(
2432
+ full_source: @lexer.sql,
2433
+ select_lp: Token::Punctuation(value_lp),
2434
+ select: select,
2435
+ select_rp: Token::Punctuation(select_rp),
2436
+ )
2437
+ end
2438
+
2439
+ value = expression(0)
2440
+ # A row value like `(a, b) = (1, 2)`
2441
+ if current_token == :COMMA
2442
+ value = require_some(given: value, trailing_sep: :COMMA) { expression(0) }
2443
+ end
2444
+ value_rp = require :RP
2445
+
2446
+ value_attr = (Node === value || Array === value) ? { value: value } : { value_tk: value }
2447
+
2448
+ ParentheticalExpression.concrete(
2449
+ full_source: @lexer.sql,
2450
+ value_lp: Token::Punctuation(value_lp),
2451
+ **value_attr,
2452
+ value_rp: Token::Punctuation(value_rp),
2453
+ )
2454
+ elsif (cast_kw = maybe :CAST)
2455
+ expression_lp = require :LP
2456
+ e = expression
2457
+ as_kw = require :AS
2458
+ t = type_name unless current_token == :RP # typetoken ::= .
2459
+ expression_rp = require :RP
2460
+
2461
+ CastExpression.concrete(
2462
+ full_source: @lexer.sql,
2463
+ cast_kw: Token::Keyword(cast_kw),
2464
+ expression_lp: Token::Punctuation(expression_lp),
2465
+ expression: e,
2466
+ as_kw: Token::Keyword(as_kw),
2467
+ as: t,
2468
+ expression_rp: Token::Punctuation(expression_rp),
2469
+ )
2470
+ elsif (case_kw = maybe :CASE)
2471
+ if current_token == :WHEN
2472
+ conditions = require_some(leading_sep: :WHEN) do |when_kw|
2473
+ case_predicate = expression
2474
+ then_kw = require :THEN
2475
+ case_consequence = expression
2476
+
2477
+ predicate_attr = (Node === case_predicate) ? { predicate: case_predicate } : { predicate_tk: case_predicate }
2478
+ consequence_attr = (Node === case_consequence) ? { consequence: case_consequence } : { consequence_tk: case_consequence }
2479
+
2480
+ CaseCondition.concrete(
2481
+ full_source: @lexer.sql,
2482
+ when_kw: Token::Keyword(when_kw),
2483
+ **predicate_attr,
2484
+ then_kw: Token::Keyword(then_kw),
2485
+ **consequence_attr,
2486
+ )
2487
+ end
2488
+ if (else_kw = maybe :ELSE)
2489
+ else_clause = expression
2490
+ end
2491
+ end_kw = require :END
2492
+
2493
+ else_clause_attr = (Node === else_clause) ? { else_clause: else_clause } : { else_clause_tk: else_clause }
2494
+
2495
+ CaseExpression.concrete(
2496
+ full_source: @lexer.sql,
2497
+ case_kw: Token::Keyword(case_kw),
2498
+ conditions: conditions,
2499
+ else_kw: Token::Keyword(else_kw),
2500
+ **else_clause_attr,
2501
+ end_kw: Token::Keyword(end_kw),
2502
+ )
2503
+ else
2504
+ predicate = expression
2505
+ conditions = require_some(leading_sep: :WHEN) do |when_kw|
2506
+ case_predicate = expression
2507
+ then_kw = require :THEN
2508
+ case_consequence = expression
2509
+
2510
+ CaseCondition.concrete(
2511
+ full_source: @lexer.sql,
2512
+ when_kw: Token::Keyword(when_kw),
2513
+ predicate: case_predicate,
2514
+ then_kw: Token::Keyword(then_kw),
2515
+ consequence: case_consequence,
2516
+ )
2517
+ end
2518
+ if (else_kw = maybe :ELSE)
2519
+ else_clause = expression
2520
+ end
2521
+ end_kw = require :END
2522
+
2523
+ CaseExpression.concrete(
2524
+ full_source: @lexer.sql,
2525
+ case_kw: Token::Keyword(case_kw),
2526
+ predicate: predicate,
2527
+ conditions: conditions,
2528
+ else_kw: Token::Keyword(else_kw),
2529
+ else_clause: else_clause,
2530
+ end_kw: Token::Keyword(end_kw),
2531
+ )
2532
+ end
2533
+ elsif (exists_kw = maybe :EXISTS)
2534
+ select_lp = require :LP
2535
+ select = select_stmt
2536
+ select_rp = require :RP
2537
+
2538
+ ExistsExpression.concrete(
2539
+ full_source: @lexer.sql,
2540
+ exists_kw: Token::Keyword(exists_kw),
2541
+ select_lp: Token::Punctuation(select_lp),
2542
+ select: select,
2543
+ select_rp: Token::Punctuation(select_rp),
2544
+ )
2545
+ # A string followed by a dot is a quoted table name, as in `'t'.col`
2546
+ # A string or TRUE/FALSE before a dot names a table: `'t'.x`, `false.false`. SQLite has no TRUE
2547
+ # or FALSE keyword at all, while CURRENT_* is always a literal in an expression
2548
+ elsif !((current_token in :STRING | :TRUE | :FALSE) && peek == :DOT) &&
2549
+ (literal_tk = maybe { literal_value })
2550
+ LiteralExpression.concrete(
2551
+ full_source: @lexer.sql,
2552
+ value_tk: literal_tk,
2553
+ )
2554
+ elsif (id = maybe { name(except: [:RAISE]) })
2555
+ case current_token
2556
+ in :IN
2557
+ ColumnName.concrete(
2558
+ full_source: @lexer.sql,
2559
+ column_tk: Token::Identifier(id),
2560
+ )
2561
+ in :LP
2562
+ arguments_lp = require :LP
2563
+ arguments = function_arguments
2564
+ arguments_rp = require :RP
2565
+ if (within_group_kw = maybe_all_of :WITHIN, :GROUP)
2566
+ within_lp = require :LP
2567
+ require_all_of :ORDER, :BY
2568
+ within_group = expression
2569
+ within_rp = require :RP
2570
+ end
2571
+ filter = maybe { filter_clause }
2572
+ over = maybe { over_clause }
2573
+
2574
+ FunctionReference.concrete(
2575
+ full_source: @lexer.sql,
2576
+ function_tk: Token::Identifier(id),
2577
+ arguments_lp: Token::Punctuation(arguments_lp),
2578
+ arguments: arguments,
2579
+ arguments_rp: Token::Punctuation(arguments_rp),
2580
+ within_group_kw: Token::Keyword(within_group_kw),
2581
+ within_lp: Token::Punctuation(within_lp),
2582
+ within_group: within_group,
2583
+ within_rp: Token::Punctuation(within_rp),
2584
+ filter_clause: filter,
2585
+ over_clause: over,
2586
+ )
2587
+ in :DOT
2588
+ schema_or_table_name = id
2589
+ dot = require :DOT
2590
+ table_or_column_name = name
2591
+ if (dot2 = maybe :DOT)
2592
+ column_name = name
2593
+ ColumnName.concrete(
2594
+ full_source: @lexer.sql,
2595
+ schema_tk: Token::Identifier(schema_or_table_name),
2596
+ dot1: Token::Punctuation(dot),
2597
+ table_tk: Token::Identifier(table_or_column_name),
2598
+ dot2: Token::Punctuation(dot2),
2599
+ column_tk: Token::Identifier(column_name),
2600
+ )
2601
+ else
2602
+ ColumnName.concrete(
2603
+ full_source: @lexer.sql,
2604
+ table_tk: Token::Identifier(schema_or_table_name),
2605
+ dot2: Token::Punctuation(dot),
2606
+ column_tk: Token::Identifier(table_or_column_name),
2607
+ )
2608
+ end
2609
+ else
2610
+ ColumnName.concrete(
2611
+ full_source: @lexer.sql,
2612
+ column_tk: Token::Identifier(id),
2613
+ )
2614
+ end
2615
+ elsif :RAISE == current_token
2616
+ raise_function
2617
+ # SQLite lexes "#9" as a variable too, but "#" and a digit only ever names a register
2618
+ # inside its own nested parses, so as a parameter it is a syntax error
2619
+ elsif :VARIABLE == current_token && !current_value.b.match?(/\A#\d/)
2620
+ # SQLite numbers them from ?1 up to its default SQLITE_MAX_VARIABLE_NUMBER
2621
+ if (number = current_value.b[/\A\?(\d+)\z/, 1]) && !number.to_i.between?(1, 32_766)
2622
+ expected!("?1 to ?32766")
2623
+ end
2624
+
2625
+ Variable.concrete(
2626
+ full_source: @lexer.sql,
2627
+ name_tk: Token::Variable(require(:VARIABLE)),
2628
+ )
2629
+ else
2630
+ expected!("<basic expression>")
2631
+ end
2632
+ end
2633
+
2634
+ def raise_function
2635
+ #
2636
+ # Relevant `parse.y` grammar rules:
2637
+ # expr ::= RAISE LP IGNORE RP | RAISE LP raisetype COMMA expr RP
2638
+ # raisetype ::= ROLLBACK | ABORT | FAIL
2639
+ #
2640
+ # Syntax diagram:
2641
+ # ◯─▶{ RAISE }─▶{ ( }┬─▶{ IGNORE }────────────────────▶─────────┬▶{ ) }──▶◯
2642
+ # ├─▶{ ROLLBACK }─┬▶{ , }─▶{ error-message }─┘
2643
+ # ├─▶{ ABORT }──▶─┤
2644
+ # └─▶{ FAIL }───▶─┘
2645
+ #
2646
+ # Simplified grammar:
2647
+ # RAISE LP (IGNORE | (ROLLBACK | ABORT | FAIL) COMMA expr) RP
2648
+ #
2649
+ # Relevant SQLite documentation:
2650
+ # - https://www.sqlite.org/syntax/raise-function.html
2651
+ # - https://www.sqlite.org/lang_createtrigger.html#the_raise_function
2652
+ #
2653
+ # The special RAISE expression that may _only_ occur in trigger programs.
2654
+ #
2655
+ raise_kw = require :RAISE
2656
+ raise_lp = require :LP
2657
+ if (ignore_kw = maybe :IGNORE)
2658
+ raise_rp = require :RP
2659
+
2660
+ RaiseExpression.concrete(
2661
+ full_source: @lexer.sql,
2662
+ raise_kw: Token::Keyword(raise_kw),
2663
+ raise_lp: Token::Punctuation(raise_lp),
2664
+ type_kw: Token::Keyword(ignore_kw),
2665
+ raise_rp: Token::Punctuation(raise_rp),
2666
+ )
2667
+ elsif (rollback_kw = maybe :ROLLBACK)
2668
+ comma_tk = require :COMMA
2669
+ error_message = expression
2670
+ error_message.leading = Token::Punctuation(comma_tk)
2671
+ raise_rp = require :RP
2672
+
2673
+ RaiseExpression.concrete(
2674
+ full_source: @lexer.sql,
2675
+ raise_kw: Token::Keyword(raise_kw),
2676
+ raise_lp: Token::Punctuation(raise_lp),
2677
+ type_kw: Token::Keyword(rollback_kw),
2678
+ raise_rp: Token::Punctuation(raise_rp),
2679
+ error_message: error_message,
2680
+ )
2681
+ elsif (abort_kw = maybe :ABORT)
2682
+ comma_tk = require :COMMA
2683
+ error_message = expression
2684
+ error_message.leading = Token::Punctuation(comma_tk)
2685
+ raise_rp = require :RP
2686
+
2687
+ RaiseExpression.concrete(
2688
+ full_source: @lexer.sql,
2689
+ raise_kw: Token::Keyword(raise_kw),
2690
+ raise_lp: Token::Punctuation(raise_lp),
2691
+ type_kw: Token::Keyword(abort_kw),
2692
+ raise_rp: Token::Punctuation(raise_rp),
2693
+ error_message: error_message,
2694
+ )
2695
+ elsif (fail_kw = maybe :FAIL)
2696
+ comma_tk = require :COMMA
2697
+ error_message = expression
2698
+ error_message.leading = Token::Punctuation(comma_tk)
2699
+ raise_rp = require :RP
2700
+
2701
+ RaiseExpression.concrete(
2702
+ full_source: @lexer.sql,
2703
+ raise_kw: Token::Keyword(raise_kw),
2704
+ raise_lp: Token::Punctuation(raise_lp),
2705
+ type_kw: Token::Keyword(fail_kw),
2706
+ raise_rp: Token::Punctuation(raise_rp),
2707
+ error_message: error_message,
2708
+ )
2709
+ else
2710
+ expected!(:IGNORE, :ROLLBACK, :ABORT, :FAIL)
2711
+ end
2712
+ end
2713
+
2714
+ def function_arguments
2715
+ #
2716
+ # Relevant `parse.y` grammar rules:
2717
+ # expr ::= idj LP distinct exprlist RP
2718
+ # idj LP distinct exprlist ORDER BY sortlist RP
2719
+ # idj LP STAR RP
2720
+ # idj LP distinct exprlist RP filter_over
2721
+ # idj LP distinct exprlist ORDER BY sortlist RP filter_over
2722
+ # idj LP STAR RP filter_over
2723
+ # idj LP distinct exprlist RP WITHIN GROUP LP ORDER BY expr RP filter_over
2724
+ # distinct ::= DISTINCT | ALL | .
2725
+ # exprlist ::= nexprlist | .
2726
+ # nexprlist ::= nexprlist COMMA expr | expr
2727
+ #
2728
+ # Syntax diagram:
2729
+ # ┌─▶{ DISTINCT }─▶┐ ┌──{ , }◀─┐
2730
+ # ◯─▶─┼────────────────┴▶┴▶[ expr ]┼───────────┬─┬─▶◯
2731
+ # │ ┌──────────────────────────┘ │ │
2732
+ # │ └▶{ ORDER }─▶{ BY }┬▶[ ordering-term ]─┤ │
2733
+ # │ └───────{ , }◀──────┘ │
2734
+ # └─────────────────┬▶{ * }┬▶────────────────┘
2735
+ # └───▶──┘
2736
+ #
2737
+ # Simplified grammar:
2738
+ #
2739
+ # Relevant SQLite documentation:
2740
+ # -
2741
+ #
2742
+ # [description]
2743
+ #
2744
+ return StarFunctionArgument.new if maybe :STAR
2745
+
2746
+ distinct_kw = maybe_one_of :DISTINCT, :ALL
2747
+ expressions = maybe_some(trailing_sep: :COMMA) { expression }
2748
+ if (order_by_kw = maybe_all_of :ORDER, :BY)
2749
+ order_by = require_some(trailing_sep: :COMMA) { ordering_term }
2750
+ end
2751
+ return EmptyFunctionArgument.new unless distinct_kw || expressions.any? || order_by_kw
2752
+
2753
+ FunctionArguments.concrete(
2754
+ full_source: @lexer.sql,
2755
+ distinct_kw: Token::Keyword(distinct_kw),
2756
+ expressions: expressions,
2757
+ order_by_kw: Token::Keyword(order_by_kw),
2758
+ order_by: order_by,
2759
+ )
2760
+ end
2761
+
2762
+ def frame_spec
2763
+ # Relevant `parse.y` grammar rules:
2764
+ #
2765
+ # Syntax diagram:
2766
+ # ◯┬─▶{ RANGE }──┬┬▶{ BETWEEN }┬─▶{ UNBOUNDED }─▶{ PRECEDING }┬▶{ AND }┬─▶[ expr ]─▶{ PRECEDING }────▶─┐
2767
+ # ├─▶{ ROWS }───┤│ ├─▶[ expr ]─▶{ PRECEDING }─────┤ ├─▶{ CURRENT }─▶{ ROW }───────▶─┤
2768
+ # └─▶{ GROUPS }─┘│ ├─▶{ CURRENT }─▶{ ROW }────────┤ ├─▶[ expr ]─▶{ FOLLOWING }────▶─┤
2769
+ # │ └─▶[ expr ]─▶{ FOLLOWING }─────┘ └─▶{ UNBOUNDED }─▶{ FOLLOWING }─┤
2770
+ # ├─▶{ UNBOUNDED }─▶{ PRECEDING }────────────────────────────────────────────────────▶─┤
2771
+ # ├─▶[ expr ]─▶{ PRECEDING }─────────────────────────────────────────────────────────▶─┤
2772
+ # └─▶{ CURRENT }─▶{ ROW }────────────────────────────────────────────────────────────▶─┤
2773
+ # ┌────────────────────────────────────────────────────────────┘
2774
+ # ├─▶{ EXCLUDE }─▶{ NO }─▶{ OTHERS }───▶─┐
2775
+ # ├─▶{ EXCLUDE }─▶{ CURRENT }─▶{ ROW }─▶─┤
2776
+ # ├─▶{ EXCLUDE }─▶{ GROUP }────────────▶─┤
2777
+ # ├─▶{ EXCLUDE }─▶{ TIES }─────────────▶─┤
2778
+ # └──────────────────────────────────────┴─────────▶◯
2779
+ #
2780
+ # Simplified grammar:
2781
+ #
2782
+ # Relevant SQLite documentation:
2783
+ # -
2784
+ #
2785
+ # [description]
2786
+ #
2787
+ if current_token in :RANGE | :ROWS | :GROUPS
2788
+ type_tk = require current_token
2789
+ if (between_kw = maybe :BETWEEN)
2790
+ if (unbounded_kw = maybe :UNBOUNDED)
2791
+ precedence = 1
2792
+ preceding_kw = require :PRECEDING
2793
+ starting_boundary = FrameBoundary.concrete(
2794
+ full_source: @lexer.sql,
2795
+ type_tk: Token::Keyword(preceding_kw),
2796
+ value_tk: Token::Keyword(unbounded_kw),
2797
+ )
2798
+ elsif (current_row_kw = maybe_all_of :CURRENT, :ROW)
2799
+ precedence = 3
2800
+ starting_boundary = FrameBoundary.concrete(
2801
+ full_source: @lexer.sql,
2802
+ type_tk: Token::Keyword(current_row_kw),
2803
+ )
2804
+ elsif (e = maybe { expression })
2805
+ if (preceding_kw = maybe :PRECEDING)
2806
+ precedence = 2
2807
+ starting_boundary = FrameBoundary.concrete(
2808
+ full_source: @lexer.sql,
2809
+ type_tk: Token::Keyword(preceding_kw),
2810
+ value: e
2811
+ )
2812
+ elsif (following_kw = maybe :FOLLOWING)
2813
+ precedence = 4
2814
+ starting_boundary = FrameBoundary.concrete(
2815
+ full_source: @lexer.sql,
2816
+ type_tk: Token::Keyword(following_kw),
2817
+ value: e
2818
+ )
2819
+ else
2820
+ expected!(:PRECEDING, :FOLLOWING)
2821
+ end
2822
+ else
2823
+ expected!("UNBOUNDED PRECEDING", "CURRENT ROW", "expr")
2824
+ end
2825
+ and_kw = require :AND
2826
+ if (current_row_kw = maybe_all_of :CURRENT, :ROW)
2827
+ expected!("UNBOUNDED FOLLOWING", "expr") if 3 < precedence
2828
+ ending_boundary = FrameBoundary.concrete(
2829
+ full_source: @lexer.sql,
2830
+ type_tk: Token::Keyword(current_row_kw),
2831
+ )
2832
+ elsif (unbounded_kw = maybe :UNBOUNDED)
2833
+ following_kw = require :FOLLOWING
2834
+ ending_boundary = FrameBoundary.concrete(
2835
+ full_source: @lexer.sql,
2836
+ type_tk: Token::Keyword(following_kw),
2837
+ value_tk: Token::Keyword(unbounded_kw),
2838
+ )
2839
+ elsif (e = maybe { expression })
2840
+ if (preceding_kw = maybe :PRECEDING)
2841
+ expected!("CURRENT ROW", "UNBOUNDED FOLLOWING", "expr") if 2 < precedence
2842
+ ending_boundary = FrameBoundary.concrete(
2843
+ full_source: @lexer.sql,
2844
+ type_tk: Token::Keyword(preceding_kw),
2845
+ value: e
2846
+ )
2847
+ elsif (following_kw = maybe :FOLLOWING)
2848
+ expected!("UNBOUNDED FOLLOWING") if 4 < precedence
2849
+ ending_boundary = FrameBoundary.concrete(
2850
+ full_source: @lexer.sql,
2851
+ type_tk: Token::Keyword(following_kw),
2852
+ value: e
2853
+ )
2854
+ else
2855
+ expected!(:PRECEDING, :FOLLOWING)
2856
+ end
2857
+ else
2858
+ expected!("CURRENT ROW", "UNBOUNDED FOLLOWING", "expr")
2859
+ end
2860
+ elsif (unbounded_kw = maybe :UNBOUNDED)
2861
+ preceding_kw = require :PRECEDING
2862
+ starting_boundary = FrameBoundary.concrete(
2863
+ full_source: @lexer.sql,
2864
+ type_tk: Token::Keyword(preceding_kw),
2865
+ value_tk: Token::Keyword(unbounded_kw),
2866
+ )
2867
+ ending_boundary = nil
2868
+ elsif (current_row_kw = maybe_all_of :CURRENT, :ROW)
2869
+ starting_boundary = FrameBoundary.concrete(
2870
+ full_source: @lexer.sql,
2871
+ type_tk: Token::Keyword(current_row_kw),
2872
+ )
2873
+ ending_boundary = nil
2874
+ elsif (e = maybe { expression })
2875
+ preceding_kw = require :PRECEDING
2876
+ starting_boundary = FrameBoundary.concrete(
2877
+ full_source: @lexer.sql,
2878
+ type_tk: Token::Keyword(preceding_kw),
2879
+ value: e
2880
+ )
2881
+ ending_boundary = nil
2882
+ else
2883
+ expected!(:BETWEEN, :UNBOUNDED, :CURRENT, "expr")
2884
+ end
2885
+
2886
+ if (exclude_kw = maybe :EXCLUDE)
2887
+ exclude_tk = require_one_of(:GROUP, :TIES, [:NO, :OTHERS].freeze, [:CURRENT, :ROW].freeze)
2888
+ end
2889
+
2890
+ FrameSpec.concrete(
2891
+ full_source: @lexer.sql,
2892
+ type_tk: Token::Keyword(type_tk),
2893
+ between_kw: Token::Keyword(between_kw),
2894
+ and_kw: Token::Keyword(and_kw),
2895
+ starting_boundary: starting_boundary,
2896
+ ending_boundary: ending_boundary,
2897
+ exclude_kw: Token::Keyword(exclude_kw),
2898
+ exclude_tk: Token::Keyword(exclude_tk),
2899
+ )
2900
+ else
2901
+ expected!(:RANGE, :ROWS, :GROUPS)
2902
+ end
2903
+ end
2904
+
2905
+ # ---------- Objects ----------
2906
+
2907
+ def type_name
2908
+ #
2909
+ # Used by:
2910
+ # - column_def
2911
+ # - expression
2912
+ #
2913
+ # Relevant `parse.y` grammar rules:
2914
+ # typetoken ::= . | typename. | typename LP signed RP | typename LP signed COMMA signed RP
2915
+ # typename ::= ids | typename ids
2916
+ # signed ::= plus_num | minus_num
2917
+ # plus_num ::= PLUS number | number
2918
+ # minus_num ::= MINUS number
2919
+ # number ::= INTEGER | FLOAT
2920
+ #
2921
+ # Syntax diagram:
2922
+ # ◯─┬▶{ name }─┬┬──────────────────────────────▶─────────────────────────────┬─▶◯
2923
+ # └────◀─────┘├─▶{ ( }─▶[ signed-number ]─▶{ ) }─────────────────────────▶─┤
2924
+ # └─▶{ ( }─▶[ signed-number ]─▶{ , }─▶[ signed-number ]─▶{ ) }─┘
2925
+ #
2926
+ # Simplified grammar:
2927
+ # name [name]* [LP signed_number [, signed_number] RP]
2928
+ #
2929
+ # Relevant SQLite documentation:
2930
+ # - https://www.sqlite.org/syntax/type-name.html
2931
+ # - https://www.sqlite.org/datatype3.html
2932
+ # - https://www.sqlite.org/flextypegood.html
2933
+ #
2934
+ # A typetoken is really zero or more tokens that form a type name such
2935
+ # as can be found after the column name in a CREATE TABLE statement.
2936
+ # Multiple tokens are concatenated to form the value of the typetoken.
2937
+ #
2938
+ text = scan do
2939
+ require_some(trailing_sep: nil) do
2940
+ identifier_or_string(
2941
+ # Don't consume tokens that mark the start of a `column_constraint`
2942
+ except: Set[:CONSTRAINT, :PRIMARY, :NOT, :NULL, :UNIQUE, :CHECK, :DEFAULT, :COLLATE, :REFERENCES, :GENERATED, :AS]
2943
+ )
2944
+ end
2945
+ if maybe :LP
2946
+ signed_number
2947
+ signed_number if maybe :COMMA
2948
+ require :RP
2949
+ end
2950
+ end
2951
+
2952
+ ColumnType.concrete(
2953
+ full_source: @lexer.sql,
2954
+ text_span: Token::Keyword(text),
2955
+ )
2956
+ end
2957
+
2958
+ def table_name
2959
+ #
2960
+ # Used by:
2961
+ # - drop table
2962
+ # - delete
2963
+ # - update
2964
+ # - insert
2965
+ # - alter table
2966
+ #
2967
+ # Relevant `parse.y` grammar rules:
2968
+ # fullname ::= nm | nm DOT nm
2969
+ #
2970
+ # Syntax diagram:
2971
+ # ◯─▶┬▶{ schema-name }─▶{ . }┬▶{ object-name }─▶◯
2972
+ # └───────────▶───────────┘
2973
+ #
2974
+ # Simplified grammar:
2975
+ # name [DOT name]
2976
+ #
2977
+ # Relevant SQLite documentation:
2978
+ # - https://www.sqlite.org/lang_createtable.html
2979
+ # - https://www.sqlite.org/lang_createvtab.html
2980
+ #
2981
+ # A table or virtual table name, where the schema name is optional.
2982
+ #
2983
+
2984
+ if peek == :DOT
2985
+ schema_tk = name
2986
+ dot_tk = require :DOT
2987
+ table_tk = name
2988
+
2989
+ TableName.concrete(
2990
+ full_source: @lexer.sql,
2991
+ schema_tk: Token::Identifier(schema_tk),
2992
+ dot_tk: Token::Punctuation(dot_tk),
2993
+ table_tk: Token::Identifier(table_tk),
2994
+ )
2995
+ else
2996
+ table_tk = name
2997
+
2998
+ TableName.concrete(
2999
+ full_source: @lexer.sql,
3000
+ table_tk: Token::Identifier(table_tk),
3001
+ )
3002
+ end
3003
+ end
3004
+
3005
+ # `xfullname` plus the optional `indexed_opt` - the target table of INSERT, UPDATE and DELETE.
3006
+ # Unlike in FROM, an alias here needs the AS
3007
+ def qualified_table_name(indexed:)
3008
+ #
3009
+ # Relevant `parse.y` grammar rules:
3010
+ # xfullname ::= nm | nm DOT nm | nm DOT nm AS nm | nm AS nm
3011
+ # indexed_opt ::= . | indexed_by
3012
+ # indexed_by ::= INDEXED BY nm | NOT INDEXED
3013
+ #
3014
+ # Relevant SQLite documentation:
3015
+ # - https://www.sqlite.org/syntax/qualified-table-name.html
3016
+ #
3017
+ table_tk = name
3018
+ if (dot_tk = maybe :DOT)
3019
+ schema_tk = table_tk
3020
+ table_tk = name
3021
+ end
3022
+ if (as_kw = maybe :AS)
3023
+ alias_tk = name
3024
+ end
3025
+ indexed_kw, index_tk = indexed_by if indexed
3026
+
3027
+ TableName.concrete(
3028
+ full_source: @lexer.sql,
3029
+ schema_tk: Token::Identifier(schema_tk),
3030
+ dot_tk: Token::Punctuation(dot_tk),
3031
+ table_tk: Token::Identifier(table_tk),
3032
+ as_kw: Token::Keyword(as_kw),
3033
+ alias_tk: Token::Identifier(alias_tk),
3034
+ indexed_kw: Token::Keyword(indexed_kw),
3035
+ index_tk: Token::Identifier(index_tk),
3036
+ )
3037
+ end
3038
+
3039
+ def column_reference
3040
+ ColumnName.concrete(
3041
+ full_source: @lexer.sql,
3042
+ column_tk: Token::Identifier(name),
3043
+ )
3044
+ end
3045
+
3046
+ def set_list
3047
+ #
3048
+ # Relevant `parse.y` grammar rules:
3049
+ # setlist ::= setlist COMMA nm EQ expr | setlist COMMA LP idlist RP EQ expr |
3050
+ # nm EQ expr | LP idlist RP EQ expr
3051
+ #
3052
+ require_some(trailing_sep: :COMMA) do
3053
+ if (columns_lp = maybe :LP)
3054
+ columns = require_some(trailing_sep: :COMMA) { column_reference }
3055
+ columns_rp = require :RP
3056
+ else
3057
+ columns = [column_reference]
3058
+ end
3059
+ eq_tk = require :EQ
3060
+ value = expression
3061
+
3062
+ Assignment.concrete(
3063
+ full_source: @lexer.sql,
3064
+ columns_lp: Token::Punctuation(columns_lp),
3065
+ columns: columns,
3066
+ columns_rp: Token::Punctuation(columns_rp),
3067
+ eq_tk: Token::Punctuation(eq_tk),
3068
+ value: value,
3069
+ )
3070
+ end
3071
+ end
3072
+
3073
+ def upsert_clause
3074
+ #
3075
+ # Syntax diagram:
3076
+ # ◯─▶{ ON }─▶{ CONFLICT }┬───────────────────────────────────────────────────────────┬┐
3077
+ # └▶{ ( }┬▶[ indexed-column ]┬▶{ ) }┬────────────────────────┤│
3078
+ # └───────{ , }◀──────┘ └▶{ WHERE }─▶[ expr ]─┘ ││
3079
+ # ┌───────────────────────────────────────────◀────────────────────────────────────┘│
3080
+ # └▶{ DO }┬▶{ NOTHING }─────────────────────────────────────────────────────────────┬─▶◯
3081
+ # └▶{ UPDATE }─▶{ SET }─▶[ assignment … ]┬───────────────────────┬──────────┘
3082
+ # └▶{ WHERE }─▶[ expr ]──┘
3083
+ #
3084
+ # Relevant SQLite documentation:
3085
+ # - https://www.sqlite.org/lang_upsert.html
3086
+ #
3087
+ on_conflict_kw = require_all_of :ON, :CONFLICT
3088
+ if (target_lp = maybe :LP)
3089
+ targets = require_some(trailing_sep: :COMMA) { ordering_term }
3090
+ target_rp = require :RP
3091
+ if (target_where_kw = maybe :WHERE)
3092
+ target_condition = expression
3093
+ end
3094
+ end
3095
+ do_kw = require :DO
3096
+ action_kw = require_one_of :NOTHING, :UPDATE
3097
+ if action_kw[0] == :UPDATE
3098
+ set_kw = require :SET
3099
+ assignments = set_list
3100
+ if (where_kw = maybe :WHERE)
3101
+ condition = expression
3102
+ end
3103
+ end
3104
+
3105
+ UpsertClause.concrete(
3106
+ full_source: @lexer.sql,
3107
+ on_conflict_kw: Token::Keyword(on_conflict_kw),
3108
+ target_lp: Token::Punctuation(target_lp),
3109
+ targets: targets,
3110
+ target_rp: Token::Punctuation(target_rp),
3111
+ target_where_kw: Token::Keyword(target_where_kw),
3112
+ target_condition: target_condition,
3113
+ do_kw: Token::Keyword(do_kw),
3114
+ action_kw: Token::Keyword(action_kw),
3115
+ set_kw: Token::Keyword(set_kw),
3116
+ assignments: assignments,
3117
+ where_kw: Token::Keyword(where_kw),
3118
+ condition: condition,
3119
+ )
3120
+ end
3121
+
3122
+ def returning_clause
3123
+ #
3124
+ # Relevant SQLite documentation:
3125
+ # - https://www.sqlite.org/lang_returning.html
3126
+ #
3127
+ return unless (returning_kw = maybe :RETURNING)
3128
+
3129
+ ReturningClause.concrete(
3130
+ full_source: @lexer.sql,
3131
+ returning_kw: Token::Keyword(returning_kw),
3132
+ columns: require_some(trailing_sep: :COMMA) { result_column },
3133
+ )
3134
+ end
3135
+
3136
+ # The statement-level tail of SELECT. UPDATE and DELETE take it too when SQLite is built
3137
+ # with SQLITE_ENABLE_UPDATE_DELETE_LIMIT, so we accept it there as well
3138
+ def order_and_limit
3139
+ if (order_by_kw = maybe_all_of :ORDER, :BY)
3140
+ orderings = require_some(trailing_sep: :COMMA) { ordering_term }
3141
+ end
3142
+ if (limit_kw = maybe :LIMIT)
3143
+ limit = expression
3144
+ if (offset_tk = maybe :OFFSET)
3145
+ offset = expression
3146
+ elsif (offset_tk = maybe :COMMA)
3147
+ # `LIMIT 5, 10` means skip 5 and take 10, the reverse of what it looks like
3148
+ offset = limit
3149
+ limit = expression
3150
+ end
3151
+ end
3152
+
3153
+ {
3154
+ order_by_kw: Token::Keyword(order_by_kw),
3155
+ orderings: orderings,
3156
+ limit_kw: Token::Keyword(limit_kw),
3157
+ limit: limit,
3158
+ offset_tk: Token::Keyword(offset_tk),
3159
+ offset: offset,
3160
+ }
3161
+ end
3162
+
3163
+ def result_column
3164
+ #
3165
+ # Relevant `parse.y` grammar rules:
3166
+ # selcollist ::= sclp expr as | sclp STAR | sclp nm DOT STAR
3167
+ # sclp ::= selcollist COMMA | .
3168
+ # as ::= AS nm | ids | .
3169
+ # nm ::= idj | STRING
3170
+ #
3171
+ # Syntax diagram:
3172
+ # ◯┬─▶[ expr ]─┬─────────────▶───────────────┬─▶◯
3173
+ # │ ├─▶{ AS }┬▶{ column-alias }─▶─┤
3174
+ # │ └────▶───┘ │
3175
+ # ├─▶{ * }────────────────────────────────▶─┤
3176
+ # └─▶{ table-name }─▶{ . }─▶{ * }─────────▶─┘
3177
+ #
3178
+ # Simplified grammar:
3179
+ # expr [[AS] alias] | * | table.*
3180
+ #
3181
+ # Relevant SQLite documentation:
3182
+ # - https://www.sqlite.org/syntax/result-column.html
3183
+ #
3184
+ # One of the columns a select core returns.
3185
+ #
3186
+ if (star_tk = maybe :STAR)
3187
+ StarResultColumn.concrete(
3188
+ full_source: @lexer.sql,
3189
+ star_tk: Token::Punctuation(star_tk),
3190
+ )
3191
+ elsif peek(2) == [:DOT, :STAR]
3192
+ table_tk = name
3193
+ dot_tk = require :DOT
3194
+ star_tk = require :STAR
3195
+
3196
+ StarResultColumn.concrete(
3197
+ full_source: @lexer.sql,
3198
+ table_tk: Token::Identifier(table_tk),
3199
+ dot_tk: Token::Punctuation(dot_tk),
3200
+ star_tk: Token::Punctuation(star_tk),
3201
+ )
3202
+ else
3203
+ result = expression
3204
+ as_kw, alias_tk = as_alias
3205
+ result_attr = (Node === result) ? { result: result } : { result_tk: result }
3206
+
3207
+ ResultColumn.concrete(
3208
+ full_source: @lexer.sql,
3209
+ **result_attr,
3210
+ as_kw: Token::Keyword(as_kw),
3211
+ alias_tk: Token::Identifier(alias_tk),
3212
+ )
3213
+ end
3214
+ end
3215
+
3216
+ # `as ::= AS nm | ids | .` - without the AS only an identifier or a string will do,
3217
+ # so keywords like INDEXED or the join keywords are never mistaken for an alias
3218
+ def as_alias
3219
+ if (as_kw = maybe :AS)
3220
+ [as_kw, name]
3221
+ else
3222
+ [nil, maybe { identifier_or_string }]
3223
+ end
3224
+ end
3225
+
3226
+ # indexed_by ::= INDEXED BY nm | NOT INDEXED
3227
+ def indexed_by
3228
+ if (indexed_kw = maybe_all_of :INDEXED, :BY)
3229
+ [indexed_kw, name]
3230
+ else
3231
+ [maybe_all_of(:NOT, :INDEXED), nil]
3232
+ end
3233
+ end
3234
+
3235
+ def join_clause
3236
+ #
3237
+ # Relevant `parse.y` grammar rules:
3238
+ # seltablist ::= stl_prefix nm dbnm as on_using |
3239
+ # stl_prefix nm dbnm as indexed_by on_using |
3240
+ # stl_prefix nm dbnm LP exprlist RP as on_using |
3241
+ # stl_prefix LP select RP as on_using |
3242
+ # stl_prefix LP seltablist RP as on_using
3243
+ # stl_prefix ::= seltablist joinop | .
3244
+ # joinop ::= COMMA | JOIN | JOIN_KW JOIN | JOIN_KW nm JOIN | JOIN_KW nm nm JOIN
3245
+ # on_using ::= ON expr | USING LP idlist RP | .
3246
+ #
3247
+ # Syntax diagram:
3248
+ # ◯─▶[ table-or-subquery ]┬───────────────────────────────▶────────────────────────────────┬─▶◯
3249
+ # └┬▶[ join-operator ]─▶[ table-or-subquery ]─▶[ join-constraint ]┬┘
3250
+ # └───────────────────────────────◀──────────────────────────────┘
3251
+ #
3252
+ # Simplified grammar:
3253
+ # table_or_subquery [join_operator table_or_subquery [join_constraint]]*
3254
+ #
3255
+ # Relevant SQLite documentation:
3256
+ # - https://www.sqlite.org/syntax/join-clause.html
3257
+ #
3258
+ # Everything after FROM. A comma between sources is a join operator as well.
3259
+ #
3260
+ left = table_or_subquery
3261
+ joins = []
3262
+
3263
+ while (operator = join_operator)
3264
+ source = table_or_subquery
3265
+ constraint = join_constraint
3266
+
3267
+ joins << Join.concrete(
3268
+ full_source: @lexer.sql,
3269
+ operator: operator,
3270
+ source: source,
3271
+ constraint: constraint,
3272
+ )
3273
+ end
3274
+
3275
+ return left if joins.empty?
3276
+
3277
+ JoinClause.concrete(
3278
+ full_source: @lexer.sql,
3279
+ left: left,
3280
+ joins: joins,
3281
+ )
3282
+ end
3283
+
3284
+ JOIN_KEYWORDS = Set[:NATURAL, :LEFT, :RIGHT, :FULL, :INNER, :OUTER, :CROSS].freeze
3285
+
3286
+ def join_operator
3287
+ #
3288
+ # Syntax diagram:
3289
+ # ◯┬──────────────▶─────────────────────────────────────────┬─▶◯
3290
+ # ├─▶{ , }──────────────────────────────────────────────▶─┤
3291
+ # ├─▶{ NATURAL }─┬┬─▶{ LEFT }──┬┬────────────┬┬▶{ JOIN }─┘
3292
+ # └──────▶───────┘├─▶{ RIGHT }─┤└▶{ OUTER }─▶┘│
3293
+ # ├─▶{ FULL }──┘ │
3294
+ # ├─▶{ INNER }───────────────▶┤
3295
+ # └─▶{ CROSS }───────────────▶┘
3296
+ #
3297
+ # Relevant SQLite documentation:
3298
+ # - https://www.sqlite.org/syntax/join-operator.html
3299
+ #
3300
+ if (comma_tk = maybe :COMMA)
3301
+ return JoinOperator.concrete(
3302
+ full_source: @lexer.sql,
3303
+ operator_tk: Token::Punctuation(comma_tk),
3304
+ )
3305
+ end
3306
+
3307
+ keywords = []
3308
+ keywords << advance while keywords.size < 3 && JOIN_KEYWORDS.include?(current_token)
3309
+ join_tk = keywords.any? ? require(:JOIN) : maybe(:JOIN)
3310
+ return unless join_tk
3311
+
3312
+ JoinOperator.concrete(
3313
+ full_source: @lexer.sql,
3314
+ operator_tk: Token::Keyword(token_span_from(*keywords, join_tk)),
3315
+ )
3316
+ end
3317
+
3318
+ def join_constraint
3319
+ #
3320
+ # Syntax diagram:
3321
+ # ◯─┬─▶{ ON }─▶[ expr ]─────────────────────────────▶─┬─▶◯
3322
+ # ├─▶{ USING }─▶{ ( }┬▶{ column-name }┬▶{ ) }────▶─┤
3323
+ # │ └──────{ , }◀────┘ │
3324
+ # └────────────────────────▶────────────────────────┘
3325
+ #
3326
+ # Relevant SQLite documentation:
3327
+ # - https://www.sqlite.org/syntax/join-constraint.html
3328
+ #
3329
+ if (on_kw = maybe :ON)
3330
+ JoinOnConstraint.concrete(
3331
+ full_source: @lexer.sql,
3332
+ on_kw: Token::Keyword(on_kw),
3333
+ expression: expression,
3334
+ )
3335
+ elsif (using_kw = maybe :USING)
3336
+ columns_lp = require :LP
3337
+ columns = require_some(trailing_sep: :COMMA) { column_reference }
3338
+ columns_rp = require :RP
3339
+
3340
+ JoinUsingConstraint.concrete(
3341
+ full_source: @lexer.sql,
3342
+ using_kw: Token::Keyword(using_kw),
3343
+ columns_lp: Token::Punctuation(columns_lp),
3344
+ columns: columns,
3345
+ columns_rp: Token::Punctuation(columns_rp),
3346
+ )
3347
+ end
3348
+ end
3349
+
3350
+ def table_or_subquery
3351
+ #
3352
+ # Relevant `parse.y` grammar rules:
3353
+ # seltablist ::= stl_prefix nm dbnm as on_using |
3354
+ # stl_prefix nm dbnm as indexed_by on_using |
3355
+ # stl_prefix nm dbnm LP exprlist RP as on_using |
3356
+ # stl_prefix LP select RP as on_using |
3357
+ # stl_prefix LP seltablist RP as on_using
3358
+ # indexed_by ::= INDEXED BY nm | NOT INDEXED
3359
+ # as ::= AS nm | ids | .
3360
+ # dbnm ::= . | DOT nm
3361
+ #
3362
+ # Syntax diagram:
3363
+ # ┌───────┬────────────────┐
3364
+ # ◯┬─────────────────────────┬─{ table-name }┴▶{ AS }┴▶{ table-alias }┴┬──────────┬─▶◯
3365
+ # ├─▶{ schema-name }─▶{ . }─┤ ┌───────────────────◀─────────────────┘ │
3366
+ # │ │ ├─▶{ INDEXED }─▶{ BY }─▶{ index-name }─────────▶─┤
3367
+ # │ │ └─▶{ NOT }─▶{ INDEXED }────────────────────────▶─┤
3368
+ # │ │ ┌──{ , }◀─┐ │
3369
+ # ├─▶───────────────────────┴───{ table-function-name }─▶{ ( }┴▶[ expr ]┴▶{ ) }┬─┤
3370
+ # │ ┌────────┬───────◀───────────┘ │
3371
+ # │ ┌──────────────┴▶{ AS }┬┴▶{ table-alias }───▶─┤
3372
+ # ├─▶{ ( }─▶[ select-stmt ]─▶{ ) }─┴──────────────────────┴────────────────────▶─┤
3373
+ # └─▶{ ( }┬┬▶[ table-or-subquery ]┬┬▶{ ) }─────────────────────────────────────▶─┘
3374
+ # │└─────────{ , }◀───────┘│
3375
+ # └─▶────[ join-clause ]───┘
3376
+ #
3377
+ # Simplified grammar:
3378
+ # [schema .] table [[AS] alias] [INDEXED BY index | NOT INDEXED]
3379
+ # | [schema .] function (expr [, expr]*) [[AS] alias]
3380
+ # | (select_stmt) [[AS] alias]
3381
+ # | (join_clause) [[AS] alias]
3382
+ #
3383
+ # Relevant SQLite documentation:
3384
+ # - https://www.sqlite.org/syntax/table-or-subquery.html
3385
+ #
3386
+ # One source of rows in a FROM clause.
3387
+ #
3388
+ if (source_lp = maybe :LP)
3389
+ if subquery_start?
3390
+ select = select_stmt
3391
+ source_rp = require :RP
3392
+ as_kw, alias_tk = as_alias
3393
+
3394
+ SubquerySource.concrete(
3395
+ full_source: @lexer.sql,
3396
+ select_lp: Token::Punctuation(source_lp),
3397
+ select: select,
3398
+ select_rp: Token::Punctuation(source_rp),
3399
+ as_kw: Token::Keyword(as_kw),
3400
+ alias_tk: Token::Identifier(alias_tk),
3401
+ )
3402
+ else
3403
+ source = join_clause
3404
+ source_rp = require :RP
3405
+ as_kw, alias_tk = as_alias
3406
+
3407
+ ParentheticalSource.concrete(
3408
+ full_source: @lexer.sql,
3409
+ source_lp: Token::Punctuation(source_lp),
3410
+ source: source,
3411
+ source_rp: Token::Punctuation(source_rp),
3412
+ as_kw: Token::Keyword(as_kw),
3413
+ alias_tk: Token::Identifier(alias_tk),
3414
+ )
3415
+ end
3416
+ else
3417
+ schema_tk = nil
3418
+ object_tk = name
3419
+ if (dot_tk = maybe :DOT)
3420
+ schema_tk = object_tk
3421
+ object_tk = name
3422
+ end
3423
+
3424
+ if (arguments_lp = maybe :LP)
3425
+ arguments = maybe_some(trailing_sep: :COMMA) { expression }
3426
+ arguments_rp = require :RP
3427
+ as_kw, alias_tk = as_alias
3428
+
3429
+ TableFunction.concrete(
3430
+ full_source: @lexer.sql,
3431
+ schema_tk: Token::Identifier(schema_tk),
3432
+ dot_tk: Token::Punctuation(dot_tk),
3433
+ function_tk: Token::Identifier(object_tk),
3434
+ arguments_lp: Token::Punctuation(arguments_lp),
3435
+ arguments: arguments,
3436
+ arguments_rp: Token::Punctuation(arguments_rp),
3437
+ as_kw: Token::Keyword(as_kw),
3438
+ alias_tk: Token::Identifier(alias_tk),
3439
+ )
3440
+ else
3441
+ as_kw, alias_tk = as_alias
3442
+ indexed_kw, index_tk = indexed_by
3443
+
3444
+ TableName.concrete(
3445
+ full_source: @lexer.sql,
3446
+ schema_tk: Token::Identifier(schema_tk),
3447
+ dot_tk: Token::Punctuation(dot_tk),
3448
+ table_tk: Token::Identifier(object_tk),
3449
+ as_kw: Token::Keyword(as_kw),
3450
+ alias_tk: Token::Identifier(alias_tk),
3451
+ indexed_kw: Token::Keyword(indexed_kw),
3452
+ index_tk: Token::Identifier(index_tk),
3453
+ )
3454
+ end
3455
+ end
3456
+ end
3457
+
3458
+ def literal_value
3459
+ #
3460
+ # Relevant `parse.y` grammar rules:
3461
+ # term ::= NULL | FLOAT | BLOB | STRING | INTEGER | CTIME_KW | QNUMBER
3462
+ #
3463
+ # Syntax diagram:
3464
+ # ◯─┬─▶{ numeric-literal }────▶─┬─▶◯
3465
+ # ├─▶{ string-literal }─────▶─┤
3466
+ # ├─▶{ blob-literal }───────▶─┤
3467
+ # ├─▶{ NULL }───────────────▶─┤
3468
+ # ├─▶{ TRUE }───────────────▶─┤
3469
+ # ├─▶{ FALSE }──────────────▶─┤
3470
+ # ├─▶{ CURRENT_TIME }───────▶─┤
3471
+ # ├─▶{ CURRENT_DATE }───────▶─┤
3472
+ # └─▶{ CURRENT_TIMESTAMP }──▶─┘
3473
+ #
3474
+ # Simplified grammar:
3475
+ # NULL | CURRENT_TIME | CURRENT_DATE | CURRENT_TIMESTAMP | INTEGER | FLOAT | STRING | BLOB | TRUE | FALSE | QNUMBER
3476
+ #
3477
+ # Relevant SQLite documentation:
3478
+ # - https://www.sqlite.org/syntax/literal-value.html
3479
+ #
3480
+ # A value
3481
+ #
3482
+ case current_token
3483
+ when :NULL
3484
+ tk = require :NULL
3485
+ Token::Null(tk)
3486
+ when :CURRENT_TIME
3487
+ tk = require :CURRENT_TIME
3488
+ Token::Current(tk)
3489
+ when :CURRENT_DATE
3490
+ tk = require :CURRENT_DATE
3491
+ Token::Current(tk)
3492
+ when :CURRENT_TIMESTAMP
3493
+ tk = require :CURRENT_TIMESTAMP
3494
+ Token::Current(tk)
3495
+ when :TRUE
3496
+ tk = require :TRUE
3497
+ Token::True(tk)
3498
+ when :FALSE
3499
+ tk = require :FALSE
3500
+ Token::False(tk)
3501
+ when :QNUMBER
3502
+ tk = require_qnumber
3503
+ Token::Numeric(tk)
3504
+ when :INTEGER
3505
+ tk = require :INTEGER
3506
+ Token::Numeric(tk)
3507
+ when :FLOAT
3508
+ tk = require :FLOAT
3509
+ Token::Numeric(tk)
3510
+ when :STRING
3511
+ tk = require :STRING
3512
+ Token::String(tk)
3513
+ when :BLOB
3514
+ tk = require :BLOB
3515
+ Token::Blob(tk)
3516
+ else
3517
+ expected!(:INTEGER, :FLOAT, :QNUMBER, :STRING, :BLOB, :NULL, :TRUE, :FALSE, :CURRENT_TIME, :CURRENT_DATE, :CURRENT_TIMESTAMP)
3518
+ end
3519
+ end
3520
+
3521
+ # `nulls`: whether NULLS FIRST/LAST may follow, as in the sortlist of a PRIMARY KEY or UNIQUE -
3522
+ # not in the eidlist of a foreign key, a view or a CTE
3523
+ def indexed_column(nulls: false)
3524
+ # Used by:
3525
+ # - ccons, tcons, creat-view, cte
3526
+ #
3527
+ # Relevant `parse.y` grammar rules:
3528
+ # eidlist ::= eidlist COMMA nm collate sortorder | nm collate sortorder
3529
+ # collate ::= . | COLLATE ids.
3530
+ # sortorder ::= . | ASC | DESC
3531
+ # nm ::= idj | STRING
3532
+ # (PRIMARY KEY and UNIQUE take a sortlist, whose items may also end NULLS FIRST or NULLS LAST)
3533
+ #
3534
+ # Syntax diagram:
3535
+ # ◯──▶{ column-name }─┬─▶───────────────────────────────┬┬────────▶───┬──▶◯
3536
+ # └─▶{ COLLATE }─▶{ collation-name }┘├─▶{ ASC }─▶─┤
3537
+ # └─▶{ DESC }──┘
3538
+ #
3539
+ # Simplified grammar:
3540
+ # name [COLLATE identifier] [ASC | DESC] [NULLS FIRST | NULLS LAST]
3541
+ #
3542
+ # Relevant SQLite documentation:
3543
+ # - https://www.sqlite.org/syntax/indexed-column.html
3544
+ #
3545
+ # The eidlist non-terminal (Expression Id List) generates an ExprList
3546
+ # from a list of identifiers. The identifier names are in ExprList.a[].zName.
3547
+ # This list is stored in an ExprList rather than an IdList so that it
3548
+ # can be easily sent to sqlite3ColumnsExprList().
3549
+ #
3550
+ # IMPORTANT COMPATIBILITY NOTE: Some prior versions of SQLite accepted
3551
+ # COLLATE clauses and ASC or DESC keywords on ID lists in inappropriate
3552
+ # places - places that might have been stored in the sqlite_schema table.
3553
+ # Those extra features were ignored. But because they might be in some
3554
+ # (busted) old databases, we need to continue parsing them when loading
3555
+ # historical schemas.
3556
+ #
3557
+ column_name = name(except: [:COLLATE, :ASC, :DESC].freeze)
3558
+ if (collate_kw = maybe :COLLATE)
3559
+ collation_tk = identifier_or_string(except: [:ASC, :DESC].freeze)
3560
+ end
3561
+ direction_tk = maybe_one_of :ASC, :DESC
3562
+ nulls_tk = maybe_one_of [:NULLS, :FIRST].freeze, [:NULLS, :LAST].freeze if nulls
3563
+
3564
+ IndexedColumn.concrete(
3565
+ full_source: @lexer.sql,
3566
+ name_tk: Token::Identifier(column_name),
3567
+ collate_kw: Token::Keyword(collate_kw),
3568
+ collation_tk: Token::Keyword(collation_tk),
3569
+ direction_tk: Token::Keyword(direction_tk),
3570
+ nulls_tk: Token::Keyword(nulls_tk),
3571
+ )
3572
+ end
3573
+
3574
+ def ordering_term
3575
+ #
3576
+ # Relevant `parse.y` grammar rules:
3577
+ # sortlist ::= sortlist COMMA expr sortorder nulls |
3578
+ # expr sortorder nulls
3579
+ # sortorder ::= ASC | DESC | .
3580
+ # nulls ::= NULLS FIRST | NULLS LAST | .
3581
+ # eidlist ::= eidlist COMMA nm collate sortorder |
3582
+ # nm collate sortorder
3583
+ # collate ::= . |
3584
+ # COLLATE ids.
3585
+ #
3586
+ # Syntax diagram:
3587
+ # ◯─▶[ expr ]┬───────────────▶────────────────┬▶┬──────▶─────┬▶┬──────────▶─────────────┬─▶◯
3588
+ # └▶{ COLLATE }─▶{ collation-name }┘ ├▶{ ASC }──▶─┤ ├▶{ NULLS }─▶{ FIRST }─▶─┤
3589
+ # └▶{ DESC }─▶─┘ └▶{ NULLS }─▶{ LAST }──▶─┘
3590
+ #
3591
+ # Simplified grammar:
3592
+ #
3593
+ # Relevant SQLite documentation:
3594
+ # -
3595
+ #
3596
+ # [description]
3597
+ #
3598
+ e = expression
3599
+ if (collate_kw = maybe(:COLLATE))
3600
+ collation = identifier
3601
+ end
3602
+ if current_token in :ASC | :DESC
3603
+ direction = require current_token
3604
+ end
3605
+ if (nulls_kw = maybe(:NULLS))
3606
+ nulls = require_one_of(:FIRST, :LAST)
3607
+ end
3608
+
3609
+ OrderingTerm.concrete(
3610
+ full_source: @lexer.sql,
3611
+ expression: e,
3612
+ collate_kw: Token::Keyword(collate_kw),
3613
+ collation_tk: Token::Identifier(collation),
3614
+ direction_tk: Token::Keyword(direction),
3615
+ nulls_kw: Token::Keyword(nulls_kw),
3616
+ nulls_tk: Token::Keyword(nulls),
3617
+ )
3618
+ end
3619
+
3620
+ def signed_number
3621
+ #
3622
+ # Used by:
3623
+ # - pragma_value
3624
+ # - type_name
3625
+ #
3626
+ # Relevant `parse.y` grammar rules:
3627
+ # signed ::= plus_num | minus_num
3628
+ # plus_num ::= PLUS number | number
3629
+ # minus_num ::= MINUS number
3630
+ # number ::= INTEGER | FLOAT
3631
+ #
3632
+ # Syntax diagram:
3633
+ # ◯─┬───▶────┬─▶{ numeric-literal }─▶◯
3634
+ # ├─▶{ + }─┤
3635
+ # └─▶{ - }─┘
3636
+ #
3637
+ # Simplified grammar:
3638
+ # [PLUS | MINUS] INTEGER | FLOAT
3639
+ #
3640
+ # Relevant SQLite documentation:
3641
+ # - https://www.sqlite.org/syntax/signed-number.html
3642
+ #
3643
+ # A signed number is a numeric literal with an optional sign.
3644
+ #
3645
+ sign_tk = maybe_one_of :PLUS, :MINUS
3646
+ value_tk = require_one_of :INTEGER, :FLOAT
3647
+
3648
+ return value_tk unless sign_tk
3649
+
3650
+ sign_tk, beg, _fin = sign_tk
3651
+ value_tk, _beg, fin = value_tk
3652
+
3653
+ [[sign_tk, value_tk], beg, fin]
3654
+ end
3655
+
3656
+ # ---------- Token Classes ----------
3657
+
3658
+ def name(except: [])
3659
+ # Relevant `parse.y` grammar rules:
3660
+ # // The name of a column or table can be any of the following:
3661
+ # nm ::= idj | STRING
3662
+ #
3663
+ if current_token in :STRING
3664
+ require current_token
3665
+ elsif (idj = maybe { identifier_or_join_keyword(except: except) })
3666
+ idj
3667
+ else
3668
+ expected!(:STRING, "idj")
3669
+ end
3670
+ end
3671
+
3672
+ def identifier(except: [])
3673
+ # Relevant `parse.y` grammar rules:
3674
+ # // An IDENTIFIER can be a generic identifier, or one of several
3675
+ # // keywords. Any non-standard keyword can also be an identifier.
3676
+ # %token_class id ID | INDEXED
3677
+ #
3678
+ if current_token in :ID | :INDEXED
3679
+ require current_token
3680
+ elsif !except.include?(current_token) && TOKEN_FALLBACKS.include?(current_token)
3681
+ require current_token
3682
+ else
3683
+ expected!(:ID, :INDEXED, "fallback")
3684
+ end
3685
+ end
3686
+
3687
+ def identifier_or_string(except: [])
3688
+ # Relevant `parse.y` grammar rules:
3689
+ # // And "ids" is an identifer-or-string.
3690
+ # %token_class ids ID | STRING
3691
+ #
3692
+ if current_token in :ID | :STRING
3693
+ require current_token
3694
+ elsif !except.include?(current_token) && TOKEN_FALLBACKS.include?(current_token)
3695
+ require current_token
3696
+ else
3697
+ expected!(:ID, :STRING, "fallback")
3698
+ end
3699
+ end
3700
+
3701
+ def identifier_or_join_keyword(except: [])
3702
+ # Relevant `parse.y` grammar rules:
3703
+ # // An identifier or a join-keyword
3704
+ # %token_class idj ID | INDEXED | JOIN_KW
3705
+ #
3706
+ if current_token in :ID | :INDEXED | :CROSS | :FULL | :INNER | :LEFT | :NATURAL | :OUTER | :RIGHT
3707
+ require current_token
3708
+ elsif !except.include?(current_token) && TOKEN_FALLBACKS.include?(current_token)
3709
+ require current_token
3710
+ else
3711
+ expected!(:ID, :INDEXED, :JOIN_KW, "fallback")
3712
+ end
3713
+ end
3714
+
3715
+ private
3716
+
3717
+ # ---------- Parsing Helpers ----------
3718
+
3719
+ def require(token)
3720
+ if token == current_token
3721
+ advance
3722
+ else
3723
+ expected!(token)
3724
+ end
3725
+ end
3726
+
3727
+ def maybe(token = nil) # &block
3728
+ if block_given?
3729
+ start_buffer = @peek_buffer.dup
3730
+ start_lexer_cursor = @lexer.cursor
3731
+ start_lexer_pos = @lexer.anchor
3732
+ start_last_consumed = @last_consumed_token
3733
+ start_last_consumed_fin = @last_consumed_fin
3734
+
3735
+ result = catch(:ERROR) { yield }
3736
+
3737
+ if ErrorMessage === result
3738
+ @peek_buffer = start_buffer
3739
+ @lexer.cursor = start_lexer_cursor
3740
+ @lexer.anchor = start_lexer_pos
3741
+ @last_consumed_token = start_last_consumed
3742
+ @last_consumed_fin = start_last_consumed_fin
3743
+ nil
3744
+ else
3745
+ result
3746
+ end
3747
+ elsif token == current_token
3748
+ advance
3749
+ end
3750
+ end
3751
+
3752
+ # The span of whatever the block consumes. Positions come from the buffered tokens - the
3753
+ # lexer itself may already be several tokens ahead, looking ahead for us
3754
+ def scan(&block)
3755
+ beg = ensure_buffer[0][1]
3756
+ block.call
3757
+ [[], beg, @last_consumed_fin]
3758
+ end
3759
+
3760
+ def require_all_of(*tokens)
3761
+ i, len = 0, tokens.length
3762
+ start_pos, end_pos = nil
3763
+ while i < len
3764
+ if tokens[i] == current_token
3765
+ _tok, beg, fin = advance
3766
+ start_pos = beg if i == 0 # first iteration
3767
+ end_pos = fin if i == (len - 1) # last iteration
3768
+
3769
+ i += 1
3770
+ else
3771
+ # TODO: only show the tokens from `i` onwards
3772
+ expected!(tokens)
3773
+ end
3774
+ end
3775
+
3776
+ [tokens, start_pos, end_pos].freeze
3777
+ end
3778
+
3779
+ def maybe_all_of(*tokens)
3780
+ advancing = true
3781
+ i, len = 0, tokens.length
3782
+ buf = ensure_buffer(len)
3783
+ while i < len
3784
+ tok, * = buf[i]
3785
+ if tokens[i] != tok
3786
+ advancing = false
3787
+ break
3788
+ end
3789
+ i += 1
3790
+ end
3791
+
3792
+ require_all_of(*tokens) if advancing
3793
+ end
3794
+
3795
+ def maybe_one_of(*tokens)
3796
+ i, len = 0, tokens.length
3797
+ has_nil = false
3798
+ while i < len
3799
+ check = tokens[i]
3800
+ if Symbol === check && (result = maybe check)
3801
+ return result
3802
+ elsif Array === check && (result = maybe_all_of(*check))
3803
+ return result
3804
+ elsif check.nil?
3805
+ has_nil = true
3806
+ i += 1
3807
+ else
3808
+ i += 1
3809
+ end
3810
+ end
3811
+
3812
+ has_nil ? false : nil
3813
+ end
3814
+
3815
+ def require_one_of(*tokens)
3816
+ maybe_one_of(*tokens) || expected!(*tokens)
3817
+ end
3818
+
3819
+ def require_some(trailing_sep: nil, leading_sep: nil, given: nil, &)
3820
+ a = []
3821
+
3822
+ if leading_sep
3823
+ sep_tk = (Array === leading_sep) ? require_one_of(*leading_sep) : require(leading_sep)
3824
+ val = yield(sep_tk)
3825
+ insert = if Node === val
3826
+ val.leading = Token::Punctuation(sep_tk)
3827
+ val
3828
+ else
3829
+ [sep_tk, val]
3830
+ end
3831
+ elsif trailing_sep
3832
+ val = (given || yield)
3833
+ sep_tk = (Array === trailing_sep) ? maybe_one_of(*trailing_sep) : maybe(trailing_sep)
3834
+ insert = if Node === val
3835
+ val.trailing = Token::Punctuation(sep_tk) if sep_tk
3836
+ val
3837
+ else
3838
+ [val, sep_tk]
3839
+ end
3840
+ else
3841
+ val = (given || yield)
3842
+ insert = val
3843
+ end
3844
+ a << insert
3845
+
3846
+ collect(into: a, trailing_sep: trailing_sep, leading_sep: leading_sep, last_sep: sep_tk, &)
3847
+
3848
+ return token_span_from(*a) if a.all?(Array)
3849
+
3850
+ a
3851
+ end
3852
+
3853
+ def maybe_some(trailing_sep: nil, leading_sep: nil, &)
3854
+ a = []
3855
+
3856
+ if leading_sep
3857
+ sep_tk = (Array === leading_sep) ? maybe_one_of(*leading_sep) : maybe(leading_sep)
3858
+ return if sep_tk.nil?
3859
+
3860
+ val = yield(sep_tk)
3861
+ insert = if Node === val
3862
+ val.leading = Token::Punctuation(sep_tk)
3863
+ val
3864
+ else
3865
+ [sep_tk, val]
3866
+ end
3867
+ elsif trailing_sep
3868
+ val = maybe { yield }
3869
+ return a if val.nil?
3870
+
3871
+ sep_tk = (Array === trailing_sep) ? maybe_one_of(*trailing_sep) : maybe(trailing_sep)
3872
+ insert = if Node === val
3873
+ val.trailing = Token::Punctuation(sep_tk) if sep_tk
3874
+ val
3875
+ else
3876
+ [val, sep_tk]
3877
+ end
3878
+ else
3879
+ insert = maybe { yield }
3880
+ end
3881
+
3882
+ if insert
3883
+ a << insert
3884
+
3885
+ collect(into: a, trailing_sep: trailing_sep, leading_sep: leading_sep, last_sep: sep_tk, &)
3886
+
3887
+ return token_span_from(*a) if a.all?(Array)
3888
+
3889
+ a
3890
+ else
3891
+ a
3892
+ end
3893
+ end
3894
+
3895
+ # `last_sep` is what followed the last item in `into`: nil when no separator did, so the list has
3896
+ # ended; a separator token, so another item must follow; false when an optional one was left out
3897
+ def collect(into: [], trailing_sep: nil, leading_sep: nil, last_sep: false, &block)
3898
+ while true
3899
+ break if trailing_sep && last_sep.nil?
3900
+
3901
+ if leading_sep
3902
+ sep_tk = (Array === leading_sep) ? maybe_one_of(*leading_sep) : maybe(leading_sep)
3903
+ break if sep_tk.nil?
3904
+
3905
+ val = yield(sep_tk)
3906
+
3907
+ insert = if Node === val
3908
+ val.leading = Token::Punctuation(sep_tk)
3909
+ val
3910
+ else
3911
+ [sep_tk, val]
3912
+ end
3913
+
3914
+ elsif trailing_sep
3915
+ val = last_sep ? yield : maybe { yield }
3916
+ break if val.nil?
3917
+
3918
+ last_sep = sep_tk = (Array === trailing_sep) ? maybe_one_of(*trailing_sep) : maybe(trailing_sep)
3919
+
3920
+ insert = if Node === val
3921
+ val.trailing = Token::Punctuation(sep_tk) if sep_tk
3922
+ val
3923
+ else
3924
+ [val, sep_tk]
3925
+ end
3926
+
3927
+ else
3928
+ val = maybe { yield }
3929
+ break if val.nil?
3930
+
3931
+ insert = val
3932
+ end
3933
+
3934
+ into << insert
3935
+ end
3936
+
3937
+ into
3938
+ end
3939
+
3940
+ def token_span_from(*spans)
3941
+ spans.compact!
3942
+ i, len = 0, spans.length
3943
+ start_pos, end_pos = nil
3944
+ tokens = []
3945
+ while i < len
3946
+ tok, beg, fin = spans[i]
3947
+ (Array === tok) ? (tokens.concat(tok)) : (tokens << tok)
3948
+ start_pos = beg if i == 0 # first iteration
3949
+ end_pos = fin if i == (len - 1) # last iteration
3950
+
3951
+ i += 1
3952
+ end
3953
+
3954
+ [tokens, start_pos, end_pos]
3955
+ end
3956
+
3957
+ # Access the current token type in the lexer stream.
3958
+ # Reads but doesn't consume the token at the head of the buffer.
3959
+ def current_token
3960
+ buf = ensure_buffer
3961
+ buf[0][0]
3962
+ end
3963
+
3964
+ # SQLite lexes any number holding a "_" as a QNUMBER, and accepts it as a term only if every
3965
+ # "_" sits between two digits - hex digits after 0x
3966
+ def require_qnumber
3967
+ value = current_value
3968
+ misplaced = value.match?(/\A0x/i) ? /(?<!\h)_|_(?!\h)/ : /(?<!\d)_|_(?!\d)/
3969
+ expected!(:INTEGER, :FLOAT) if value.match?(misplaced)
3970
+ require :QNUMBER
3971
+ end
3972
+
3973
+ def statement_end?
3974
+ current_token.nil? || current_token == :SEMI
3975
+ end
3976
+
3977
+ def subquery_start?
3978
+ current_token in :SELECT | :VALUES | :WITH
3979
+ end
3980
+
3981
+ # Access the string value for the current token.
3982
+ # Reads but doesn't consume the start_pos and end_pos values at the head of the buffer.
3983
+ def current_value
3984
+ buf = ensure_buffer
3985
+ sql = @lexer.sql
3986
+ _tk, beg, fin = buf[0]
3987
+
3988
+ sql.byteslice(beg, (fin - beg))
3989
+ end
3990
+
3991
+ # Peek at the next `n` tokens in the lexer stream.
3992
+ # Does not advance the lexer cursor and does not consider the current token.
3993
+ def peek(n = 1)
3994
+ buf = ensure_buffer(n + 1)
3995
+ return buf[1][0] if n == 1 # symbol element of the 2nd token
3996
+
3997
+ [].tap do |a|
3998
+ i = 1
3999
+ while i <= n
4000
+ tok, * = buf[i]
4001
+ a << tok
4002
+ i += 1
4003
+ end
4004
+ end
4005
+ end
4006
+
4007
+ # Advance the implicit cursor pointing to the first token in the buffer by `n` tokens.
4008
+ # Returns the new token at the head of the buffer.
4009
+ def advance
4010
+ ensure_buffer
4011
+ token = @peek_buffer.shift
4012
+ @last_consumed_fin = token[2]
4013
+ @last_consumed_token = token[0]
4014
+ token
4015
+ end
4016
+
4017
+ # Ensure that the `@peek_buffer` has at least `size` tokens.
4018
+ # Note: the buffer is a nested array of `[token, start_pos, end_pos]` triples
4019
+ def ensure_buffer(size = 1)
4020
+ lexer = @lexer
4021
+ @peek_buffer.tap do |buf|
4022
+ while buf.size < size
4023
+ token = lexer.next_token
4024
+ if token == :WINDOW || token == :OVER || token == :FILTER
4025
+ token = contextual_keyword(token, buf.empty? ? @last_consumed_token : buf.last[0])
4026
+ end
4027
+ buf << [token, lexer.anchor, lexer.cursor].freeze
4028
+ end
4029
+ end
4030
+ end
4031
+
4032
+ # Like SQLite's tokenize.c, decide from the tokens around them whether WINDOW, OVER and FILTER are
4033
+ # keywords here or names: WINDOW only before `name AS`, OVER only after `)` and before `(` or a
4034
+ # name, FILTER only after `)` and before `(`
4035
+ def contextual_keyword(token, previous)
4036
+ following, after = lex_ahead
4037
+ keyword =
4038
+ case token
4039
+ when :WINDOW then name_token?(following) && after == :AS
4040
+ when :OVER then previous == :RP && (following == :LP || name_token?(following))
4041
+ when :FILTER then previous == :RP && following == :LP
4042
+ end
4043
+ keyword ? token : :ID
4044
+ end
4045
+
4046
+ # The types of the two tokens after the one just lexed, leaving the lexer where it was
4047
+ def lex_ahead
4048
+ cursor, anchor = @lexer.cursor, @lexer.anchor
4049
+ [@lexer.next_token, @lexer.next_token]
4050
+ ensure
4051
+ @lexer.cursor, @lexer.anchor = cursor, anchor
4052
+ end
4053
+
4054
+ # What SQLite's tokenize.c counts as a name when looking around WINDOW and OVER
4055
+ def name_token?(token)
4056
+ (token in :ID | :STRING | :WINDOW | :OVER) || JOIN_KEYWORDS.include?(token) || TOKEN_FALLBACKS.include?(token)
4057
+ end
4058
+
4059
+ def expected!(*expected)
4060
+ token, beg, fin = ensure_buffer[0]
4061
+ throw :ERROR, ErrorMessage.new(@lexer.sql, token, beg, fin, expected)
4062
+ end
4063
+ end
4064
+ end