sqllens 1.8.0 → 1.8.1
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/dist/bigquery/parse.d.ts +4 -0
- package/dist/bigquery/parse.js +27 -1
- package/dist/databricks/parse.d.ts +4 -0
- package/dist/databricks/parse.js +15 -0
- package/dist/duckdb/parse.d.ts +4 -0
- package/dist/duckdb/parse.js +15 -0
- package/dist/fragment-grammar.d.ts +48 -0
- package/dist/fragment-grammar.js +93 -0
- package/dist/fragment.d.ts +29 -0
- package/dist/fragment.js +76 -0
- package/dist/minijinja/parse.js +73 -5
- package/dist/minijinja/regions.d.ts +7 -0
- package/dist/mysql/parse.d.ts +4 -0
- package/dist/mysql/parse.js +15 -0
- package/dist/postgres/parse.d.ts +4 -0
- package/dist/postgres/parse.js +15 -0
- package/dist/redshift/parse.d.ts +4 -0
- package/dist/redshift/parse.js +15 -0
- package/dist/snowflake/parse.d.ts +4 -0
- package/dist/snowflake/parse.js +15 -0
- package/dist/sqlite/parse.d.ts +4 -0
- package/dist/sqlite/parse.js +15 -0
- package/dist/symbols/symbols.js +5 -0
- package/dist/trino/parse.d.ts +4 -0
- package/dist/trino/parse.js +15 -0
- package/dist/tsql/parse.d.ts +4 -0
- package/dist/tsql/parse.js +15 -0
- package/package.json +1 -1
package/dist/bigquery/parse.d.ts
CHANGED
|
@@ -9,3 +9,7 @@ export type { ParseResult } from "../parse-result.js";
|
|
|
9
9
|
* only when SLL fails — same result LL alone would give, just faster on valid input.
|
|
10
10
|
*/
|
|
11
11
|
export declare function parseBigQuery(sql: string): ParseResult;
|
|
12
|
+
/** The fragment entries (src/fragment.ts): the grammar's own rules for a statement batch
|
|
13
|
+
* (`root`), an expression, a FROM-slot source, a CTE list (no WITH) and a select list;
|
|
14
|
+
* the driver anchors each at EOF. For templated macro bodies. */
|
|
15
|
+
export declare const fragmentGrammar: import("../fragment-grammar.js").FragmentGrammar;
|
package/dist/bigquery/parse.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { BailErrorStrategy, CharStream, CommonTokenStream, PredictionMode } from "antlr4ng";
|
|
1
|
+
import { BailErrorStrategy, CharStream, CommonTokenStream, PredictionMode, Token as AntlrToken, } from "antlr4ng";
|
|
2
2
|
import { GoogleSQLLexer } from "../generated/bigquery/GoogleSQLLexer.js";
|
|
3
3
|
import { GoogleSQLParser } from "../generated/bigquery/GoogleSQLParser.js";
|
|
4
|
+
import { defineFragmentGrammar, separatedList } from "../fragment-grammar.js";
|
|
4
5
|
import { dotPathTokenSource } from "./dot-path.js";
|
|
5
6
|
import { postParseDiagnostics } from "./post-validate.js";
|
|
6
7
|
import { makeErrorCollector } from "../parse-diagnostics.js";
|
|
@@ -67,3 +68,28 @@ export function parseBigQuery(sql) {
|
|
|
67
68
|
return withTokens({ tree, errors: diagnostics.length, diagnostics, sllFallback: true });
|
|
68
69
|
}
|
|
69
70
|
}
|
|
71
|
+
/** The fragment entries (src/fragment.ts): the grammar's own rules for a statement batch
|
|
72
|
+
* (`root`), an expression, a FROM-slot source, a CTE list (no WITH) and a select list;
|
|
73
|
+
* the driver anchors each at EOF. For templated macro bodies. */
|
|
74
|
+
export const fragmentGrammar = defineFragmentGrammar({
|
|
75
|
+
// The same token pipeline as parseBigQuery: the DOT_IDENTIFIER path rewrite and the
|
|
76
|
+
// literal-escape validation ride the lex; the post-parse tree checks ride each parse.
|
|
77
|
+
lex: (text) => {
|
|
78
|
+
const lexer = new GoogleSQLLexer(CharStream.fromString(text));
|
|
79
|
+
lexer.removeErrorListeners();
|
|
80
|
+
const { source, escapeDiagnostics } = dotPathTokenSource(text, lexer);
|
|
81
|
+
const stream = new CommonTokenStream(source);
|
|
82
|
+
stream.fill();
|
|
83
|
+
return { tokens: stream.getTokens().filter((t) => t.type !== AntlrToken.EOF), diagnostics: escapeDiagnostics };
|
|
84
|
+
},
|
|
85
|
+
newLexer: (input) => new GoogleSQLLexer(input),
|
|
86
|
+
newParser: (tokens) => new GoogleSQLParser(tokens),
|
|
87
|
+
postParse: postParseDiagnostics,
|
|
88
|
+
entries: {
|
|
89
|
+
statement: (p) => p.root(),
|
|
90
|
+
expression: (p) => p.expression(),
|
|
91
|
+
tableSource: (p) => p.from_clause_contents(),
|
|
92
|
+
cteList: separatedList((p) => p.with_clause_entry(), GoogleSQLLexer.COMMA_SYMBOL),
|
|
93
|
+
selectList: (p) => p.select_list(),
|
|
94
|
+
},
|
|
95
|
+
});
|
|
@@ -9,3 +9,7 @@ export type { ParseResult } from "../parse-result.js";
|
|
|
9
9
|
* result LL alone would produce, so correctness is unchanged — just faster.
|
|
10
10
|
*/
|
|
11
11
|
export declare function parseDatabricks(sql: string): ParseResult;
|
|
12
|
+
/** The fragment entries (src/fragment.ts): the grammar's own rules for a statement batch
|
|
13
|
+
* (`multiStatement`), an expression, a FROM-slot source, a CTE list (no WITH) and a select list;
|
|
14
|
+
* the driver anchors each at EOF. For templated macro bodies. */
|
|
15
|
+
export declare const fragmentGrammar: import("../fragment-grammar.js").FragmentGrammar;
|
package/dist/databricks/parse.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BailErrorStrategy, CharStream, CommonTokenStream, PredictionMode, } from "antlr4ng";
|
|
2
2
|
import { DatabricksLexer } from "../generated/databricks/DatabricksLexer.js";
|
|
3
3
|
import { DatabricksParser } from "../generated/databricks/DatabricksParser.js";
|
|
4
|
+
import { defineFragmentGrammar, separatedList } from "../fragment-grammar.js";
|
|
4
5
|
import { makeErrorCollector } from "../parse-diagnostics.js";
|
|
5
6
|
import { CONSUMED_AS_RULES, deriveConsumedAs } from "../token/consumed-as.js";
|
|
6
7
|
import { mapTokens } from "../token/map.js";
|
|
@@ -72,3 +73,17 @@ function attachErrorCounter(lexer, parser, listener) {
|
|
|
72
73
|
parser.removeErrorListeners();
|
|
73
74
|
parser.addErrorListener(listener);
|
|
74
75
|
}
|
|
76
|
+
/** The fragment entries (src/fragment.ts): the grammar's own rules for a statement batch
|
|
77
|
+
* (`multiStatement`), an expression, a FROM-slot source, a CTE list (no WITH) and a select list;
|
|
78
|
+
* the driver anchors each at EOF. For templated macro bodies. */
|
|
79
|
+
export const fragmentGrammar = defineFragmentGrammar({
|
|
80
|
+
newLexer: (input) => new DatabricksLexer(input),
|
|
81
|
+
newParser: (tokens) => new DatabricksParser(tokens),
|
|
82
|
+
entries: {
|
|
83
|
+
statement: (p) => p.multiStatement(),
|
|
84
|
+
expression: (p) => p.expression(),
|
|
85
|
+
tableSource: (p) => p.relation(),
|
|
86
|
+
cteList: separatedList((p) => p.namedQuery(), DatabricksLexer.COMMA),
|
|
87
|
+
selectList: (p) => p.namedExpressionSeq(),
|
|
88
|
+
},
|
|
89
|
+
});
|
package/dist/duckdb/parse.d.ts
CHANGED
|
@@ -8,3 +8,7 @@ export type { ParseResult } from "../parse-result.js";
|
|
|
8
8
|
* only when SLL fails — same result LL alone would give, just faster on valid input.
|
|
9
9
|
*/
|
|
10
10
|
export declare function parseDuckdb(sql: string): ParseResult;
|
|
11
|
+
/** The fragment entries (src/fragment.ts): the grammar's own rules for a statement batch
|
|
12
|
+
* (`root`), an expression, a FROM-slot source, a CTE list (no WITH) and a select list;
|
|
13
|
+
* the driver anchors each at EOF. For templated macro bodies. */
|
|
14
|
+
export declare const fragmentGrammar: import("../fragment-grammar.js").FragmentGrammar;
|
package/dist/duckdb/parse.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BailErrorStrategy, CharStream, CommonTokenStream, PredictionMode, } from "antlr4ng";
|
|
2
2
|
import { DuckdbLexer } from "../generated/duckdb/DuckdbLexer.js";
|
|
3
3
|
import { DuckdbParser } from "../generated/duckdb/DuckdbParser.js";
|
|
4
|
+
import { defineFragmentGrammar } from "../fragment-grammar.js";
|
|
4
5
|
import { makeErrorCollector } from "../parse-diagnostics.js";
|
|
5
6
|
import { CONSUMED_AS_RULES, deriveConsumedAs } from "../token/consumed-as.js";
|
|
6
7
|
import { mapTokens } from "../token/map.js";
|
|
@@ -63,3 +64,17 @@ function attachErrorCounter(lexer, parser, listener) {
|
|
|
63
64
|
parser.removeErrorListeners();
|
|
64
65
|
parser.addErrorListener(listener);
|
|
65
66
|
}
|
|
67
|
+
/** The fragment entries (src/fragment.ts): the grammar's own rules for a statement batch
|
|
68
|
+
* (`root`), an expression, a FROM-slot source, a CTE list (no WITH) and a select list;
|
|
69
|
+
* the driver anchors each at EOF. For templated macro bodies. */
|
|
70
|
+
export const fragmentGrammar = defineFragmentGrammar({
|
|
71
|
+
newLexer: (input) => new DuckdbLexer(input),
|
|
72
|
+
newParser: (tokens) => new DuckdbParser(tokens),
|
|
73
|
+
entries: {
|
|
74
|
+
statement: (p) => p.root(),
|
|
75
|
+
expression: (p) => p.a_expr(),
|
|
76
|
+
tableSource: (p) => p.table_ref(),
|
|
77
|
+
cteList: (p) => p.cte_list(),
|
|
78
|
+
selectList: (p) => p.target_list(),
|
|
79
|
+
},
|
|
80
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { CharStream, CommonTokenStream, type Lexer, type Parser, type ParserRuleContext, Token as AntlrToken } from "antlr4ng";
|
|
2
|
+
import { type SyntaxDiagnostic } from "./parse-diagnostics.js";
|
|
3
|
+
/** What a fragment parses as. `statement` is the dialect's ordinary full-file entry. */
|
|
4
|
+
export type FragmentKind = "statement" | "expression" | "tableSource" | "cteList" | "selectList";
|
|
5
|
+
/** Every kind, in the order a macro body is tried: the most common reading first. */
|
|
6
|
+
export declare const FRAGMENT_KINDS: readonly FragmentKind[];
|
|
7
|
+
export interface FragmentLex {
|
|
8
|
+
/** Text-native tokens, trivia included, EOF excluded. */
|
|
9
|
+
tokens: AntlrToken[];
|
|
10
|
+
/** Token-derived diagnostics the dialect's statement entry would also report (bigquery's
|
|
11
|
+
* literal-escape validation); positioned in the text's coordinates. */
|
|
12
|
+
diagnostics: SyntaxDiagnostic[];
|
|
13
|
+
}
|
|
14
|
+
export interface FragmentParse {
|
|
15
|
+
tree: ParserRuleContext;
|
|
16
|
+
/** Empty when the slice parsed clean as this kind (the rule is EOF-anchored). */
|
|
17
|
+
diagnostics: SyntaxDiagnostic[];
|
|
18
|
+
}
|
|
19
|
+
export interface FragmentGrammar {
|
|
20
|
+
/** Lex a whole text exactly as the dialect's statement entry does (same token rewrites). */
|
|
21
|
+
lex(text: string): FragmentLex;
|
|
22
|
+
/**
|
|
23
|
+
* Parse an already-lexed slice as `kind`. `bail` = SLL + bail strategy: returns undefined on
|
|
24
|
+
* the first syntax error (fast clean/not-clean probe); otherwise full LL with recovery and
|
|
25
|
+
* positioned diagnostics.
|
|
26
|
+
*/
|
|
27
|
+
parse(slice: readonly AntlrToken[], kind: FragmentKind, bail: boolean): FragmentParse | undefined;
|
|
28
|
+
}
|
|
29
|
+
/** A fragment entry: drives one or more of the grammar's OWN rules over the parser. The grammar
|
|
30
|
+
* is untouched (no `x EOF` wrapper rules: an extra caller widens a rule's SLL follow set and
|
|
31
|
+
* flips prediction on unrelated input); the EOF anchor is checked by the driver instead. */
|
|
32
|
+
export type FragmentEntry<P extends Parser> = (parser: P) => ParserRuleContext;
|
|
33
|
+
export interface FragmentGrammarSpec<P extends Parser> {
|
|
34
|
+
/** Lex a whole text; defaults to the plain lexer with no diagnostics. */
|
|
35
|
+
lex?: (text: string) => FragmentLex;
|
|
36
|
+
newLexer: (input: CharStream) => Lexer;
|
|
37
|
+
newParser: (tokens: CommonTokenStream) => P;
|
|
38
|
+
entries: {
|
|
39
|
+
readonly [K in FragmentKind]: FragmentEntry<P>;
|
|
40
|
+
};
|
|
41
|
+
/** Tree-walking checks the dialect's statement entry runs after the parse (bigquery). */
|
|
42
|
+
postParse?: (tree: ParserRuleContext) => SyntaxDiagnostic[];
|
|
43
|
+
}
|
|
44
|
+
/** `item (sep item)*` driven from outside the grammar, for dialects without a list rule
|
|
45
|
+
* (a CTE list without its WITH, a select list). The tree is the FIRST item's. */
|
|
46
|
+
export declare function separatedList<P extends Parser>(item: FragmentEntry<P>, separator: number): FragmentEntry<P>;
|
|
47
|
+
/** Bind a dialect's lexer, parser and fragment entry rules into a `FragmentGrammar`. */
|
|
48
|
+
export declare function defineFragmentGrammar<P extends Parser>(spec: FragmentGrammarSpec<P>): FragmentGrammar;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Fragment grammar: the per-dialect entry set for parsing an SQL FRAGMENT — a
|
|
3
|
+
// templated macro body that is not a statement (an expression, a FROM-slot
|
|
4
|
+
// source, a CTE list pasted after WITH, a select list). Each dialect's parse.ts
|
|
5
|
+
// binds its lexer, parser and the five EOF-anchored `*_fragment` rules through
|
|
6
|
+
// `defineFragmentGrammar`; src/fragment.ts holds the registry and the driver.
|
|
7
|
+
//
|
|
8
|
+
// The parse runs over an already-lexed token slice (antlr4ng's ListTokenSource),
|
|
9
|
+
// never over a re-lexed substring, so every token, span and diagnostic stays in
|
|
10
|
+
// the coordinates of the text the tokens were lexed from.
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
import { BailErrorStrategy, CharStream, CommonTokenStream, ListTokenSource, PredictionMode, Token as AntlrToken, } from "antlr4ng";
|
|
13
|
+
import { makeErrorCollector } from "./parse-diagnostics.js";
|
|
14
|
+
/** Every kind, in the order a macro body is tried: the most common reading first. */
|
|
15
|
+
export const FRAGMENT_KINDS = [
|
|
16
|
+
"statement",
|
|
17
|
+
"expression",
|
|
18
|
+
"tableSource",
|
|
19
|
+
"cteList",
|
|
20
|
+
"selectList",
|
|
21
|
+
];
|
|
22
|
+
/** `item (sep item)*` driven from outside the grammar, for dialects without a list rule
|
|
23
|
+
* (a CTE list without its WITH, a select list). The tree is the FIRST item's. */
|
|
24
|
+
export function separatedList(item, separator) {
|
|
25
|
+
return (parser) => {
|
|
26
|
+
const first = item(parser);
|
|
27
|
+
while (parser.inputStream.LA(1) === separator) {
|
|
28
|
+
parser.inputStream.consume(); // between rules there is no context to attach the separator to
|
|
29
|
+
item(parser);
|
|
30
|
+
}
|
|
31
|
+
return first;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** The EOF anchor: the entry returned but input remains — the same diagnostic shape ANTLR
|
|
35
|
+
* emits for an EOF-anchored rule, positioned at the leftover token. */
|
|
36
|
+
function trailingInput(parser) {
|
|
37
|
+
const tok = parser.inputStream.LT(1);
|
|
38
|
+
if (!tok || tok.type === AntlrToken.EOF)
|
|
39
|
+
return undefined;
|
|
40
|
+
const text = tok.text ?? "";
|
|
41
|
+
return {
|
|
42
|
+
message: `extraneous input '${text}' expecting <EOF>`,
|
|
43
|
+
line: tok.line,
|
|
44
|
+
column: tok.column,
|
|
45
|
+
offset: tok.start,
|
|
46
|
+
length: text.length || 1,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** Bind a dialect's lexer, parser and fragment entry rules into a `FragmentGrammar`. */
|
|
50
|
+
export function defineFragmentGrammar(spec) {
|
|
51
|
+
const { newLexer, newParser, entries, postParse } = spec;
|
|
52
|
+
const lex = spec.lex ??
|
|
53
|
+
((text) => {
|
|
54
|
+
const lexer = newLexer(CharStream.fromString(text));
|
|
55
|
+
lexer.removeErrorListeners();
|
|
56
|
+
return { tokens: lexer.getAllTokens(), diagnostics: [] };
|
|
57
|
+
});
|
|
58
|
+
return {
|
|
59
|
+
lex,
|
|
60
|
+
parse(slice, kind, bail) {
|
|
61
|
+
const tokens = new CommonTokenStream(new ListTokenSource([...slice]));
|
|
62
|
+
const parser = newParser(tokens);
|
|
63
|
+
const collector = makeErrorCollector();
|
|
64
|
+
parser.removeErrorListeners();
|
|
65
|
+
parser.addErrorListener(collector.listener);
|
|
66
|
+
const sim = parser.interpreter;
|
|
67
|
+
if (bail) {
|
|
68
|
+
parser.errorHandler = new BailErrorStrategy();
|
|
69
|
+
sim.predictionMode = PredictionMode.SLL;
|
|
70
|
+
let tree;
|
|
71
|
+
try {
|
|
72
|
+
tree = entries[kind](parser);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
// A grammar action can report through the listener without throwing (bigquery's
|
|
78
|
+
// join-balance check); that is not a clean parse either. Nor is leftover input.
|
|
79
|
+
if (collector.diagnostics.length > 0 || trailingInput(parser))
|
|
80
|
+
return undefined;
|
|
81
|
+
const post = postParse?.(tree) ?? [];
|
|
82
|
+
return post.length === 0 ? { tree, diagnostics: [] } : undefined;
|
|
83
|
+
}
|
|
84
|
+
sim.predictionMode = PredictionMode.LL;
|
|
85
|
+
const tree = entries[kind](parser);
|
|
86
|
+
const trailing = trailingInput(parser);
|
|
87
|
+
return {
|
|
88
|
+
tree,
|
|
89
|
+
diagnostics: [...collector.diagnostics, ...(trailing ? [trailing] : []), ...(postParse?.(tree) ?? [])],
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ParserRuleContext } from "antlr4ng";
|
|
2
|
+
import type { Dialect } from "./dialect.js";
|
|
3
|
+
import { type FragmentKind } from "./fragment-grammar.js";
|
|
4
|
+
import type { SyntaxDiagnostic } from "./parse-diagnostics.js";
|
|
5
|
+
export type { FragmentKind } from "./fragment-grammar.js";
|
|
6
|
+
/** A half-open [start, end) offset range of the lexed text (a `PartSpan` fits). */
|
|
7
|
+
export interface FragmentRange {
|
|
8
|
+
start: number;
|
|
9
|
+
end: number;
|
|
10
|
+
}
|
|
11
|
+
export interface FragmentResult {
|
|
12
|
+
/** The kind the ranges parsed as; when `clean` is false, the kind that got furthest. */
|
|
13
|
+
kind: FragmentKind;
|
|
14
|
+
/** True when the ranges parsed with zero syntax errors as `kind`. */
|
|
15
|
+
clean: boolean;
|
|
16
|
+
tree: ParserRuleContext;
|
|
17
|
+
/** Positioned in the text's coordinates. Empty when clean. */
|
|
18
|
+
diagnostics: SyntaxDiagnostic[];
|
|
19
|
+
}
|
|
20
|
+
export interface FragmentSession {
|
|
21
|
+
/**
|
|
22
|
+
* Parse the tokens inside `ranges` (a token counts when it starts inside one) as a fragment.
|
|
23
|
+
* Returns undefined when the ranges hold no default-channel token (nothing to parse; no
|
|
24
|
+
* verdict). Never throws: every attempt runs a recovering parser over a fixed kind list.
|
|
25
|
+
*/
|
|
26
|
+
parse(ranges: readonly FragmentRange[], kinds?: readonly FragmentKind[]): FragmentResult | undefined;
|
|
27
|
+
}
|
|
28
|
+
/** Lex `text` once with `dialect`'s statement-entry token pipeline; parse ranges of it after. */
|
|
29
|
+
export declare function openFragments(text: string, dialect: Dialect): FragmentSession;
|
package/dist/fragment.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Fragment parsing: parse RANGES of a text as an SQL fragment, trying each
|
|
3
|
+
// FragmentKind in order until one parses clean. The consumer is the minijinja
|
|
4
|
+
// front end (a `{% macro %}` body is whatever gets pasted at the call site: a
|
|
5
|
+
// statement, an expression, a FROM-slot source, a CTE list, a select list); the
|
|
6
|
+
// statement entry is right for a model file and wrong for most macro bodies.
|
|
7
|
+
//
|
|
8
|
+
// The text is lexed ONCE (`openFragments`) and every parse cuts its token slice
|
|
9
|
+
// from that lex, so the tokens (and every diagnostic they position) stay in the
|
|
10
|
+
// text's own coordinates: no remap. A fast SLL+bail probe per kind finds the
|
|
11
|
+
// clean reading; when none is clean the kinds are re-run with recovery and the
|
|
12
|
+
// reading that got furthest before its first error supplies the diagnostics —
|
|
13
|
+
// a heuristic for the broken-input case only (the reading closest to what was
|
|
14
|
+
// meant), never a claim: `clean` is false and the region carries no verdict.
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
import { FRAGMENT_KINDS } from "./fragment-grammar.js";
|
|
17
|
+
import { fragmentGrammar as bigquery } from "./bigquery/parse.js";
|
|
18
|
+
import { fragmentGrammar as databricks } from "./databricks/parse.js";
|
|
19
|
+
import { fragmentGrammar as duckdb } from "./duckdb/parse.js";
|
|
20
|
+
import { fragmentGrammar as mysql } from "./mysql/parse.js";
|
|
21
|
+
import { fragmentGrammar as postgres } from "./postgres/parse.js";
|
|
22
|
+
import { fragmentGrammar as redshift } from "./redshift/parse.js";
|
|
23
|
+
import { fragmentGrammar as snowflake } from "./snowflake/parse.js";
|
|
24
|
+
import { fragmentGrammar as sqlite } from "./sqlite/parse.js";
|
|
25
|
+
import { fragmentGrammar as trino } from "./trino/parse.js";
|
|
26
|
+
import { fragmentGrammar as tsql } from "./tsql/parse.js";
|
|
27
|
+
const FRAGMENT_GRAMMARS = {
|
|
28
|
+
databricks,
|
|
29
|
+
tsql,
|
|
30
|
+
snowflake,
|
|
31
|
+
bigquery,
|
|
32
|
+
redshift,
|
|
33
|
+
postgres,
|
|
34
|
+
duckdb,
|
|
35
|
+
trino,
|
|
36
|
+
sqlite,
|
|
37
|
+
mysql,
|
|
38
|
+
};
|
|
39
|
+
/** Lex `text` once with `dialect`'s statement-entry token pipeline; parse ranges of it after. */
|
|
40
|
+
export function openFragments(text, dialect) {
|
|
41
|
+
const grammar = FRAGMENT_GRAMMARS[dialect];
|
|
42
|
+
const lexed = grammar.lex(text);
|
|
43
|
+
const inRanges = (offset, ranges) => ranges.some((r) => offset >= r.start && offset < r.end);
|
|
44
|
+
return {
|
|
45
|
+
parse(ranges, kinds = FRAGMENT_KINDS) {
|
|
46
|
+
const slice = lexed.tokens.filter((t) => inRanges(t.start, ranges));
|
|
47
|
+
if (!slice.some((t) => t.channel === 0))
|
|
48
|
+
return undefined;
|
|
49
|
+
// Token-derived diagnostics of the slice (bigquery literal escapes) hold under every reading.
|
|
50
|
+
const lexDiags = lexed.diagnostics.filter((d) => d.offset !== undefined && inRanges(d.offset, ranges));
|
|
51
|
+
if (lexDiags.length === 0) {
|
|
52
|
+
for (const kind of kinds) {
|
|
53
|
+
const clean = grammar.parse(slice, kind, true);
|
|
54
|
+
if (clean)
|
|
55
|
+
return { kind, clean: true, tree: clean.tree, diagnostics: [] };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
let best;
|
|
59
|
+
let bestAt = -1;
|
|
60
|
+
for (const kind of kinds) {
|
|
61
|
+
const attempt = grammar.parse(slice, kind, false);
|
|
62
|
+
if (!attempt)
|
|
63
|
+
continue;
|
|
64
|
+
const diagnostics = [...lexDiags, ...attempt.diagnostics];
|
|
65
|
+
if (diagnostics.length === 0)
|
|
66
|
+
return { kind, clean: true, tree: attempt.tree, diagnostics };
|
|
67
|
+
const at = attempt.diagnostics[0]?.offset ?? Number.MAX_SAFE_INTEGER;
|
|
68
|
+
if (at > bestAt) {
|
|
69
|
+
bestAt = at;
|
|
70
|
+
best = { kind, clean: false, tree: attempt.tree, diagnostics };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return best;
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
package/dist/minijinja/parse.js
CHANGED
|
@@ -47,6 +47,7 @@ import { CharStream, CommonTokenStream, ListTokenSource, Token as AntlrToken } f
|
|
|
47
47
|
import { parse } from "../api.js";
|
|
48
48
|
import { debugRethrow } from "../debug.js";
|
|
49
49
|
import { MinijinjaLexer } from "../generated/minijinja/MinijinjaLexer.js";
|
|
50
|
+
import { openFragments } from "../fragment.js";
|
|
50
51
|
import { endPosition } from "../ir/span.js";
|
|
51
52
|
import { MinijinjaParser } from "../generated/minijinja/MinijinjaParser.js";
|
|
52
53
|
import { makeErrorCollector } from "../parse-diagnostics.js";
|
|
@@ -241,6 +242,66 @@ function scrubPlaceholderDiagnostics(diags, tagRanges, text, placeholder) {
|
|
|
241
242
|
});
|
|
242
243
|
return { diagnostics, bySegment };
|
|
243
244
|
}
|
|
245
|
+
/**
|
|
246
|
+
* Re-read every `{% macro %}` body as an SQL fragment. The whole-file statement parse's
|
|
247
|
+
* diagnostics are replaced wholesale: they are recovery noise once a body has derailed it
|
|
248
|
+
* (a body that is a CASE expression leaves "missing 'CASE' at EOF" far outside itself).
|
|
249
|
+
* What replaces them: the text OUTSIDE the macro bodies read as a statement batch (a model
|
|
250
|
+
* file that also defines a macro keeps its real errors), plus each body's own fragment
|
|
251
|
+
* read. Lexer diagnostics (offset-less) are kept as they are. Each region learns what its
|
|
252
|
+
* body is (`body`); a body with no SQL token gets no verdict and no diagnostic. Mutates the
|
|
253
|
+
* regions' `body` field only. Files without a macro region are untouched.
|
|
254
|
+
*/
|
|
255
|
+
function reparseMacroBodies(regions, diagnostics, placeholder, dialect) {
|
|
256
|
+
const macros = [];
|
|
257
|
+
// Macros can sit under an if/for (a guarded definition); a macro inside a macro body is
|
|
258
|
+
// covered by the outer body's read and not visited on its own.
|
|
259
|
+
const visit = (list) => {
|
|
260
|
+
for (const region of list) {
|
|
261
|
+
if (region.kind === "macro")
|
|
262
|
+
macros.push(region);
|
|
263
|
+
else
|
|
264
|
+
for (const arm of region.arms)
|
|
265
|
+
visit(arm.children);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
visit(regions);
|
|
269
|
+
if (macros.length === 0)
|
|
270
|
+
return diagnostics;
|
|
271
|
+
const fragments = openFragments(placeholder, dialect);
|
|
272
|
+
const bodies = [];
|
|
273
|
+
const own = [];
|
|
274
|
+
for (const region of macros) {
|
|
275
|
+
const arm = region.arms[0];
|
|
276
|
+
if (!arm)
|
|
277
|
+
continue;
|
|
278
|
+
// An unclosed macro (mid-edit, no endmacro yet) closes at its own opening tag with an empty
|
|
279
|
+
// body; its SQL runs to the end of the text, so that is what gets read.
|
|
280
|
+
const unclosed = arm.bodySpan.end <= arm.bodySpan.start && region.span.end === arm.tagSpan.end;
|
|
281
|
+
const body = unclosed ? { start: arm.tagSpan.end, end: placeholder.length } : arm.bodySpan;
|
|
282
|
+
if (body.end <= body.start)
|
|
283
|
+
continue;
|
|
284
|
+
bodies.push(body);
|
|
285
|
+
const fragment = fragments.parse([body]);
|
|
286
|
+
if (!fragment)
|
|
287
|
+
continue;
|
|
288
|
+
own.push(...fragment.diagnostics);
|
|
289
|
+
if (fragment.clean)
|
|
290
|
+
region.body = fragment.kind;
|
|
291
|
+
}
|
|
292
|
+
// The remainder: everything between the bodies, as the statement batch it always was.
|
|
293
|
+
const remainder = [];
|
|
294
|
+
let at = 0;
|
|
295
|
+
for (const body of [...bodies].sort((a, b) => a.start - b.start)) {
|
|
296
|
+
if (body.start > at)
|
|
297
|
+
remainder.push({ start: at, end: body.start });
|
|
298
|
+
at = Math.max(at, body.end);
|
|
299
|
+
}
|
|
300
|
+
if (at < placeholder.length)
|
|
301
|
+
remainder.push({ start: at, end: placeholder.length });
|
|
302
|
+
const rest = fragments.parse(remainder, ["statement"]);
|
|
303
|
+
return [...diagnostics.filter((d) => d.offset === undefined), ...(rest?.diagnostics ?? []), ...own];
|
|
304
|
+
}
|
|
244
305
|
/** The core build — total by construction (every composed piece is total). */
|
|
245
306
|
function build(text, dialect, provider) {
|
|
246
307
|
const { segments, placeholder, tagTokens } = segment(text, provider);
|
|
@@ -313,7 +374,18 @@ function build(text, dialect, provider) {
|
|
|
313
374
|
// surface a consumer naturally reads — and the raw fill-quoting messages are
|
|
314
375
|
// engine-internal, never public (the gold__vendor F5 leak, 2026-07-06: the raw
|
|
315
376
|
// "mismatched input 'jjjj…'" reached a user's screen through sql.diagnostics).
|
|
316
|
-
|
|
377
|
+
// Step 6 (R4): pair the control tags into regions + extract set/macro symbols.
|
|
378
|
+
// Both are total; they ride inside build()'s caller try/catch for totality.
|
|
379
|
+
const regions = templateRegions(tags, text);
|
|
380
|
+
const symbols = templateSymbols(tags);
|
|
381
|
+
// Macro bodies (issue #48): a `{% macro %}` body is whatever gets pasted at the call site,
|
|
382
|
+
// so the whole-file statement parse is the wrong reading for most of them. Each top-level
|
|
383
|
+
// macro body is re-read from the placeholder as a fragment (statement, expression, FROM-slot
|
|
384
|
+
// source, CTE list, select list; src/fragment.ts) and its own diagnostics replace whatever
|
|
385
|
+
// the statement parse reported inside that body. The IR and tokens stay the whole-file
|
|
386
|
+
// parse's: the fragment verdict rides the region as `body`.
|
|
387
|
+
const sqlDiagnostics = reparseMacroBodies(regions, sqlResult.diagnostics, placeholder, dialect);
|
|
388
|
+
const { diagnostics: scrubbed, bySegment } = scrubPlaceholderDiagnostics(sqlDiagnostics, tagRanges, text, placeholder);
|
|
317
389
|
// Fold the scrubbed SQL diagnostics into the same per-tag map as the jinja ones
|
|
318
390
|
// (Task 10) — a tag's diagnostics are its own jinja parse errors PLUS whatever
|
|
319
391
|
// SQL diagnostics the scrubber widened onto it.
|
|
@@ -329,10 +401,6 @@ function build(text, dialect, provider) {
|
|
|
329
401
|
}
|
|
330
402
|
const finalSql = { ...sqlResult, diagnostics: scrubbed };
|
|
331
403
|
const diagnostics = [...scrubbed, ...jinjaDiagnostics].sort((a, b) => (a.offset ?? 0) - (b.offset ?? 0));
|
|
332
|
-
// Step 6 (R4): pair the control tags into regions + extract set/macro symbols.
|
|
333
|
-
// Both are total; they ride inside build()'s caller try/catch for totality.
|
|
334
|
-
const regions = templateRegions(tags, text);
|
|
335
|
-
const symbols = templateSymbols(tags);
|
|
336
404
|
return {
|
|
337
405
|
tokens,
|
|
338
406
|
sql: finalSql,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { FragmentKind } from "../fragment-grammar.js";
|
|
1
2
|
import type { PartSpan } from "../ir/part-span.js";
|
|
2
3
|
import type { TagNode } from "./tag-ast.js";
|
|
3
4
|
/** One arm of a control region — an `if`/`elif`/`else` branch, or the single body of a `for`/`macro`. */
|
|
@@ -18,6 +19,12 @@ export interface TemplateRegion {
|
|
|
18
19
|
arms: TemplateArm[];
|
|
19
20
|
/** Opening tag start → closing tag end (or the last known tag end when unbalanced). */
|
|
20
21
|
span: PartSpan;
|
|
22
|
+
/**
|
|
23
|
+
* `macro` regions only: what the body parsed clean as — a statement, an expression, a
|
|
24
|
+
* FROM-slot source, a CTE list (pasted after WITH), or a select list. Absent when the body
|
|
25
|
+
* holds no SQL or parsed clean as none of them (the diagnostics then say where it broke).
|
|
26
|
+
*/
|
|
27
|
+
body?: FragmentKind;
|
|
21
28
|
}
|
|
22
29
|
/** A go-to-def template symbol — a `{% set %}` target or a `{% macro %}` name. */
|
|
23
30
|
export interface TemplateSymbol {
|
package/dist/mysql/parse.d.ts
CHANGED
|
@@ -8,3 +8,7 @@ export type { ParseResult } from "../parse-result.js";
|
|
|
8
8
|
* only when SLL fails — same result LL alone would give, just faster on valid input.
|
|
9
9
|
*/
|
|
10
10
|
export declare function parseMysql(sql: string): ParseResult;
|
|
11
|
+
/** The fragment entries (src/fragment.ts): the grammar's own rules for a statement batch
|
|
12
|
+
* (`root`), an expression, a FROM-slot source, a CTE list (no WITH) and a select list;
|
|
13
|
+
* the driver anchors each at EOF. For templated macro bodies. */
|
|
14
|
+
export declare const fragmentGrammar: import("../fragment-grammar.js").FragmentGrammar;
|
package/dist/mysql/parse.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BailErrorStrategy, CharStream, CommonTokenStream, PredictionMode, } from "antlr4ng";
|
|
2
2
|
import { MysqlLexer } from "../generated/mysql/MysqlLexer.js";
|
|
3
3
|
import { MysqlParser } from "../generated/mysql/MysqlParser.js";
|
|
4
|
+
import { defineFragmentGrammar } from "../fragment-grammar.js";
|
|
4
5
|
import { makeErrorCollector } from "../parse-diagnostics.js";
|
|
5
6
|
import { CONSUMED_AS_RULES, deriveConsumedAs } from "../token/consumed-as.js";
|
|
6
7
|
import { mapTokens } from "../token/map.js";
|
|
@@ -69,3 +70,17 @@ function attachErrorCounter(lexer, parser, listener) {
|
|
|
69
70
|
parser.removeErrorListeners();
|
|
70
71
|
parser.addErrorListener(listener);
|
|
71
72
|
}
|
|
73
|
+
/** The fragment entries (src/fragment.ts): the grammar's own rules for a statement batch
|
|
74
|
+
* (`root`), an expression, a FROM-slot source, a CTE list (no WITH) and a select list;
|
|
75
|
+
* the driver anchors each at EOF. For templated macro bodies. */
|
|
76
|
+
export const fragmentGrammar = defineFragmentGrammar({
|
|
77
|
+
newLexer: (input) => new MysqlLexer(input),
|
|
78
|
+
newParser: (tokens) => new MysqlParser(tokens),
|
|
79
|
+
entries: {
|
|
80
|
+
statement: (p) => p.root(),
|
|
81
|
+
expression: (p) => p.expression(),
|
|
82
|
+
tableSource: (p) => p.tableSource(),
|
|
83
|
+
cteList: (p) => p.commonTableExpressions(),
|
|
84
|
+
selectList: (p) => p.selectElements(),
|
|
85
|
+
},
|
|
86
|
+
});
|
package/dist/postgres/parse.d.ts
CHANGED
|
@@ -8,3 +8,7 @@ export type { ParseResult } from "../parse-result.js";
|
|
|
8
8
|
* only when SLL fails — same result LL alone would give, just faster on valid input.
|
|
9
9
|
*/
|
|
10
10
|
export declare function parsePostgres(sql: string): ParseResult;
|
|
11
|
+
/** The fragment entries (src/fragment.ts): the grammar's own rules for a statement batch
|
|
12
|
+
* (`root`), an expression, a FROM-slot source, a CTE list (no WITH) and a select list;
|
|
13
|
+
* the driver anchors each at EOF. For templated macro bodies. */
|
|
14
|
+
export declare const fragmentGrammar: import("../fragment-grammar.js").FragmentGrammar;
|
package/dist/postgres/parse.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BailErrorStrategy, CharStream, CommonTokenStream, PredictionMode, } from "antlr4ng";
|
|
2
2
|
import { PostgresLexer } from "../generated/postgres/PostgresLexer.js";
|
|
3
3
|
import { PostgresParser } from "../generated/postgres/PostgresParser.js";
|
|
4
|
+
import { defineFragmentGrammar } from "../fragment-grammar.js";
|
|
4
5
|
import { makeErrorCollector } from "../parse-diagnostics.js";
|
|
5
6
|
import { CONSUMED_AS_RULES, deriveConsumedAs } from "../token/consumed-as.js";
|
|
6
7
|
import { mapTokens } from "../token/map.js";
|
|
@@ -68,3 +69,17 @@ function attachErrorCounter(lexer, parser, listener) {
|
|
|
68
69
|
parser.removeErrorListeners();
|
|
69
70
|
parser.addErrorListener(listener);
|
|
70
71
|
}
|
|
72
|
+
/** The fragment entries (src/fragment.ts): the grammar's own rules for a statement batch
|
|
73
|
+
* (`root`), an expression, a FROM-slot source, a CTE list (no WITH) and a select list;
|
|
74
|
+
* the driver anchors each at EOF. For templated macro bodies. */
|
|
75
|
+
export const fragmentGrammar = defineFragmentGrammar({
|
|
76
|
+
newLexer: (input) => new PostgresLexer(input),
|
|
77
|
+
newParser: (tokens) => new PostgresParser(tokens),
|
|
78
|
+
entries: {
|
|
79
|
+
statement: (p) => p.root(),
|
|
80
|
+
expression: (p) => p.a_expr(),
|
|
81
|
+
tableSource: (p) => p.table_ref(),
|
|
82
|
+
cteList: (p) => p.cte_list(),
|
|
83
|
+
selectList: (p) => p.target_list(),
|
|
84
|
+
},
|
|
85
|
+
});
|
package/dist/redshift/parse.d.ts
CHANGED
|
@@ -8,3 +8,7 @@ export type { ParseResult } from "../parse-result.js";
|
|
|
8
8
|
* only when SLL fails — same result LL alone would give, just faster on valid input.
|
|
9
9
|
*/
|
|
10
10
|
export declare function parseRedshift(sql: string): ParseResult;
|
|
11
|
+
/** The fragment entries (src/fragment.ts): the grammar's own rules for a statement batch
|
|
12
|
+
* (`root`), an expression, a FROM-slot source, a CTE list (no WITH) and a select list;
|
|
13
|
+
* the driver anchors each at EOF. For templated macro bodies. */
|
|
14
|
+
export declare const fragmentGrammar: import("../fragment-grammar.js").FragmentGrammar;
|
package/dist/redshift/parse.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BailErrorStrategy, CharStream, CommonTokenStream, PredictionMode, } from "antlr4ng";
|
|
2
2
|
import { RedshiftLexer } from "../generated/redshift/RedshiftLexer.js";
|
|
3
3
|
import { RedshiftParser } from "../generated/redshift/RedshiftParser.js";
|
|
4
|
+
import { defineFragmentGrammar } from "../fragment-grammar.js";
|
|
4
5
|
import { makeErrorCollector } from "../parse-diagnostics.js";
|
|
5
6
|
import { CONSUMED_AS_RULES, deriveConsumedAs } from "../token/consumed-as.js";
|
|
6
7
|
import { mapTokens } from "../token/map.js";
|
|
@@ -69,3 +70,17 @@ function attachErrorCounter(lexer, parser, listener) {
|
|
|
69
70
|
parser.removeErrorListeners();
|
|
70
71
|
parser.addErrorListener(listener);
|
|
71
72
|
}
|
|
73
|
+
/** The fragment entries (src/fragment.ts): the grammar's own rules for a statement batch
|
|
74
|
+
* (`root`), an expression, a FROM-slot source, a CTE list (no WITH) and a select list;
|
|
75
|
+
* the driver anchors each at EOF. For templated macro bodies. */
|
|
76
|
+
export const fragmentGrammar = defineFragmentGrammar({
|
|
77
|
+
newLexer: (input) => new RedshiftLexer(input),
|
|
78
|
+
newParser: (tokens) => new RedshiftParser(tokens),
|
|
79
|
+
entries: {
|
|
80
|
+
statement: (p) => p.root(),
|
|
81
|
+
expression: (p) => p.a_expr(),
|
|
82
|
+
tableSource: (p) => p.table_ref(),
|
|
83
|
+
cteList: (p) => p.cte_list(),
|
|
84
|
+
selectList: (p) => p.target_list(),
|
|
85
|
+
},
|
|
86
|
+
});
|
|
@@ -8,3 +8,7 @@ export type { ParseResult } from "../parse-result.js";
|
|
|
8
8
|
* only when SLL fails — same result LL alone would give, just faster on valid input.
|
|
9
9
|
*/
|
|
10
10
|
export declare function parseSnowflake(sql: string): ParseResult;
|
|
11
|
+
/** The fragment entries (src/fragment.ts): the grammar's own rules for a statement batch
|
|
12
|
+
* (`snowflake_file`), an expression, a FROM-slot source, a CTE list (no WITH) and a select list;
|
|
13
|
+
* the driver anchors each at EOF. For templated macro bodies. */
|
|
14
|
+
export declare const fragmentGrammar: import("../fragment-grammar.js").FragmentGrammar;
|
package/dist/snowflake/parse.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BailErrorStrategy, CharStream, CommonTokenStream, PredictionMode, } from "antlr4ng";
|
|
2
2
|
import { SnowflakeLexer } from "../generated/snowflake/SnowflakeLexer.js";
|
|
3
3
|
import { SnowflakeParser } from "../generated/snowflake/SnowflakeParser.js";
|
|
4
|
+
import { defineFragmentGrammar, separatedList } from "../fragment-grammar.js";
|
|
4
5
|
import { makeErrorCollector } from "../parse-diagnostics.js";
|
|
5
6
|
import { CONSUMED_AS_RULES, deriveConsumedAs } from "../token/consumed-as.js";
|
|
6
7
|
import { mapTokens } from "../token/map.js";
|
|
@@ -69,3 +70,17 @@ function attachErrorCounter(lexer, parser, listener) {
|
|
|
69
70
|
parser.removeErrorListeners();
|
|
70
71
|
parser.addErrorListener(listener);
|
|
71
72
|
}
|
|
73
|
+
/** The fragment entries (src/fragment.ts): the grammar's own rules for a statement batch
|
|
74
|
+
* (`snowflake_file`), an expression, a FROM-slot source, a CTE list (no WITH) and a select list;
|
|
75
|
+
* the driver anchors each at EOF. For templated macro bodies. */
|
|
76
|
+
export const fragmentGrammar = defineFragmentGrammar({
|
|
77
|
+
newLexer: (input) => new SnowflakeLexer(input),
|
|
78
|
+
newParser: (tokens) => new SnowflakeParser(tokens),
|
|
79
|
+
entries: {
|
|
80
|
+
statement: (p) => p.snowflake_file(),
|
|
81
|
+
expression: (p) => p.expr(),
|
|
82
|
+
tableSource: (p) => p.table_source(),
|
|
83
|
+
cteList: separatedList((p) => p.common_table_expression(), SnowflakeLexer.COMMA),
|
|
84
|
+
selectList: (p) => p.select_list(),
|
|
85
|
+
},
|
|
86
|
+
});
|
package/dist/sqlite/parse.d.ts
CHANGED
|
@@ -8,3 +8,7 @@ export type { ParseResult } from "../parse-result.js";
|
|
|
8
8
|
* only when SLL fails — same result LL alone would give, just faster on valid input.
|
|
9
9
|
*/
|
|
10
10
|
export declare function parseSqlite(sql: string): ParseResult;
|
|
11
|
+
/** The fragment entries (src/fragment.ts): the grammar's own rules for a statement batch
|
|
12
|
+
* (`parse`), an expression, a FROM-slot source, a CTE list (no WITH) and a select list;
|
|
13
|
+
* the driver anchors each at EOF. For templated macro bodies. */
|
|
14
|
+
export declare const fragmentGrammar: import("../fragment-grammar.js").FragmentGrammar;
|
package/dist/sqlite/parse.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BailErrorStrategy, CharStream, CommonTokenStream, PredictionMode, } from "antlr4ng";
|
|
2
2
|
import { SqliteLexer } from "../generated/sqlite/SqliteLexer.js";
|
|
3
3
|
import { SqliteParser } from "../generated/sqlite/SqliteParser.js";
|
|
4
|
+
import { defineFragmentGrammar, separatedList } from "../fragment-grammar.js";
|
|
4
5
|
import { makeErrorCollector } from "../parse-diagnostics.js";
|
|
5
6
|
import { CONSUMED_AS_RULES, deriveConsumedAs } from "../token/consumed-as.js";
|
|
6
7
|
import { mapTokens } from "../token/map.js";
|
|
@@ -69,3 +70,17 @@ function attachErrorCounter(lexer, parser, listener) {
|
|
|
69
70
|
parser.removeErrorListeners();
|
|
70
71
|
parser.addErrorListener(listener);
|
|
71
72
|
}
|
|
73
|
+
/** The fragment entries (src/fragment.ts): the grammar's own rules for a statement batch
|
|
74
|
+
* (`parse`), an expression, a FROM-slot source, a CTE list (no WITH) and a select list;
|
|
75
|
+
* the driver anchors each at EOF. For templated macro bodies. */
|
|
76
|
+
export const fragmentGrammar = defineFragmentGrammar({
|
|
77
|
+
newLexer: (input) => new SqliteLexer(input),
|
|
78
|
+
newParser: (tokens) => new SqliteParser(tokens),
|
|
79
|
+
entries: {
|
|
80
|
+
statement: (p) => p.parse(),
|
|
81
|
+
expression: (p) => p.expr(),
|
|
82
|
+
tableSource: (p) => p.join_clause(),
|
|
83
|
+
cteList: separatedList((p) => p.common_table_expression(), SqliteLexer.COMMA),
|
|
84
|
+
selectList: separatedList((p) => p.result_column(), SqliteLexer.COMMA),
|
|
85
|
+
},
|
|
86
|
+
});
|
package/dist/symbols/symbols.js
CHANGED
|
@@ -383,6 +383,11 @@ function emitColumns(scope, frame, out, schema, sourceSyms, expandStarOf) {
|
|
|
383
383
|
// recovered from an unmodelled `other` node) honestly gets no node.
|
|
384
384
|
const columnNodes = columnExprsByCst(body);
|
|
385
385
|
for (const ref of body.columns) {
|
|
386
|
+
// A template hole in a scalar slot (`select {{ my_macro() }}`) is not a column reference:
|
|
387
|
+
// its `parts` is the placeholder fill, and a symbol named after it (bound to whatever source
|
|
388
|
+
// the single-source rule picks) is a wrong claim. Consumers reach the tag through `tags`.
|
|
389
|
+
if (ref.template)
|
|
390
|
+
continue;
|
|
386
391
|
const res = resolveColumnRef(scope, ref, schema);
|
|
387
392
|
const modifiers = ["reference"];
|
|
388
393
|
// A reference that binds to a source outside this scope is correlated.
|
package/dist/trino/parse.d.ts
CHANGED
|
@@ -8,3 +8,7 @@ export type { ParseResult } from "../parse-result.js";
|
|
|
8
8
|
* only when SLL fails — same result LL alone would give, just faster on valid input.
|
|
9
9
|
*/
|
|
10
10
|
export declare function parseTrino(sql: string): ParseResult;
|
|
11
|
+
/** The fragment entries (src/fragment.ts): the grammar's own rules for a statement batch
|
|
12
|
+
* (`root`), an expression, a FROM-slot source, a CTE list (no WITH) and a select list;
|
|
13
|
+
* the driver anchors each at EOF. For templated macro bodies. */
|
|
14
|
+
export declare const fragmentGrammar: import("../fragment-grammar.js").FragmentGrammar;
|
package/dist/trino/parse.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BailErrorStrategy, CharStream, CommonTokenStream, PredictionMode, } from "antlr4ng";
|
|
2
2
|
import { TrinoLexer } from "../generated/trino/TrinoLexer.js";
|
|
3
3
|
import { TrinoParser } from "../generated/trino/TrinoParser.js";
|
|
4
|
+
import { defineFragmentGrammar, separatedList } from "../fragment-grammar.js";
|
|
4
5
|
import { makeErrorCollector } from "../parse-diagnostics.js";
|
|
5
6
|
import { CONSUMED_AS_RULES, deriveConsumedAs } from "../token/consumed-as.js";
|
|
6
7
|
import { mapTokens } from "../token/map.js";
|
|
@@ -66,3 +67,17 @@ function attachErrorCounter(lexer, parser, listener) {
|
|
|
66
67
|
parser.removeErrorListeners();
|
|
67
68
|
parser.addErrorListener(listener);
|
|
68
69
|
}
|
|
70
|
+
/** The fragment entries (src/fragment.ts): the grammar's own rules for a statement batch
|
|
71
|
+
* (`root`), an expression, a FROM-slot source, a CTE list (no WITH) and a select list;
|
|
72
|
+
* the driver anchors each at EOF. For templated macro bodies. */
|
|
73
|
+
export const fragmentGrammar = defineFragmentGrammar({
|
|
74
|
+
newLexer: (input) => new TrinoLexer(input),
|
|
75
|
+
newParser: (tokens) => new TrinoParser(tokens),
|
|
76
|
+
entries: {
|
|
77
|
+
statement: (p) => p.root(),
|
|
78
|
+
expression: (p) => p.expression(),
|
|
79
|
+
tableSource: (p) => p.relation(),
|
|
80
|
+
cteList: separatedList((p) => p.namedQuery(), TrinoLexer.COMMA),
|
|
81
|
+
selectList: separatedList((p) => p.selectItem(), TrinoLexer.COMMA),
|
|
82
|
+
},
|
|
83
|
+
});
|
package/dist/tsql/parse.d.ts
CHANGED
|
@@ -10,3 +10,7 @@ export type { ParseResult } from "../parse-result.js";
|
|
|
10
10
|
* on the first conflict), fall back to full LL only when SLL fails — same result LL alone would give.
|
|
11
11
|
*/
|
|
12
12
|
export declare function parseTSql(sql: string): ParseResult;
|
|
13
|
+
/** The fragment entries (src/fragment.ts): the grammar's own rules for a statement batch
|
|
14
|
+
* (`tsql_file`), an expression, a FROM-slot source, a CTE list (no WITH) and a select list;
|
|
15
|
+
* the driver anchors each at EOF. For templated macro bodies. */
|
|
16
|
+
export declare const fragmentGrammar: import("../fragment-grammar.js").FragmentGrammar;
|
package/dist/tsql/parse.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BailErrorStrategy, CharStream, CommonTokenStream, PredictionMode, } from "antlr4ng";
|
|
2
2
|
import { TSqlLexer } from "../generated/tsql/TSqlLexer.js";
|
|
3
3
|
import { TSqlParser } from "../generated/tsql/TSqlParser.js";
|
|
4
|
+
import { defineFragmentGrammar, separatedList } from "../fragment-grammar.js";
|
|
4
5
|
import { makeErrorCollector } from "../parse-diagnostics.js";
|
|
5
6
|
import { CONSUMED_AS_RULES, deriveConsumedAs } from "../token/consumed-as.js";
|
|
6
7
|
import { mapTokens } from "../token/map.js";
|
|
@@ -73,3 +74,17 @@ function attachErrorCounter(lexer, parser, listener) {
|
|
|
73
74
|
parser.removeErrorListeners();
|
|
74
75
|
parser.addErrorListener(listener);
|
|
75
76
|
}
|
|
77
|
+
/** The fragment entries (src/fragment.ts): the grammar's own rules for a statement batch
|
|
78
|
+
* (`tsql_file`), an expression, a FROM-slot source, a CTE list (no WITH) and a select list;
|
|
79
|
+
* the driver anchors each at EOF. For templated macro bodies. */
|
|
80
|
+
export const fragmentGrammar = defineFragmentGrammar({
|
|
81
|
+
newLexer: (input) => new TSqlLexer(input),
|
|
82
|
+
newParser: (tokens) => new TSqlParser(tokens),
|
|
83
|
+
entries: {
|
|
84
|
+
statement: (p) => p.tsql_file(),
|
|
85
|
+
expression: (p) => p.expression(),
|
|
86
|
+
tableSource: (p) => p.table_source(),
|
|
87
|
+
cteList: separatedList((p) => p.common_table_expression(), TSqlLexer.COMMA),
|
|
88
|
+
selectList: (p) => p.select_list(),
|
|
89
|
+
},
|
|
90
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sqllens",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"description": "A TypeScript SQL parser and static analyzer: parse, resolve names, infer types, and trace column lineage across many SQL dialects (Databricks, T-SQL, Snowflake, BigQuery, Redshift, PostgreSQL, DuckDB, Trino, SQLite, MySQL).",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|