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