@typed-sql/sqlite 2.0.0-rc.2 → 2.1.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.
- package/CHANGELOG.md +122 -0
- package/README.md +3 -3
- package/dist/packages/sqlite/src/capabilities.d.ts +18 -0
- package/dist/packages/sqlite/src/capabilities.d.ts.map +1 -0
- package/dist/packages/sqlite/src/capabilities.js +155 -0
- package/dist/packages/sqlite/src/capabilities.js.map +1 -0
- package/dist/packages/sqlite/src/catalog/coercions.d.ts +6 -0
- package/dist/packages/sqlite/src/catalog/coercions.d.ts.map +1 -0
- package/dist/packages/sqlite/src/catalog/coercions.js +41 -0
- package/dist/packages/sqlite/src/catalog/coercions.js.map +1 -0
- package/dist/packages/sqlite/src/catalog/date-time.d.ts +5 -0
- package/dist/packages/sqlite/src/catalog/date-time.d.ts.map +1 -0
- package/dist/packages/sqlite/src/catalog/date-time.js +20 -0
- package/dist/packages/sqlite/src/catalog/date-time.js.map +1 -0
- package/dist/packages/sqlite/src/catalog/extensions.d.ts +5 -0
- package/dist/packages/sqlite/src/catalog/extensions.d.ts.map +1 -0
- package/dist/packages/sqlite/src/catalog/extensions.js +65 -0
- package/dist/packages/sqlite/src/catalog/extensions.js.map +1 -0
- package/dist/packages/sqlite/src/catalog/functions.d.ts +57 -0
- package/dist/packages/sqlite/src/catalog/functions.d.ts.map +1 -0
- package/dist/packages/sqlite/src/catalog/functions.js +165 -0
- package/dist/packages/sqlite/src/catalog/functions.js.map +1 -0
- package/dist/packages/sqlite/src/catalog/index.d.ts +8 -0
- package/dist/packages/sqlite/src/catalog/index.d.ts.map +1 -0
- package/dist/packages/sqlite/src/catalog/index.js +8 -0
- package/dist/packages/sqlite/src/catalog/index.js.map +1 -0
- package/dist/packages/sqlite/src/catalog/json.d.ts +5 -0
- package/dist/packages/sqlite/src/catalog/json.d.ts.map +1 -0
- package/dist/packages/sqlite/src/catalog/json.js +146 -0
- package/dist/packages/sqlite/src/catalog/json.js.map +1 -0
- package/dist/packages/sqlite/src/catalog/math.d.ts +4 -0
- package/dist/packages/sqlite/src/catalog/math.d.ts.map +1 -0
- package/dist/packages/sqlite/src/catalog/math.js +44 -0
- package/dist/packages/sqlite/src/catalog/math.js.map +1 -0
- package/dist/packages/sqlite/src/catalog/operators.d.ts +10 -0
- package/dist/packages/sqlite/src/catalog/operators.d.ts.map +1 -0
- package/dist/packages/sqlite/src/catalog/operators.js +52 -0
- package/dist/packages/sqlite/src/catalog/operators.js.map +1 -0
- package/dist/packages/sqlite/src/index.d.ts +5 -2
- package/dist/packages/sqlite/src/index.d.ts.map +1 -1
- package/dist/packages/sqlite/src/index.js +15 -16
- package/dist/packages/sqlite/src/index.js.map +1 -1
- package/dist/packages/sqlite/src/node-sqlite.d.ts +18 -3
- package/dist/packages/sqlite/src/node-sqlite.d.ts.map +1 -1
- package/dist/packages/sqlite/src/node-sqlite.js +136 -25
- package/dist/packages/sqlite/src/node-sqlite.js.map +1 -1
- package/dist/packages/sqlite/src/parser/index.d.ts +8 -0
- package/dist/packages/sqlite/src/parser/index.d.ts.map +1 -0
- package/dist/packages/sqlite/src/parser/index.js +4 -0
- package/dist/packages/sqlite/src/parser/index.js.map +1 -0
- package/dist/packages/sqlite/src/parser/parser.d.ts +14 -0
- package/dist/packages/sqlite/src/parser/parser.d.ts.map +1 -0
- package/dist/packages/sqlite/src/parser/parser.js +1122 -0
- package/dist/packages/sqlite/src/parser/parser.js.map +1 -0
- package/dist/packages/sqlite/src/parser/tokenizer.d.ts +14 -0
- package/dist/packages/sqlite/src/parser/tokenizer.d.ts.map +1 -0
- package/dist/packages/sqlite/src/parser/tokenizer.js +375 -0
- package/dist/packages/sqlite/src/parser/tokenizer.js.map +1 -0
- package/dist/packages/sqlite/src/parser/types.d.ts +313 -0
- package/dist/packages/sqlite/src/parser/types.d.ts.map +1 -0
- package/dist/packages/sqlite/src/parser/types.js +9 -0
- package/dist/packages/sqlite/src/parser/types.js.map +1 -0
- package/dist/packages/sqlite/src/parser/walk.d.ts +13 -0
- package/dist/packages/sqlite/src/parser/walk.d.ts.map +1 -0
- package/dist/packages/sqlite/src/parser/walk.js +180 -0
- package/dist/packages/sqlite/src/parser/walk.js.map +1 -0
- package/dist/packages/sqlite/src/prepared.d.ts +4 -3
- package/dist/packages/sqlite/src/prepared.d.ts.map +1 -1
- package/dist/packages/sqlite/src/prepared.js +7 -14
- package/dist/packages/sqlite/src/prepared.js.map +1 -1
- package/dist/packages/sqlite/src/provider.d.ts +27 -5
- package/dist/packages/sqlite/src/provider.d.ts.map +1 -1
- package/dist/packages/sqlite/src/provider.js +345 -14
- package/dist/packages/sqlite/src/provider.js.map +1 -1
- package/dist/packages/sqlite/src/resolver.d.ts +1 -1
- package/dist/packages/sqlite/src/resolver.d.ts.map +1 -1
- package/dist/packages/sqlite/src/resolver.js +989 -124
- package/dist/packages/sqlite/src/resolver.js.map +1 -1
- package/dist/packages/sqlite/src/runtime.d.ts +2 -0
- package/dist/packages/sqlite/src/runtime.d.ts.map +1 -1
- package/dist/packages/sqlite/src/runtime.js +1 -1
- package/dist/packages/sqlite/src/runtime.js.map +1 -1
- package/dist/packages/sqlite/src/schema-sql.d.ts +8 -0
- package/dist/packages/sqlite/src/schema-sql.d.ts.map +1 -0
- package/dist/packages/sqlite/src/schema-sql.js +157 -0
- package/dist/packages/sqlite/src/schema-sql.js.map +1 -0
- package/dist/packages/sqlite/src/semantics.d.ts +1 -1
- package/dist/packages/sqlite/src/semantics.d.ts.map +1 -1
- package/dist/packages/sqlite/src/semantics.js +44 -6
- package/dist/packages/sqlite/src/semantics.js.map +1 -1
- package/dist/packages/sqlite/src/snapshot.d.ts +10 -4
- package/dist/packages/sqlite/src/snapshot.d.ts.map +1 -1
- package/dist/packages/sqlite/src/snapshot.js +136 -3
- package/dist/packages/sqlite/src/snapshot.js.map +1 -1
- package/dist/packages/sqlite/src/support.d.ts +19 -0
- package/dist/packages/sqlite/src/support.d.ts.map +1 -0
- package/dist/packages/sqlite/src/support.js +59 -0
- package/dist/packages/sqlite/src/support.js.map +1 -0
- package/dist/packages/sqlite/src/type-policy.d.ts +3 -2
- package/dist/packages/sqlite/src/type-policy.d.ts.map +1 -1
- package/dist/packages/sqlite/src/type-policy.js +2 -12
- package/dist/packages/sqlite/src/type-policy.js.map +1 -1
- package/package.json +8 -8
|
@@ -0,0 +1,1122 @@
|
|
|
1
|
+
import { SqlToolkitError, TokenCursor } from "@typed-sql/ast/toolkit";
|
|
2
|
+
import { SqlTokenizeError, tokenize } from "./tokenizer.js";
|
|
3
|
+
import { mergeRanges, } from "./types.js";
|
|
4
|
+
export const DEFAULT_MAX_PARSE_DEPTH = 128;
|
|
5
|
+
export class SqlParseError extends Error {
|
|
6
|
+
code;
|
|
7
|
+
range;
|
|
8
|
+
constructor(message, range, code = "TSQ001") {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "SqlParseError";
|
|
11
|
+
this.code = code;
|
|
12
|
+
this.range = range;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const precedence = new Map([
|
|
16
|
+
["OR", 1],
|
|
17
|
+
["AND", 2],
|
|
18
|
+
["=", 3],
|
|
19
|
+
["!=", 3],
|
|
20
|
+
["<>", 3],
|
|
21
|
+
["<", 3],
|
|
22
|
+
["<=", 3],
|
|
23
|
+
[">", 3],
|
|
24
|
+
[">=", 3],
|
|
25
|
+
["IS", 3],
|
|
26
|
+
["LIKE", 3],
|
|
27
|
+
["ILIKE", 3],
|
|
28
|
+
["SIMILAR TO", 3],
|
|
29
|
+
["~", 3],
|
|
30
|
+
["~*", 3],
|
|
31
|
+
["!~", 3],
|
|
32
|
+
["!~*", 3],
|
|
33
|
+
["@>", 3],
|
|
34
|
+
["<@", 3],
|
|
35
|
+
["?", 3],
|
|
36
|
+
["?|", 3],
|
|
37
|
+
["?&", 3],
|
|
38
|
+
["&&", 3],
|
|
39
|
+
["||", 4],
|
|
40
|
+
["->", 4],
|
|
41
|
+
["->>", 4],
|
|
42
|
+
["#>", 4],
|
|
43
|
+
["#>>", 4],
|
|
44
|
+
["+", 5],
|
|
45
|
+
["-", 5],
|
|
46
|
+
["*", 6],
|
|
47
|
+
["/", 6],
|
|
48
|
+
["%", 6],
|
|
49
|
+
["^", 7],
|
|
50
|
+
]);
|
|
51
|
+
const typeContinuationWords = new Set([
|
|
52
|
+
"BIT",
|
|
53
|
+
"CHARACTER",
|
|
54
|
+
"DOUBLE",
|
|
55
|
+
"INTERVAL",
|
|
56
|
+
"PRECISION",
|
|
57
|
+
"TIME",
|
|
58
|
+
"TIMESTAMP",
|
|
59
|
+
"VARYING",
|
|
60
|
+
"WITH",
|
|
61
|
+
"WITHOUT",
|
|
62
|
+
"ZONE",
|
|
63
|
+
]);
|
|
64
|
+
class Parser {
|
|
65
|
+
#source;
|
|
66
|
+
#cursor;
|
|
67
|
+
constructor(source, options) {
|
|
68
|
+
this.#source = source;
|
|
69
|
+
this.#cursor = new TokenCursor(tokenize(source, options), {
|
|
70
|
+
maxDepth: options.maxDepth ?? DEFAULT_MAX_PARSE_DEPTH,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
parse() {
|
|
74
|
+
const statement = this.#parseStatement();
|
|
75
|
+
this.#matchPunctuation(";");
|
|
76
|
+
this.#expect("eof", "end of query");
|
|
77
|
+
return statement;
|
|
78
|
+
}
|
|
79
|
+
#parseStatement() {
|
|
80
|
+
return this.#nested(() => {
|
|
81
|
+
const withClause = this.#current().value === "WITH" ? this.#parseWithClause() : undefined;
|
|
82
|
+
if (this.#current().value === "SELECT")
|
|
83
|
+
return this.#parseSelect(withClause);
|
|
84
|
+
if (this.#current().value === "INSERT" || this.#current().value === "REPLACE")
|
|
85
|
+
return this.#parseInsert(withClause);
|
|
86
|
+
if (this.#current().value === "UPDATE")
|
|
87
|
+
return this.#parseUpdate(withClause);
|
|
88
|
+
if (this.#current().value === "DELETE")
|
|
89
|
+
return this.#parseDelete(withClause);
|
|
90
|
+
throw this.#error(`Expected SELECT, INSERT, UPDATE, or DELETE, found ${this.#current().text || "end of query"}`, this.#current().range);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
#parseWithClause() {
|
|
94
|
+
const start = this.#expectKeyword("WITH").range;
|
|
95
|
+
const recursive = this.#matchKeyword("RECURSIVE");
|
|
96
|
+
const queries = [];
|
|
97
|
+
do {
|
|
98
|
+
const name = this.#parseIdentifier();
|
|
99
|
+
const columns = [];
|
|
100
|
+
if (this.#matchPunctuation("(")) {
|
|
101
|
+
if (!this.#matchPunctuation(")")) {
|
|
102
|
+
do
|
|
103
|
+
columns.push(this.#parseIdentifier());
|
|
104
|
+
while (this.#matchPunctuation(","));
|
|
105
|
+
this.#expectPunctuation(")");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
this.#expectKeyword("AS");
|
|
109
|
+
this.#expectPunctuation("(");
|
|
110
|
+
const statement = this.#parseStatement();
|
|
111
|
+
const close = this.#expectPunctuation(")");
|
|
112
|
+
queries.push({ name, columns, statement, range: mergeRanges(name.range, close.range) });
|
|
113
|
+
} while (this.#matchPunctuation(","));
|
|
114
|
+
return { recursive, queries, range: mergeRanges(start, queries.at(-1)?.range ?? start) };
|
|
115
|
+
}
|
|
116
|
+
#parseSelect(withClause) {
|
|
117
|
+
const start = withClause?.range ?? this.#current().range;
|
|
118
|
+
this.#expectKeyword("SELECT");
|
|
119
|
+
let distinct = false;
|
|
120
|
+
const distinctOn = [];
|
|
121
|
+
if (this.#matchKeyword("DISTINCT")) {
|
|
122
|
+
distinct = true;
|
|
123
|
+
if (this.#matchKeyword("ON")) {
|
|
124
|
+
this.#expectPunctuation("(");
|
|
125
|
+
distinctOn.push(...this.#parseExpressionList());
|
|
126
|
+
this.#expectPunctuation(")");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
else
|
|
130
|
+
this.#matchKeyword("ALL");
|
|
131
|
+
const columns = this.#parseSelectList();
|
|
132
|
+
let from;
|
|
133
|
+
const joins = [];
|
|
134
|
+
let where;
|
|
135
|
+
const groupBy = [];
|
|
136
|
+
let having;
|
|
137
|
+
const windows = [];
|
|
138
|
+
let orderBy = [];
|
|
139
|
+
let limit;
|
|
140
|
+
let offset;
|
|
141
|
+
const locking = [];
|
|
142
|
+
const compounds = [];
|
|
143
|
+
if (this.#matchKeyword("FROM")) {
|
|
144
|
+
from = this.#parseTableReference();
|
|
145
|
+
while (true) {
|
|
146
|
+
if (this.#matchPunctuation(",")) {
|
|
147
|
+
const table = this.#parseTableReference();
|
|
148
|
+
joins.push({ kind: "cross", table, range: table.range });
|
|
149
|
+
}
|
|
150
|
+
else if (this.#isJoinStart())
|
|
151
|
+
joins.push(this.#parseJoin());
|
|
152
|
+
else
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (this.#matchKeyword("WHERE"))
|
|
157
|
+
where = this.#parseExpression();
|
|
158
|
+
if (this.#matchKeyword("GROUP")) {
|
|
159
|
+
this.#expectKeyword("BY");
|
|
160
|
+
groupBy.push(...this.#parseExpressionList());
|
|
161
|
+
}
|
|
162
|
+
if (this.#matchKeyword("HAVING"))
|
|
163
|
+
having = this.#parseExpression();
|
|
164
|
+
if (this.#matchKeyword("WINDOW")) {
|
|
165
|
+
do {
|
|
166
|
+
const name = this.#parseIdentifier();
|
|
167
|
+
this.#expectKeyword("AS");
|
|
168
|
+
const specification = this.#parseWindowSpecification();
|
|
169
|
+
windows.push({ name, specification, range: mergeRanges(name.range, specification.range) });
|
|
170
|
+
} while (this.#matchPunctuation(","));
|
|
171
|
+
}
|
|
172
|
+
if (this.#matchKeyword("ORDER")) {
|
|
173
|
+
this.#expectKeyword("BY");
|
|
174
|
+
orderBy = this.#parseOrderByList();
|
|
175
|
+
}
|
|
176
|
+
if (this.#matchKeyword("LIMIT")) {
|
|
177
|
+
limit = this.#parseExpression();
|
|
178
|
+
if (this.#matchPunctuation(",")) {
|
|
179
|
+
offset = limit;
|
|
180
|
+
limit = this.#parseExpression();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (this.#matchKeyword("OFFSET"))
|
|
184
|
+
offset = this.#parseExpression();
|
|
185
|
+
while (this.#current().value === "FOR")
|
|
186
|
+
locking.push(this.#parseSelectLockingClause());
|
|
187
|
+
if (this.#current().value === "LOCK") {
|
|
188
|
+
throw this.#error("LOCK IN SHARE MODE is MySQL syntax", this.#current().range);
|
|
189
|
+
}
|
|
190
|
+
if (["UNION", "INTERSECT", "EXCEPT"].includes(this.#current().value)) {
|
|
191
|
+
if (orderBy.length > 0 || limit !== undefined || offset !== undefined || locking.length > 0) {
|
|
192
|
+
throw this.#error("ORDER BY, LIMIT, OFFSET, and locking clauses must follow the final compound SELECT", this.#current().range);
|
|
193
|
+
}
|
|
194
|
+
const operatorToken = this.#current();
|
|
195
|
+
this.#advance();
|
|
196
|
+
const all = this.#matchKeyword("ALL");
|
|
197
|
+
if (all && operatorToken.value !== "UNION") {
|
|
198
|
+
throw this.#error(`SQLite does not support ${operatorToken.value} ALL`, this.#previous().range);
|
|
199
|
+
}
|
|
200
|
+
const parsed = this.#parseSelect();
|
|
201
|
+
const { orderBy: trailingOrderBy, limit: trailingLimit, offset: trailingOffset, locking: trailingLocking, ...arm } = parsed;
|
|
202
|
+
const statement = { ...arm, orderBy: [], locking: [] };
|
|
203
|
+
const flattened = this.#flattenCompoundStatement(statement);
|
|
204
|
+
orderBy = [...trailingOrderBy];
|
|
205
|
+
limit = trailingLimit;
|
|
206
|
+
offset = trailingOffset;
|
|
207
|
+
locking.push(...trailingLocking);
|
|
208
|
+
compounds.push({
|
|
209
|
+
operator: operatorToken.value.toLowerCase(),
|
|
210
|
+
all,
|
|
211
|
+
statement: flattened.statement,
|
|
212
|
+
range: mergeRanges(operatorToken.range, flattened.statement.range),
|
|
213
|
+
});
|
|
214
|
+
compounds.push(...flattened.compounds);
|
|
215
|
+
}
|
|
216
|
+
const end = this.#previous().range;
|
|
217
|
+
return {
|
|
218
|
+
kind: "select",
|
|
219
|
+
...(withClause === undefined ? {} : { with: withClause }),
|
|
220
|
+
distinct,
|
|
221
|
+
distinctOn,
|
|
222
|
+
columns,
|
|
223
|
+
...(from === undefined ? {} : { from }),
|
|
224
|
+
joins,
|
|
225
|
+
...(where === undefined ? {} : { where }),
|
|
226
|
+
groupBy,
|
|
227
|
+
...(having === undefined ? {} : { having }),
|
|
228
|
+
windows,
|
|
229
|
+
orderBy,
|
|
230
|
+
...(limit === undefined ? {} : { limit }),
|
|
231
|
+
...(offset === undefined ? {} : { offset }),
|
|
232
|
+
locking,
|
|
233
|
+
compounds,
|
|
234
|
+
range: mergeRanges(start, end),
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
#flattenCompoundStatement(statement) {
|
|
238
|
+
const { compounds, ...head } = statement;
|
|
239
|
+
const flattened = [];
|
|
240
|
+
for (const compound of compounds) {
|
|
241
|
+
const nested = this.#flattenCompoundStatement(compound.statement);
|
|
242
|
+
flattened.push({ ...compound, statement: nested.statement }, ...nested.compounds);
|
|
243
|
+
}
|
|
244
|
+
return { statement: { ...head, compounds: [] }, compounds: flattened };
|
|
245
|
+
}
|
|
246
|
+
#parseSelectLockingClause() {
|
|
247
|
+
const start = this.#expectKeyword("FOR").range;
|
|
248
|
+
let strength;
|
|
249
|
+
if (this.#matchKeyword("UPDATE"))
|
|
250
|
+
strength = "update";
|
|
251
|
+
else if (this.#matchKeyword("SHARE"))
|
|
252
|
+
strength = "share";
|
|
253
|
+
else if (this.#matchKeyword("NO")) {
|
|
254
|
+
throw this.#error("FOR NO KEY UPDATE is PostgreSQL syntax", start);
|
|
255
|
+
}
|
|
256
|
+
else if (this.#matchKeyword("KEY")) {
|
|
257
|
+
throw this.#error("FOR KEY SHARE is PostgreSQL syntax", start);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
throw this.#error("Expected UPDATE, NO KEY UPDATE, SHARE, or KEY SHARE after FOR", this.#current().range);
|
|
261
|
+
}
|
|
262
|
+
const relations = [];
|
|
263
|
+
if (this.#matchKeyword("OF")) {
|
|
264
|
+
do
|
|
265
|
+
relations.push(this.#parseIdentifier());
|
|
266
|
+
while (this.#matchPunctuation(","));
|
|
267
|
+
}
|
|
268
|
+
let wait;
|
|
269
|
+
if (this.#matchKeyword("NOWAIT"))
|
|
270
|
+
wait = "nowait";
|
|
271
|
+
else if (this.#matchKeyword("SKIP")) {
|
|
272
|
+
this.#expectKeyword("LOCKED");
|
|
273
|
+
wait = "skip-locked";
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
strength,
|
|
277
|
+
relations,
|
|
278
|
+
...(wait === undefined ? {} : { wait }),
|
|
279
|
+
range: mergeRanges(start, this.#previous().range),
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
#parseInsert(withClause) {
|
|
283
|
+
const start = withClause?.range ?? this.#current().range;
|
|
284
|
+
const replace = this.#matchKeyword("REPLACE");
|
|
285
|
+
if (!replace)
|
|
286
|
+
this.#expectKeyword("INSERT");
|
|
287
|
+
let conflictAlgorithm = replace ? "replace" : undefined;
|
|
288
|
+
if (!replace && this.#matchKeyword("OR"))
|
|
289
|
+
conflictAlgorithm = this.#parseConflictAlgorithm();
|
|
290
|
+
this.#expectKeyword("INTO");
|
|
291
|
+
const table = this.#parseNamedTableReference(true);
|
|
292
|
+
const columns = [];
|
|
293
|
+
if (this.#matchPunctuation("(")) {
|
|
294
|
+
if (!this.#matchPunctuation(")")) {
|
|
295
|
+
do
|
|
296
|
+
columns.push(this.#parseIdentifier());
|
|
297
|
+
while (this.#matchPunctuation(","));
|
|
298
|
+
this.#expectPunctuation(")");
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
let source;
|
|
302
|
+
if (this.#matchKeyword("DEFAULT")) {
|
|
303
|
+
const sourceStart = this.#previous().range;
|
|
304
|
+
const end = this.#expectKeyword("VALUES").range;
|
|
305
|
+
source = { kind: "default-values", range: mergeRanges(sourceStart, end) };
|
|
306
|
+
}
|
|
307
|
+
else if (this.#matchKeyword("VALUES")) {
|
|
308
|
+
const sourceStart = this.#previous().range;
|
|
309
|
+
const rows = [];
|
|
310
|
+
do {
|
|
311
|
+
this.#expectPunctuation("(");
|
|
312
|
+
const row = this.#matchPunctuation(")") ? [] : [...this.#parseExpressionList()];
|
|
313
|
+
const close = this.#previous().value === ")" ? this.#previous() : this.#expectPunctuation(")");
|
|
314
|
+
rows.push(row);
|
|
315
|
+
if (close.value !== ")")
|
|
316
|
+
throw this.#error("Expected ) after VALUES row", close.range);
|
|
317
|
+
} while (this.#matchPunctuation(","));
|
|
318
|
+
source = { kind: "values", rows, range: mergeRanges(sourceStart, this.#previous().range) };
|
|
319
|
+
}
|
|
320
|
+
else if (this.#current().value === "SELECT" || this.#current().value === "WITH") {
|
|
321
|
+
const statement = this.#parseStatement();
|
|
322
|
+
if (statement.kind !== "select")
|
|
323
|
+
throw this.#error("INSERT source must be SELECT or VALUES", statement.range);
|
|
324
|
+
source = statement;
|
|
325
|
+
}
|
|
326
|
+
else
|
|
327
|
+
throw this.#error("Expected VALUES, DEFAULT VALUES, or SELECT after INSERT target", this.#current().range);
|
|
328
|
+
const upserts = [];
|
|
329
|
+
while (this.#current().value === "ON")
|
|
330
|
+
upserts.push(this.#parseUpsert());
|
|
331
|
+
const returning = this.#matchKeyword("RETURNING") ? this.#parseSelectList() : [];
|
|
332
|
+
return {
|
|
333
|
+
kind: "insert",
|
|
334
|
+
...(withClause === undefined ? {} : { with: withClause }),
|
|
335
|
+
table,
|
|
336
|
+
...(conflictAlgorithm === undefined ? {} : { conflictAlgorithm }),
|
|
337
|
+
columns,
|
|
338
|
+
source,
|
|
339
|
+
upserts,
|
|
340
|
+
returning,
|
|
341
|
+
range: mergeRanges(start, this.#previous().range),
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
#parseUpdate(withClause) {
|
|
345
|
+
const start = withClause?.range ?? this.#expectKeyword("UPDATE").range;
|
|
346
|
+
if (withClause !== undefined)
|
|
347
|
+
this.#expectKeyword("UPDATE");
|
|
348
|
+
const conflictAlgorithm = this.#matchKeyword("OR") ? this.#parseConflictAlgorithm() : undefined;
|
|
349
|
+
const table = this.#parseNamedTableReference(true);
|
|
350
|
+
this.#expectKeyword("SET");
|
|
351
|
+
const assignments = [];
|
|
352
|
+
do {
|
|
353
|
+
const column = this.#parseIdentifier();
|
|
354
|
+
this.#expectOperator("=");
|
|
355
|
+
const value = this.#parseExpression();
|
|
356
|
+
assignments.push({ column, value, range: mergeRanges(column.range, value.range) });
|
|
357
|
+
} while (this.#matchPunctuation(","));
|
|
358
|
+
let from;
|
|
359
|
+
const joins = [];
|
|
360
|
+
if (this.#matchKeyword("FROM")) {
|
|
361
|
+
from = this.#parseTableReference();
|
|
362
|
+
while (this.#isJoinStart())
|
|
363
|
+
joins.push(this.#parseJoin());
|
|
364
|
+
}
|
|
365
|
+
const where = this.#matchKeyword("WHERE") ? this.#parseExpression() : undefined;
|
|
366
|
+
const returning = this.#matchKeyword("RETURNING") ? this.#parseSelectList() : [];
|
|
367
|
+
return {
|
|
368
|
+
kind: "update",
|
|
369
|
+
...(withClause === undefined ? {} : { with: withClause }),
|
|
370
|
+
table,
|
|
371
|
+
...(conflictAlgorithm === undefined ? {} : { conflictAlgorithm }),
|
|
372
|
+
assignments,
|
|
373
|
+
...(from === undefined ? {} : { from }),
|
|
374
|
+
joins,
|
|
375
|
+
...(where === undefined ? {} : { where }),
|
|
376
|
+
returning,
|
|
377
|
+
range: mergeRanges(start, this.#previous().range),
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
#parseConflictAlgorithm() {
|
|
381
|
+
const algorithm = this.#current().value.toLowerCase();
|
|
382
|
+
if (!["rollback", "abort", "fail", "ignore", "replace"].includes(algorithm))
|
|
383
|
+
throw this.#error("Expected ROLLBACK, ABORT, FAIL, IGNORE, or REPLACE after OR", this.#current().range);
|
|
384
|
+
this.#advance();
|
|
385
|
+
return algorithm;
|
|
386
|
+
}
|
|
387
|
+
#parseUpsert() {
|
|
388
|
+
const start = this.#expectKeyword("ON").range;
|
|
389
|
+
this.#expectKeyword("CONFLICT");
|
|
390
|
+
const target = [];
|
|
391
|
+
let targetWhere;
|
|
392
|
+
if (this.#matchPunctuation("(")) {
|
|
393
|
+
do {
|
|
394
|
+
const expression = this.#parseExpression();
|
|
395
|
+
let collation;
|
|
396
|
+
if (this.#matchKeyword("COLLATE"))
|
|
397
|
+
collation = this.#parseIdentifier(true);
|
|
398
|
+
const direction = this.#matchKeyword("ASC") ? "asc" : this.#matchKeyword("DESC") ? "desc" : undefined;
|
|
399
|
+
target.push({
|
|
400
|
+
expression,
|
|
401
|
+
...(collation === undefined ? {} : { collation }),
|
|
402
|
+
...(direction === undefined ? {} : { direction }),
|
|
403
|
+
range: mergeRanges(expression.range, this.#previous().range),
|
|
404
|
+
});
|
|
405
|
+
} while (this.#matchPunctuation(","));
|
|
406
|
+
this.#expectPunctuation(")");
|
|
407
|
+
if (this.#matchKeyword("WHERE"))
|
|
408
|
+
targetWhere = this.#parseExpression();
|
|
409
|
+
}
|
|
410
|
+
this.#expectKeyword("DO");
|
|
411
|
+
if (this.#matchKeyword("NOTHING")) {
|
|
412
|
+
const range = mergeRanges(start, this.#previous().range);
|
|
413
|
+
return {
|
|
414
|
+
target,
|
|
415
|
+
...(targetWhere === undefined ? {} : { targetWhere }),
|
|
416
|
+
action: { kind: "nothing", range },
|
|
417
|
+
range,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
this.#expectKeyword("UPDATE");
|
|
421
|
+
if (target.length === 0)
|
|
422
|
+
throw this.#error("DO UPDATE requires a conflict target", this.#previous().range);
|
|
423
|
+
this.#expectKeyword("SET");
|
|
424
|
+
const assignments = [];
|
|
425
|
+
do {
|
|
426
|
+
const column = this.#parseIdentifier();
|
|
427
|
+
this.#expectOperator("=");
|
|
428
|
+
const value = this.#parseExpression();
|
|
429
|
+
assignments.push({ column, value, range: mergeRanges(column.range, value.range) });
|
|
430
|
+
} while (this.#matchPunctuation(","));
|
|
431
|
+
const where = this.#matchKeyword("WHERE") ? this.#parseExpression() : undefined;
|
|
432
|
+
const actionRange = mergeRanges(start, where?.range ?? assignments.at(-1)?.range ?? this.#previous().range);
|
|
433
|
+
return {
|
|
434
|
+
target,
|
|
435
|
+
...(targetWhere === undefined ? {} : { targetWhere }),
|
|
436
|
+
action: {
|
|
437
|
+
kind: "update",
|
|
438
|
+
assignments,
|
|
439
|
+
...(where === undefined ? {} : { where }),
|
|
440
|
+
range: actionRange,
|
|
441
|
+
},
|
|
442
|
+
range: actionRange,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
#parseDelete(withClause) {
|
|
446
|
+
const start = withClause?.range ?? this.#expectKeyword("DELETE").range;
|
|
447
|
+
if (withClause !== undefined)
|
|
448
|
+
this.#expectKeyword("DELETE");
|
|
449
|
+
this.#expectKeyword("FROM");
|
|
450
|
+
const table = this.#parseNamedTableReference(true);
|
|
451
|
+
const using = [];
|
|
452
|
+
if (this.#matchKeyword("USING")) {
|
|
453
|
+
do
|
|
454
|
+
using.push(this.#parseTableReference());
|
|
455
|
+
while (this.#matchPunctuation(","));
|
|
456
|
+
}
|
|
457
|
+
const where = this.#matchKeyword("WHERE") ? this.#parseExpression() : undefined;
|
|
458
|
+
const returning = this.#matchKeyword("RETURNING") ? this.#parseSelectList() : [];
|
|
459
|
+
return {
|
|
460
|
+
kind: "delete",
|
|
461
|
+
...(withClause === undefined ? {} : { with: withClause }),
|
|
462
|
+
table,
|
|
463
|
+
using,
|
|
464
|
+
...(where === undefined ? {} : { where }),
|
|
465
|
+
returning,
|
|
466
|
+
range: mergeRanges(start, this.#previous().range),
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
#parseSelectList() {
|
|
470
|
+
const items = [];
|
|
471
|
+
do {
|
|
472
|
+
const expression = this.#parseExpression();
|
|
473
|
+
let alias;
|
|
474
|
+
if (this.#matchKeyword("AS"))
|
|
475
|
+
alias = this.#parseIdentifier();
|
|
476
|
+
else if (this.#current().kind === "identifier" || this.#current().kind === "quoted-identifier")
|
|
477
|
+
alias = this.#parseIdentifier();
|
|
478
|
+
items.push({
|
|
479
|
+
expression,
|
|
480
|
+
...(alias === undefined ? {} : { alias }),
|
|
481
|
+
range: mergeRanges(expression.range, alias?.range ?? expression.range),
|
|
482
|
+
});
|
|
483
|
+
} while (this.#matchPunctuation(","));
|
|
484
|
+
return items;
|
|
485
|
+
}
|
|
486
|
+
#parseTableReference() {
|
|
487
|
+
const lateral = this.#matchKeyword("LATERAL");
|
|
488
|
+
const start = lateral ? this.#previous().range : this.#current().range;
|
|
489
|
+
if (this.#matchPunctuation("(")) {
|
|
490
|
+
if (!this.#isStatementStart()) {
|
|
491
|
+
throw this.#error("A parenthesized FROM item must be a SELECT subquery", this.#current().range);
|
|
492
|
+
}
|
|
493
|
+
const statement = this.#parseStatement();
|
|
494
|
+
if (statement.kind !== "select")
|
|
495
|
+
throw this.#error("FROM subquery must be SELECT", statement.range);
|
|
496
|
+
this.#expectPunctuation(")");
|
|
497
|
+
this.#matchKeyword("AS");
|
|
498
|
+
const alias = this.#parseIdentifier();
|
|
499
|
+
return { kind: "subquery", query: statement, alias, lateral, range: mergeRanges(start, alias.range) };
|
|
500
|
+
}
|
|
501
|
+
const first = this.#parseIdentifier();
|
|
502
|
+
let schema;
|
|
503
|
+
let tableName = first;
|
|
504
|
+
if (this.#matchPunctuation(".")) {
|
|
505
|
+
schema = first;
|
|
506
|
+
tableName = this.#parseIdentifier();
|
|
507
|
+
}
|
|
508
|
+
if (this.#matchPunctuation("(")) {
|
|
509
|
+
const args = this.#matchPunctuation(")") ? [] : [...this.#parseExpressionList()];
|
|
510
|
+
const close = this.#previous().value === ")" ? this.#previous() : this.#expectPunctuation(")");
|
|
511
|
+
let alias;
|
|
512
|
+
if (this.#matchKeyword("AS"))
|
|
513
|
+
alias = this.#parseIdentifier();
|
|
514
|
+
else if (this.#current().kind === "identifier" || this.#current().kind === "quoted-identifier")
|
|
515
|
+
alias = this.#parseIdentifier();
|
|
516
|
+
return {
|
|
517
|
+
kind: "table-function",
|
|
518
|
+
name: tableName,
|
|
519
|
+
...(schema === undefined ? {} : { schema }),
|
|
520
|
+
arguments: args,
|
|
521
|
+
...(alias === undefined ? {} : { alias }),
|
|
522
|
+
lateral,
|
|
523
|
+
range: mergeRanges(start, alias?.range ?? close.range),
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
return this.#finishNamedTableReference(schema, tableName, true, lateral, start);
|
|
527
|
+
}
|
|
528
|
+
#parseNamedTableReference(allowAlias, lateral = false, start = this.#current().range) {
|
|
529
|
+
const first = this.#parseIdentifier();
|
|
530
|
+
let schema;
|
|
531
|
+
let name = first;
|
|
532
|
+
if (this.#matchPunctuation(".")) {
|
|
533
|
+
schema = first;
|
|
534
|
+
name = this.#parseIdentifier();
|
|
535
|
+
}
|
|
536
|
+
return this.#finishNamedTableReference(schema, name, allowAlias, lateral, start);
|
|
537
|
+
}
|
|
538
|
+
#finishNamedTableReference(schema, name, allowAlias, lateral, start) {
|
|
539
|
+
let alias;
|
|
540
|
+
if (allowAlias && this.#matchKeyword("AS"))
|
|
541
|
+
alias = this.#parseIdentifier();
|
|
542
|
+
else if (allowAlias && (this.#current().kind === "identifier" || this.#current().kind === "quoted-identifier"))
|
|
543
|
+
alias = this.#parseIdentifier();
|
|
544
|
+
return {
|
|
545
|
+
kind: "table",
|
|
546
|
+
name,
|
|
547
|
+
...(schema === undefined ? {} : { schema }),
|
|
548
|
+
...(alias === undefined ? {} : { alias }),
|
|
549
|
+
lateral,
|
|
550
|
+
range: mergeRanges(start, alias?.range ?? name.range),
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
#isJoinStart() {
|
|
554
|
+
return ["JOIN", "INNER", "LEFT", "RIGHT", "FULL", "CROSS"].includes(this.#current().value);
|
|
555
|
+
}
|
|
556
|
+
#parseJoin() {
|
|
557
|
+
const start = this.#current().range;
|
|
558
|
+
let kind = "inner";
|
|
559
|
+
if (this.#matchKeyword("INNER"))
|
|
560
|
+
kind = "inner";
|
|
561
|
+
else if (this.#matchKeyword("LEFT"))
|
|
562
|
+
kind = "left";
|
|
563
|
+
else if (this.#matchKeyword("RIGHT"))
|
|
564
|
+
kind = "right";
|
|
565
|
+
else if (this.#matchKeyword("FULL"))
|
|
566
|
+
kind = "full";
|
|
567
|
+
else if (this.#matchKeyword("CROSS"))
|
|
568
|
+
kind = "cross";
|
|
569
|
+
this.#matchKeyword("OUTER");
|
|
570
|
+
this.#expectKeyword("JOIN");
|
|
571
|
+
const table = this.#parseTableReference();
|
|
572
|
+
if (kind === "cross")
|
|
573
|
+
return { kind, table, range: mergeRanges(start, table.range) };
|
|
574
|
+
if (this.#matchKeyword("ON")) {
|
|
575
|
+
const on = this.#parseExpression();
|
|
576
|
+
return { kind, table, on, range: mergeRanges(start, on.range) };
|
|
577
|
+
}
|
|
578
|
+
if (this.#matchKeyword("USING")) {
|
|
579
|
+
this.#expectPunctuation("(");
|
|
580
|
+
const using = [];
|
|
581
|
+
do
|
|
582
|
+
using.push(this.#parseIdentifier());
|
|
583
|
+
while (this.#matchPunctuation(","));
|
|
584
|
+
const close = this.#expectPunctuation(")");
|
|
585
|
+
return { kind, table, using, range: mergeRanges(start, close.range) };
|
|
586
|
+
}
|
|
587
|
+
throw this.#error("JOIN requires ON or USING", this.#current().range);
|
|
588
|
+
}
|
|
589
|
+
#parseExpressionList() {
|
|
590
|
+
const expressions = [];
|
|
591
|
+
do
|
|
592
|
+
expressions.push(this.#parseExpression());
|
|
593
|
+
while (this.#matchPunctuation(","));
|
|
594
|
+
return expressions;
|
|
595
|
+
}
|
|
596
|
+
#parseOrderByList() {
|
|
597
|
+
const orderBy = [];
|
|
598
|
+
do {
|
|
599
|
+
const expression = this.#parseExpression();
|
|
600
|
+
const direction = this.#matchKeyword("ASC") ? "asc" : this.#matchKeyword("DESC") ? "desc" : undefined;
|
|
601
|
+
let nulls;
|
|
602
|
+
if (this.#matchKeyword("NULLS")) {
|
|
603
|
+
if (this.#matchKeyword("FIRST"))
|
|
604
|
+
nulls = "first";
|
|
605
|
+
else {
|
|
606
|
+
this.#expectKeyword("LAST");
|
|
607
|
+
nulls = "last";
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
orderBy.push({
|
|
611
|
+
expression,
|
|
612
|
+
...(direction === undefined ? {} : { direction }),
|
|
613
|
+
...(nulls === undefined ? {} : { nulls }),
|
|
614
|
+
range: mergeRanges(expression.range, this.#previous().range),
|
|
615
|
+
});
|
|
616
|
+
} while (this.#matchPunctuation(","));
|
|
617
|
+
return orderBy;
|
|
618
|
+
}
|
|
619
|
+
#parseExpression(minimum = 0) {
|
|
620
|
+
return this.#nested(() => {
|
|
621
|
+
let left = this.#parseUnary();
|
|
622
|
+
while (true) {
|
|
623
|
+
const negated = this.#current().value === "NOT" &&
|
|
624
|
+
["IN", "BETWEEN", "LIKE", "ILIKE", "SIMILAR"].includes(this.#peekToken(1).value);
|
|
625
|
+
const special = negated ? this.#peekToken(1).value : this.#current().value;
|
|
626
|
+
if (special === "IN") {
|
|
627
|
+
const strength = 3;
|
|
628
|
+
if (strength < minimum)
|
|
629
|
+
break;
|
|
630
|
+
if (negated)
|
|
631
|
+
this.#advance();
|
|
632
|
+
this.#advance();
|
|
633
|
+
const start = left.range;
|
|
634
|
+
let values;
|
|
635
|
+
let end;
|
|
636
|
+
if (this.#current().value !== "(") {
|
|
637
|
+
const table = this.#parseTableReference();
|
|
638
|
+
if (table.kind === "subquery")
|
|
639
|
+
throw this.#error("IN subquery must be parenthesized", table.range);
|
|
640
|
+
values = table;
|
|
641
|
+
end = table.range;
|
|
642
|
+
}
|
|
643
|
+
else {
|
|
644
|
+
this.#expectPunctuation("(");
|
|
645
|
+
if (this.#isStatementStart()) {
|
|
646
|
+
const statement = this.#parseStatement();
|
|
647
|
+
if (statement.kind !== "select")
|
|
648
|
+
throw this.#error("IN subquery must be SELECT", statement.range);
|
|
649
|
+
values = statement;
|
|
650
|
+
}
|
|
651
|
+
else {
|
|
652
|
+
if (this.#current().value === ")")
|
|
653
|
+
throw this.#error("IN requires at least one value", this.#current().range);
|
|
654
|
+
values = this.#parseExpressionList();
|
|
655
|
+
}
|
|
656
|
+
const close = this.#previous().value === ")" ? this.#previous() : this.#expectPunctuation(")");
|
|
657
|
+
end = close.range;
|
|
658
|
+
}
|
|
659
|
+
left = { kind: "in", expression: left, values, negated, range: mergeRanges(start, end) };
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
if (special === "BETWEEN") {
|
|
663
|
+
const strength = 3;
|
|
664
|
+
if (strength < minimum)
|
|
665
|
+
break;
|
|
666
|
+
if (negated)
|
|
667
|
+
this.#advance();
|
|
668
|
+
this.#advance();
|
|
669
|
+
const lower = this.#parseExpression(strength + 1);
|
|
670
|
+
this.#expectKeyword("AND");
|
|
671
|
+
const upper = this.#parseExpression(strength + 1);
|
|
672
|
+
left = {
|
|
673
|
+
kind: "between",
|
|
674
|
+
expression: left,
|
|
675
|
+
lower,
|
|
676
|
+
upper,
|
|
677
|
+
negated,
|
|
678
|
+
range: mergeRanges(left.range, upper.range),
|
|
679
|
+
};
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
let operator = this.#current().value.toUpperCase();
|
|
683
|
+
if (special === "SIMILAR")
|
|
684
|
+
operator = negated ? "NOT SIMILAR TO" : "SIMILAR TO";
|
|
685
|
+
else if (negated)
|
|
686
|
+
operator = `NOT ${special}`;
|
|
687
|
+
const strength = precedence.get(operator.replace(/^NOT /u, ""));
|
|
688
|
+
if (strength === undefined || strength < minimum)
|
|
689
|
+
break;
|
|
690
|
+
if (negated)
|
|
691
|
+
this.#advance();
|
|
692
|
+
this.#advance();
|
|
693
|
+
if (operator.endsWith("SIMILAR TO"))
|
|
694
|
+
this.#expectKeyword("TO");
|
|
695
|
+
if (operator === "IS") {
|
|
696
|
+
if (this.#matchKeyword("NOT"))
|
|
697
|
+
operator = "IS NOT";
|
|
698
|
+
if (this.#matchKeyword("DISTINCT")) {
|
|
699
|
+
this.#expectKeyword("FROM");
|
|
700
|
+
operator += " DISTINCT FROM";
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
const right = this.#parseExpression(strength + 1);
|
|
704
|
+
left = { kind: "binary", left, operator, right, range: mergeRanges(left.range, right.range) };
|
|
705
|
+
}
|
|
706
|
+
return left;
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
#parseUnary() {
|
|
710
|
+
const token = this.#current();
|
|
711
|
+
if (this.#matchKeyword("NOT") || this.#matchOperator("+") || this.#matchOperator("-") || this.#matchOperator("~")) {
|
|
712
|
+
const expression = this.#parseUnary();
|
|
713
|
+
return {
|
|
714
|
+
kind: "unary",
|
|
715
|
+
operator: token.value.toUpperCase(),
|
|
716
|
+
expression,
|
|
717
|
+
range: mergeRanges(token.range, expression.range),
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
if (this.#matchKeyword("EXISTS")) {
|
|
721
|
+
this.#expectPunctuation("(");
|
|
722
|
+
const statement = this.#parseStatement();
|
|
723
|
+
if (statement.kind !== "select")
|
|
724
|
+
throw this.#error("EXISTS requires a SELECT subquery", statement.range);
|
|
725
|
+
const close = this.#expectPunctuation(")");
|
|
726
|
+
return { kind: "exists", query: statement, range: mergeRanges(token.range, close.range) };
|
|
727
|
+
}
|
|
728
|
+
return this.#parsePrimary();
|
|
729
|
+
}
|
|
730
|
+
#parsePrimary() {
|
|
731
|
+
const token = this.#current();
|
|
732
|
+
if (this.#matchPunctuation("(")) {
|
|
733
|
+
if (this.#isStatementStart()) {
|
|
734
|
+
const statement = this.#parseStatement();
|
|
735
|
+
if (statement.kind !== "select")
|
|
736
|
+
throw this.#error("Scalar subquery must be SELECT", statement.range);
|
|
737
|
+
const close = this.#expectPunctuation(")");
|
|
738
|
+
return { kind: "subquery", query: statement, range: mergeRanges(token.range, close.range) };
|
|
739
|
+
}
|
|
740
|
+
const first = this.#parseExpression();
|
|
741
|
+
if (this.#matchPunctuation(",")) {
|
|
742
|
+
const elements = [first];
|
|
743
|
+
do
|
|
744
|
+
elements.push(this.#parseExpression());
|
|
745
|
+
while (this.#matchPunctuation(","));
|
|
746
|
+
const close = this.#expectPunctuation(")");
|
|
747
|
+
return { kind: "row", elements, range: mergeRanges(token.range, close.range) };
|
|
748
|
+
}
|
|
749
|
+
this.#expectPunctuation(")");
|
|
750
|
+
return first;
|
|
751
|
+
}
|
|
752
|
+
if (this.#matchKeyword("ARRAY")) {
|
|
753
|
+
this.#expectPunctuation("[");
|
|
754
|
+
const elements = this.#matchPunctuation("]") ? [] : [...this.#parseExpressionList()];
|
|
755
|
+
const close = this.#previous().value === "]" ? this.#previous() : this.#expectPunctuation("]");
|
|
756
|
+
return { kind: "array", elements, range: mergeRanges(token.range, close.range) };
|
|
757
|
+
}
|
|
758
|
+
if (this.#matchKeyword("ROW")) {
|
|
759
|
+
this.#expectPunctuation("(");
|
|
760
|
+
const elements = this.#matchPunctuation(")") ? [] : [...this.#parseExpressionList()];
|
|
761
|
+
const close = this.#previous().value === ")" ? this.#previous() : this.#expectPunctuation(")");
|
|
762
|
+
return { kind: "row", elements, range: mergeRanges(token.range, close.range) };
|
|
763
|
+
}
|
|
764
|
+
if (this.#matchKeyword("CAST"))
|
|
765
|
+
return this.#parseCast(token.range);
|
|
766
|
+
if (this.#matchKeyword("CASE"))
|
|
767
|
+
return this.#parseCase(token.range);
|
|
768
|
+
if (this.#matchKeyword("NULL"))
|
|
769
|
+
return { kind: "literal", value: null, range: token.range };
|
|
770
|
+
if (this.#matchKeyword("TRUE"))
|
|
771
|
+
return { kind: "literal", value: true, range: token.range };
|
|
772
|
+
if (this.#matchKeyword("FALSE"))
|
|
773
|
+
return { kind: "literal", value: false, range: token.range };
|
|
774
|
+
if (this.#matchKeyword("DEFAULT"))
|
|
775
|
+
return { kind: "column", column: { name: "DEFAULT", quoted: false, range: token.range }, range: token.range };
|
|
776
|
+
if (token.kind === "number") {
|
|
777
|
+
this.#advance();
|
|
778
|
+
return { kind: "literal", value: Number(token.value), range: token.range };
|
|
779
|
+
}
|
|
780
|
+
if (token.kind === "string") {
|
|
781
|
+
this.#advance();
|
|
782
|
+
return { kind: "literal", value: token.value, range: token.range };
|
|
783
|
+
}
|
|
784
|
+
if (token.kind === "parameter") {
|
|
785
|
+
this.#advance();
|
|
786
|
+
return { kind: "parameter", index: Number(token.value), range: token.range };
|
|
787
|
+
}
|
|
788
|
+
if (this.#matchOperator("*"))
|
|
789
|
+
return { kind: "star", range: token.range };
|
|
790
|
+
if (this.#isIdentifierLike(token)) {
|
|
791
|
+
const first = this.#parseIdentifier(true);
|
|
792
|
+
if (this.#matchPunctuation("("))
|
|
793
|
+
return this.#parseCall(undefined, first);
|
|
794
|
+
if (this.#matchPunctuation(".")) {
|
|
795
|
+
if (this.#matchOperator("*"))
|
|
796
|
+
return { kind: "star", relation: first, range: mergeRanges(first.range, this.#previous().range) };
|
|
797
|
+
const second = this.#parseIdentifier(true);
|
|
798
|
+
if (this.#matchPunctuation("("))
|
|
799
|
+
return this.#parseCall(first, second);
|
|
800
|
+
return { kind: "column", relation: first, column: second, range: mergeRanges(first.range, second.range) };
|
|
801
|
+
}
|
|
802
|
+
return { kind: "column", column: first, range: first.range };
|
|
803
|
+
}
|
|
804
|
+
throw this.#error(`Expected expression, found ${token.text || "end of query"}`, token.range);
|
|
805
|
+
}
|
|
806
|
+
#parseCall(schema, name) {
|
|
807
|
+
const distinct = this.#matchKeyword("DISTINCT");
|
|
808
|
+
const args = [];
|
|
809
|
+
let close;
|
|
810
|
+
if (this.#matchPunctuation(")"))
|
|
811
|
+
close = this.#previous();
|
|
812
|
+
else {
|
|
813
|
+
do
|
|
814
|
+
args.push(this.#parseExpression());
|
|
815
|
+
while (this.#matchPunctuation(","));
|
|
816
|
+
close = this.#expectPunctuation(")");
|
|
817
|
+
}
|
|
818
|
+
let filter;
|
|
819
|
+
if (this.#matchKeyword("FILTER")) {
|
|
820
|
+
this.#expectPunctuation("(");
|
|
821
|
+
this.#expectKeyword("WHERE");
|
|
822
|
+
filter = this.#parseExpression();
|
|
823
|
+
close = this.#expectPunctuation(")");
|
|
824
|
+
}
|
|
825
|
+
let over;
|
|
826
|
+
if (this.#matchKeyword("OVER")) {
|
|
827
|
+
if (this.#current().kind === "identifier" || this.#current().kind === "quoted-identifier")
|
|
828
|
+
over = this.#parseIdentifier();
|
|
829
|
+
else
|
|
830
|
+
over = this.#parseWindowSpecification();
|
|
831
|
+
close = { ...close, range: over.range };
|
|
832
|
+
}
|
|
833
|
+
return {
|
|
834
|
+
kind: "call",
|
|
835
|
+
name,
|
|
836
|
+
...(schema === undefined ? {} : { schema }),
|
|
837
|
+
arguments: args,
|
|
838
|
+
distinct,
|
|
839
|
+
...(filter === undefined ? {} : { filter }),
|
|
840
|
+
...(over === undefined ? {} : { over }),
|
|
841
|
+
range: mergeRanges(schema?.range ?? name.range, close.range),
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
#parseWindowSpecification() {
|
|
845
|
+
const start = this.#expectPunctuation("(").range;
|
|
846
|
+
let base;
|
|
847
|
+
const partitionBy = [];
|
|
848
|
+
let orderBy = [];
|
|
849
|
+
let frame;
|
|
850
|
+
if ((this.#current().kind === "identifier" || this.#current().kind === "quoted-identifier") &&
|
|
851
|
+
!["PARTITION", "ORDER", "ROWS", "RANGE", "GROUPS"].includes(this.#current().value)) {
|
|
852
|
+
base = this.#parseIdentifier();
|
|
853
|
+
}
|
|
854
|
+
if (this.#matchKeyword("PARTITION")) {
|
|
855
|
+
this.#expectKeyword("BY");
|
|
856
|
+
partitionBy.push(...this.#parseExpressionList());
|
|
857
|
+
}
|
|
858
|
+
if (this.#matchKeyword("ORDER")) {
|
|
859
|
+
this.#expectKeyword("BY");
|
|
860
|
+
orderBy = this.#parseOrderByList();
|
|
861
|
+
}
|
|
862
|
+
if (["ROWS", "RANGE", "GROUPS"].includes(this.#current().value))
|
|
863
|
+
frame = this.#parseWindowFrame();
|
|
864
|
+
const close = this.#expectPunctuation(")");
|
|
865
|
+
return {
|
|
866
|
+
...(base === undefined ? {} : { base }),
|
|
867
|
+
partitionBy,
|
|
868
|
+
orderBy,
|
|
869
|
+
...(frame === undefined ? {} : { frame }),
|
|
870
|
+
range: mergeRanges(start, close.range),
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
#parseWindowFrame() {
|
|
874
|
+
const unitToken = this.#current();
|
|
875
|
+
this.#advance();
|
|
876
|
+
const unit = unitToken.value.toLowerCase();
|
|
877
|
+
let start;
|
|
878
|
+
let end;
|
|
879
|
+
if (this.#matchKeyword("BETWEEN")) {
|
|
880
|
+
start = this.#parseWindowBoundary();
|
|
881
|
+
this.#expectKeyword("AND");
|
|
882
|
+
end = this.#parseWindowBoundary();
|
|
883
|
+
}
|
|
884
|
+
else {
|
|
885
|
+
start = this.#parseWindowBoundary();
|
|
886
|
+
end = { kind: "current-row", range: start.range };
|
|
887
|
+
}
|
|
888
|
+
if (start.kind === "unbounded-following")
|
|
889
|
+
throw this.#error("A window frame cannot start with UNBOUNDED FOLLOWING", start.range);
|
|
890
|
+
if (end.kind === "unbounded-preceding")
|
|
891
|
+
throw this.#error("A window frame cannot end with UNBOUNDED PRECEDING", end.range);
|
|
892
|
+
const boundaryOrder = {
|
|
893
|
+
"unbounded-preceding": 0,
|
|
894
|
+
preceding: 1,
|
|
895
|
+
"current-row": 2,
|
|
896
|
+
following: 3,
|
|
897
|
+
"unbounded-following": 4,
|
|
898
|
+
};
|
|
899
|
+
if (boundaryOrder[end.kind] < boundaryOrder[start.kind])
|
|
900
|
+
throw this.#error("A window frame end cannot precede its start", end.range);
|
|
901
|
+
let exclude = "no-others";
|
|
902
|
+
if (this.#matchKeyword("EXCLUDE")) {
|
|
903
|
+
if (this.#matchKeyword("NO")) {
|
|
904
|
+
this.#expectKeyword("OTHERS");
|
|
905
|
+
}
|
|
906
|
+
else if (this.#matchKeyword("CURRENT")) {
|
|
907
|
+
this.#expectKeyword("ROW");
|
|
908
|
+
exclude = "current-row";
|
|
909
|
+
}
|
|
910
|
+
else if (this.#matchKeyword("GROUP"))
|
|
911
|
+
exclude = "group";
|
|
912
|
+
else {
|
|
913
|
+
this.#expectKeyword("TIES");
|
|
914
|
+
exclude = "ties";
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
return { unit, start, end, exclude, range: mergeRanges(unitToken.range, this.#previous().range) };
|
|
918
|
+
}
|
|
919
|
+
#parseWindowBoundary() {
|
|
920
|
+
const start = this.#current().range;
|
|
921
|
+
if (this.#matchKeyword("UNBOUNDED")) {
|
|
922
|
+
if (this.#matchKeyword("PRECEDING"))
|
|
923
|
+
return { kind: "unbounded-preceding", range: mergeRanges(start, this.#previous().range) };
|
|
924
|
+
this.#expectKeyword("FOLLOWING");
|
|
925
|
+
return { kind: "unbounded-following", range: mergeRanges(start, this.#previous().range) };
|
|
926
|
+
}
|
|
927
|
+
if (this.#matchKeyword("CURRENT")) {
|
|
928
|
+
this.#expectKeyword("ROW");
|
|
929
|
+
return { kind: "current-row", range: mergeRanges(start, this.#previous().range) };
|
|
930
|
+
}
|
|
931
|
+
const expression = this.#parseExpression();
|
|
932
|
+
if (this.#matchKeyword("PRECEDING"))
|
|
933
|
+
return { kind: "preceding", expression, range: mergeRanges(start, this.#previous().range) };
|
|
934
|
+
this.#expectKeyword("FOLLOWING");
|
|
935
|
+
return { kind: "following", expression, range: mergeRanges(start, this.#previous().range) };
|
|
936
|
+
}
|
|
937
|
+
#parseCast(start) {
|
|
938
|
+
this.#expectPunctuation("(");
|
|
939
|
+
const expression = this.#parseExpression();
|
|
940
|
+
this.#expectKeyword("AS");
|
|
941
|
+
const databaseType = this.#parseTypeName(true);
|
|
942
|
+
const close = this.#expectPunctuation(")");
|
|
943
|
+
return { kind: "cast", expression, databaseType, syntax: "cast", range: mergeRanges(start, close.range) };
|
|
944
|
+
}
|
|
945
|
+
#parseTypeName(stopAtClose) {
|
|
946
|
+
const start = this.#current();
|
|
947
|
+
if (!this.#isIdentifierLike(start))
|
|
948
|
+
throw this.#error(`Expected identifier, found ${start.text || "end of query"}`, start.range);
|
|
949
|
+
this.#advance();
|
|
950
|
+
let end = start;
|
|
951
|
+
let schemaSeparatorAllowed = true;
|
|
952
|
+
while (true) {
|
|
953
|
+
if (schemaSeparatorAllowed && this.#matchPunctuation(".")) {
|
|
954
|
+
this.#parseIdentifier(true);
|
|
955
|
+
end = this.#previous();
|
|
956
|
+
schemaSeparatorAllowed = false;
|
|
957
|
+
continue;
|
|
958
|
+
}
|
|
959
|
+
schemaSeparatorAllowed = false;
|
|
960
|
+
if (this.#matchPunctuation("(")) {
|
|
961
|
+
let depth = 1;
|
|
962
|
+
while (depth > 0) {
|
|
963
|
+
const token = this.#current();
|
|
964
|
+
if (token.kind === "eof")
|
|
965
|
+
throw this.#error("Unterminated type modifier", token.range);
|
|
966
|
+
this.#advance();
|
|
967
|
+
if (token.value === "(")
|
|
968
|
+
depth += 1;
|
|
969
|
+
else if (token.value === ")")
|
|
970
|
+
depth -= 1;
|
|
971
|
+
end = token;
|
|
972
|
+
}
|
|
973
|
+
continue;
|
|
974
|
+
}
|
|
975
|
+
if (this.#isIdentifierLike(this.#current()) && typeContinuationWords.has(this.#current().value.toUpperCase())) {
|
|
976
|
+
end = this.#advance();
|
|
977
|
+
continue;
|
|
978
|
+
}
|
|
979
|
+
if (this.#matchPunctuation("[")) {
|
|
980
|
+
end = this.#expectPunctuation("]");
|
|
981
|
+
continue;
|
|
982
|
+
}
|
|
983
|
+
break;
|
|
984
|
+
}
|
|
985
|
+
if (stopAtClose && this.#current().value !== ")")
|
|
986
|
+
throw this.#error(`Expected ) after type name, found ${this.#current().text}`, this.#current().range);
|
|
987
|
+
return {
|
|
988
|
+
name: this.#source.slice(start.range.start, end.range.end).trim(),
|
|
989
|
+
range: mergeRanges(start.range, end.range),
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
#parseCase(start) {
|
|
993
|
+
const operand = this.#current().value === "WHEN" ? undefined : this.#parseExpression();
|
|
994
|
+
const branches = [];
|
|
995
|
+
while (this.#matchKeyword("WHEN")) {
|
|
996
|
+
const whenStart = this.#previous().range;
|
|
997
|
+
const when = this.#parseExpression();
|
|
998
|
+
this.#expectKeyword("THEN");
|
|
999
|
+
const then = this.#parseExpression();
|
|
1000
|
+
branches.push({ when, then, range: mergeRanges(whenStart, then.range) });
|
|
1001
|
+
}
|
|
1002
|
+
if (branches.length === 0)
|
|
1003
|
+
throw this.#error("CASE requires at least one WHEN branch", this.#current().range);
|
|
1004
|
+
const elseExpression = this.#matchKeyword("ELSE") ? this.#parseExpression() : undefined;
|
|
1005
|
+
const end = this.#expectKeyword("END");
|
|
1006
|
+
return {
|
|
1007
|
+
kind: "case",
|
|
1008
|
+
...(operand === undefined ? {} : { operand }),
|
|
1009
|
+
branches,
|
|
1010
|
+
...(elseExpression === undefined ? {} : { elseExpression }),
|
|
1011
|
+
range: mergeRanges(start, end.range),
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
#parseIdentifier(allowKeyword = false) {
|
|
1015
|
+
const token = this.#current();
|
|
1016
|
+
if (!this.#isIdentifierLike(token) || (token.kind === "keyword" && !allowKeyword)) {
|
|
1017
|
+
throw this.#error(`Expected identifier, found ${token.text || "end of query"}`, token.range);
|
|
1018
|
+
}
|
|
1019
|
+
this.#advance();
|
|
1020
|
+
return { name: token.value, quoted: token.kind === "quoted-identifier", range: token.range };
|
|
1021
|
+
}
|
|
1022
|
+
#isIdentifierLike(token) {
|
|
1023
|
+
return token.kind === "identifier" || token.kind === "quoted-identifier" || token.kind === "keyword";
|
|
1024
|
+
}
|
|
1025
|
+
#isStatementStart() {
|
|
1026
|
+
return ["SELECT", "INSERT", "REPLACE", "UPDATE", "DELETE", "WITH"].includes(this.#current().value);
|
|
1027
|
+
}
|
|
1028
|
+
#current() {
|
|
1029
|
+
return this.#cursor.current();
|
|
1030
|
+
}
|
|
1031
|
+
#peekToken(offset) {
|
|
1032
|
+
return this.#cursor.peek(offset);
|
|
1033
|
+
}
|
|
1034
|
+
#previous() {
|
|
1035
|
+
return this.#cursor.previous();
|
|
1036
|
+
}
|
|
1037
|
+
#advance() {
|
|
1038
|
+
return this.#cursor.advance();
|
|
1039
|
+
}
|
|
1040
|
+
#matchKeyword(keyword) {
|
|
1041
|
+
if (this.#current().kind === "keyword" && this.#current().value === keyword) {
|
|
1042
|
+
this.#advance();
|
|
1043
|
+
return true;
|
|
1044
|
+
}
|
|
1045
|
+
return false;
|
|
1046
|
+
}
|
|
1047
|
+
#matchOperator(operator) {
|
|
1048
|
+
if (this.#current().kind === "operator" && this.#current().value === operator) {
|
|
1049
|
+
this.#advance();
|
|
1050
|
+
return true;
|
|
1051
|
+
}
|
|
1052
|
+
return false;
|
|
1053
|
+
}
|
|
1054
|
+
#matchPunctuation(value) {
|
|
1055
|
+
if (this.#current().kind === "punctuation" && this.#current().value === value) {
|
|
1056
|
+
this.#advance();
|
|
1057
|
+
return true;
|
|
1058
|
+
}
|
|
1059
|
+
return false;
|
|
1060
|
+
}
|
|
1061
|
+
#expectKeyword(keyword) {
|
|
1062
|
+
const token = this.#current();
|
|
1063
|
+
if (!this.#matchKeyword(keyword))
|
|
1064
|
+
throw this.#error(`Expected ${keyword}, found ${token.text || "end of query"}`, token.range);
|
|
1065
|
+
return token;
|
|
1066
|
+
}
|
|
1067
|
+
#expectOperator(operator) {
|
|
1068
|
+
const token = this.#current();
|
|
1069
|
+
if (!this.#matchOperator(operator))
|
|
1070
|
+
throw this.#error(`Expected ${operator}, found ${token.text || "end of query"}`, token.range);
|
|
1071
|
+
return token;
|
|
1072
|
+
}
|
|
1073
|
+
#expectPunctuation(value) {
|
|
1074
|
+
const token = this.#current();
|
|
1075
|
+
if (!this.#matchPunctuation(value))
|
|
1076
|
+
throw this.#error(`Expected ${value}, found ${token.text || "end of query"}`, token.range);
|
|
1077
|
+
return token;
|
|
1078
|
+
}
|
|
1079
|
+
#expect(kind, label) {
|
|
1080
|
+
const token = this.#current();
|
|
1081
|
+
if (token.kind !== kind)
|
|
1082
|
+
throw this.#error(`Expected ${label}, found ${token.text || "end of query"}`, token.range);
|
|
1083
|
+
this.#advance();
|
|
1084
|
+
return token;
|
|
1085
|
+
}
|
|
1086
|
+
#nested(fn) {
|
|
1087
|
+
return this.#cursor.nested(fn);
|
|
1088
|
+
}
|
|
1089
|
+
#error(message, range, code = "TSQ001") {
|
|
1090
|
+
return new SqlParseError(message, range, code);
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
function deepFreeze(value, seen = new Set()) {
|
|
1094
|
+
if (typeof value !== "object" || value === null || seen.has(value))
|
|
1095
|
+
return value;
|
|
1096
|
+
seen.add(value);
|
|
1097
|
+
for (const nested of Object.values(value))
|
|
1098
|
+
deepFreeze(nested, seen);
|
|
1099
|
+
return Object.freeze(value);
|
|
1100
|
+
}
|
|
1101
|
+
function parse(source, options) {
|
|
1102
|
+
try {
|
|
1103
|
+
return deepFreeze(new Parser(source, options).parse());
|
|
1104
|
+
}
|
|
1105
|
+
catch (error) {
|
|
1106
|
+
if (error instanceof SqlTokenizeError)
|
|
1107
|
+
throw new SqlParseError(error.message, error.range, error.code);
|
|
1108
|
+
if (error instanceof SqlToolkitError)
|
|
1109
|
+
throw new SqlParseError(error.message, error.range, error.code);
|
|
1110
|
+
throw error;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
export function parseStatement(source, options = {}) {
|
|
1114
|
+
return parse(source, options);
|
|
1115
|
+
}
|
|
1116
|
+
export function parseSelect(source, options = {}) {
|
|
1117
|
+
const statement = parse(source, options);
|
|
1118
|
+
if (statement.kind !== "select")
|
|
1119
|
+
throw new SqlParseError(`Expected SELECT, found ${statement.kind.toUpperCase()}`, statement.range);
|
|
1120
|
+
return statement;
|
|
1121
|
+
}
|
|
1122
|
+
//# sourceMappingURL=parser.js.map
|