sqllens 0.1.1 → 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.
Files changed (103) hide show
  1. package/LICENSE +0 -10
  2. package/README.md +361 -125
  3. package/THIRD-PARTY-NOTICES.md +20 -0
  4. package/dist/api.d.ts +9 -7
  5. package/dist/api.js +21 -6
  6. package/dist/bigquery/parse.d.ts +5 -18
  7. package/dist/bigquery/parse.js +1 -1
  8. package/dist/completion/complete.d.ts +3 -1
  9. package/dist/completion/complete.js +4 -2
  10. package/dist/completion/config.d.ts +1 -1
  11. package/dist/completion/config.js +66 -0
  12. package/dist/completion/parser-factory.d.ts +1 -1
  13. package/dist/completion/parser-factory.js +36 -0
  14. package/dist/databricks/parse.d.ts +4 -17
  15. package/dist/derived-dialects.d.ts +8 -0
  16. package/dist/derived-dialects.js +68 -0
  17. package/dist/dialect-symbols.d.ts +1 -1
  18. package/dist/dialect-symbols.js +8 -0
  19. package/dist/dialect.d.ts +3 -0
  20. package/dist/dialect.js +1 -0
  21. package/dist/document/document.d.ts +159 -5
  22. package/dist/document/document.js +473 -18
  23. package/dist/document/node-at.js +2 -132
  24. package/dist/document/shift.d.ts +6 -0
  25. package/dist/document/shift.js +11 -0
  26. package/dist/document/split.d.ts +1 -1
  27. package/dist/duckdb/parse.d.ts +4 -16
  28. package/dist/generated/mysql/MysqlLexer.d.ts +1198 -0
  29. package/dist/generated/mysql/MysqlLexer.js +7357 -0
  30. package/dist/generated/mysql/MysqlParser.d.ts +10992 -0
  31. package/dist/generated/mysql/MysqlParser.js +84850 -0
  32. package/dist/generated/mysql/MysqlParserListener.d.ts +7592 -0
  33. package/dist/generated/mysql/MysqlParserListener.js +6958 -0
  34. package/dist/generated/sqlite/SqliteLexer.d.ts +210 -0
  35. package/dist/generated/sqlite/SqliteLexer.js +945 -0
  36. package/dist/generated/sqlite/SqliteParser.d.ts +2115 -0
  37. package/dist/generated/sqlite/SqliteParser.js +15832 -0
  38. package/dist/generated/sqlite/SqliteParserListener.d.ts +1276 -0
  39. package/dist/generated/sqlite/SqliteParserListener.js +1160 -0
  40. package/dist/ident/fold.js +49 -0
  41. package/dist/index.d.ts +16 -7
  42. package/dist/index.js +19 -10
  43. package/dist/infer/dialect.js +26 -0
  44. package/dist/infer/mysql.d.ts +20 -0
  45. package/dist/infer/mysql.js +155 -0
  46. package/dist/infer/sqlite.d.ts +12 -0
  47. package/dist/infer/sqlite.js +121 -0
  48. package/dist/ir/ir.d.ts +19 -1
  49. package/dist/ir/part-span.d.ts +27 -1
  50. package/dist/ir/part-span.js +71 -9
  51. package/dist/ir/walk.d.ts +9 -0
  52. package/dist/ir/walk.js +135 -0
  53. package/dist/lineage/lineage.d.ts +4 -1
  54. package/dist/lineage/lineage.js +5 -1
  55. package/dist/minijinja/apply-tags.d.ts +12 -3
  56. package/dist/minijinja/apply-tags.js +30 -16
  57. package/dist/minijinja/engine.d.ts +4 -0
  58. package/dist/minijinja/engine.js +11 -0
  59. package/dist/minijinja/index.d.ts +8 -0
  60. package/dist/minijinja/index.js +8 -0
  61. package/dist/minijinja/parse.d.ts +3 -44
  62. package/dist/minijinja/parse.js +72 -12
  63. package/dist/minijinja/tag-ast.js +16 -2
  64. package/dist/minijinja/variants.d.ts +10 -2
  65. package/dist/minijinja/variants.js +43 -6
  66. package/dist/mysql/lower.d.ts +13 -0
  67. package/dist/mysql/lower.js +1443 -0
  68. package/dist/mysql/parse.d.ts +10 -0
  69. package/dist/mysql/parse.js +70 -0
  70. package/dist/parse-result.d.ts +11 -0
  71. package/dist/parse-result.js +1 -0
  72. package/dist/postgres/parse.d.ts +4 -16
  73. package/dist/qualify/check-calls.js +33 -6
  74. package/dist/qualify/qualify.d.ts +8 -1
  75. package/dist/qualify/qualify.js +21 -5
  76. package/dist/qualify/template-provider.d.ts +2 -19
  77. package/dist/redshift/parse.d.ts +4 -16
  78. package/dist/references/references.js +2 -0
  79. package/dist/scope/walk.d.ts +15 -0
  80. package/dist/scope/walk.js +73 -0
  81. package/dist/session.d.ts +83 -0
  82. package/dist/session.js +159 -0
  83. package/dist/signature/signatures.d.ts +1 -1
  84. package/dist/signature/signatures.js +139 -0
  85. package/dist/snowflake/parse.d.ts +4 -16
  86. package/dist/sqlite/lower.d.ts +11 -0
  87. package/dist/sqlite/lower.js +1093 -0
  88. package/dist/sqlite/parse.d.ts +10 -0
  89. package/dist/sqlite/parse.js +70 -0
  90. package/dist/symbols/symbols.d.ts +14 -0
  91. package/dist/symbols/symbols.js +115 -3
  92. package/dist/template/engine.d.ts +65 -0
  93. package/dist/template/engine.js +1 -0
  94. package/dist/token/classify.d.ts +1 -1
  95. package/dist/token/classify.js +31 -0
  96. package/dist/token/map.d.ts +1 -1
  97. package/dist/token/tokenize.d.ts +1 -1
  98. package/dist/token/tokenize.js +4 -0
  99. package/dist/trino/parse.d.ts +4 -15
  100. package/dist/tsql/parse.d.ts +4 -16
  101. package/package.json +16 -3
  102. package/dist/adapters.d.ts +0 -8
  103. package/dist/adapters.js +0 -43
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
@@ -1,28 +1,83 @@
1
1
  # sqllens
2
2
 
3
- A TypeScript SQL parser and static analyzer. It parses SQL into a tree, lowers it
4
- to a dialect-neutral IR, and runs a semantic layer over that IR: name resolution
5
- (scope), schema-fed qualification, type inference, and column lineage. Give it a
3
+ [![npm version](https://img.shields.io/npm/v/sqllens)](https://www.npmjs.com/package/sqllens) [![license](https://img.shields.io/npm/l/sqllens)](LICENSE)
4
+
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
6
9
  query and it tells you the query's sources, its output columns, their types, and
7
10
  where each column comes from. The parsers are generated TypeScript on the
8
- [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)).
15
+
16
+ The front end is error-tolerant and token-first, so the library drives editor
17
+ features (completion, hover, diagnostics, go-to-definition) over incomplete,
18
+ mid-edit text. See [Editor / language tooling](#editor--language-tooling). An LSP
19
+ (Language Server Protocol) server built on it lives in the repo, but it is
20
+ experimental and not part of the published package.
21
+
22
+ ```bash
23
+ npm install sqllens
24
+ ```
9
25
 
10
- The front end is error-tolerant and token-first, so the same library powers
11
- editor tooling an LSP and a SQL debugger — over incomplete, mid-edit text. See
12
- [Editor / language tooling](#editor--language-tooling).
26
+ ```ts
27
+ import { analyze, Schema } from "sqllens";
28
+
29
+ const schema = new Schema({ orders: { id: "int", total: "decimal" } });
30
+ const q = analyze("SELECT total FROM orders WHERE total > 100", "postgres", { schema });
31
+
32
+ q.diagnostics; // [] — names and types check against the schema
33
+ q.lineage.originsOf("total"); // → orders.total
34
+ ```
13
35
 
14
36
  ## Dialects
15
37
 
16
- | Dialect | Parse + lower | Semantic layer | Notes |
17
- |---|---|---|---|
18
- | Databricks (Spark SQL) | yes | yes | grammar forked from apache/spark |
19
- | T-SQL | yes | yes | grammar forked from grammars-v4 `sql/tsql` |
20
- | Snowflake | yes | yes | grammar forked from grammars-v4 `sql/snowflake` |
21
- | BigQuery (GoogleSQL) | yes | yes | grammar forked from `bytebase/parser` `googlesql/`; gated against ZetaSQL's `.test` corpus |
22
- | Redshift | yes | yes | grammar forked from Bytebase's Postgres-derived Redshift grammar (BSD-3) |
23
- | PostgreSQL | yes | yes | grammar forked from `bytebase/parser` `postgresql/` (BSD-3, PG18 keywords) |
24
- | DuckDB | yes | yes | grammar forked from this repo's own postgres pair (no open ANTLR grammar exists) |
25
- | Trino | yes | yes | grammar is the first-party trinodb `SqlBase.g4` (release 482), mechanically split; covers dbt-trino + dbt-athena |
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.
41
+
42
+ | Dialect | Derived dialects | Parse + lower | Semantic layer | Notes |
43
+ |---|---|---|---|---|
44
+ | Databricks (Spark SQL) | Apache Spark, AWS Glue | yes | yes | grammar forked from apache/spark |
45
+ | T-SQL | SQL Server, Microsoft Fabric, Azure Synapse | yes | yes | grammar forked from grammars-v4 `sql/tsql` |
46
+ | Snowflake | — | yes | yes | grammar forked from grammars-v4 `sql/snowflake` |
47
+ | BigQuery (GoogleSQL) | — | yes | yes | grammar forked from `bytebase/parser` `googlesql/`; gated against ZetaSQL's `.test` corpus |
48
+ | Redshift | — | yes | yes | grammar forked from Bytebase's Postgres-derived Redshift grammar (BSD-3) |
49
+ | PostgreSQL | — | yes | yes | grammar forked from `bytebase/parser` `postgresql/` (BSD-3, PG18 keywords) |
50
+ | DuckDB | — | yes | yes | grammar forked from this repo's own postgres pair (no open ANTLR grammar exists) |
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` |
54
+
55
+ Each grammar began as a fork of the upstream noted above, but most are now far from
56
+ verbatim copies. They've had substantial extension and correction, driven by a full
57
+ comparison against each dialect's official reference documentation and the reference
58
+ corpus extracted from those docs, so they reach well past their fork points.
59
+
60
+ A **derived dialect** is an engine that has no grammar of its own but whose SQL
61
+ the primary grammar already parses, because its SQL is a subset of (or the same as)
62
+ the primary dialect's. Microsoft Fabric runs a restricted subset of T-SQL, Amazon
63
+ Athena's engine is Trino, and AWS Glue runs Spark. Each one is checked against real
64
+ SQL from that engine before it goes on the list.
65
+
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
+ ```
26
81
 
27
82
  The semantic layer is dialect-agnostic: it operates on the shared IR and runs
28
83
  unchanged on every dialect. Only the parse and lower stages are dialect-specific.
@@ -33,113 +88,289 @@ unchanged on every dialect. Only the parse and lower stages are dialect-specific
33
88
  parse → lower → resolveScopes → qualify → infer / lineage / symbols
34
89
  ```
35
90
 
36
- - **parse** text concrete syntax tree (CST), with a syntax-error count.
37
- - **lower** CST a dialect-neutral IR (`QueryExpr` / `SelectExpr` / `Expr` …);
38
- also reports the statement kind (query / dml / ddl / …).
39
- - **resolveScopes** — a schema-free symbol table: visible sources, CTE
40
- resolution, output columns.
41
- - **qualify** with a schema: `*` expansion, unknown-table/column diagnostics,
42
- column types.
43
- - **infer / lineage / symbols** type inference, base-table lineage per output
44
- column, and a kind×modifier symbol model.
45
-
46
- ## Status
47
-
48
- Pre-release, and not yet published to npm. The library is consumed as TypeScript
49
- (no build emit yet packaging is a later step). The public API (`src/index.ts`)
50
- is uniform across all eight dialects: `parse` and `analyze` take the dialect as a
51
- parameter, and every per-dialect `parse*` / `lower` plus the shared passes stay
52
- exported as lower-level building blocks. The editor-facing surface `tokenize`,
53
- `SqlDocument`, `complete`, `signatureAt` lives on the same barrel.
91
+ Each stage produces one value, and that value is what a specific editor feature
92
+ reads from. Only the first two stages, parse and lower, are dialect-specific;
93
+ everything after them is shared and runs unchanged across every dialect.
94
+
95
+ **parse** turns SQL text into a *concrete syntax tree* (CST): the full parse tree,
96
+ every token and grammar node exactly as written, nothing dropped or simplified. It
97
+ also hands back the token stream and a syntax-error count. The CST is faithful but
98
+ verbose and dialect-shaped, so nothing downstream reads it directly. It backs
99
+ syntax squiggles (the underline under a parse error) and semantic tokens
100
+ (dialect-aware highlighting).
101
+
102
+ **lower** walks the CST into an *intermediate representation* (IR): a small,
103
+ dialect-neutral tree of nodes such as `QueryExpr`, `SelectExpr`, and `Expr` that
104
+ mean the same thing whether the SQL came from Snowflake or T-SQL. (In the API this
105
+ value is the `ast` field, the *abstract syntax tree*, a cleaned-up counterpart to
106
+ the CST.) It also tags each statement with its kind: a query, DML (data
107
+ manipulation: `INSERT` / `UPDATE` / `DELETE`), or DDL (data definition:
108
+ `CREATE` / `ALTER` / `DROP`). lower never throws, so even half-typed, broken SQL
109
+ still yields an IR the rest of the pipeline can run on.
110
+
111
+ **resolveScopes** builds a symbol table over the IR with no schema required. For
112
+ each query scope it works out the visible sources (tables, subqueries, and CTEs; a
113
+ *common table expression* is the `WITH name AS (…)` temporary result set), resolves
114
+ names against them, and computes the query's output columns. It needs no catalog,
115
+ so the features it powers work on any file with zero configuration: go-to-definition,
116
+ find-references, and document highlight.
117
+
118
+ **qualify** is the first stage that takes a *schema*, the catalog of tables and
119
+ their column types. With it, qualify expands `SELECT *` into the real column list,
120
+ raises unknown-table and unknown-column diagnostics, and binds each column
121
+ reference to the source it comes from, with the column's type. This is what turns
122
+ on the schema-dependent semantic squiggles (an unknown column can only be flagged
123
+ once the schema is known) and answers `bindingOf`, which tells you which source a
124
+ given column resolves to.
125
+
126
+ **infer** computes the type and nullability of every expression, from a bare
127
+ column to `a + b`, `COALESCE(…)`, a `CASE`, or a function call. It powers hover
128
+ (the type shown when you point at an expression) and inlay hints (inline type
129
+ annotations).
130
+
131
+ **lineage** traces each output column back to the base-table columns it derives
132
+ from, through CTEs, subqueries, and joins, and records every hop on the way. It
133
+ powers the lineage panel and go-to-origin (jump from an output column to the
134
+ physical column it ultimately reads).
135
+
136
+ **symbols** derives a `Sym` model: every named thing (source, column, CTE),
137
+ classified by kind and modifier. It backs the editor outline / document-symbols
138
+ list and code-lens annotations.
54
139
 
55
140
  ## Usage
56
141
 
57
- `dialect` is `"databricks" | "tsql" | "snowflake" | "bigquery" | "redshift" | "postgres" | "duckdb" | "trino"`.
142
+ Two ways in: `analyze` for one-shot analysis of a string, and a session for a
143
+ document you hold open and edit. Templated SQL (dbt models) is the same API with
144
+ one more option; it changes the input, not the shape of what you get back.
145
+ For a step-by-step walkthrough of the templated path, from a raw dbt model to
146
+ branch variants, see [TUTORIAL.md](TUTORIAL.md).
58
147
 
59
- Those eight grammars serve more dbt adapters than that, because several adapters
60
- are SQL front ends over an engine already covered. `adapterDialect` resolves a
61
- profiles.yml `type:` value (or a dialect name) to the dialect that parses its
62
- SQL — so consumers don't re-derive the family knowledge:
63
-
64
- ```ts
65
- import { adapterDialect, ADAPTER_DIALECTS } from "sqllens";
66
-
67
- adapterDialect("athena"); // "trino" — Athena engine v3 executes on Trino
68
- adapterDialect("glue"); // "databricks" — AWS Glue runs Spark; Databricks SQL = Spark SQL
69
- adapterDialect("fabric"); // "tsql" — same for "synapse" and "sqlserver"
70
- adapterDialect("presto"); // "trino" — the pre-rename Trino adapter
71
- adapterDialect("oracle"); // undefined — not served; never a guess
72
- ```
148
+ ### One-shot: `analyze`
73
149
 
74
- The map is exact by contract: only adapters whose SQL surface the corpus gates
75
- genuinely represent are listed. The LSP's `.sqllens.json` accepts adapter types
76
- through the same map, so `{ "dialect": "athena" }` works in rules and `default`.
77
-
78
- The surface is
79
- **layered** — each tier is a terminal value you can stop at — and **composable**:
80
- every semantic method takes the closest upstream result (so passing it does no
81
- rework) or a raw string / IR via an idempotent lift helper.
150
+ `analyze` runs the whole pipeline and hands back a result you read directly:
82
151
 
83
152
  ```ts
84
- import { parse, analyze, Schema } from "sqllens";
85
-
86
- // Tier 1 — just the IR. No semantic layer pulled in.
87
- const { ast, errors, cst } = parse("SELECT a, b FROM t WHERE a > 1", "tsql");
88
- // ast = dialect-neutral IR (frozen — no pass mutates it); cst = raw antlr tree (escape hatch)
89
- // ast.statement -> "query" | "dml" | "ddl" | …
153
+ import { analyze, Schema } from "sqllens";
90
154
 
91
- // Whole pipeline in one call.
92
155
  const schema = new Schema({ t: { a: "int", b: "string" } });
93
156
  const a = analyze("SELECT a, b FROM t", "tsql", { schema });
157
+
94
158
  a.scopes; // name resolution (ScopeTree)
95
- a.diagnostics; // unknown-table/column diagnostics
159
+ a.diagnostics; // unknown-table / column diagnostics
96
160
  a.qualification.columnsOf(a.scopes.root); // * expansion
97
161
  a.types.typeOf(expr, scope); // per-expression types
98
162
  a.lineage.originsOf("a"); // base-table origins of an output column
99
163
  a.symbols; // kind × modifier symbol model
100
164
  ```
101
165
 
102
- Compose tier by tier pass any upstream result (or a string) to any later pass,
103
- and only the missing steps run. No exported signature takes or returns a raw
104
- `Map`/`Set`/`Record`:
166
+ ### A document you keep: the session
167
+
168
+ An editor holds a file that changes. The entry for that is a session: it parses on
169
+ construction, caches per statement, and an edit reuses everything it didn't touch.
170
+
171
+ ```ts
172
+ import { SqlSession, Schema } from "sqllens";
173
+
174
+ const s = SqlSession.create("SELECT amount FROM sales", "databricks", { schema });
175
+
176
+ // properties — cheap reads of what construction already produced
177
+ s.ast; // the dialect-neutral IR (frozen)
178
+ s.tokens; // the token stream: every token, exact spans — present even mid-edit
179
+ s.scopes; // name resolution; needs no schema
180
+
181
+ // verbs — parentheses execute a pass (memoized against the schema's version)
182
+ s.diagnostics(); // syntax + schema-fed, one document-ordered list
183
+ s.lineage(); // column lineage for the output columns
184
+ s.deriveSymbols(); // the outline / symbol model
185
+
186
+ // cursor verbs — offset in, spans out
187
+ s.completeAt(14); // completions at an offset (works on broken, mid-keystroke text)
188
+ s.referencesAt(9); // declaration + every occurrence of the symbol under the cursor
189
+ s.typeAt(9); // inferred type of the expression under the cursor
190
+
191
+ // edits are immutable: a new session, caches carried over
192
+ const next = s.withText("SELECT amount, id FROM sales");
193
+ ```
194
+
195
+ The convention throughout: properties are cheap, parentheses do work. Every verb
196
+ is a one-line delegation to a free function (`qualify`, `lineage`, `deriveSymbols`,
197
+ `referencesAt`, …) that stays exported, so a slim consumer can skip the session,
198
+ import only the functions it calls, and bundle nothing else.
199
+
200
+ ### Stage-wise building blocks
201
+
202
+ Every stage is also its own entry point, and each result is a value you can stop
203
+ at or pass to the next; hand a result forward and only the missing steps run:
105
204
 
106
205
  ```ts
107
206
  import { parse, qualify, lineage, deriveSymbols, toScopes, Schema } from "sqllens";
108
207
 
109
- const { ast } = parse(sql, "snowflake");
110
- const scopes = toScopes(ast, { dialect: "snowflake" }); // idempotent lift; identity if already a ScopeTree
208
+ const { ast, errors, cst } = parse("SELECT a, b FROM t", "snowflake");
209
+ // ast = dialect-neutral IR (frozen); cst = the raw antlr tree (escape hatch)
210
+
211
+ const scopes = toScopes(ast, { dialect: "snowflake" }); // idempotent lift
111
212
  qualify(scopes, schema); // reuses scopes — never re-parses or re-resolves
112
- lineage(scopes, schema); // safe to call on the same scopes, in any order
113
- deriveSymbols(scopes); // independent results, no cross-contamination
213
+ lineage(scopes, schema); // safe on the same scopes, in any order
214
+ deriveSymbols(scopes); // independent results
114
215
  ```
115
216
 
116
- The per-dialect entries (`parseDatabricks` / `parseTSql` / `parseSnowflake` /
117
- `parseBigQuery` / `parseRedshift` / `parsePostgres` / `parseDuckdb` / `parseTrino`,
118
- each `lower`, and the raw `resolveScopes` / `inferType`) remain exported for
119
- callers that want a single stage.
217
+ The per-dialect entries (`parseDatabricks` `parseTrino`, each `lower`, and the
218
+ raw `resolveScopes` / `inferType`) stay exported for callers that want a single
219
+ stage.
220
+
221
+ ### Templated SQL (dbt models)
222
+
223
+ A dbt model is not plain SQL; it is minijinja-templated SQL (`{{ ref('orders') }}`,
224
+ `{% if %}` …). sqllens parses that raw text natively, without rendering: tags get
225
+ exact spans, a `ref` in a FROM slot becomes a real table source that carries its
226
+ model name, and everything downstream (scopes, diagnostics, types, lineage,
227
+ completion) runs on the templated document unchanged.
228
+
229
+ Templating is declared, never guessed. You hand the session a template engine; no
230
+ engine means plain SQL:
231
+
232
+ ```ts
233
+ import { SqlSession } from "sqllens";
234
+ import { minijinja } from "sqllens/minijinja"; // its own entry point — plain-SQL consumers never load it
235
+
236
+ const s = SqlSession.create(modelText, "databricks", {
237
+ templating: minijinja(),
238
+ provider, // optional — template knowledge, see extension points below
239
+ schema,
240
+ });
241
+
242
+ s.ast; // IR: {{ ref('orders') }} in FROM is a TableSource named "orders"
243
+ s.tokens; // ONE stream: SQL tokens + template tokens (channel 2, role "minijinja")
244
+ s.diagnostics(); // SQL + template + schema-fed, merged, all in document coordinates
245
+ s.tags; // every tag with exact spans (ref / source / macro / var / control)
246
+ s.regions; // {% if %} / {% for %} structure — folding, branch enumeration
247
+ s.tagOf(node); // the tag an IR node came from; nodeOf(tag) goes the other way
248
+ ```
249
+
250
+ Why no auto-detection: `{{ … }}` inside a SQL string literal is a template to dbt
251
+ and literal text to everyone else. No scanner can tell which was meant, and sqllens
252
+ never guesses. The host declares it (file association, language id, or config).
253
+ Declaring an engine on a file that turns out to have no tags costs nothing and
254
+ changes nothing: the result is byte-identical to a plain parse, with empty template
255
+ facets.
256
+
257
+ Everything works with no provider at all: the shipped defaults answer what they can
258
+ (a `ref` is a relation named by its literal argument; `config` renders nothing) and
259
+ everything else reports as unknown, never guessed. A provider only makes results
260
+ more precise.
261
+
262
+ ### Extension points
263
+
264
+ sqllens knows SQL and template *syntax*. Everything it cannot know (your catalog,
265
+ what your macros expand to, what `var('x')` holds) enters through three interfaces.
266
+ All are optional, and every one answers misses the same way: a miss is "unknown",
267
+ never a guess, and no diagnostic fires on missing knowledge.
268
+
269
+ `SchemaProvider` is the catalog: which tables exist, with their column types.
270
+ `Schema` is the upfront form (a plain mapping, as in the examples above).
271
+ `CallbackSchema` is the lazy form for hosts with a live catalog: sqllens records
272
+ what it missed, your `prime()` resolves the misses asynchronously and bumps a
273
+ version, and the next read reflects it. An LSP republishes diagnostics on exactly
274
+ that signal.
275
+
276
+ `TemplateProvider` is template knowledge: what template calls *mean*. Subclass
277
+ `DefaultTemplateProvider` and override only what your host knows; each method
278
+ answers one question:
279
+
280
+ ```ts
281
+ class MyDbtProvider extends DefaultTemplateProvider {
282
+ relationOf(call) { /* ref/source → the physical relation, with columns */ }
283
+ valueOf(call) { /* var/env_var → the scalar type it yields */ }
284
+ shapeOf(call) { /* a macro's expansion shape: "expr" | "predicate" | "column-list" | "statement" … */ }
285
+ columnsOf(call) { /* a column-list macro's output columns */ }
286
+ }
287
+ ```
288
+
289
+ One instance per document. Answers are synchronous, from a warm cache, with the
290
+ same miss-recording + `prime()` + version protocol as the schema. The base class
291
+ alone is fully functional; it is what the zero-provider examples above run on. The
292
+ payoff of each override is direct: `relationOf` turns "`{{ ref('orders') }}` is
293
+ exempt from checks" into "`orders` has these columns, and `o.totall` is a real
294
+ unknown-column diagnostic"; `shapeOf` makes a macro standing in a statement slot
295
+ parse cleanly; `valueOf` gives `{{ var('limit') }}` a type that inference can use.
296
+
297
+ `TemplateEngine` is template syntax, and is rare. The engine owns how templated
298
+ text is parsed; minijinja ships as the only implementation and nearly every
299
+ consumer just passes it. Implementing your own (another template language over SQL)
300
+ is supported, but it is a contract, not a callback: your result must satisfy the
301
+ invariants the conformance gates check. Tokens tile the source byte-for-byte, every
302
+ span is in original document coordinates, broken input never throws, and tag-free
303
+ text is identical to a plain parse.
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
+ ```
120
339
 
121
340
  ## Editor / language tooling
122
341
 
123
342
  The front end is error-tolerant and token-first, so it serves editor features
124
- that run on incomplete, mid-edit text they never need a clean parse:
125
-
126
- - **`tokenize(sql, dialect)`** and **`parse(...).tokens`** give a first-class token
127
- stream: every token with its exact span, role, and channel. Always available,
128
- even when the parse has errors.
129
- - **`lower()` never throws** on broken or partial input you get a flagged
130
- `query` IR back, so every downstream pass stays total.
131
- - **`SqlDocument`** is a persistent, immutable, position-addressable per-file
132
- model. It runs `parse → resolveScopes` once (plus lazy `analyze(schema)`),
133
- caches the result, and answers `tokenAt` / `nodeAt`. An edit yields a new
134
- document; an O(log n) `LineIndex` maps positions offsets.
135
- - **`complete(doc, offset, schema?)`** scope-aware completion (keywords,
136
- columns, tables, functions) from an ATN candidate walk over the grammar (our
137
- own, no third-party dependency).
138
- - **`signatureAt(doc, offset)`** — parameter hints from a curated per-dialect
343
+ that run on incomplete, mid-edit text. They never need a clean parse:
344
+
345
+ - `tokenize(sql, dialect)` and `parse(...).tokens` give a first-class token
346
+ stream: every token with its exact span, role, and channel. Available even when
347
+ the parse has errors.
348
+ - `lower()` never throws on broken or partial input; you get a flagged `query` IR
349
+ back, so every downstream pass stays total.
350
+ - `SqlDocument` is a persistent, immutable, position-addressable per-file model.
351
+ It runs `parse → resolveScopes` once (plus lazy `analyze(schema)`), caches the
352
+ result, and answers `tokenAt` / `nodeAt`. An edit yields a new document; an
353
+ O(log n) `LineIndex` maps positions to offsets.
354
+ - `completeAt(doc, offset, schema?)`: scope-aware completion (keywords, columns,
355
+ tables, functions) from an ATN (Augmented Transition Network, the grammar's
356
+ state-machine form) candidate walk over the grammar, our own, with no third-party
357
+ dependency.
358
+ - `signatureAt(doc, offset)`: parameter hints from a curated per-dialect
139
359
  function-signature table; the long tail degrades to name + active-argument.
140
- - **`referencesAt(scopes, offset, schema?)`** every occurrence (plus the
141
- declaration) of the symbol under the cursor; backs find-references, document
142
- highlight, and code-lens reference counts.
360
+ - `referencesAt(scopes, offset, schema?)`: every occurrence (plus the declaration)
361
+ of the symbol under the cursor; backs find-references, document highlight, and
362
+ code-lens reference counts.
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
+ ```
143
374
 
144
375
  ```ts
145
376
  import { SqlDocument, Schema } from "sqllens";
@@ -150,22 +381,19 @@ doc.tokenAt(7); // token under an offset
150
381
  const next = doc.withText("SELECT amount, id FROM sales", 2); // immutable edit → new doc
151
382
  ```
152
383
 
153
- ## Language server
384
+ ## Language server (experimental)
154
385
 
155
- An LSP (Language Server Protocol) server built on the library, in `src/lsp/`. It
156
- holds one `SqlDocument` per open file (rebuilt on edit) and reaches the library
157
- only through the public API surface above it adds no analysis of its own, only
158
- protocol translation.
386
+ An LSP (Language Server Protocol) server built on the library lives in `src/lsp/`.
387
+ It is experimental and **not part of the published npm package**: the package ships
388
+ the library only, and the server is source you run from the repo. It holds one
389
+ `SqlDocument` per open file (rebuilt on edit) and reaches the library only through
390
+ the public API, and adds no analysis of its own beyond protocol translation.
159
391
 
160
- LSP is a large protocol roughly thirty request types across document-sync,
161
- language, and workspace features so "supports LSP" is not one bit but a long
162
- checklist. A SQL server needs a subset, but more of it maps to SQL than it first
163
- looks — a CTE / view / model is the SQL analog of a definition, and the
164
- dependency graph between them is a call hierarchy. A few features genuinely don't
165
- apply (type hierarchy, document color, monikers); a few are deliberately deferred
166
- (formatting, project-wide navigation). The coverage, feature by feature:
392
+ A SQL server needs only a subset of LSP's ~30 request types: some don't apply to
393
+ SQL (type hierarchy, document color, monikers), and a few are deferred (formatting,
394
+ project-wide navigation). Where the server stands today, feature by feature:
167
395
 
168
- **Language features**
396
+ ### Language features
169
397
 
170
398
  | Feature | Status |
171
399
  | --- | --- |
@@ -185,7 +413,7 @@ apply (type hierarchy, document color, monikers); a few are deliberately deferre
185
413
  | Go to declaration | ◻️ not yet |
186
414
  | Go to type definition | ◻️ not yet |
187
415
  | Go to implementation | ◻️ not yet — name → its defining query (view / model); needs the project model |
188
- | Call hierarchy | ◻️ not yet — the CTE / dbt-model dependency graph |
416
+ | Call hierarchy | ◻️ not yet — the CTE / view / model dependency graph |
189
417
  | Document link | ◻️ not yet |
190
418
  | Linked editing range | ◻️ not yet — live alias / name sync-edit |
191
419
  | Code action (quick fixes) | ◻️ next phase |
@@ -196,7 +424,7 @@ apply (type hierarchy, document color, monikers); a few are deliberately deferre
196
424
  | Document color | — n/a — no color literals |
197
425
  | Moniker | — n/a — LSIF / cross-repo indexing concern |
198
426
 
199
- **Diagnostics & document sync**
427
+ ### Diagnostics & document sync
200
428
 
201
429
  | Feature | Status |
202
430
  | --- | --- |
@@ -209,7 +437,7 @@ apply (type hierarchy, document color, monikers); a few are deliberately deferre
209
437
  | Save notifications (`didSave` / `willSave`) | ◻️ not yet |
210
438
  | Notebook document sync | ◻️ not yet |
211
439
 
212
- **Workspace features**
440
+ ### Workspace features
213
441
 
214
442
  | Feature | Status |
215
443
  | --- | --- |
@@ -223,17 +451,24 @@ deferred items are tracked work: rename and
223
451
  code actions are the next LSP phase, workspace symbols need the project model,
224
452
  and formatting is expected to wrap an existing external formatter.
225
453
 
226
- ## Generating the parsers
454
+ ## How sqllens compares
227
455
 
228
- `src/generated/` is a build product and is gitignored. After a fresh clone, or
229
- after editing any `.g4`, generate the parsers (the lexer must generate before the
230
- parser, which the driver handles):
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:
231
460
 
232
- ```bash
233
- npm run gen -- databricks # | tsql | snowflake | bigquery | redshift | postgres | duckdb | trino
234
- npm run typecheck
235
- npm test
236
- ```
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.
237
472
 
238
473
  ## Architecture
239
474
 
@@ -242,14 +477,15 @@ dialect is a standalone pair of split `.g4` files (a lexer grammar + a parser
242
477
  grammar), forked from its best starting point and edited in place. Everything
243
478
  downstream of `lower` is shared and dialect-neutral.
244
479
 
245
- ## Contributing
480
+ ## Building from source & contributing
246
481
 
247
- See [CONTRIBUTING.md](CONTRIBUTING.md). In short: the conformance corpora are the
248
- gate a grammar change that regresses a corpus is not done and grammar work is
249
- test-driven against those corpora.
482
+ `npm install sqllens` needs no build step on your side. To build it yourself from
483
+ source, or to work on a grammar, you regenerate the parsers from the `.g4` files
484
+ first. [CONTRIBUTING.md](CONTRIBUTING.md) has the setup, the command list, and the
485
+ corpus-gate workflow.
250
486
 
251
487
  ## License
252
488
 
253
- MIT see [LICENSE](LICENSE). The forked grammars under `grammars/` keep their
489
+ MIT. See [LICENSE](LICENSE). The forked grammars under `grammars/` keep their
254
490
  upstream licenses (Apache-2.0 for Databricks; MIT for T-SQL and Snowflake; BSD-3
255
491
  for BigQuery and Redshift); see [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md).
@@ -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.