sqllens 1.3.0 → 1.5.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 +19 -0
- package/dist/bigquery/behavior.js +2 -1
- package/dist/bigquery/fold.d.ts +6 -0
- package/dist/bigquery/fold.js +8 -0
- package/dist/bigquery/lower.js +37 -6
- package/dist/completion/complete.d.ts +1 -1
- package/dist/completion/complete.js +166 -20
- package/dist/completion/jinja-slot.d.ts +6 -0
- package/dist/completion/jinja-slot.js +12 -2
- package/dist/databricks/behavior.js +2 -1
- package/dist/databricks/fold.d.ts +4 -0
- package/dist/databricks/fold.js +6 -0
- package/dist/databricks/lower.js +10 -3
- package/dist/dialect-behavior/behavior.d.ts +5 -0
- package/dist/duckdb/behavior.js +2 -1
- package/dist/duckdb/fold.d.ts +4 -0
- package/dist/duckdb/fold.js +6 -0
- package/dist/duckdb/lower.js +39 -8
- package/dist/index.d.ts +1 -1
- package/dist/ir/ir.d.ts +16 -6
- package/dist/ir/qualified-name.d.ts +43 -0
- package/dist/ir/qualified-name.js +74 -0
- package/dist/lineage/hops.js +7 -3
- package/dist/lineage/lineage.js +2 -1
- package/dist/minijinja/apply-tags.js +43 -11
- package/dist/mysql/behavior.js +2 -1
- package/dist/mysql/fold.d.ts +4 -0
- package/dist/mysql/fold.js +6 -0
- package/dist/mysql/lower.js +19 -4
- package/dist/postgres/behavior.js +2 -1
- package/dist/postgres/fold.d.ts +5 -0
- package/dist/postgres/fold.js +7 -0
- package/dist/postgres/lower.js +21 -5
- package/dist/qualify/qualify.d.ts +1 -1
- package/dist/qualify/qualify.js +75 -50
- package/dist/qualify/schema-provider.d.ts +13 -0
- package/dist/qualify/schema.d.ts +18 -1
- package/dist/qualify/schema.js +60 -6
- package/dist/qualify/template-provider.d.ts +5 -2
- package/dist/qualify/template-provider.js +5 -2
- package/dist/redshift/behavior.js +2 -1
- package/dist/redshift/fold.d.ts +4 -0
- package/dist/redshift/fold.js +6 -0
- package/dist/redshift/lower.js +20 -4
- package/dist/scope/scope.d.ts +24 -2
- package/dist/scope/scope.js +65 -13
- package/dist/sema/resolve.js +21 -10
- package/dist/snowflake/behavior.js +2 -1
- package/dist/snowflake/fold.d.ts +4 -0
- package/dist/snowflake/fold.js +6 -0
- package/dist/snowflake/lower.js +23 -4
- package/dist/sqlite/behavior.js +2 -1
- package/dist/sqlite/fold.d.ts +4 -0
- package/dist/sqlite/fold.js +6 -0
- package/dist/sqlite/lower.js +11 -2
- package/dist/trino/behavior.js +2 -1
- package/dist/trino/fold.d.ts +3 -0
- package/dist/trino/fold.js +5 -0
- package/dist/trino/lower.js +22 -4
- package/dist/tsql/behavior.js +2 -1
- package/dist/tsql/fold.d.ts +5 -0
- package/dist/tsql/fold.js +7 -0
- package/dist/tsql/lower.js +18 -5
- package/package.json +1 -1
package/dist/duckdb/lower.js
CHANGED
|
@@ -3,7 +3,13 @@ import { DuckdbParser as P } from "../generated/duckdb/DuckdbParser.js";
|
|
|
3
3
|
import { keywordCategory, swallowedCategories, swallowedStatements } from "../ir/statement.js";
|
|
4
4
|
import { partSpansOf } from "../ir/part-span.js";
|
|
5
5
|
import { freezeIR } from "../ir/freeze.js";
|
|
6
|
-
import {
|
|
6
|
+
import { qualifiedNameOf } from "../ir/qualified-name.js";
|
|
7
|
+
import { displayName, DUCKDB_NAME_CONFIG } from "./fold.js";
|
|
8
|
+
/** The structured name for a table source's raw parts (issue #38) — role assignment + identity
|
|
9
|
+
* key + fqn happen HERE, at lowering, where the dialect's namespace shape is known. */
|
|
10
|
+
function relationOf(rawParts) {
|
|
11
|
+
return qualifiedNameOf(rawParts, DUCKDB_NAME_CONFIG);
|
|
12
|
+
}
|
|
7
13
|
// ---------------------------------------------------------------------------
|
|
8
14
|
// Lowering — DuckDB (fork of this repo's grammars/postgres pair, TVL lineage)
|
|
9
15
|
// CST -> the shared dialect-neutral IR (src/ir/ir.ts). The semantic layer runs
|
|
@@ -270,8 +276,15 @@ function lowerPivotStmt(stmt) {
|
|
|
270
276
|
const isPivot = stmt.ruleIndex === P.RULE_pivotstmt;
|
|
271
277
|
const from = [];
|
|
272
278
|
const qn = directChildrenOfRule(stmt, P.RULE_qualified_name)[0];
|
|
273
|
-
if (qn)
|
|
274
|
-
|
|
279
|
+
if (qn) {
|
|
280
|
+
const pivotParts = nameParts(qn);
|
|
281
|
+
from.push({
|
|
282
|
+
kind: "table",
|
|
283
|
+
relation: relationOf(pivotParts),
|
|
284
|
+
namePartSpans: columnPartSpans(qn),
|
|
285
|
+
cst: qn,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
275
288
|
const sw = directChildrenOfRule(stmt, P.RULE_select_with_parens)[0];
|
|
276
289
|
if (sw) {
|
|
277
290
|
const inner = innerSelect(sw);
|
|
@@ -649,9 +662,10 @@ function buildPrimarySource(tr, unsupported) {
|
|
|
649
662
|
// FROM 'file.parquet' — a string-literal relation (replacement scan, data/overview.md).
|
|
650
663
|
const fileRel = directChildrenOfRule(tr, P.RULE_sconst)[0];
|
|
651
664
|
if (fileRel) {
|
|
665
|
+
const fileRelName = [stripStringQuotes(fileRel.getText())];
|
|
652
666
|
return {
|
|
653
667
|
kind: "table",
|
|
654
|
-
|
|
668
|
+
relation: relationOf(fileRelName),
|
|
655
669
|
namePartSpans: partSpansOf([fileRel]),
|
|
656
670
|
alias,
|
|
657
671
|
aliasCst,
|
|
@@ -680,9 +694,10 @@ function buildPrimarySource(tr, unsupported) {
|
|
|
680
694
|
if (funcTable) {
|
|
681
695
|
const fname = firstShallow(funcTable, P.RULE_func_name);
|
|
682
696
|
const funcAlias = directChildrenOfRule(tr, P.RULE_func_alias_clause)[0];
|
|
697
|
+
const funcTableName = [fname ? lastName(fname) : funcTable.getText()];
|
|
683
698
|
return {
|
|
684
699
|
kind: "table",
|
|
685
|
-
|
|
700
|
+
relation: relationOf(funcTableName),
|
|
686
701
|
alias: alias ?? (funcAlias ? funcAliasName(funcAlias) : undefined),
|
|
687
702
|
aliasCst,
|
|
688
703
|
cst: tr,
|
|
@@ -690,7 +705,14 @@ function buildPrimarySource(tr, unsupported) {
|
|
|
690
705
|
}
|
|
691
706
|
const jsonTable = directChildrenOfRule(tr, P.RULE_json_table)[0];
|
|
692
707
|
if (jsonTable) {
|
|
693
|
-
return {
|
|
708
|
+
return {
|
|
709
|
+
kind: "table",
|
|
710
|
+
relation: relationOf(["json_table"]),
|
|
711
|
+
alias,
|
|
712
|
+
aliasCst,
|
|
713
|
+
columnAliases,
|
|
714
|
+
cst: tr,
|
|
715
|
+
};
|
|
694
716
|
}
|
|
695
717
|
const nestedTr = directChildrenOfRule(tr, P.RULE_table_ref)[0];
|
|
696
718
|
if (nestedTr) {
|
|
@@ -703,14 +725,23 @@ function buildPrimarySource(tr, unsupported) {
|
|
|
703
725
|
if (innerFrom.length)
|
|
704
726
|
return innerFrom[0];
|
|
705
727
|
}
|
|
706
|
-
|
|
728
|
+
const tableName = [textOrEmpty(tr)];
|
|
729
|
+
return { kind: "table", relation: relationOf(tableName), alias, aliasCst, columnAliases, cst: tr };
|
|
707
730
|
}
|
|
708
731
|
const nestedJoinConditions = [];
|
|
709
732
|
function buildTableFromRelation(rel, alias, aliasCst, columnAliases) {
|
|
710
733
|
const qn = directChildrenOfRule(rel, P.RULE_qualified_name)[0];
|
|
711
734
|
const parts = qn ? nameParts(qn) : [textOrEmpty(rel)];
|
|
712
735
|
const namePartSpans = qn ? columnPartSpans(qn) : undefined;
|
|
713
|
-
return {
|
|
736
|
+
return {
|
|
737
|
+
kind: "table",
|
|
738
|
+
relation: relationOf(parts),
|
|
739
|
+
namePartSpans,
|
|
740
|
+
alias,
|
|
741
|
+
aliasCst,
|
|
742
|
+
columnAliases,
|
|
743
|
+
cst: rel,
|
|
744
|
+
};
|
|
714
745
|
}
|
|
715
746
|
/** The alias identifier node — under table_alias (AS slot) or bare_table_alias (AS-less slot). */
|
|
716
747
|
function aliasIdentNode(aliasClause) {
|
package/dist/index.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ export { lower as lowerSqlite } from "./sqlite/lower.js";
|
|
|
22
22
|
export { lower as lowerMysql } from "./mysql/lower.js";
|
|
23
23
|
export type { ParseResult } from "./parse-result.js";
|
|
24
24
|
export { type TemplatedParseResult, type TemplatedParseOptions } from "./template/engine.js";
|
|
25
|
-
export type { Clause, ColumnRef, CteDef, Expr, GraphElement, GraphTableSource, Join, JoinKind, LateralViewSource, LimitInfo, PartSpan, PipeBranch, PipeExpr, PipeSetItem, PipeStage, PivotInfo, Projection, QueryBody, QueryExpr, SelectExpr, SetOpExpr, Source, SubquerySource, TableSource, TemplateExprInfo, TemplateSourceInfo, UnpivotInfo, UnsupportedFlag, WindowSpec, } from "./ir/ir.js";
|
|
25
|
+
export type { Clause, ColumnRef, CteDef, Expr, GraphElement, GraphTableSource, Join, JoinKind, LateralViewSource, LimitInfo, PartSpan, PipeBranch, PipeExpr, PipeSetItem, PipeStage, PivotInfo, Projection, QualifiedName, QueryBody, QueryExpr, SelectExpr, SetOpExpr, Source, SubquerySource, TableSource, TemplateExprInfo, TemplateSourceInfo, UnpivotInfo, UnsupportedFlag, WindowSpec, } from "./ir/ir.js";
|
|
26
26
|
export { partSpanOf, partSpansOf } from "./ir/part-span.js";
|
|
27
27
|
export { endPosition } from "./ir/span.js";
|
|
28
28
|
export { childExprs, walkExprs, allQueryExprs } from "./ir/walk.js";
|
package/dist/ir/ir.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { ParserRuleContext } from "antlr4ng";
|
|
2
2
|
import type { PartSpan } from "./part-span.js";
|
|
3
|
+
import type { QualifiedName } from "./qualified-name.js";
|
|
3
4
|
import type { StatementCategory } from "./statement.js";
|
|
4
5
|
export type { PartSpan } from "./part-span.js";
|
|
6
|
+
export type { QualifiedName, QualifiedNameConfig, NameRole } from "./qualified-name.js";
|
|
5
7
|
/**
|
|
6
8
|
* The identity of one template call, the `expansion()` key. Args are LITERAL
|
|
7
9
|
* string values: quote-stripped, escapes NOT resolved — an argument whose
|
|
@@ -647,12 +649,16 @@ export interface TemplateSourceInfo {
|
|
|
647
649
|
}
|
|
648
650
|
export interface TableSource {
|
|
649
651
|
kind: "table";
|
|
650
|
-
/**
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
*
|
|
654
|
-
|
|
655
|
-
|
|
652
|
+
/** The STRUCTURED name (issue #38): the object's own `name`, `parts` exactly as written, roles
|
|
653
|
+
* right-aligned per the dialect's namespace, the folded identity `key`, and a display-ready
|
|
654
|
+
* `fqn`. Built by the dialect's lower() (the one layer that knows the namespace shape);
|
|
655
|
+
* consumers read structure here and never re-derive it from a parts array. */
|
|
656
|
+
relation: QualifiedName;
|
|
657
|
+
/** Per-part spans PARALLEL to `relation.parts`, one per multipart segment — same all-or-nothing
|
|
658
|
+
* convention as `ColumnRef.partSpans` (absent when any part lacks a real token). Lets a
|
|
659
|
+
* consumer hit-test a cursor on `catalog` vs `schema` vs `t` in `catalog.schema.t`, and recover
|
|
660
|
+
* each part's raw (delimiter-included) source text via span + document text without re-scanning
|
|
661
|
+
* the token stream. */
|
|
656
662
|
namePartSpans?: PartSpan[];
|
|
657
663
|
alias?: string;
|
|
658
664
|
/** The alias identifier's own CST node (for its precise span), when aliased. */
|
|
@@ -670,6 +676,10 @@ export interface TableSource {
|
|
|
670
676
|
/** Present when this source was written as a minijinja template tag in a FROM/JOIN slot; attached
|
|
671
677
|
* post-lower by the jinja front end (src/minijinja/apply-tags.ts). See TemplateSourceInfo. */
|
|
672
678
|
template?: TemplateSourceInfo;
|
|
679
|
+
/** True when the LIBRARY built this node rather than lowering it from user syntax (a graph
|
|
680
|
+
* element variable exposed as a relation). Catalog existence semantics don't apply: a closed
|
|
681
|
+
* world's unknown-table never fires for a synthesized source. */
|
|
682
|
+
synthesized?: true;
|
|
673
683
|
cst: ParserRuleContext;
|
|
674
684
|
}
|
|
675
685
|
export interface SubquerySource {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type FoldRule } from "../ident/fold.js";
|
|
2
|
+
/** Qualifier roles, normalized across dialects (vendor vocabulary noted per dialect at its
|
|
3
|
+
* config): "catalog" covers database/project, "schema" covers dataset/attached-database,
|
|
4
|
+
* "server" is tsql's linked-server level. The object's own name is never a role — it is the
|
|
5
|
+
* final part, always. */
|
|
6
|
+
export type NameRole = "server" | "catalog" | "schema";
|
|
7
|
+
/** A dialect's namespace shape: its qualifier roles OUTER→INNER, and the fold/delimiter rule
|
|
8
|
+
* its identifiers obey. Declared once per dialect in src/<dialect>/fold.ts, doc-cited there. */
|
|
9
|
+
export interface QualifiedNameConfig {
|
|
10
|
+
roles: readonly NameRole[];
|
|
11
|
+
rule: FoldRule;
|
|
12
|
+
}
|
|
13
|
+
/** A structured, dialect-resolved name. Plain frozen data like every IR node. */
|
|
14
|
+
export interface QualifiedName {
|
|
15
|
+
/** The object's OWN name (the last part), delimiters stripped, as written. */
|
|
16
|
+
name: string;
|
|
17
|
+
/** Every part as written, outermost first, delimiters stripped. An elided middle part is "". */
|
|
18
|
+
parts: string[];
|
|
19
|
+
/** tsql linked-server level. Absent = not written. */
|
|
20
|
+
server?: string;
|
|
21
|
+
/** Catalog level (vendor: catalog / database / project). Absent = not written. */
|
|
22
|
+
catalog?: string;
|
|
23
|
+
/** Schema level (vendor: schema / dataset / attached database). Absent = not written;
|
|
24
|
+
* "" = written-and-elided (`db..table`). */
|
|
25
|
+
schema?: string;
|
|
26
|
+
/** Folded identity parts (the dialect's own quoting-aware fold). COMPARE with these — never
|
|
27
|
+
* display them, never re-fold. */
|
|
28
|
+
key: string[];
|
|
29
|
+
/** The qualified name as ONE display-ready string. Source-derived: the raw parts joined as
|
|
30
|
+
* written (quoting kept). Synthesized: parts joined with dialect quoting where needed. */
|
|
31
|
+
fqn: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Build a QualifiedName from SOURCE-derived raw parts (as they appear in the text, delimiters
|
|
35
|
+
* intact where the user wrote them). `fqn` preserves the user's spelling and quoting exactly.
|
|
36
|
+
*/
|
|
37
|
+
export declare function qualifiedNameOf(rawParts: string[], config: QualifiedNameConfig): QualifiedName;
|
|
38
|
+
/**
|
|
39
|
+
* Build a QualifiedName from SYNTHESIZED plain parts (a provider's resolved relation — no source
|
|
40
|
+
* text exists). `fqn` renders each part with the dialect's quoting exactly where the part needs
|
|
41
|
+
* delimiters to round-trip; plain identifier parts stay bare.
|
|
42
|
+
*/
|
|
43
|
+
export declare function synthesizedQualifiedName(parts: string[], config: QualifiedNameConfig): QualifiedName;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// QualifiedName — the structured relation/column name the IR carries (issue #38).
|
|
3
|
+
//
|
|
4
|
+
// Root cause this type exists to fix: LOSS OF INFORMATION AT LOWERING. A dotted name's
|
|
5
|
+
// structure (which part is the catalog, which the schema; what was quoted; what identity each
|
|
6
|
+
// part folds to) was flattened to string[] at lower time, and every downstream layer re-derived
|
|
7
|
+
// it by heuristic — collapsing, in practice, to last-part matching. The two standing principles
|
|
8
|
+
// applied to names:
|
|
9
|
+
//
|
|
10
|
+
// lossless — `parts` is exactly what was written (partial qualification represented, never
|
|
11
|
+
// defaulted; a tsql elided middle part `db..table` is an EXPLICITLY EMPTY part);
|
|
12
|
+
// `fqn` joins the raw parts as written, quoting included. Nothing re-cased,
|
|
13
|
+
// nothing invented.
|
|
14
|
+
// never-wrong — roles are assigned only to parts that exist, right-aligned against the
|
|
15
|
+
// dialect's namespace; a part beyond the dialect's depth gets NO role. `key` is
|
|
16
|
+
// the folded identity under the dialect's own quoting semantics; consumers
|
|
17
|
+
// compare keys and never fold or parse dotted strings themselves.
|
|
18
|
+
//
|
|
19
|
+
// The dialect's lower() builds these (it is the one layer that knows both the CST shape and the
|
|
20
|
+
// dialect's namespace); each dialect's config lives next to its fold rule in
|
|
21
|
+
// src/<dialect>/fold.ts. This module is dialect-agnostic mechanics only.
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
import { displayWith, foldWith } from "../ident/fold.js";
|
|
24
|
+
/** Right-align the dialect's roles onto the qualifier parts (everything before the last part).
|
|
25
|
+
* Only parts that exist get a role; qualifier parts beyond the dialect's depth get none. */
|
|
26
|
+
function assignRoles(q, stripped, roles) {
|
|
27
|
+
const qualifiers = stripped.slice(0, -1);
|
|
28
|
+
const n = Math.min(qualifiers.length, roles.length);
|
|
29
|
+
for (let j = 0; j < n; j++) {
|
|
30
|
+
const role = roles[roles.length - 1 - j];
|
|
31
|
+
q[role] = qualifiers[qualifiers.length - 1 - j];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Build a QualifiedName from SOURCE-derived raw parts (as they appear in the text, delimiters
|
|
36
|
+
* intact where the user wrote them). `fqn` preserves the user's spelling and quoting exactly.
|
|
37
|
+
*/
|
|
38
|
+
export function qualifiedNameOf(rawParts, config) {
|
|
39
|
+
const parts = rawParts.map((p) => displayWith(config.rule, p));
|
|
40
|
+
const q = {
|
|
41
|
+
name: parts[parts.length - 1] ?? "",
|
|
42
|
+
parts,
|
|
43
|
+
key: rawParts.map((p) => foldWith(config.rule, p, "table")),
|
|
44
|
+
fqn: rawParts.join("."),
|
|
45
|
+
};
|
|
46
|
+
assignRoles(q, parts, config.roles);
|
|
47
|
+
return q;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Build a QualifiedName from SYNTHESIZED plain parts (a provider's resolved relation — no source
|
|
51
|
+
* text exists). `fqn` renders each part with the dialect's quoting exactly where the part needs
|
|
52
|
+
* delimiters to round-trip; plain identifier parts stay bare.
|
|
53
|
+
*/
|
|
54
|
+
export function synthesizedQualifiedName(parts, config) {
|
|
55
|
+
const q = {
|
|
56
|
+
name: parts[parts.length - 1] ?? "",
|
|
57
|
+
parts: [...parts],
|
|
58
|
+
key: parts.map((p) => foldWith(config.rule, p, "table")),
|
|
59
|
+
fqn: parts.map((p) => renderPart(p, config.rule)).join("."),
|
|
60
|
+
};
|
|
61
|
+
assignRoles(q, parts, config.roles);
|
|
62
|
+
return q;
|
|
63
|
+
}
|
|
64
|
+
/** True when `part` round-trips as a bare identifier (letters/digits/underscore, non-digit lead).
|
|
65
|
+
* Anything else needs the dialect's delimiters. Reserved words are a documented boundary: this
|
|
66
|
+
* renderer does not consult keyword lists. */
|
|
67
|
+
const PLAIN_IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
68
|
+
function renderPart(part, rule) {
|
|
69
|
+
if (PLAIN_IDENT.test(part))
|
|
70
|
+
return part;
|
|
71
|
+
const [open, close] = rule.delimiters[0] ?? ['"', '"'];
|
|
72
|
+
const body = rule.escapeStyle === "backslash" ? part.split(close).join(`\\${close}`) : part.split(close).join(close + close);
|
|
73
|
+
return `${open}${body}${close}`;
|
|
74
|
+
}
|
package/dist/lineage/hops.js
CHANGED
|
@@ -129,10 +129,14 @@ function followColumn(scope, parts, walk, trail = []) {
|
|
|
129
129
|
return [{ kind: "unresolved", via }];
|
|
130
130
|
const { source, column } = binding;
|
|
131
131
|
if (source.kind === "table") {
|
|
132
|
-
// A recursive CTE's self-reference is a plain table here (see Walk.activeCtes) — cycle-guard
|
|
133
|
-
|
|
132
|
+
// A recursive CTE's self-reference is a plain table here (see Walk.activeCtes) — cycle-guard
|
|
133
|
+
// it. activeCtes keys are the DEFAULT ("other") fold of the declared raw name, so fold the
|
|
134
|
+
// self-reference's raw text the same way (single-part ⇒ relation.fqn IS the raw text).
|
|
135
|
+
if (source.name.length === 1 && walk.activeCtes.has(behaviorOf(scope).fold(source.source.relation.fqn))) {
|
|
134
136
|
return [{ kind: "unresolved", via }];
|
|
135
|
-
|
|
137
|
+
}
|
|
138
|
+
// Origins are DISPLAY-facing: the as-written parts, never the folded key.
|
|
139
|
+
return [{ kind: "origin", origin: { table: source.source.relation.parts, column }, via }];
|
|
136
140
|
}
|
|
137
141
|
const child = childScopeOf(source);
|
|
138
142
|
if (!child) {
|
package/dist/lineage/lineage.js
CHANGED
|
@@ -172,8 +172,9 @@ function columnRefOrigins(parts, scope, schema, seen) {
|
|
|
172
172
|
* lineage walk (src/lineage/hops.ts) can defer pipe/lateral/graphtable/pivot sources — the ones
|
|
173
173
|
* it has no hop model for — to this exact shared origin walk, so the two walks can't drift. */
|
|
174
174
|
export function columnOrigins(src, column, schema, seen) {
|
|
175
|
+
// Origins are DISPLAY-facing: the as-written parts (relation.parts), never the folded key.
|
|
175
176
|
if (src.kind === "table")
|
|
176
|
-
return [{ table: src.
|
|
177
|
+
return [{ table: src.source.relation.parts, column }];
|
|
177
178
|
if (src.kind === "cte")
|
|
178
179
|
return derivedOrigins(src.ref.scope, column, src.ref.def.columnAliases, schema, seen);
|
|
179
180
|
if (src.kind === "subquery")
|
|
@@ -17,8 +17,9 @@
|
|
|
17
17
|
// TemplateProvider for a call's relation (`provider.relationOf`) and carries no ref/source vocabulary
|
|
18
18
|
// itself. The NEUTRAL provider answers nothing, so a bare parse leaves calls opaque; a
|
|
19
19
|
// DbtTemplateProvider names ref/source. Naming is never-wrong: a resolved name comes from the call's
|
|
20
|
-
// literal args, and a call whose relation the provider does not resolve
|
|
21
|
-
// stays opaque. We NEVER fabricate a name
|
|
20
|
+
// literal args, and a call whose relation the provider does not resolve takes the RAW TAG TEXT as its
|
|
21
|
+
// name ({{ ref('m') }} verbatim) and stays opaque. We NEVER fabricate a name — in particular the
|
|
22
|
+
// placeholder fill (scaffolding this library invented) never escapes as one (issue #35).
|
|
22
23
|
//
|
|
23
24
|
// The IR is frozen after lower(); this transform REBUILDS with STRUCTURAL SHARING (new objects only on
|
|
24
25
|
// changed paths, an unchanged subtree keeps its original already-frozen reference) and re-freezes the
|
|
@@ -26,6 +27,8 @@
|
|
|
26
27
|
// returns the input `ast` unchanged.
|
|
27
28
|
// ---------------------------------------------------------------------------
|
|
28
29
|
import { freezeIR } from "../ir/freeze.js";
|
|
30
|
+
import { qualifiedNameOf, synthesizedQualifiedName } from "../ir/qualified-name.js";
|
|
31
|
+
import { resolveBehavior } from "../dialect-behavior/registry.js";
|
|
29
32
|
/** Record a freshly built node's correlation to the tag it came from, then return it unchanged
|
|
30
33
|
* (a passthrough so call sites stay expression-shaped). A scalar-slot tag lowers to BOTH a
|
|
31
34
|
* column Expr (`kind: "column"`) and a parallel ColumnRef record (`kind: "columnref"`, same
|
|
@@ -56,7 +59,16 @@ export function applyTemplateTags(ast, tags, text, provider) {
|
|
|
56
59
|
const relTags = tags.filter((t) => (t.kind === "call" && !t.incomplete) || t.kind === "other");
|
|
57
60
|
if (relTags.length === 0)
|
|
58
61
|
return { ast, byNode, byTag };
|
|
59
|
-
const
|
|
62
|
+
const nameConfig = ast.dialect !== undefined ? resolveBehavior(ast.dialect).nameConfig : undefined;
|
|
63
|
+
const ctx = {
|
|
64
|
+
relTags,
|
|
65
|
+
sets: resolveSets(tags, text, provider),
|
|
66
|
+
text,
|
|
67
|
+
provider,
|
|
68
|
+
byNode,
|
|
69
|
+
byTag,
|
|
70
|
+
...(nameConfig ? { nameConfig } : {}),
|
|
71
|
+
};
|
|
60
72
|
const next = transformQuery(ast, ctx);
|
|
61
73
|
// Scalar-slot marking: every column-shaped node whose token is a tag's placeholder
|
|
62
74
|
// fill gets a `template` marker (span + provider key), so inference resolves it
|
|
@@ -152,7 +164,10 @@ function markTemplateExprs(node, ctx) {
|
|
|
152
164
|
// never wrong.
|
|
153
165
|
// ---------------------------------------------------------------------------
|
|
154
166
|
/** Match a direct string-literal argument's raw text (no escapes, one token). */
|
|
155
|
-
|
|
167
|
+
/** A literal argument's value: a quoted string (quote-stripped) or a bare numeric literal —
|
|
168
|
+
* both are the user's own text, never computed. Everything else stays `null` (computed). */
|
|
169
|
+
const LITERAL_ARG = /^(?:(['"])([^'"\\]*)\1|(-?\d+(?:\.\d+)?))$/;
|
|
170
|
+
const literalValueOf = (m) => m[2] ?? m[3];
|
|
156
171
|
/** The raw text of a span. */
|
|
157
172
|
function sliceSpan(text, span) {
|
|
158
173
|
return text.slice(span.start, span.end);
|
|
@@ -172,11 +187,11 @@ export function callOf(mc, text) {
|
|
|
172
187
|
const kw = KWARG_RE.exec(raw);
|
|
173
188
|
if (kw) {
|
|
174
189
|
const m = LITERAL_ARG.exec(kw[2].trim());
|
|
175
|
-
kwargs.push({ name: kw[1], value: m ? m
|
|
190
|
+
kwargs.push({ name: kw[1], value: m ? literalValueOf(m) : null });
|
|
176
191
|
}
|
|
177
192
|
else {
|
|
178
193
|
const m = LITERAL_ARG.exec(raw);
|
|
179
|
-
args.push(m ? m
|
|
194
|
+
args.push(m ? literalValueOf(m) : null);
|
|
180
195
|
}
|
|
181
196
|
}
|
|
182
197
|
return {
|
|
@@ -398,6 +413,23 @@ function transformTableSource(src, ctx) {
|
|
|
398
413
|
// where `jjj…` was a fabrication.
|
|
399
414
|
const aliasTok = src.aliasCst?.start;
|
|
400
415
|
const base = aliasTok != null && inSpan(aliasTok.start, tag.tagSpan) ? withoutAlias(src) : src;
|
|
416
|
+
// An unresolved source's name is the RAW TAG TEXT — the bytes the user actually wrote. The
|
|
417
|
+
// placeholder fill is scaffolding this library invented so the grammar parses; letting it
|
|
418
|
+
// escape as a relation name (scope sources, lineage dependencies, go-to-def) is fabrication
|
|
419
|
+
// under the never-wrong rule (issue #35, reported by anvil).
|
|
420
|
+
const rawTagName = [ctx.text.slice(tag.tagSpan.start, tag.tagSpan.end)];
|
|
421
|
+
// Renaming a source rebuilds `relation` (#38): a provider-resolved name is SYNTHESIZED (plain
|
|
422
|
+
// logical parts, quoted where rendering needs it); the raw-tag-text fallback is source text.
|
|
423
|
+
// Without a dialect tag on the ast (never the parseTemplated path) the source keeps its
|
|
424
|
+
// original relation — the documented degrade.
|
|
425
|
+
const renamed = (b, parts, synthesized) => {
|
|
426
|
+
if (!ctx.nameConfig)
|
|
427
|
+
return b;
|
|
428
|
+
const relation = synthesized
|
|
429
|
+
? synthesizedQualifiedName(parts, ctx.nameConfig)
|
|
430
|
+
: qualifiedNameOf(parts, ctx.nameConfig);
|
|
431
|
+
return { ...b, relation };
|
|
432
|
+
};
|
|
401
433
|
// NOTE: `template.span` intentionally aliases `tag.tagSpan` BY REFERENCE. freezeIR
|
|
402
434
|
// therefore also freezes the TagNode.tagSpan object returned in `.tags`, benign
|
|
403
435
|
// since spans are read-only. Every call marker carries its `call`, the provider key
|
|
@@ -406,24 +438,24 @@ function transformTableSource(src, ctx) {
|
|
|
406
438
|
const call = callOf(tag, ctx.text);
|
|
407
439
|
const rel = ctx.provider.relationOf(call);
|
|
408
440
|
// A call in a FROM slot (ref/source/a TVF-like macro). When the provider resolves its
|
|
409
|
-
// relation, carry the resolved name; otherwise
|
|
441
|
+
// relation, carry the resolved name; otherwise the raw tag text (never the fill).
|
|
410
442
|
// Either way the `call` keeps it consultable, so an unresolved call is not a dead end: a
|
|
411
443
|
// provider added later resolves it. ref vs source is not stored here, it is call.name.
|
|
412
|
-
const named = rel ?
|
|
444
|
+
const named = rel ? renamed(base, [...rel.nameParts], true) : renamed(base, rawTagName, false);
|
|
413
445
|
const template = { kind: "call", span: tag.tagSpan, call };
|
|
414
446
|
return attach(ctx, { ...named, template }, tag);
|
|
415
447
|
}
|
|
416
448
|
// Non-call expression tag (var / env_var / other) in a FROM slot. A bare `{{ t }}` resolving
|
|
417
449
|
// through a `{% set t = … %}` single-call RHS carries the resolved relation name (when the
|
|
418
450
|
// provider resolved it) or the call identity alone; every other case gets the opaque "expr"
|
|
419
|
-
// marker
|
|
451
|
+
// marker. In every unresolved case the name is the raw tag text, never the fill.
|
|
420
452
|
const ident = tag.kind === "other" ? bareIdentOf(tag, ctx.text) : undefined;
|
|
421
453
|
const resolved = ident !== undefined ? ctx.sets.get(ident) : undefined;
|
|
422
454
|
if (resolved) {
|
|
423
|
-
const named = resolved.name ?
|
|
455
|
+
const named = resolved.name ? renamed(base, [...resolved.name], true) : renamed(base, rawTagName, false);
|
|
424
456
|
const template = { kind: "call", span: tag.tagSpan, indirect: true, call: resolved.call };
|
|
425
457
|
return attach(ctx, { ...named, template }, tag);
|
|
426
458
|
}
|
|
427
459
|
const template = { kind: "expr", span: tag.tagSpan, opaque: true };
|
|
428
|
-
return attach(ctx, { ...base, template }, tag);
|
|
460
|
+
return attach(ctx, { ...renamed(base, rawTagName, false), template }, tag);
|
|
429
461
|
}
|
package/dist/mysql/behavior.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// The mysql DialectBehavior: everything the semantic layer needs for mysql, assembled from
|
|
2
2
|
// this folder's own pieces. The registry wires it; nothing here reaches a central per-dialect table.
|
|
3
3
|
import { mysqlLiteral, mysqlParseType, MYSQL_FUNCTION_RETURNS } from "./infer.js";
|
|
4
|
-
import { displayName, fold, foldTableName, matchesSourceKey } from "./fold.js";
|
|
4
|
+
import { displayName, fold, foldTableName, matchesSourceKey, MYSQL_NAME_CONFIG } from "./fold.js";
|
|
5
5
|
import { likePatternToRegExp } from "../scope/like-pattern.js";
|
|
6
6
|
import { SIGNATURES } from "../signature/signatures.js";
|
|
7
7
|
import { acceptsFor } from "../dialect-behavior/coerce-rules.js";
|
|
@@ -10,6 +10,7 @@ export const mysqlBehavior = {
|
|
|
10
10
|
displayName,
|
|
11
11
|
foldTableName,
|
|
12
12
|
matchesSourceKey,
|
|
13
|
+
nameConfig: MYSQL_NAME_CONFIG,
|
|
13
14
|
likeMatch: (pattern, value) => likePatternToRegExp(pattern).test(value),
|
|
14
15
|
literal: mysqlLiteral,
|
|
15
16
|
parseType: mysqlParseType,
|
package/dist/mysql/fold.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { type FoldRule, type IdentKind } from "../ident/fold.js";
|
|
2
|
+
import type { QualifiedNameConfig } from "../ir/qualified-name.js";
|
|
2
3
|
export declare const MYSQL_FOLD_RULE: FoldRule;
|
|
4
|
+
/** schema.table, two levels — MySQL's database IS its schema
|
|
5
|
+
* (dev.mysql.com/doc/refman/8.4/en/identifier-qualifiers.html). */
|
|
6
|
+
export declare const MYSQL_NAME_CONFIG: QualifiedNameConfig;
|
|
3
7
|
/** Fold an identifier to its MySQL identity key. */
|
|
4
8
|
export declare function fold(raw: string, kind?: IdentKind): string;
|
|
5
9
|
/** Presentation twin: strip delimiters, no case change. */
|
package/dist/mysql/fold.js
CHANGED
|
@@ -33,6 +33,12 @@ export const MYSQL_FOLD_RULE = {
|
|
|
33
33
|
unquoted: "lower",
|
|
34
34
|
quoted: "lower",
|
|
35
35
|
};
|
|
36
|
+
/** schema.table, two levels — MySQL's database IS its schema
|
|
37
|
+
* (dev.mysql.com/doc/refman/8.4/en/identifier-qualifiers.html). */
|
|
38
|
+
export const MYSQL_NAME_CONFIG = {
|
|
39
|
+
roles: ["schema"],
|
|
40
|
+
rule: MYSQL_FOLD_RULE,
|
|
41
|
+
};
|
|
36
42
|
/** Fold an identifier to its MySQL identity key. */
|
|
37
43
|
export function fold(raw, kind = "other") {
|
|
38
44
|
return foldWith(MYSQL_FOLD_RULE, raw, kind);
|
package/dist/mysql/lower.js
CHANGED
|
@@ -3,6 +3,13 @@ import { MysqlParser as P } from "../generated/mysql/MysqlParser.js";
|
|
|
3
3
|
import { keywordCategory, swallowedCategories, swallowedStatements } from "../ir/statement.js";
|
|
4
4
|
import { collapsePartSpans, dotIdPartSpanOf, partSpanOf } from "../ir/part-span.js";
|
|
5
5
|
import { freezeIR } from "../ir/freeze.js";
|
|
6
|
+
import { qualifiedNameOf } from "../ir/qualified-name.js";
|
|
7
|
+
import { MYSQL_NAME_CONFIG } from "./fold.js";
|
|
8
|
+
/** The structured name for a table source's raw parts (issue #38) — role assignment + identity
|
|
9
|
+
* key + fqn happen HERE, at lowering, where the dialect's namespace shape is known. */
|
|
10
|
+
function relationOf(rawParts) {
|
|
11
|
+
return qualifiedNameOf(rawParts, MYSQL_NAME_CONFIG);
|
|
12
|
+
}
|
|
6
13
|
// ---------------------------------------------------------------------------
|
|
7
14
|
// Lowering — MySQL (grammars-v4 sql/mysql/Positive-Technologies fork, Ivan
|
|
8
15
|
// Kochurkin's split MySqlLexer/MySqlParser) CST -> the shared, dialect-neutral
|
|
@@ -636,7 +643,7 @@ function buildFrom(fromClause) {
|
|
|
636
643
|
// tableSource: tableSourceItem joinPart* | '(' tableSourceItem joinPart* ')' | jsonTable.
|
|
637
644
|
const jt = directChildrenOfRule(ts, P.RULE_jsonTable)[0];
|
|
638
645
|
if (jt) {
|
|
639
|
-
from.push({ kind: "table",
|
|
646
|
+
from.push({ kind: "table", relation: relationOf([jt.getText()]), cst: jt });
|
|
640
647
|
continue;
|
|
641
648
|
}
|
|
642
649
|
const base = directChildrenOfRule(ts, P.RULE_tableSourceItem)[0];
|
|
@@ -709,16 +716,24 @@ function buildSourceItem(item, fromSubqueries) {
|
|
|
709
716
|
const seq = directChildrenOfRule(item, P.RULE_sequenceFunctionName)[0];
|
|
710
717
|
if (seq) {
|
|
711
718
|
const alias = directChildrenOfRule(item, P.RULE_uid)[0];
|
|
712
|
-
return [
|
|
719
|
+
return [
|
|
720
|
+
{
|
|
721
|
+
kind: "table",
|
|
722
|
+
relation: relationOf([seq.getText()]),
|
|
723
|
+
alias: alias?.getText(),
|
|
724
|
+
aliasCst: alias,
|
|
725
|
+
cst: item,
|
|
726
|
+
},
|
|
727
|
+
];
|
|
713
728
|
}
|
|
714
|
-
return [{ kind: "table",
|
|
729
|
+
return [{ kind: "table", relation: relationOf([item.getText()]), cst: item }];
|
|
715
730
|
}
|
|
716
731
|
function tableSourceFromName(tn, alias, cst) {
|
|
717
732
|
const fullId = directChildrenOfRule(tn, P.RULE_fullId)[0] ?? tn;
|
|
718
733
|
const { parts, spans } = dottedParts(fullId);
|
|
719
734
|
return {
|
|
720
735
|
kind: "table",
|
|
721
|
-
|
|
736
|
+
relation: relationOf(parts.length ? parts : [tn.getText()]),
|
|
722
737
|
namePartSpans: collapsePartSpans(spans),
|
|
723
738
|
alias: alias?.getText(),
|
|
724
739
|
aliasCst: alias,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { acceptsFor } from "../dialect-behavior/coerce-rules.js";
|
|
2
2
|
import { likePatternToRegExp } from "../scope/like-pattern.js";
|
|
3
3
|
import { SIGNATURES } from "../signature/signatures.js";
|
|
4
|
-
import { displayName, fold, foldTableName, matchesSourceKey } from "./fold.js";
|
|
4
|
+
import { displayName, fold, foldTableName, matchesSourceKey, POSTGRES_NAME_CONFIG } from "./fold.js";
|
|
5
5
|
import { postgresLiteral, postgresParseType, POSTGRES_FUNCTION_RETURNS } from "./infer.js";
|
|
6
6
|
// PostgreSQL implicit coercion: str->num=true, bool<->num=false.
|
|
7
7
|
export const postgresBehavior = {
|
|
@@ -9,6 +9,7 @@ export const postgresBehavior = {
|
|
|
9
9
|
displayName,
|
|
10
10
|
foldTableName,
|
|
11
11
|
matchesSourceKey,
|
|
12
|
+
nameConfig: POSTGRES_NAME_CONFIG,
|
|
12
13
|
likeMatch: (pattern, value) => likePatternToRegExp(pattern).test(value),
|
|
13
14
|
literal: postgresLiteral,
|
|
14
15
|
parseType: postgresParseType,
|
package/dist/postgres/fold.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { type FoldRule, type IdentKind } from "../ident/fold.js";
|
|
2
|
+
import type { QualifiedNameConfig } from "../ir/qualified-name.js";
|
|
2
3
|
export declare const POSTGRES_FOLD_RULE: FoldRule;
|
|
4
|
+
/** database.schema.table (postgresql.org/docs/18/sql-syntax-lexical + ddl-schemas — cross-
|
|
5
|
+
* database references are rejected at runtime but the three-part form parses). Normalized
|
|
6
|
+
* vocabulary: catalog = database. */
|
|
7
|
+
export declare const POSTGRES_NAME_CONFIG: QualifiedNameConfig;
|
|
3
8
|
/** Fold an identifier to its PostgreSQL identity key. */
|
|
4
9
|
export declare function fold(raw: string, kind?: IdentKind): string;
|
|
5
10
|
/** Presentation twin: strip delimiters, no case change. */
|
package/dist/postgres/fold.js
CHANGED
|
@@ -20,6 +20,13 @@ export const POSTGRES_FOLD_RULE = {
|
|
|
20
20
|
unquoted: "ascii-lower",
|
|
21
21
|
quoted: "preserve",
|
|
22
22
|
};
|
|
23
|
+
/** database.schema.table (postgresql.org/docs/18/sql-syntax-lexical + ddl-schemas — cross-
|
|
24
|
+
* database references are rejected at runtime but the three-part form parses). Normalized
|
|
25
|
+
* vocabulary: catalog = database. */
|
|
26
|
+
export const POSTGRES_NAME_CONFIG = {
|
|
27
|
+
roles: ["catalog", "schema"],
|
|
28
|
+
rule: POSTGRES_FOLD_RULE,
|
|
29
|
+
};
|
|
23
30
|
/** Fold an identifier to its PostgreSQL identity key. */
|
|
24
31
|
export function fold(raw, kind = "other") {
|
|
25
32
|
return foldWith(POSTGRES_FOLD_RULE, raw, kind);
|
package/dist/postgres/lower.js
CHANGED
|
@@ -3,7 +3,13 @@ import { PostgresParser as P } from "../generated/postgres/PostgresParser.js";
|
|
|
3
3
|
import { keywordCategory, swallowedCategories, swallowedStatements } from "../ir/statement.js";
|
|
4
4
|
import { partSpansOf } from "../ir/part-span.js";
|
|
5
5
|
import { freezeIR } from "../ir/freeze.js";
|
|
6
|
-
import {
|
|
6
|
+
import { qualifiedNameOf } from "../ir/qualified-name.js";
|
|
7
|
+
import { displayName, POSTGRES_NAME_CONFIG } from "./fold.js";
|
|
8
|
+
/** The structured name for a table source's raw parts (issue #38) — role assignment + identity
|
|
9
|
+
* key + fqn happen HERE, at lowering, where the dialect's namespace shape is known. */
|
|
10
|
+
function relationOf(rawParts) {
|
|
11
|
+
return qualifiedNameOf(rawParts, POSTGRES_NAME_CONFIG);
|
|
12
|
+
}
|
|
7
13
|
// ---------------------------------------------------------------------------
|
|
8
14
|
// Lowering — PostgreSQL (bytebase/parser postgresql/ fork, TVL-lineage grammar)
|
|
9
15
|
// CST -> the shared dialect-neutral IR (src/ir/ir.ts). The semantic layer runs
|
|
@@ -559,7 +565,7 @@ function buildPrimarySource(tr, unsupported) {
|
|
|
559
565
|
if (jsonTable) {
|
|
560
566
|
return {
|
|
561
567
|
kind: "table",
|
|
562
|
-
|
|
568
|
+
relation: relationOf(["json_table"]),
|
|
563
569
|
alias,
|
|
564
570
|
aliasCst,
|
|
565
571
|
columnAliases: columnAliases ?? jsonTableColumns(jsonTable),
|
|
@@ -571,9 +577,10 @@ function buildPrimarySource(tr, unsupported) {
|
|
|
571
577
|
if (funcTable) {
|
|
572
578
|
const fname = firstShallow(funcTable, P.RULE_func_name);
|
|
573
579
|
const funcAlias = directChildrenOfRule(tr, P.RULE_func_alias_clause)[0];
|
|
580
|
+
const funcTableName = [fname ? lastName(fname) : funcTable.getText()];
|
|
574
581
|
return {
|
|
575
582
|
kind: "table",
|
|
576
|
-
|
|
583
|
+
relation: relationOf(funcTableName),
|
|
577
584
|
alias: alias ?? (funcAlias ? funcAliasName(funcAlias) : undefined),
|
|
578
585
|
aliasCst,
|
|
579
586
|
cst: tr,
|
|
@@ -592,7 +599,8 @@ function buildPrimarySource(tr, unsupported) {
|
|
|
592
599
|
if (innerFrom.length)
|
|
593
600
|
return innerFrom[0];
|
|
594
601
|
}
|
|
595
|
-
|
|
602
|
+
const tableName = [textOrEmpty(tr)];
|
|
603
|
+
return { kind: "table", relation: relationOf(tableName), alias, aliasCst, columnAliases, cst: tr };
|
|
596
604
|
}
|
|
597
605
|
/** The output column names of a JSON_TABLE: every named column definition, NESTED levels included
|
|
598
606
|
* (they flatten into the same row type). */
|
|
@@ -618,7 +626,15 @@ function buildTableFromRelation(rel, alias, aliasCst, columnAliases) {
|
|
|
618
626
|
const qn = directChildrenOfRule(rel, P.RULE_qualified_name)[0];
|
|
619
627
|
const parts = qn ? nameParts(qn) : [textOrEmpty(rel)];
|
|
620
628
|
const namePartSpans = qn ? columnPartSpans(qn) : undefined;
|
|
621
|
-
return {
|
|
629
|
+
return {
|
|
630
|
+
kind: "table",
|
|
631
|
+
relation: relationOf(parts),
|
|
632
|
+
namePartSpans,
|
|
633
|
+
alias,
|
|
634
|
+
aliasCst,
|
|
635
|
+
columnAliases,
|
|
636
|
+
cst: rel,
|
|
637
|
+
};
|
|
622
638
|
}
|
|
623
639
|
function aliasName(aliasClause) {
|
|
624
640
|
const ta = firstShallow(aliasClause, P.RULE_table_alias);
|
|
@@ -4,7 +4,7 @@ import type { Column } from "./schema.js";
|
|
|
4
4
|
import { type SchemaProvider } from "./schema-provider.js";
|
|
5
5
|
import { type ResolvedColumn } from "../sema/resolve.js";
|
|
6
6
|
export interface Diagnostic {
|
|
7
|
-
kind: "unknown-table" | "unknown-column" | "ambiguous-column" | "unknown-field" | "wrong-arity" | "wrong-argument-type";
|
|
7
|
+
kind: "unknown-table" | "ambiguous-table" | "unknown-column" | "ambiguous-column" | "unknown-field" | "wrong-arity" | "wrong-argument-type";
|
|
8
8
|
message: string;
|
|
9
9
|
/** Start of the offending node: 1-based line, 0-based column. */
|
|
10
10
|
line: number;
|