sqllens 1.0.0 → 1.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/LICENSE +0 -10
- package/README.md +94 -12
- package/THIRD-PARTY-NOTICES.md +20 -0
- package/dist/api.js +6 -0
- package/dist/completion/config.js +66 -0
- package/dist/completion/parser-factory.js +36 -0
- package/dist/derived-dialects.js +19 -1
- package/dist/dialect-symbols.js +8 -0
- package/dist/dialect.d.ts +2 -2
- package/dist/generated/mysql/MysqlLexer.d.ts +1198 -0
- package/dist/generated/mysql/MysqlLexer.js +7357 -0
- package/dist/generated/mysql/MysqlParser.d.ts +10992 -0
- package/dist/generated/mysql/MysqlParser.js +84850 -0
- package/dist/generated/mysql/MysqlParserListener.d.ts +7592 -0
- package/dist/generated/mysql/MysqlParserListener.js +6958 -0
- package/dist/generated/sqlite/SqliteLexer.d.ts +210 -0
- package/dist/generated/sqlite/SqliteLexer.js +945 -0
- package/dist/generated/sqlite/SqliteParser.d.ts +2115 -0
- package/dist/generated/sqlite/SqliteParser.js +15832 -0
- package/dist/generated/sqlite/SqliteParserListener.d.ts +1276 -0
- package/dist/generated/sqlite/SqliteParserListener.js +1160 -0
- package/dist/ident/fold.js +49 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/infer/dialect.js +26 -0
- package/dist/infer/mysql.d.ts +20 -0
- package/dist/infer/mysql.js +155 -0
- package/dist/infer/sqlite.d.ts +12 -0
- package/dist/infer/sqlite.js +121 -0
- package/dist/ir/part-span.d.ts +16 -1
- package/dist/ir/part-span.js +38 -10
- package/dist/minijinja/parse.js +1 -1
- package/dist/mysql/lower.d.ts +13 -0
- package/dist/mysql/lower.js +1443 -0
- package/dist/mysql/parse.d.ts +10 -0
- package/dist/mysql/parse.js +70 -0
- package/dist/qualify/check-calls.js +33 -6
- package/dist/signature/signatures.js +139 -0
- package/dist/sqlite/lower.d.ts +11 -0
- package/dist/sqlite/lower.js +1093 -0
- package/dist/sqlite/parse.d.ts +10 -0
- package/dist/sqlite/parse.js +70 -0
- package/dist/token/classify.js +31 -0
- package/dist/token/tokenize.js +4 -0
- package/package.json +12 -3
package/LICENSE
CHANGED
|
@@ -19,13 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
21
|
SOFTWARE.
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
This MIT license covers the original sqllens source: everything under src/
|
|
26
|
-
(excluding generated output), tools/, tests/, and the project documentation.
|
|
27
|
-
|
|
28
|
-
The hand-maintained ANTLR grammars under grammars/ are forks of third-party
|
|
29
|
-
grammars and remain under their upstream licenses (Apache-2.0, BSD-3-Clause, and
|
|
30
|
-
MIT, depending on the grammar). Each grammar file retains its original license
|
|
31
|
-
header, and the full per-grammar attributions are in THIRD-PARTY-NOTICES.md.
|
package/README.md
CHANGED
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/sqllens) [](LICENSE)
|
|
4
4
|
|
|
5
|
-
A TypeScript SQL parser and static analyzer. It parses SQL into a tree
|
|
6
|
-
to a dialect-neutral intermediate representation (IR), and runs
|
|
7
|
-
over that IR: name resolution (scope), schema-fed qualification,
|
|
8
|
-
and column lineage. Give it a
|
|
5
|
+
A TypeScript SQL parser and static analyzer. It parses SQL into a syntax tree
|
|
6
|
+
(AST), lowers it to a dialect-neutral intermediate representation (IR), and runs
|
|
7
|
+
a semantic layer over that IR: name resolution (scope), schema-fed qualification,
|
|
8
|
+
type inference, and column lineage. Give it a
|
|
9
9
|
query and it tells you the query's sources, its output columns, their types, and
|
|
10
10
|
where each column comes from. The parsers are generated TypeScript on the
|
|
11
|
-
[antlr4ng](https://github.com/mike-lischke/antlr4ng) runtime.
|
|
11
|
+
[antlr4ng](https://github.com/mike-lischke/antlr4ng) runtime. Dialects covered:
|
|
12
|
+
Databricks (Spark SQL), T-SQL, Snowflake, BigQuery (GoogleSQL), Redshift,
|
|
13
|
+
PostgreSQL, DuckDB, Trino, SQLite, and MySQL, plus derived engines such as
|
|
14
|
+
Athena, Fabric, and MariaDB (see [Dialects](#dialects)).
|
|
12
15
|
|
|
13
16
|
The front end is error-tolerant and token-first, so the library drives editor
|
|
14
17
|
features (completion, hover, diagnostics, go-to-definition) over incomplete,
|
|
@@ -32,9 +35,9 @@ q.lineage.originsOf("total"); // → orders.total
|
|
|
32
35
|
|
|
33
36
|
## Dialects
|
|
34
37
|
|
|
35
|
-
sqllens implements
|
|
36
|
-
|
|
37
|
-
of
|
|
38
|
+
sqllens implements the major SQL dialects directly, each with its own grammar.
|
|
39
|
+
More engines are covered as *derived dialects*: their SQL is already parsed by
|
|
40
|
+
one of the primary grammars.
|
|
38
41
|
|
|
39
42
|
| Dialect | Derived dialects | Parse + lower | Semantic layer | Notes |
|
|
40
43
|
|---|---|---|---|---|
|
|
@@ -46,6 +49,8 @@ of those eight grammars, for 15 engines in total.
|
|
|
46
49
|
| PostgreSQL | — | yes | yes | grammar forked from `bytebase/parser` `postgresql/` (BSD-3, PG18 keywords) |
|
|
47
50
|
| DuckDB | — | yes | yes | grammar forked from this repo's own postgres pair (no open ANTLR grammar exists) |
|
|
48
51
|
| Trino | Presto, Amazon Athena | yes | yes | grammar is the first-party trinodb `SqlBase.g4` (release 482), mechanically split |
|
|
52
|
+
| SQLite | — | yes | yes | grammar forked from grammars-v4 `sql/sqlite` (MIT); entry rule `parse` |
|
|
53
|
+
| MySQL | MariaDB (partial — ordinary DQL/DML only, MariaDB-only extensions unmodeled) | yes | yes | grammar forked from grammars-v4 `sql/mysql/Positive-Technologies` (MIT); entry rule `root` |
|
|
49
54
|
|
|
50
55
|
Each grammar began as a fork of the upstream noted above, but most are now far from
|
|
51
56
|
verbatim copies. They've had substantial extension and correction, driven by a full
|
|
@@ -58,9 +63,21 @@ the primary dialect's. Microsoft Fabric runs a restricted subset of T-SQL, Amazo
|
|
|
58
63
|
Athena's engine is Trino, and AWS Glue runs Spark. Each one is checked against real
|
|
59
64
|
SQL from that engine before it goes on the list.
|
|
60
65
|
|
|
61
|
-
In code, the `dialect` argument is
|
|
62
|
-
engine name (or a dialect name) into the
|
|
63
|
-
|
|
66
|
+
In code, the `dialect` argument is a plain string, and `resolveDialect` turns an
|
|
67
|
+
engine name (or a dialect name) into the dialect that parses its SQL:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { parse, resolveDialect } from "sqllens";
|
|
71
|
+
|
|
72
|
+
// dialect strings: "databricks" | "tsql" | "snowflake" | "bigquery" |
|
|
73
|
+
// "redshift" | "postgres" | "duckdb" | "trino" | "sqlite" | "mysql"
|
|
74
|
+
parse("SELECT 1", "snowflake");
|
|
75
|
+
|
|
76
|
+
// engine name → the dialect that parses it
|
|
77
|
+
resolveDialect("athena"); // "trino"
|
|
78
|
+
resolveDialect("fabric"); // "tsql"
|
|
79
|
+
resolveDialect("mariadb"); // "mysql"
|
|
80
|
+
```
|
|
64
81
|
|
|
65
82
|
The semantic layer is dialect-agnostic: it operates on the shared IR and runs
|
|
66
83
|
unchanged on every dialect. Only the parse and lower stages are dialect-specific.
|
|
@@ -73,7 +90,7 @@ parse → lower → resolveScopes → qualify → infer / lineage / symbols
|
|
|
73
90
|
|
|
74
91
|
Each stage produces one value, and that value is what a specific editor feature
|
|
75
92
|
reads from. Only the first two stages, parse and lower, are dialect-specific;
|
|
76
|
-
everything after them is shared and runs unchanged across
|
|
93
|
+
everything after them is shared and runs unchanged across every dialect.
|
|
77
94
|
|
|
78
95
|
**parse** turns SQL text into a *concrete syntax tree* (CST): the full parse tree,
|
|
79
96
|
every token and grammar node exactly as written, nothing dropped or simplified. It
|
|
@@ -285,6 +302,41 @@ invariants the conformance gates check. Tokens tile the source byte-for-byte, ev
|
|
|
285
302
|
span is in original document coordinates, broken input never throws, and tag-free
|
|
286
303
|
text is identical to a plain parse.
|
|
287
304
|
|
|
305
|
+
## Broken and incomplete SQL
|
|
306
|
+
|
|
307
|
+
sqllens is error-tolerant by construction, because its first consumer is an
|
|
308
|
+
editor and editor input is mid-keystroke most of the time. Parsing broken,
|
|
309
|
+
partial, or invalid SQL never throws: syntax errors come back as positioned
|
|
310
|
+
diagnostics (line, column, offset, length), ready for editor squiggles, and
|
|
311
|
+
the rest of the result stays usable.
|
|
312
|
+
|
|
313
|
+
```ts
|
|
314
|
+
import { parse } from "sqllens";
|
|
315
|
+
|
|
316
|
+
// mid-edit input: a dangling comma and an unfinished WHERE
|
|
317
|
+
const r = parse("SELECT total, FROM orders WHERE", "postgres");
|
|
318
|
+
|
|
319
|
+
r.errors; // 1 — counted, not thrown
|
|
320
|
+
r.diagnostics[0]; // { message: "mismatched input ','…", line: 1, column: 12, offset: 12, length: 1 }
|
|
321
|
+
r.ast; // still a usable IR — lower() is total on broken input
|
|
322
|
+
r.tokens.length; // 10 — the full token stream, exact spans intact
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Every downstream pass keeps the same contract: `lower()` yields a flagged IR
|
|
326
|
+
instead of throwing, statement-level containment keeps one broken statement
|
|
327
|
+
from taking down its neighbors, and the interactive features run on the
|
|
328
|
+
broken text directly:
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
import { SqlSession, Schema } from "sqllens";
|
|
332
|
+
|
|
333
|
+
const schema = new Schema({ orders: { id: "int", total: "decimal" } });
|
|
334
|
+
|
|
335
|
+
// the projection slot is empty — the user just hasn't typed it yet
|
|
336
|
+
const s = SqlSession.create("SELECT FROM orders", "postgres", { schema });
|
|
337
|
+
s.completeAt(7); // candidates for the empty slot: total, id, keywords, functions
|
|
338
|
+
```
|
|
339
|
+
|
|
288
340
|
## Editor / language tooling
|
|
289
341
|
|
|
290
342
|
The front end is error-tolerant and token-first, so it serves editor features
|
|
@@ -309,6 +361,17 @@ that run on incomplete, mid-edit text. They never need a clean parse:
|
|
|
309
361
|
of the symbol under the cursor; backs find-references, document highlight, and
|
|
310
362
|
code-lens reference counts.
|
|
311
363
|
|
|
364
|
+
To tokenize SQL without parsing at all, `tokenize` is lexer-only and works on
|
|
365
|
+
any text, including text no parser would accept:
|
|
366
|
+
|
|
367
|
+
```ts
|
|
368
|
+
import { tokenize } from "sqllens";
|
|
369
|
+
|
|
370
|
+
const tokens = tokenize("SELECT amount FROM sales", "snowflake");
|
|
371
|
+
tokens[0]; // { text: "SELECT", start: 0, stop: 5, line: 1, column: 0, role: "keyword", channel: 0, … }
|
|
372
|
+
tokens[1]; // whitespace rides the hidden channel: { text: " ", channel: 1, role: "whitespace", … }
|
|
373
|
+
```
|
|
374
|
+
|
|
312
375
|
```ts
|
|
313
376
|
import { SqlDocument, Schema } from "sqllens";
|
|
314
377
|
|
|
@@ -388,6 +451,25 @@ deferred items are tracked work: rename and
|
|
|
388
451
|
code actions are the next LSP phase, workspace symbols need the project model,
|
|
389
452
|
and formatting is expected to wrap an existing external formatter.
|
|
390
453
|
|
|
454
|
+
## How sqllens compares
|
|
455
|
+
|
|
456
|
+
The SQL-parser field splits into parse-only libraries and semantic tools bound
|
|
457
|
+
to a single borrowed parser. The full survey, with the whole field catalogued,
|
|
458
|
+
is in [docs/sql-parser-landscape.md](docs/sql-parser-landscape.md); the short
|
|
459
|
+
version against the libraries people usually reach for:
|
|
460
|
+
|
|
461
|
+
| | Language | Dialect breadth | Semantic analysis | Error-tolerant, editor-grade |
|
|
462
|
+
|---|---|---|---|---|
|
|
463
|
+
| **sqllens** | TypeScript | Databricks, T-SQL, Snowflake, BigQuery, Redshift, PostgreSQL, DuckDB, Trino, SQLite, MySQL | scope, schema qualification, type inference, column lineage, symbols | yes: parses mid-keystroke input, positioned diagnostics, total pipeline |
|
|
464
|
+
| [sqlglot](https://github.com/tobymao/sqlglot) | Python | 31 dialects | transpile, optimize, qualify, lineage | no: a batch library, not built for per-keystroke reparse |
|
|
465
|
+
| [node-sql-parser](https://github.com/taozhi8833998/node-sql-parser) | JS/TS | MySQL, PostgreSQL, and more | table/column lists only; no lineage, no types | no |
|
|
466
|
+
| [sqllineage](https://github.com/reata/sqllineage) | Python | via sqlfluff's parser | column lineage only | no |
|
|
467
|
+
| [libpg_query](https://github.com/pganalyze/libpg_query) | C (bindings) | PostgreSQL, exact | parse only | no: one syntax error fails the whole buffer |
|
|
468
|
+
|
|
469
|
+
The corner sqllens occupies: multi-dialect breadth, schema-fed semantics, and
|
|
470
|
+
editor-grade error tolerance in one TypeScript library. Each piece exists
|
|
471
|
+
elsewhere; the combination did not.
|
|
472
|
+
|
|
391
473
|
## Architecture
|
|
392
474
|
|
|
393
475
|
One folder per dialect; no shared "core" grammar and no grammar inheritance. Each
|
package/THIRD-PARTY-NOTICES.md
CHANGED
|
@@ -100,6 +100,26 @@ Forked from [antlr/grammars-v4](https://github.com/antlr/grammars-v4)
|
|
|
100
100
|
`sql/snowflake`. Copyright (c) 2022 Michał Lorek. Licensed under the MIT License
|
|
101
101
|
(full text retained in the file header).
|
|
102
102
|
|
|
103
|
+
### SQLite grammar — MIT
|
|
104
|
+
|
|
105
|
+
`grammars/sqlite/SqliteLexer.g4`, `grammars/sqlite/SqliteParser.g4`
|
|
106
|
+
|
|
107
|
+
Forked from [antlr/grammars-v4](https://github.com/antlr/grammars-v4) `sql/sqlite`
|
|
108
|
+
(upstream commit `8af0d4c26c796ea27c15c3d85418f2d0f77c3adb`, retrieved 2026-07-10).
|
|
109
|
+
Copyright (c) 2020 Martin Mirchev; (c) 2014 Bart Kiers. Licensed under the MIT
|
|
110
|
+
License (full text retained in the file headers).
|
|
111
|
+
|
|
112
|
+
### MySQL grammar — MIT
|
|
113
|
+
|
|
114
|
+
`grammars/mysql/MysqlLexer.g4`, `grammars/mysql/MysqlParser.g4`
|
|
115
|
+
|
|
116
|
+
Forked from [antlr/grammars-v4](https://github.com/antlr/grammars-v4)
|
|
117
|
+
`sql/mysql/Positive-Technologies` (upstream commit
|
|
118
|
+
`bf61744020dc46f2d7b8761e35b0c0cb39b3f31a`, retrieved 2026-07-10) — not the
|
|
119
|
+
`sql/mysql/Oracle` sibling variant. Copyright (c) 2015-2017 Ivan Kochurkin,
|
|
120
|
+
Positive Technologies; (c) 2017 Ivan Khudyashev. Licensed under the MIT License
|
|
121
|
+
(full text retained in the file headers).
|
|
122
|
+
|
|
103
123
|
## Runtime and build dependencies (not redistributed in source)
|
|
104
124
|
|
|
105
125
|
- **antlr4ng** — the TypeScript ANTLR runtime (BSD-3-Clause). Runtime dependency.
|
package/dist/api.js
CHANGED
|
@@ -27,6 +27,10 @@ import { parseDuckdb } from "./duckdb/parse.js";
|
|
|
27
27
|
import { lower as lowerDuckdb } from "./duckdb/lower.js";
|
|
28
28
|
import { parseTrino } from "./trino/parse.js";
|
|
29
29
|
import { lower as lowerTrino } from "./trino/lower.js";
|
|
30
|
+
import { parseSqlite } from "./sqlite/parse.js";
|
|
31
|
+
import { lower as lowerSqlite } from "./sqlite/lower.js";
|
|
32
|
+
import { parseMysql } from "./mysql/parse.js";
|
|
33
|
+
import { lower as lowerMysql } from "./mysql/lower.js";
|
|
30
34
|
import { resolveScopes } from "./scope/scope.js";
|
|
31
35
|
import { qualify as qualifyScopes } from "./qualify/qualify.js";
|
|
32
36
|
import { OPEN_PROVIDER } from "./qualify/template-provider.js";
|
|
@@ -43,6 +47,8 @@ const DIALECTS = {
|
|
|
43
47
|
postgres: { parse: parsePostgres, lower: lowerPostgres },
|
|
44
48
|
duckdb: { parse: parseDuckdb, lower: lowerDuckdb },
|
|
45
49
|
trino: { parse: parseTrino, lower: lowerTrino },
|
|
50
|
+
sqlite: { parse: parseSqlite, lower: lowerSqlite },
|
|
51
|
+
mysql: { parse: parseMysql, lower: lowerMysql },
|
|
46
52
|
};
|
|
47
53
|
/**
|
|
48
54
|
* Parse one statement (or a dialect's statement batch) and lower it to the IR. Dispatches on
|
|
@@ -15,6 +15,10 @@ import { DuckdbLexer } from "../generated/duckdb/DuckdbLexer.js";
|
|
|
15
15
|
import { DuckdbParser } from "../generated/duckdb/DuckdbParser.js";
|
|
16
16
|
import { TrinoLexer } from "../generated/trino/TrinoLexer.js";
|
|
17
17
|
import { TrinoParser } from "../generated/trino/TrinoParser.js";
|
|
18
|
+
import { SqliteLexer } from "../generated/sqlite/SqliteLexer.js";
|
|
19
|
+
import { SqliteParser } from "../generated/sqlite/SqliteParser.js";
|
|
20
|
+
import { MysqlLexer } from "../generated/mysql/MysqlLexer.js";
|
|
21
|
+
import { MysqlParser } from "../generated/mysql/MysqlParser.js";
|
|
18
22
|
// Databricks (Spark grammar) name-reference rules — each cited by its grammar rule:
|
|
19
23
|
// identifierReference → the table/view/name reference used in `relationPrimary` (post-FROM),
|
|
20
24
|
// `DatabricksParser.g4:755` (`IDENTIFIER(expr)` | multipartIdentifier).
|
|
@@ -116,6 +120,52 @@ const TRINO_NAME_TOKENS = new Set([
|
|
|
116
120
|
TrinoLexer.BACKQUOTED_IDENTIFIER,
|
|
117
121
|
TrinoLexer.DIGIT_IDENTIFIER,
|
|
118
122
|
]);
|
|
123
|
+
// ── SQLite (grammars-v4 fork) ───────────────────────────────────────────────
|
|
124
|
+
// post-FROM → table_name (the relation-name leaf; the enclosing `table_or_subquery` is a
|
|
125
|
+
// wider alternation that also recurses into `select_stmt` for a parenthesized
|
|
126
|
+
// subquery/join, so marking IT preferred would swallow completion inside a nested
|
|
127
|
+
// FROM (SELECT …) — table_name alone still fires at "FROM ‹›" with nothing typed,
|
|
128
|
+
// since the ATN walk explores entering it before any token is consumed. table_name
|
|
129
|
+
// is also the slot reused by INSERT INTO/UPDATE/ALTER/DROP/CREATE TABLE's table-name
|
|
130
|
+
// position, which is a bonus, not a target).
|
|
131
|
+
// SELECT/WHERE → expr (the value/column slot; expr_base's `column_name_excluding_string` and the
|
|
132
|
+
// qualified `table_name DOT column_name` form both nest under it, matching the
|
|
133
|
+
// Snowflake `expr` precedent — a single outer entry rule for the whole
|
|
134
|
+
// precedence-chain expression grammar).
|
|
135
|
+
// table_name ALSO appears inside expr_base's qualified-column-ref and `x IN table_name` forms; since
|
|
136
|
+
// expr is the outer frame there, those inner positions report columnRules only, not tableRules — a
|
|
137
|
+
// known, accepted imprecision (same shape as the other dialects' rule choices here).
|
|
138
|
+
const SQLITE_TABLE_RULES = new Set([SqliteParser.RULE_table_name]);
|
|
139
|
+
const SQLITE_COLUMN_RULES = new Set([SqliteParser.RULE_expr]);
|
|
140
|
+
const SQLITE_PREFERRED = new Set([...SQLITE_TABLE_RULES, ...SQLITE_COLUMN_RULES]);
|
|
141
|
+
const SQLITE_RELATION_KEYWORDS = new Set([SqliteLexer.FROM_, SqliteLexer.JOIN_]);
|
|
142
|
+
// SQLite's lexer folds plain/"double"/`backtick`/[bracket]-quoted names into ONE IDENTIFIER token
|
|
143
|
+
// (SqliteLexer.g4's IDENTIFIER rule matches all four forms), so there is no separate quoted-ident
|
|
144
|
+
// token type to add, unlike T-SQL/Trino/Postgres.
|
|
145
|
+
const SQLITE_NAME_TOKENS = new Set([SqliteLexer.IDENTIFIER]);
|
|
146
|
+
// ── MySQL (grammars-v4 mysql/Positive-Technologies fork) ───────────────────
|
|
147
|
+
// post-FROM → tableName (the relation-name leaf; the enclosing `tableSourceItem` is a wider
|
|
148
|
+
// 4-way alternation whose `subqueryTableItem` arm recurses into `selectStatement`
|
|
149
|
+
// for a parenthesized subquery, so marking IT preferred would swallow completion
|
|
150
|
+
// inside a nested "FROM (SELECT ... FROM ‹›)" — same table_or_subquery-vs-table_name
|
|
151
|
+
// trap as the SQLite entry above. tableName wraps fullId -> uid, so it still fires at
|
|
152
|
+
// "FROM ‹›" with nothing typed. tableName is also reused by INSERT INTO/UPDATE/DELETE/
|
|
153
|
+
// DDL's table-name slot, a bonus, not a target).
|
|
154
|
+
// SELECT/WHERE → expression (the outer frame of the expression -> predicate -> expressionAtom
|
|
155
|
+
// precedence chain; fullColumnName nests under it, matching the Snowflake/SQLite
|
|
156
|
+
// `expr`-as-single-outer-rule precedent).
|
|
157
|
+
// tableName ALSO appears inside fullColumnName-adjacent and IN-list positions reached from inside
|
|
158
|
+
// `expression`; since expression is the outer frame there, those inner positions report columnRules
|
|
159
|
+
// only, not tableRules — the same accepted imprecision as the other dialects' choices here.
|
|
160
|
+
const MYSQL_TABLE_RULES = new Set([MysqlParser.RULE_tableName]);
|
|
161
|
+
const MYSQL_COLUMN_RULES = new Set([MysqlParser.RULE_expression]);
|
|
162
|
+
const MYSQL_PREFERRED = new Set([...MYSQL_TABLE_RULES, ...MYSQL_COLUMN_RULES]);
|
|
163
|
+
const MYSQL_RELATION_KEYWORDS = new Set([MysqlLexer.FROM, MysqlLexer.JOIN]);
|
|
164
|
+
// MySQL's `uid` rule (the identifier slot fullId/tableName bottom out on) accepts simpleId (built on
|
|
165
|
+
// the plain ID token) or STRING_LITERAL — this fork's DOUBLE_QUOTE_ID/REVERSE_QUOTE_ID alternatives
|
|
166
|
+
// are commented out of `uid`, so backtick/double-quoted names lex to, and reach `uid` through,
|
|
167
|
+
// STRING_LITERAL (docs/identifier-delimiter-contract.md's MySQL note says the same).
|
|
168
|
+
const MYSQL_NAME_TOKENS = new Set([MysqlLexer.ID, MysqlLexer.STRING_LITERAL]);
|
|
119
169
|
export const COMPLETION_CONFIG = {
|
|
120
170
|
databricks: {
|
|
121
171
|
preferredRules: DATABRICKS_PREFERRED,
|
|
@@ -181,4 +231,20 @@ export const COMPLETION_CONFIG = {
|
|
|
181
231
|
relationKeywordTokens: TRINO_RELATION_KEYWORDS,
|
|
182
232
|
nameTokens: TRINO_NAME_TOKENS,
|
|
183
233
|
},
|
|
234
|
+
sqlite: {
|
|
235
|
+
preferredRules: SQLITE_PREFERRED,
|
|
236
|
+
ignoredTokens: new Set([Token.EOF]),
|
|
237
|
+
tableRules: SQLITE_TABLE_RULES,
|
|
238
|
+
columnRules: SQLITE_COLUMN_RULES,
|
|
239
|
+
relationKeywordTokens: SQLITE_RELATION_KEYWORDS,
|
|
240
|
+
nameTokens: SQLITE_NAME_TOKENS,
|
|
241
|
+
},
|
|
242
|
+
mysql: {
|
|
243
|
+
preferredRules: MYSQL_PREFERRED,
|
|
244
|
+
ignoredTokens: new Set([Token.EOF]),
|
|
245
|
+
tableRules: MYSQL_TABLE_RULES,
|
|
246
|
+
columnRules: MYSQL_COLUMN_RULES,
|
|
247
|
+
relationKeywordTokens: MYSQL_RELATION_KEYWORDS,
|
|
248
|
+
nameTokens: MYSQL_NAME_TOKENS,
|
|
249
|
+
},
|
|
184
250
|
};
|
|
@@ -15,6 +15,10 @@ import { DuckdbLexer } from "../generated/duckdb/DuckdbLexer.js";
|
|
|
15
15
|
import { DuckdbParser } from "../generated/duckdb/DuckdbParser.js";
|
|
16
16
|
import { TrinoLexer } from "../generated/trino/TrinoLexer.js";
|
|
17
17
|
import { TrinoParser } from "../generated/trino/TrinoParser.js";
|
|
18
|
+
import { SqliteLexer } from "../generated/sqlite/SqliteLexer.js";
|
|
19
|
+
import { SqliteParser } from "../generated/sqlite/SqliteParser.js";
|
|
20
|
+
import { MysqlLexer } from "../generated/mysql/MysqlLexer.js";
|
|
21
|
+
import { MysqlParser } from "../generated/mysql/MysqlParser.js";
|
|
18
22
|
function databricksFactory(sql) {
|
|
19
23
|
const lexer = new DatabricksLexer(CharStream.fromString(sql));
|
|
20
24
|
const tokenStream = new CommonTokenStream(lexer);
|
|
@@ -141,6 +145,36 @@ function trinoFactory(sql) {
|
|
|
141
145
|
runEntry: () => parser.root(),
|
|
142
146
|
};
|
|
143
147
|
}
|
|
148
|
+
function sqliteFactory(sql) {
|
|
149
|
+
const lexer = new SqliteLexer(CharStream.fromString(sql));
|
|
150
|
+
const tokenStream = new CommonTokenStream(lexer);
|
|
151
|
+
const parser = new SqliteParser(tokenStream);
|
|
152
|
+
parser.errorHandler = new DefaultErrorStrategy();
|
|
153
|
+
lexer.removeErrorListeners();
|
|
154
|
+
parser.removeErrorListeners();
|
|
155
|
+
return {
|
|
156
|
+
parser,
|
|
157
|
+
lexer,
|
|
158
|
+
tokenStream,
|
|
159
|
+
entryRuleIndex: SqliteParser.RULE_parse,
|
|
160
|
+
runEntry: () => parser.parse(),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function mysqlFactory(sql) {
|
|
164
|
+
const lexer = new MysqlLexer(CharStream.fromString(sql));
|
|
165
|
+
const tokenStream = new CommonTokenStream(lexer);
|
|
166
|
+
const parser = new MysqlParser(tokenStream);
|
|
167
|
+
parser.errorHandler = new DefaultErrorStrategy();
|
|
168
|
+
lexer.removeErrorListeners();
|
|
169
|
+
parser.removeErrorListeners();
|
|
170
|
+
return {
|
|
171
|
+
parser,
|
|
172
|
+
lexer,
|
|
173
|
+
tokenStream,
|
|
174
|
+
entryRuleIndex: MysqlParser.RULE_root,
|
|
175
|
+
runEntry: () => parser.root(),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
144
178
|
const FACTORIES = {
|
|
145
179
|
databricks: databricksFactory,
|
|
146
180
|
tsql: tsqlFactory,
|
|
@@ -150,6 +184,8 @@ const FACTORIES = {
|
|
|
150
184
|
postgres: postgresFactory,
|
|
151
185
|
duckdb: duckdbFactory,
|
|
152
186
|
trino: trinoFactory,
|
|
187
|
+
sqlite: sqliteFactory,
|
|
188
|
+
mysql: mysqlFactory,
|
|
153
189
|
};
|
|
154
190
|
/** Build a fresh error-tolerant parser for `dialect`, lexing `sql`. */
|
|
155
191
|
export function makeParser(sql, dialect) {
|
package/dist/derived-dialects.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// ---------------------------------------------------------------------------
|
|
2
|
-
// Derived-dialect → dialect map. The
|
|
2
|
+
// Derived-dialect → dialect map. The grammars parse more than their own named
|
|
3
3
|
// engines: a *derived dialect* is an engine with no grammar of its own whose
|
|
4
4
|
// SQL surface is a subset of — or identical to — one we already parse (Amazon
|
|
5
5
|
// Athena's engine is Trino, AWS Glue runs Spark, Microsoft Fabric / Azure
|
|
@@ -20,6 +20,8 @@ export const DERIVED_DIALECTS = {
|
|
|
20
20
|
postgres: "postgres",
|
|
21
21
|
duckdb: "duckdb",
|
|
22
22
|
trino: "trino",
|
|
23
|
+
sqlite: "sqlite",
|
|
24
|
+
mysql: "mysql",
|
|
23
25
|
// our dialect name (not an engine name) — accepted so both vocabularies work
|
|
24
26
|
tsql: "tsql",
|
|
25
27
|
// Spark SQL family — Databricks SQL = Spark SQL; AWS Glue runs Spark
|
|
@@ -34,6 +36,22 @@ export const DERIVED_DIALECTS = {
|
|
|
34
36
|
// is Trino's predecessor
|
|
35
37
|
athena: "trino",
|
|
36
38
|
presto: "trino",
|
|
39
|
+
// MariaDB — forked from MySQL 5.1 and still a near-superset for ordinary DQL/DML, so mapped to
|
|
40
|
+
// the mysql grammar as a PARTIAL derived alias (Open Gap, not full coverage — MariaDB's own
|
|
41
|
+
// extensions are unmodeled). B-R5.5 spot-checked four MariaDB-specific statements against the
|
|
42
|
+
// mysql/Positive-Technologies grammar (grammars/mysql/) by actually parsing them
|
|
43
|
+
// (temp_auto/mariadb-probe.mts, parseMysql()): all four FAIL —
|
|
44
|
+
// `SELECT NEXT VALUE FOR seq_name` (mariadb.com/docs/.../sequences/next-value) — "no viable
|
|
45
|
+
// alternative" (no NEXT/VALUE/FOR sequence-expression production);
|
|
46
|
+
// `DELETE FROM t WHERE id = 1 RETURNING *` (mariadb.com/docs/.../delete) — "mismatched input
|
|
47
|
+
// 'RETURNING'" (no RETURNING clause on DELETE; MySQL itself has none either);
|
|
48
|
+
// `INSERT INTO t (a) VALUES (1) RETURNING *` (mariadb.com/docs/.../insert) — "extraneous input
|
|
49
|
+
// '*'", same missing-RETURNING gap;
|
|
50
|
+
// `CREATE SEQUENCE seq_name START WITH 1 INCREMENT BY 1` (mariadb.com/docs/.../sequences/
|
|
51
|
+
// create-sequence) — "no viable alternative" (no CREATE SEQUENCE DDL in this grammar).
|
|
52
|
+
// A plain `SELECT a, b FROM t WHERE a = 1` control probe parses with 0 errors, confirming
|
|
53
|
+
// ordinary DQL still works — the alias covers that surface, not MariaDB's own additions.
|
|
54
|
+
mariadb: "mysql",
|
|
37
55
|
// Alternate spelling of the engine name (alias class, same as our own dialect
|
|
38
56
|
// names above). Admitted 2026-07-10 on the anvil channel's request; caveat noted
|
|
39
57
|
// there: no dbt adapter is attested to emit `postgresql` as its adapter_type —
|
package/dist/dialect-symbols.js
CHANGED
|
@@ -75,6 +75,8 @@ import { RedshiftLexer } from "./generated/redshift/RedshiftLexer.js";
|
|
|
75
75
|
import { PostgresLexer } from "./generated/postgres/PostgresLexer.js";
|
|
76
76
|
import { DuckdbLexer } from "./generated/duckdb/DuckdbLexer.js";
|
|
77
77
|
import { TrinoLexer } from "./generated/trino/TrinoLexer.js";
|
|
78
|
+
import { SqliteLexer } from "./generated/sqlite/SqliteLexer.js";
|
|
79
|
+
import { MysqlLexer } from "./generated/mysql/MysqlLexer.js";
|
|
78
80
|
import { inferDialect } from "./infer/dialect.js";
|
|
79
81
|
import { HOF_LAMBDA_ARG } from "./infer/infer.js";
|
|
80
82
|
import { SCALAR_ALIASES, TSQL_ALIASES } from "./infer/types.js";
|
|
@@ -84,6 +86,8 @@ import { REDSHIFT_ALIASES } from "./infer/redshift.js";
|
|
|
84
86
|
import { POSTGRES_ALIASES } from "./infer/postgres.js";
|
|
85
87
|
import { DUCKDB_ALIASES } from "./infer/duckdb.js";
|
|
86
88
|
import { TRINO_ALIASES } from "./infer/trino.js";
|
|
89
|
+
import { SQLITE_ALIASES } from "./infer/sqlite.js";
|
|
90
|
+
import { MYSQL_ALIASES } from "./infer/mysql.js";
|
|
87
91
|
import { FUNCTION_SIGNATURES, HARVESTED_SIGNATURES } from "./signature/signatures.js";
|
|
88
92
|
// bigquery's generated lexer class is GoogleSQLLexer (the fork is Bytebase's GoogleSQL grammar).
|
|
89
93
|
const LEXERS = {
|
|
@@ -95,6 +99,8 @@ const LEXERS = {
|
|
|
95
99
|
postgres: () => new PostgresLexer(CharStream.fromString("")),
|
|
96
100
|
duckdb: () => new DuckdbLexer(CharStream.fromString("")),
|
|
97
101
|
trino: () => new TrinoLexer(CharStream.fromString("")),
|
|
102
|
+
sqlite: () => new SqliteLexer(CharStream.fromString("")),
|
|
103
|
+
mysql: () => new MysqlLexer(CharStream.fromString("")),
|
|
98
104
|
};
|
|
99
105
|
// The scalar-type-alias table per dialect (see module header, `types` set). Databricks has no
|
|
100
106
|
// dedicated table — dialect.ts's `parseType` falls back to types.ts's default (SCALAR_ALIASES).
|
|
@@ -107,6 +113,8 @@ const TYPE_ALIASES = {
|
|
|
107
113
|
postgres: POSTGRES_ALIASES,
|
|
108
114
|
duckdb: DUCKDB_ALIASES,
|
|
109
115
|
trino: TRINO_ALIASES,
|
|
116
|
+
sqlite: SQLITE_ALIASES,
|
|
117
|
+
mysql: MYSQL_ALIASES,
|
|
110
118
|
};
|
|
111
119
|
/** A bare, keyword-shaped literal token text: letters/digits/underscore, starting with a
|
|
112
120
|
* letter or underscore. Filters out punctuation (`'('`, `','`) and operator (`'<>'`, `'::'`)
|
package/dist/dialect.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/** The dialects reachable through the unified surface. Each has its own grammar/CST and a
|
|
2
|
-
* parse+lower pair; everything after lower() runs unchanged on all
|
|
3
|
-
export type Dialect = "databricks" | "tsql" | "snowflake" | "bigquery" | "redshift" | "postgres" | "duckdb" | "trino";
|
|
2
|
+
* parse+lower pair; everything after lower() runs unchanged on all of them. */
|
|
3
|
+
export type Dialect = "databricks" | "tsql" | "snowflake" | "bigquery" | "redshift" | "postgres" | "duckdb" | "trino" | "sqlite" | "mysql";
|