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.
- checksums.yaml +4 -4
- data/README.md +280 -10
- data/lib/plume/analysis/resolver.rb +574 -0
- data/lib/plume/analysis.rb +135 -0
- data/lib/plume/ast/alter_table_statement.rb +29 -0
- data/lib/plume/ast/analyze_statement.rb +15 -0
- data/lib/plume/ast/assignment.rb +18 -0
- data/lib/plume/ast/attach_statement.rb +17 -0
- data/lib/plume/ast/begin_statement.rb +17 -0
- data/lib/plume/ast/binary_expression.rb +71 -0
- data/lib/plume/ast/case_condition.rb +29 -0
- data/lib/plume/ast/case_expression.rb +22 -0
- data/lib/plume/ast/cast_expression.rb +16 -0
- data/lib/plume/ast/check_column_constraint.rb +20 -0
- data/lib/plume/ast/check_table_constraint.rb +19 -0
- data/lib/plume/ast/collate_column_constraint.rb +19 -0
- data/lib/plume/ast/collation_expression.rb +26 -0
- data/lib/plume/ast/column_constraint.rb +7 -0
- data/lib/plume/ast/column_definition.rb +16 -0
- data/lib/plume/ast/column_name.rb +22 -0
- data/lib/plume/ast/column_type.rb +25 -0
- data/lib/plume/ast/commit_statement.rb +13 -0
- data/lib/plume/ast/common_table_expression.rb +31 -0
- data/lib/plume/ast/compound_select.rb +30 -0
- data/lib/plume/ast/conflict_clause.rb +22 -0
- data/lib/plume/ast/conflict_resolution.rb +20 -0
- data/lib/plume/ast/create_index_statement.rb +29 -0
- data/lib/plume/ast/create_table_statement.rb +47 -0
- data/lib/plume/ast/create_trigger_statement.rb +43 -0
- data/lib/plume/ast/create_view_statement.rb +30 -0
- data/lib/plume/ast/create_virtual_table_statement.rb +28 -0
- data/lib/plume/ast/default_column_constraint.rb +29 -0
- data/lib/plume/ast/delete_statement.rb +28 -0
- data/lib/plume/ast/detach_statement.rb +13 -0
- data/lib/plume/ast/drop_statement.rb +21 -0
- data/lib/plume/ast/empty_function_argument.rb +6 -0
- data/lib/plume/ast/exists_expression.rb +15 -0
- data/lib/plume/ast/explain_statement.rb +15 -0
- data/lib/plume/ast/expression.rb +30 -0
- data/lib/plume/ast/foreign_key_action.rb +26 -0
- data/lib/plume/ast/foreign_key_clause.rb +50 -0
- data/lib/plume/ast/foreign_key_column_constraint.rb +16 -0
- data/lib/plume/ast/foreign_key_table_constraint.rb +20 -0
- data/lib/plume/ast/frame_boundary.rb +35 -0
- data/lib/plume/ast/frame_spec.rb +34 -0
- data/lib/plume/ast/function_arguments.rb +17 -0
- data/lib/plume/ast/function_reference.rb +29 -0
- data/lib/plume/ast/generated_as_column_constraint.rb +24 -0
- data/lib/plume/ast/indexed_column.rb +21 -0
- data/lib/plume/ast/insert_statement.rb +35 -0
- data/lib/plume/ast/join.rb +12 -0
- data/lib/plume/ast/join_clause.rb +15 -0
- data/lib/plume/ast/join_on_constraint.rb +12 -0
- data/lib/plume/ast/join_operator.rb +31 -0
- data/lib/plume/ast/join_using_constraint.rb +16 -0
- data/lib/plume/ast/like_expression.rb +19 -0
- data/lib/plume/ast/literal_expression.rb +29 -0
- data/lib/plume/ast/literal_false.rb +7 -0
- data/lib/plume/ast/literal_nil.rb +7 -0
- data/lib/plume/ast/literal_value.rb +18 -0
- data/lib/plume/ast/match_clause.rb +15 -0
- data/lib/plume/ast/node.rb +207 -0
- data/lib/plume/ast/noop_column_constraint.rb +15 -0
- data/lib/plume/ast/noop_table_constraint.rb +15 -0
- data/lib/plume/ast/not_like_expression.rb +7 -0
- data/lib/plume/ast/not_null_column_constraint.rb +17 -0
- data/lib/plume/ast/null_column_constraint.rb +17 -0
- data/lib/plume/ast/ordering_term.rb +32 -0
- data/lib/plume/ast/over_clause.rb +25 -0
- data/lib/plume/ast/parenthetical_expression.rb +37 -0
- data/lib/plume/ast/parenthetical_source.rb +19 -0
- data/lib/plume/ast/pragma_statement.rb +26 -0
- data/lib/plume/ast/primary_key_column_constraint.rb +23 -0
- data/lib/plume/ast/primary_key_table_constraint.rb +23 -0
- data/lib/plume/ast/raise_expression.rb +23 -0
- data/lib/plume/ast/reindex_statement.rb +15 -0
- data/lib/plume/ast/release_statement.rb +16 -0
- data/lib/plume/ast/result_column.rb +18 -0
- data/lib/plume/ast/returning_clause.rb +12 -0
- data/lib/plume/ast/rollback_statement.rb +19 -0
- data/lib/plume/ast/savepoint_statement.rb +16 -0
- data/lib/plume/ast/select_core.rb +27 -0
- data/lib/plume/ast/select_statement.rb +34 -0
- data/lib/plume/ast/star_function_argument.rb +6 -0
- data/lib/plume/ast/star_result_column.rb +16 -0
- data/lib/plume/ast/strict_table_option.rb +6 -0
- data/lib/plume/ast/subquery_expression.rb +14 -0
- data/lib/plume/ast/subquery_source.rb +19 -0
- data/lib/plume/ast/table_constraint.rb +7 -0
- data/lib/plume/ast/table_function.rb +28 -0
- data/lib/plume/ast/table_name.rb +28 -0
- data/lib/plume/ast/table_option.rb +7 -0
- data/lib/plume/ast/ternary_expression.rb +26 -0
- data/lib/plume/ast/token.rb +180 -0
- data/lib/plume/ast/unary_expression.rb +36 -0
- data/lib/plume/ast/unique_column_constraint.rb +17 -0
- data/lib/plume/ast/unique_table_constraint.rb +20 -0
- data/lib/plume/ast/update_statement.rb +35 -0
- data/lib/plume/ast/upsert_clause.rb +26 -0
- data/lib/plume/ast/vacuum_statement.rb +18 -0
- data/lib/plume/ast/values_clause.rb +12 -0
- data/lib/plume/ast/values_row.rb +13 -0
- data/lib/plume/ast/variable.rb +15 -0
- data/lib/plume/ast/window_definition.rb +26 -0
- data/lib/plume/ast/with_clause.rb +15 -0
- data/lib/plume/ast/without_rowid_table_option.rb +6 -0
- data/lib/plume/catalog.rb +180 -0
- data/lib/plume/lexer.rb +639 -0
- data/lib/plume/parser.rb +4015 -0
- data/lib/plume/printer.rb +471 -0
- data/lib/plume/sqlite_grammar.rb +86 -0
- data/lib/plume/union.rb +28 -0
- data/lib/plume/version.rb +1 -1
- data/lib/plume.rb +143 -3
- metadata +167 -12
data/lib/plume/parser.rb
ADDED
|
@@ -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
|