sqllens 0.1.1 → 1.0.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/README.md +280 -126
- package/dist/api.d.ts +9 -7
- package/dist/api.js +15 -6
- package/dist/bigquery/parse.d.ts +5 -18
- package/dist/bigquery/parse.js +1 -1
- package/dist/completion/complete.d.ts +3 -1
- package/dist/completion/complete.js +4 -2
- package/dist/completion/config.d.ts +1 -1
- package/dist/completion/parser-factory.d.ts +1 -1
- package/dist/databricks/parse.d.ts +4 -17
- package/dist/derived-dialects.d.ts +8 -0
- package/dist/derived-dialects.js +50 -0
- package/dist/dialect-symbols.d.ts +1 -1
- package/dist/dialect.d.ts +3 -0
- package/dist/dialect.js +1 -0
- package/dist/document/document.d.ts +159 -5
- package/dist/document/document.js +473 -18
- package/dist/document/node-at.js +2 -132
- package/dist/document/shift.d.ts +6 -0
- package/dist/document/shift.js +11 -0
- package/dist/document/split.d.ts +1 -1
- package/dist/duckdb/parse.d.ts +4 -16
- package/dist/index.d.ts +12 -7
- package/dist/index.js +15 -10
- package/dist/ir/ir.d.ts +19 -1
- package/dist/ir/part-span.d.ts +11 -0
- package/dist/ir/part-span.js +35 -1
- package/dist/ir/walk.d.ts +9 -0
- package/dist/ir/walk.js +135 -0
- package/dist/lineage/lineage.d.ts +4 -1
- package/dist/lineage/lineage.js +5 -1
- package/dist/minijinja/apply-tags.d.ts +12 -3
- package/dist/minijinja/apply-tags.js +30 -16
- package/dist/minijinja/engine.d.ts +4 -0
- package/dist/minijinja/engine.js +11 -0
- package/dist/minijinja/index.d.ts +8 -0
- package/dist/minijinja/index.js +8 -0
- package/dist/minijinja/parse.d.ts +3 -44
- package/dist/minijinja/parse.js +71 -11
- package/dist/minijinja/tag-ast.js +16 -2
- package/dist/minijinja/variants.d.ts +10 -2
- package/dist/minijinja/variants.js +43 -6
- package/dist/parse-result.d.ts +11 -0
- package/dist/parse-result.js +1 -0
- package/dist/postgres/parse.d.ts +4 -16
- package/dist/qualify/qualify.d.ts +8 -1
- package/dist/qualify/qualify.js +21 -5
- package/dist/qualify/template-provider.d.ts +2 -19
- package/dist/redshift/parse.d.ts +4 -16
- package/dist/references/references.js +2 -0
- package/dist/scope/walk.d.ts +15 -0
- package/dist/scope/walk.js +73 -0
- package/dist/session.d.ts +83 -0
- package/dist/session.js +159 -0
- package/dist/signature/signatures.d.ts +1 -1
- package/dist/snowflake/parse.d.ts +4 -16
- package/dist/symbols/symbols.d.ts +14 -0
- package/dist/symbols/symbols.js +115 -3
- package/dist/template/engine.d.ts +65 -0
- package/dist/template/engine.js +1 -0
- package/dist/token/classify.d.ts +1 -1
- package/dist/token/map.d.ts +1 -1
- package/dist/token/tokenize.d.ts +1 -1
- package/dist/trino/parse.d.ts +4 -15
- package/dist/tsql/parse.d.ts +4 -16
- package/package.json +5 -1
- package/dist/adapters.d.ts +0 -8
- package/dist/adapters.js +0 -43
package/README.md
CHANGED
|
@@ -1,28 +1,66 @@
|
|
|
1
1
|
# sqllens
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/sqllens) [](LICENSE)
|
|
4
|
+
|
|
3
5
|
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
|
|
5
|
-
(scope), schema-fed qualification, type inference,
|
|
6
|
+
to a dialect-neutral intermediate representation (IR), and runs a semantic layer
|
|
7
|
+
over that IR: name resolution (scope), schema-fed qualification, type inference,
|
|
8
|
+
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
11
|
[antlr4ng](https://github.com/mike-lischke/antlr4ng) runtime.
|
|
9
12
|
|
|
10
|
-
The front end is error-tolerant and token-first, so the
|
|
11
|
-
|
|
12
|
-
[Editor / language tooling](#editor--language-tooling).
|
|
13
|
+
The front end is error-tolerant and token-first, so the library drives editor
|
|
14
|
+
features (completion, hover, diagnostics, go-to-definition) over incomplete,
|
|
15
|
+
mid-edit text. See [Editor / language tooling](#editor--language-tooling). An LSP
|
|
16
|
+
(Language Server Protocol) server built on it lives in the repo, but it is
|
|
17
|
+
experimental and not part of the published package.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install sqllens
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { analyze, Schema } from "sqllens";
|
|
25
|
+
|
|
26
|
+
const schema = new Schema({ orders: { id: "int", total: "decimal" } });
|
|
27
|
+
const q = analyze("SELECT total FROM orders WHERE total > 100", "postgres", { schema });
|
|
28
|
+
|
|
29
|
+
q.diagnostics; // [] — names and types check against the schema
|
|
30
|
+
q.lineage.originsOf("total"); // → orders.total
|
|
31
|
+
```
|
|
13
32
|
|
|
14
33
|
## Dialects
|
|
15
34
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
|
21
|
-
|
|
22
|
-
|
|
|
23
|
-
|
|
|
24
|
-
|
|
|
25
|
-
|
|
|
35
|
+
sqllens implements eight SQL dialects directly, each with its own grammar. Seven
|
|
36
|
+
more engines are covered as *derived dialects*: their SQL is already parsed by one
|
|
37
|
+
of those eight grammars, for 15 engines in total.
|
|
38
|
+
|
|
39
|
+
| Dialect | Derived dialects | Parse + lower | Semantic layer | Notes |
|
|
40
|
+
|---|---|---|---|---|
|
|
41
|
+
| Databricks (Spark SQL) | Apache Spark, AWS Glue | yes | yes | grammar forked from apache/spark |
|
|
42
|
+
| T-SQL | SQL Server, Microsoft Fabric, Azure Synapse | yes | yes | grammar forked from grammars-v4 `sql/tsql` |
|
|
43
|
+
| Snowflake | — | yes | yes | grammar forked from grammars-v4 `sql/snowflake` |
|
|
44
|
+
| BigQuery (GoogleSQL) | — | yes | yes | grammar forked from `bytebase/parser` `googlesql/`; gated against ZetaSQL's `.test` corpus |
|
|
45
|
+
| Redshift | — | yes | yes | grammar forked from Bytebase's Postgres-derived Redshift grammar (BSD-3) |
|
|
46
|
+
| PostgreSQL | — | yes | yes | grammar forked from `bytebase/parser` `postgresql/` (BSD-3, PG18 keywords) |
|
|
47
|
+
| DuckDB | — | yes | yes | grammar forked from this repo's own postgres pair (no open ANTLR grammar exists) |
|
|
48
|
+
| Trino | Presto, Amazon Athena | yes | yes | grammar is the first-party trinodb `SqlBase.g4` (release 482), mechanically split |
|
|
49
|
+
|
|
50
|
+
Each grammar began as a fork of the upstream noted above, but most are now far from
|
|
51
|
+
verbatim copies. They've had substantial extension and correction, driven by a full
|
|
52
|
+
comparison against each dialect's official reference documentation and the reference
|
|
53
|
+
corpus extracted from those docs, so they reach well past their fork points.
|
|
54
|
+
|
|
55
|
+
A **derived dialect** is an engine that has no grammar of its own but whose SQL
|
|
56
|
+
the primary grammar already parses, because its SQL is a subset of (or the same as)
|
|
57
|
+
the primary dialect's. Microsoft Fabric runs a restricted subset of T-SQL, Amazon
|
|
58
|
+
Athena's engine is Trino, and AWS Glue runs Spark. Each one is checked against real
|
|
59
|
+
SQL from that engine before it goes on the list.
|
|
60
|
+
|
|
61
|
+
In code, the `dialect` argument is one of `"databricks" | "tsql" | "snowflake" | "bigquery" | "redshift" | "postgres" | "duckdb" | "trino"`. `resolveDialect` turns an
|
|
62
|
+
engine name (or a dialect name) into the one that parses it: `resolveDialect("athena")`
|
|
63
|
+
returns `"trino"`.
|
|
26
64
|
|
|
27
65
|
The semantic layer is dialect-agnostic: it operates on the shared IR and runs
|
|
28
66
|
unchanged on every dialect. Only the parse and lower stages are dialect-specific.
|
|
@@ -33,113 +71,243 @@ unchanged on every dialect. Only the parse and lower stages are dialect-specific
|
|
|
33
71
|
parse → lower → resolveScopes → qualify → infer / lineage / symbols
|
|
34
72
|
```
|
|
35
73
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
is
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
`
|
|
74
|
+
Each stage produces one value, and that value is what a specific editor feature
|
|
75
|
+
reads from. Only the first two stages, parse and lower, are dialect-specific;
|
|
76
|
+
everything after them is shared and runs unchanged across all eight dialects.
|
|
77
|
+
|
|
78
|
+
**parse** turns SQL text into a *concrete syntax tree* (CST): the full parse tree,
|
|
79
|
+
every token and grammar node exactly as written, nothing dropped or simplified. It
|
|
80
|
+
also hands back the token stream and a syntax-error count. The CST is faithful but
|
|
81
|
+
verbose and dialect-shaped, so nothing downstream reads it directly. It backs
|
|
82
|
+
syntax squiggles (the underline under a parse error) and semantic tokens
|
|
83
|
+
(dialect-aware highlighting).
|
|
84
|
+
|
|
85
|
+
**lower** walks the CST into an *intermediate representation* (IR): a small,
|
|
86
|
+
dialect-neutral tree of nodes such as `QueryExpr`, `SelectExpr`, and `Expr` that
|
|
87
|
+
mean the same thing whether the SQL came from Snowflake or T-SQL. (In the API this
|
|
88
|
+
value is the `ast` field, the *abstract syntax tree*, a cleaned-up counterpart to
|
|
89
|
+
the CST.) It also tags each statement with its kind: a query, DML (data
|
|
90
|
+
manipulation: `INSERT` / `UPDATE` / `DELETE`), or DDL (data definition:
|
|
91
|
+
`CREATE` / `ALTER` / `DROP`). lower never throws, so even half-typed, broken SQL
|
|
92
|
+
still yields an IR the rest of the pipeline can run on.
|
|
93
|
+
|
|
94
|
+
**resolveScopes** builds a symbol table over the IR with no schema required. For
|
|
95
|
+
each query scope it works out the visible sources (tables, subqueries, and CTEs; a
|
|
96
|
+
*common table expression* is the `WITH name AS (…)` temporary result set), resolves
|
|
97
|
+
names against them, and computes the query's output columns. It needs no catalog,
|
|
98
|
+
so the features it powers work on any file with zero configuration: go-to-definition,
|
|
99
|
+
find-references, and document highlight.
|
|
100
|
+
|
|
101
|
+
**qualify** is the first stage that takes a *schema*, the catalog of tables and
|
|
102
|
+
their column types. With it, qualify expands `SELECT *` into the real column list,
|
|
103
|
+
raises unknown-table and unknown-column diagnostics, and binds each column
|
|
104
|
+
reference to the source it comes from, with the column's type. This is what turns
|
|
105
|
+
on the schema-dependent semantic squiggles (an unknown column can only be flagged
|
|
106
|
+
once the schema is known) and answers `bindingOf`, which tells you which source a
|
|
107
|
+
given column resolves to.
|
|
108
|
+
|
|
109
|
+
**infer** computes the type and nullability of every expression, from a bare
|
|
110
|
+
column to `a + b`, `COALESCE(…)`, a `CASE`, or a function call. It powers hover
|
|
111
|
+
(the type shown when you point at an expression) and inlay hints (inline type
|
|
112
|
+
annotations).
|
|
113
|
+
|
|
114
|
+
**lineage** traces each output column back to the base-table columns it derives
|
|
115
|
+
from, through CTEs, subqueries, and joins, and records every hop on the way. It
|
|
116
|
+
powers the lineage panel and go-to-origin (jump from an output column to the
|
|
117
|
+
physical column it ultimately reads).
|
|
118
|
+
|
|
119
|
+
**symbols** derives a `Sym` model: every named thing (source, column, CTE),
|
|
120
|
+
classified by kind and modifier. It backs the editor outline / document-symbols
|
|
121
|
+
list and code-lens annotations.
|
|
54
122
|
|
|
55
123
|
## Usage
|
|
56
124
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
SQL — so consumers don't re-derive the family knowledge:
|
|
125
|
+
Two ways in: `analyze` for one-shot analysis of a string, and a session for a
|
|
126
|
+
document you hold open and edit. Templated SQL (dbt models) is the same API with
|
|
127
|
+
one more option; it changes the input, not the shape of what you get back.
|
|
128
|
+
For a step-by-step walkthrough of the templated path, from a raw dbt model to
|
|
129
|
+
branch variants, see [TUTORIAL.md](TUTORIAL.md).
|
|
63
130
|
|
|
64
|
-
|
|
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
|
-
```
|
|
131
|
+
### One-shot: `analyze`
|
|
73
132
|
|
|
74
|
-
|
|
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.
|
|
133
|
+
`analyze` runs the whole pipeline and hands back a result you read directly:
|
|
82
134
|
|
|
83
135
|
```ts
|
|
84
|
-
import {
|
|
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" | …
|
|
136
|
+
import { analyze, Schema } from "sqllens";
|
|
90
137
|
|
|
91
|
-
// Whole pipeline in one call.
|
|
92
138
|
const schema = new Schema({ t: { a: "int", b: "string" } });
|
|
93
139
|
const a = analyze("SELECT a, b FROM t", "tsql", { schema });
|
|
140
|
+
|
|
94
141
|
a.scopes; // name resolution (ScopeTree)
|
|
95
|
-
a.diagnostics; // unknown-table/column diagnostics
|
|
142
|
+
a.diagnostics; // unknown-table / column diagnostics
|
|
96
143
|
a.qualification.columnsOf(a.scopes.root); // * expansion
|
|
97
144
|
a.types.typeOf(expr, scope); // per-expression types
|
|
98
145
|
a.lineage.originsOf("a"); // base-table origins of an output column
|
|
99
146
|
a.symbols; // kind × modifier symbol model
|
|
100
147
|
```
|
|
101
148
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
149
|
+
### A document you keep: the session
|
|
150
|
+
|
|
151
|
+
An editor holds a file that changes. The entry for that is a session: it parses on
|
|
152
|
+
construction, caches per statement, and an edit reuses everything it didn't touch.
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
import { SqlSession, Schema } from "sqllens";
|
|
156
|
+
|
|
157
|
+
const s = SqlSession.create("SELECT amount FROM sales", "databricks", { schema });
|
|
158
|
+
|
|
159
|
+
// properties — cheap reads of what construction already produced
|
|
160
|
+
s.ast; // the dialect-neutral IR (frozen)
|
|
161
|
+
s.tokens; // the token stream: every token, exact spans — present even mid-edit
|
|
162
|
+
s.scopes; // name resolution; needs no schema
|
|
163
|
+
|
|
164
|
+
// verbs — parentheses execute a pass (memoized against the schema's version)
|
|
165
|
+
s.diagnostics(); // syntax + schema-fed, one document-ordered list
|
|
166
|
+
s.lineage(); // column lineage for the output columns
|
|
167
|
+
s.deriveSymbols(); // the outline / symbol model
|
|
168
|
+
|
|
169
|
+
// cursor verbs — offset in, spans out
|
|
170
|
+
s.completeAt(14); // completions at an offset (works on broken, mid-keystroke text)
|
|
171
|
+
s.referencesAt(9); // declaration + every occurrence of the symbol under the cursor
|
|
172
|
+
s.typeAt(9); // inferred type of the expression under the cursor
|
|
173
|
+
|
|
174
|
+
// edits are immutable: a new session, caches carried over
|
|
175
|
+
const next = s.withText("SELECT amount, id FROM sales");
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The convention throughout: properties are cheap, parentheses do work. Every verb
|
|
179
|
+
is a one-line delegation to a free function (`qualify`, `lineage`, `deriveSymbols`,
|
|
180
|
+
`referencesAt`, …) that stays exported, so a slim consumer can skip the session,
|
|
181
|
+
import only the functions it calls, and bundle nothing else.
|
|
182
|
+
|
|
183
|
+
### Stage-wise building blocks
|
|
184
|
+
|
|
185
|
+
Every stage is also its own entry point, and each result is a value you can stop
|
|
186
|
+
at or pass to the next; hand a result forward and only the missing steps run:
|
|
105
187
|
|
|
106
188
|
```ts
|
|
107
189
|
import { parse, qualify, lineage, deriveSymbols, toScopes, Schema } from "sqllens";
|
|
108
190
|
|
|
109
|
-
const { ast } = parse(
|
|
110
|
-
|
|
191
|
+
const { ast, errors, cst } = parse("SELECT a, b FROM t", "snowflake");
|
|
192
|
+
// ast = dialect-neutral IR (frozen); cst = the raw antlr tree (escape hatch)
|
|
193
|
+
|
|
194
|
+
const scopes = toScopes(ast, { dialect: "snowflake" }); // idempotent lift
|
|
111
195
|
qualify(scopes, schema); // reuses scopes — never re-parses or re-resolves
|
|
112
|
-
lineage(scopes, schema); // safe
|
|
113
|
-
deriveSymbols(scopes); // independent results
|
|
196
|
+
lineage(scopes, schema); // safe on the same scopes, in any order
|
|
197
|
+
deriveSymbols(scopes); // independent results
|
|
114
198
|
```
|
|
115
199
|
|
|
116
|
-
The per-dialect entries (`parseDatabricks`
|
|
117
|
-
`
|
|
118
|
-
|
|
119
|
-
|
|
200
|
+
The per-dialect entries (`parseDatabricks` … `parseTrino`, each `lower`, and the
|
|
201
|
+
raw `resolveScopes` / `inferType`) stay exported for callers that want a single
|
|
202
|
+
stage.
|
|
203
|
+
|
|
204
|
+
### Templated SQL (dbt models)
|
|
205
|
+
|
|
206
|
+
A dbt model is not plain SQL; it is minijinja-templated SQL (`{{ ref('orders') }}`,
|
|
207
|
+
`{% if %}` …). sqllens parses that raw text natively, without rendering: tags get
|
|
208
|
+
exact spans, a `ref` in a FROM slot becomes a real table source that carries its
|
|
209
|
+
model name, and everything downstream (scopes, diagnostics, types, lineage,
|
|
210
|
+
completion) runs on the templated document unchanged.
|
|
211
|
+
|
|
212
|
+
Templating is declared, never guessed. You hand the session a template engine; no
|
|
213
|
+
engine means plain SQL:
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
import { SqlSession } from "sqllens";
|
|
217
|
+
import { minijinja } from "sqllens/minijinja"; // its own entry point — plain-SQL consumers never load it
|
|
218
|
+
|
|
219
|
+
const s = SqlSession.create(modelText, "databricks", {
|
|
220
|
+
templating: minijinja(),
|
|
221
|
+
provider, // optional — template knowledge, see extension points below
|
|
222
|
+
schema,
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
s.ast; // IR: {{ ref('orders') }} in FROM is a TableSource named "orders"
|
|
226
|
+
s.tokens; // ONE stream: SQL tokens + template tokens (channel 2, role "minijinja")
|
|
227
|
+
s.diagnostics(); // SQL + template + schema-fed, merged, all in document coordinates
|
|
228
|
+
s.tags; // every tag with exact spans (ref / source / macro / var / control)
|
|
229
|
+
s.regions; // {% if %} / {% for %} structure — folding, branch enumeration
|
|
230
|
+
s.tagOf(node); // the tag an IR node came from; nodeOf(tag) goes the other way
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Why no auto-detection: `{{ … }}` inside a SQL string literal is a template to dbt
|
|
234
|
+
and literal text to everyone else. No scanner can tell which was meant, and sqllens
|
|
235
|
+
never guesses. The host declares it (file association, language id, or config).
|
|
236
|
+
Declaring an engine on a file that turns out to have no tags costs nothing and
|
|
237
|
+
changes nothing: the result is byte-identical to a plain parse, with empty template
|
|
238
|
+
facets.
|
|
239
|
+
|
|
240
|
+
Everything works with no provider at all: the shipped defaults answer what they can
|
|
241
|
+
(a `ref` is a relation named by its literal argument; `config` renders nothing) and
|
|
242
|
+
everything else reports as unknown, never guessed. A provider only makes results
|
|
243
|
+
more precise.
|
|
244
|
+
|
|
245
|
+
### Extension points
|
|
246
|
+
|
|
247
|
+
sqllens knows SQL and template *syntax*. Everything it cannot know (your catalog,
|
|
248
|
+
what your macros expand to, what `var('x')` holds) enters through three interfaces.
|
|
249
|
+
All are optional, and every one answers misses the same way: a miss is "unknown",
|
|
250
|
+
never a guess, and no diagnostic fires on missing knowledge.
|
|
251
|
+
|
|
252
|
+
`SchemaProvider` is the catalog: which tables exist, with their column types.
|
|
253
|
+
`Schema` is the upfront form (a plain mapping, as in the examples above).
|
|
254
|
+
`CallbackSchema` is the lazy form for hosts with a live catalog: sqllens records
|
|
255
|
+
what it missed, your `prime()` resolves the misses asynchronously and bumps a
|
|
256
|
+
version, and the next read reflects it. An LSP republishes diagnostics on exactly
|
|
257
|
+
that signal.
|
|
258
|
+
|
|
259
|
+
`TemplateProvider` is template knowledge: what template calls *mean*. Subclass
|
|
260
|
+
`DefaultTemplateProvider` and override only what your host knows; each method
|
|
261
|
+
answers one question:
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
class MyDbtProvider extends DefaultTemplateProvider {
|
|
265
|
+
relationOf(call) { /* ref/source → the physical relation, with columns */ }
|
|
266
|
+
valueOf(call) { /* var/env_var → the scalar type it yields */ }
|
|
267
|
+
shapeOf(call) { /* a macro's expansion shape: "expr" | "predicate" | "column-list" | "statement" … */ }
|
|
268
|
+
columnsOf(call) { /* a column-list macro's output columns */ }
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
One instance per document. Answers are synchronous, from a warm cache, with the
|
|
273
|
+
same miss-recording + `prime()` + version protocol as the schema. The base class
|
|
274
|
+
alone is fully functional; it is what the zero-provider examples above run on. The
|
|
275
|
+
payoff of each override is direct: `relationOf` turns "`{{ ref('orders') }}` is
|
|
276
|
+
exempt from checks" into "`orders` has these columns, and `o.totall` is a real
|
|
277
|
+
unknown-column diagnostic"; `shapeOf` makes a macro standing in a statement slot
|
|
278
|
+
parse cleanly; `valueOf` gives `{{ var('limit') }}` a type that inference can use.
|
|
279
|
+
|
|
280
|
+
`TemplateEngine` is template syntax, and is rare. The engine owns how templated
|
|
281
|
+
text is parsed; minijinja ships as the only implementation and nearly every
|
|
282
|
+
consumer just passes it. Implementing your own (another template language over SQL)
|
|
283
|
+
is supported, but it is a contract, not a callback: your result must satisfy the
|
|
284
|
+
invariants the conformance gates check. Tokens tile the source byte-for-byte, every
|
|
285
|
+
span is in original document coordinates, broken input never throws, and tag-free
|
|
286
|
+
text is identical to a plain parse.
|
|
120
287
|
|
|
121
288
|
## Editor / language tooling
|
|
122
289
|
|
|
123
290
|
The front end is error-tolerant and token-first, so it serves editor features
|
|
124
|
-
that run on incomplete, mid-edit text
|
|
125
|
-
|
|
126
|
-
-
|
|
127
|
-
stream: every token with its exact span, role, and channel.
|
|
128
|
-
|
|
129
|
-
-
|
|
130
|
-
|
|
131
|
-
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
-
|
|
136
|
-
|
|
137
|
-
own, no third-party
|
|
138
|
-
|
|
291
|
+
that run on incomplete, mid-edit text. They never need a clean parse:
|
|
292
|
+
|
|
293
|
+
- `tokenize(sql, dialect)` and `parse(...).tokens` give a first-class token
|
|
294
|
+
stream: every token with its exact span, role, and channel. Available even when
|
|
295
|
+
the parse has errors.
|
|
296
|
+
- `lower()` never throws on broken or partial input; you get a flagged `query` IR
|
|
297
|
+
back, so every downstream pass stays total.
|
|
298
|
+
- `SqlDocument` is a persistent, immutable, position-addressable per-file model.
|
|
299
|
+
It runs `parse → resolveScopes` once (plus lazy `analyze(schema)`), caches the
|
|
300
|
+
result, and answers `tokenAt` / `nodeAt`. An edit yields a new document; an
|
|
301
|
+
O(log n) `LineIndex` maps positions to offsets.
|
|
302
|
+
- `completeAt(doc, offset, schema?)`: scope-aware completion (keywords, columns,
|
|
303
|
+
tables, functions) from an ATN (Augmented Transition Network, the grammar's
|
|
304
|
+
state-machine form) candidate walk over the grammar, our own, with no third-party
|
|
305
|
+
dependency.
|
|
306
|
+
- `signatureAt(doc, offset)`: parameter hints from a curated per-dialect
|
|
139
307
|
function-signature table; the long tail degrades to name + active-argument.
|
|
140
|
-
-
|
|
141
|
-
|
|
142
|
-
|
|
308
|
+
- `referencesAt(scopes, offset, schema?)`: every occurrence (plus the declaration)
|
|
309
|
+
of the symbol under the cursor; backs find-references, document highlight, and
|
|
310
|
+
code-lens reference counts.
|
|
143
311
|
|
|
144
312
|
```ts
|
|
145
313
|
import { SqlDocument, Schema } from "sqllens";
|
|
@@ -150,22 +318,19 @@ doc.tokenAt(7); // token under an offset
|
|
|
150
318
|
const next = doc.withText("SELECT amount, id FROM sales", 2); // immutable edit → new doc
|
|
151
319
|
```
|
|
152
320
|
|
|
153
|
-
## Language server
|
|
321
|
+
## Language server (experimental)
|
|
154
322
|
|
|
155
|
-
An LSP (Language Server Protocol) server built on the library
|
|
156
|
-
|
|
157
|
-
only
|
|
158
|
-
|
|
323
|
+
An LSP (Language Server Protocol) server built on the library lives in `src/lsp/`.
|
|
324
|
+
It is experimental and **not part of the published npm package**: the package ships
|
|
325
|
+
the library only, and the server is source you run from the repo. It holds one
|
|
326
|
+
`SqlDocument` per open file (rebuilt on edit) and reaches the library only through
|
|
327
|
+
the public API, and adds no analysis of its own beyond protocol translation.
|
|
159
328
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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:
|
|
329
|
+
A SQL server needs only a subset of LSP's ~30 request types: some don't apply to
|
|
330
|
+
SQL (type hierarchy, document color, monikers), and a few are deferred (formatting,
|
|
331
|
+
project-wide navigation). Where the server stands today, feature by feature:
|
|
167
332
|
|
|
168
|
-
|
|
333
|
+
### Language features
|
|
169
334
|
|
|
170
335
|
| Feature | Status |
|
|
171
336
|
| --- | --- |
|
|
@@ -185,7 +350,7 @@ apply (type hierarchy, document color, monikers); a few are deliberately deferre
|
|
|
185
350
|
| Go to declaration | ◻️ not yet |
|
|
186
351
|
| Go to type definition | ◻️ not yet |
|
|
187
352
|
| Go to implementation | ◻️ not yet — name → its defining query (view / model); needs the project model |
|
|
188
|
-
| Call hierarchy | ◻️ not yet — the CTE /
|
|
353
|
+
| Call hierarchy | ◻️ not yet — the CTE / view / model dependency graph |
|
|
189
354
|
| Document link | ◻️ not yet |
|
|
190
355
|
| Linked editing range | ◻️ not yet — live alias / name sync-edit |
|
|
191
356
|
| Code action (quick fixes) | ◻️ next phase |
|
|
@@ -196,7 +361,7 @@ apply (type hierarchy, document color, monikers); a few are deliberately deferre
|
|
|
196
361
|
| Document color | — n/a — no color literals |
|
|
197
362
|
| Moniker | — n/a — LSIF / cross-repo indexing concern |
|
|
198
363
|
|
|
199
|
-
|
|
364
|
+
### Diagnostics & document sync
|
|
200
365
|
|
|
201
366
|
| Feature | Status |
|
|
202
367
|
| --- | --- |
|
|
@@ -209,7 +374,7 @@ apply (type hierarchy, document color, monikers); a few are deliberately deferre
|
|
|
209
374
|
| Save notifications (`didSave` / `willSave`) | ◻️ not yet |
|
|
210
375
|
| Notebook document sync | ◻️ not yet |
|
|
211
376
|
|
|
212
|
-
|
|
377
|
+
### Workspace features
|
|
213
378
|
|
|
214
379
|
| Feature | Status |
|
|
215
380
|
| --- | --- |
|
|
@@ -223,18 +388,6 @@ deferred items are tracked work: rename and
|
|
|
223
388
|
code actions are the next LSP phase, workspace symbols need the project model,
|
|
224
389
|
and formatting is expected to wrap an existing external formatter.
|
|
225
390
|
|
|
226
|
-
## Generating the parsers
|
|
227
|
-
|
|
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):
|
|
231
|
-
|
|
232
|
-
```bash
|
|
233
|
-
npm run gen -- databricks # | tsql | snowflake | bigquery | redshift | postgres | duckdb | trino
|
|
234
|
-
npm run typecheck
|
|
235
|
-
npm test
|
|
236
|
-
```
|
|
237
|
-
|
|
238
391
|
## Architecture
|
|
239
392
|
|
|
240
393
|
One folder per dialect; no shared "core" grammar and no grammar inheritance. Each
|
|
@@ -242,14 +395,15 @@ dialect is a standalone pair of split `.g4` files (a lexer grammar + a parser
|
|
|
242
395
|
grammar), forked from its best starting point and edited in place. Everything
|
|
243
396
|
downstream of `lower` is shared and dialect-neutral.
|
|
244
397
|
|
|
245
|
-
##
|
|
398
|
+
## Building from source & contributing
|
|
246
399
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
400
|
+
`npm install sqllens` needs no build step on your side. To build it yourself from
|
|
401
|
+
source, or to work on a grammar, you regenerate the parsers from the `.g4` files
|
|
402
|
+
first. [CONTRIBUTING.md](CONTRIBUTING.md) has the setup, the command list, and the
|
|
403
|
+
corpus-gate workflow.
|
|
250
404
|
|
|
251
405
|
## License
|
|
252
406
|
|
|
253
|
-
MIT
|
|
407
|
+
MIT. See [LICENSE](LICENSE). The forked grammars under `grammars/` keep their
|
|
254
408
|
upstream licenses (Apache-2.0 for Databricks; MIT for T-SQL and Snowflake; BSD-3
|
|
255
409
|
for BigQuery and Redshift); see [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md).
|
package/dist/api.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ParserRuleContext } from "antlr4ng";
|
|
2
|
-
import type { Expr, QueryExpr } from "./ir/ir.js";
|
|
2
|
+
import type { Expr, Projection, QueryExpr } from "./ir/ir.js";
|
|
3
3
|
import type { SyntaxDiagnostic } from "./parse-diagnostics.js";
|
|
4
4
|
import { type Scope, type ScopeTree } from "./scope/scope.js";
|
|
5
5
|
import { type Qualification } from "./qualify/qualify.js";
|
|
@@ -10,9 +10,8 @@ export type { Nullability } from "./infer/nullability.js";
|
|
|
10
10
|
import { originsOf as exprOriginsOf, type ColumnLineage, type Origin } from "./lineage/lineage.js";
|
|
11
11
|
import { type StarExpansion, type Sym } from "./symbols/symbols.js";
|
|
12
12
|
import type { Token } from "./token/token.js";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
export type Dialect = "databricks" | "tsql" | "snowflake" | "bigquery" | "redshift" | "postgres" | "duckdb" | "trino";
|
|
13
|
+
import type { Dialect } from "./dialect.js";
|
|
14
|
+
export type { Dialect } from "./dialect.js";
|
|
16
15
|
/** Options carrying the dialect, needed only when a lift helper enters from a raw string. */
|
|
17
16
|
export interface DialectOpts {
|
|
18
17
|
dialect?: Dialect;
|
|
@@ -103,22 +102,25 @@ export declare class Lineage {
|
|
|
103
102
|
/** Per output column: the base-table columns it derives from. */
|
|
104
103
|
readonly all: readonly ColumnLineage[];
|
|
105
104
|
private readonly byOutput;
|
|
105
|
+
private readonly byNode;
|
|
106
106
|
constructor(columns: ColumnLineage[]);
|
|
107
107
|
/** The base-table origins of a named output column, or [] if there is no such output. */
|
|
108
108
|
originsOf(column: string): Origin[];
|
|
109
|
+
/** Origins keyed by the producing Projection node — unambiguous under duplicate output names. */
|
|
110
|
+
originsOfNode(projection: Projection): Origin[];
|
|
109
111
|
}
|
|
110
112
|
export { exprOriginsOf as originsOfExpr };
|
|
111
113
|
export { lineageAt, lineageOf, type LineageHop, type ViaStep } from "./lineage/hops.js";
|
|
112
114
|
export { tokenize } from "./token/tokenize.js";
|
|
113
115
|
export type { Token, TokenRole } from "./token/token.js";
|
|
114
|
-
export { SqlDocument, type DocumentAnalysis, type StatementCell } from "./document/document.js";
|
|
116
|
+
export { SqlDocument, type DocumentAnalysis, type StatementCell, type DocumentVariant, type UnionCte, } from "./document/document.js";
|
|
115
117
|
export { LineIndex } from "./document/line-index.js";
|
|
116
118
|
export type { StatementCellSpan } from "./document/split.js";
|
|
117
|
-
export { complete, type Completion } from "./completion/complete.js";
|
|
119
|
+
export { complete, completeAt, type Completion } from "./completion/complete.js";
|
|
118
120
|
export { signatureAt, type SignatureInfo } from "./signature/signature.js";
|
|
119
121
|
export { FUNCTION_SIGNATURES, HARVESTED_SIGNATURES, lookupSignature, hasSignature, type FnSignature, type ParamSig, } from "./signature/signatures.js";
|
|
120
122
|
export { referencesAt, type Occurrence, type Occurrences } from "./references/references.js";
|
|
121
123
|
export { dialectSymbols, type DialectSymbols } from "./dialect-symbols.js";
|
|
122
124
|
export { CallbackSchema, type SchemaProvider, type TableResolver } from "./qualify/schema-provider.js";
|
|
123
|
-
export {
|
|
125
|
+
export { DERIVED_DIALECTS, resolveDialect } from "./derived-dialects.js";
|
|
124
126
|
export { foldIdentifier, displayName, type IdentKind } from "./ident/fold.js";
|
package/dist/api.js
CHANGED
|
@@ -157,16 +157,25 @@ export class Lineage {
|
|
|
157
157
|
/** Per output column: the base-table columns it derives from. */
|
|
158
158
|
all;
|
|
159
159
|
byOutput;
|
|
160
|
+
byNode;
|
|
160
161
|
constructor(columns) {
|
|
161
162
|
this.all = columns;
|
|
162
163
|
this.byOutput = new Map();
|
|
163
|
-
|
|
164
|
+
this.byNode = new WeakMap();
|
|
165
|
+
for (const c of columns) {
|
|
164
166
|
this.byOutput.set(c.output, c.origins);
|
|
167
|
+
if (c.projection)
|
|
168
|
+
this.byNode.set(c.projection, c.origins);
|
|
169
|
+
}
|
|
165
170
|
}
|
|
166
171
|
/** The base-table origins of a named output column, or [] if there is no such output. */
|
|
167
172
|
originsOf(column) {
|
|
168
173
|
return this.byOutput.get(column) ?? [];
|
|
169
174
|
}
|
|
175
|
+
/** Origins keyed by the producing Projection node — unambiguous under duplicate output names. */
|
|
176
|
+
originsOfNode(projection) {
|
|
177
|
+
return this.byNode.get(projection) ?? [];
|
|
178
|
+
}
|
|
170
179
|
}
|
|
171
180
|
// Re-export the single-expression origin walk under its building-block name (distinct from the
|
|
172
181
|
// Lineage wrapper) so consumers can trace one expression without a full query lineage.
|
|
@@ -185,11 +194,11 @@ export { tokenize } from "./token/tokenize.js";
|
|
|
185
194
|
// It composes the surface above (parse/toScopes/qualify/deriveSymbols/TypeInfo); the import cycle
|
|
186
195
|
// (api re-exports SqlDocument, document imports from api) is safe because document.ts only calls
|
|
187
196
|
// these at call time, never at module-eval time.
|
|
188
|
-
export { SqlDocument } from "./document/document.js";
|
|
197
|
+
export { SqlDocument, } from "./document/document.js";
|
|
189
198
|
export { LineIndex } from "./document/line-index.js";
|
|
190
199
|
// Scope-aware completion over a SqlDocument — the broken-input editor feature (keywords + schema
|
|
191
200
|
// tables/columns + function names at the caret). Total: never throws.
|
|
192
|
-
export { complete } from "./completion/complete.js";
|
|
201
|
+
export { complete, completeAt } from "./completion/complete.js";
|
|
193
202
|
// Signature help over a SqlDocument — the broken-input editor feature that shows parameter hints
|
|
194
203
|
// while typing inside a call's parens. Lookup order: curated (hand-verified) → harvested (doc-derived
|
|
195
204
|
// long tail, from tools/harvest-signatures.mjs) → name-only fallback. A pure token scan; never throws.
|
|
@@ -207,9 +216,9 @@ export { dialectSymbols } from "./dialect-symbols.js";
|
|
|
207
216
|
// lazy resolver whose prime() bumps a version to invalidate SqlDocument.analyze's memo) both
|
|
208
217
|
// satisfy `SchemaProvider`; every analysis entry point accepts the interface.
|
|
209
218
|
export { CallbackSchema } from "./qualify/schema-provider.js";
|
|
210
|
-
// The
|
|
211
|
-
//
|
|
212
|
-
export {
|
|
219
|
+
// The derived-dialect → dialect map: resolve an engine name (athena, glue, fabric, spark, …) to the
|
|
220
|
+
// dialect that parses its SQL, so consumers don't re-derive the family knowledge.
|
|
221
|
+
export { DERIVED_DIALECTS, resolveDialect } from "./derived-dialects.js";
|
|
213
222
|
// The dialect-true identifier fold — the identity key for name comparison (unquote + case-fold per
|
|
214
223
|
// the dialect's documented rules) and its display twin (unquote only). Exported so an embedding
|
|
215
224
|
// consumer comparing names against IR/scope output folds the same way the pipeline does.
|