import type { ColumnRef, CteDef, Expr, GraphTableSource, LateralViewSource, LimitInfo, PipeStage, PivotInfo, QueryBody, QueryExpr, SubquerySource, TableSource, UnpivotInfo, VariableDecl } from "../ir/ir.js"; import type { StatementCategory } from "../ir/statement.js"; import { likePatternToRegExp } from "./like-pattern.js"; export interface ScopeTree { /** Discriminant tag — lets api.ts's `isScopeTree` identify a ScopeTree structurally * instead of shape-sniffing `root`/`statement`. */ kind: "scopes"; root: Scope; /** The statement category the dialect's lower() reported (query / dml / ddl / dcl / tcl / * utility / compound / other). "other" when the lowered query carried none. */ statement: StatementCategory; } export interface Scope { /** The query body this scope describes (a SELECT, or a set operation). */ body: QueryBody; /** Visible relations, keyed by alias (or the table's last name part). NOTE: two sources whose * keys collide (joined FQN tables sharing a last part) occupy ONE slot here — resolution that * must see every source reads `sourceList` (#38). */ sources: Map; /** Every visible relation in registration order, with its binding key — the collision-proof * twin of `sources`. Qualifier matching and ambiguity detection walk THIS (#38). */ sourceList: { key: string; source: ResolvedSource; }[]; /** CTEs defined for this query block, keyed by normalized name. */ ctes: Map; /** Output column names, or "unknown" when a star/anonymous projection needs a schema. */ outputs: string[] | "unknown"; /** For a set-op body, the left/right branch scopes (also in `children`). */ branches?: { left: Scope; right: Scope; }; /** For a pipe body (body.kind === "pipe"): the input relation's scope and the ordered per-stage * scopes (all also in `children`). The pipe's output is the last stage's output (input's if empty). */ pipe?: { input: Scope; stages: Scope[]; }; /** When this scope IS a pipe stage: the stage it resolves and the scope of the relation entering it * (the previous stage, or the pipe input for the first). Its `sources` hold that incoming relation * (unqualified) plus any source the stage adds (a JOIN); its outputs are the stage transform applied * to the incoming columns. */ pipeStage?: PipeStage; pipeIncoming?: Scope; parent?: Scope; children: Scope[]; /** The dialect this query was lowered from ("databricks" | "tsql"). Drives dialect-specific * type inference (function/literal/type knowledge); the rest of the layer ignores it. */ dialect: string; /** This QueryExpr's own variable declarations (T-SQL DECLARE, or a routine's signature * parameters), copied from `QueryExpr.declarations` by `buildQueryScope` onto the scope it * builds: the top scope for a top-level DECLARE, or (for a routine) the container's own scope * AND each inner statement's own scope, independently (a DECLARE nested in a routine body * carries its OWN declarations here, distinct from the container's parameters). Absent means * none. A `variable` reference resolves against the WHOLE tree (pooled) via `rootDeclarations` * below, not just this one level. */ declarations?: readonly VariableDecl[]; /** This QueryExpr's own ORDER BY sort expressions, copied from `QueryExpr.orderBy` by * `buildQueryScope` the same way `declarations` above is (additive), so a consumer that needs * "this scope's own trailing ORDER BY" (src/scope/clauses.ts's `clausesOf`) reads it straight off * the Scope instead of re-matching a QueryExpr by body identity. Absent means none. A set-op * branch / pipe stage scope (built via `buildBodyScope`, no owning QueryExpr) never carries this. */ orderBy?: Expr[]; /** This QueryExpr's own row-limiting clause (LIMIT / TOP / OFFSET-FETCH), copied from * `QueryExpr.limit` the same way `orderBy` above is. Absent means none. */ limit?: LimitInfo; } export interface CteRef { def: CteDef; scope: Scope; } export type ResolvedSource = /** `name` is the relation's FOLDED IDENTITY key parts (source.relation.key) — the catalog * lookup currency. Display text lives on source.relation (parts/fqn); never show `name`. */ { kind: "table"; name: string[]; source: TableSource; } | { kind: "cte"; ref: CteRef; source: TableSource; } | { kind: "subquery"; scope: Scope; source: SubquerySource; } | { kind: "lateral"; source: LateralViewSource; } /** The relation entering a pipe stage — the previous stage's (or the pipe input's) output, exposed * unqualified. Carries no name of its own; its columns are that scope's outputs. */ | { kind: "relation"; scope: Scope; } /** A GRAPH_TABLE(…) relation — its own scope binds the graph element variables; its output columns * are the COLUMNS / RETURN list. Behaves like a derived (subquery) relation to the enclosing query. */ | { kind: "graphtable"; scope: Scope; source: GraphTableSource; } /** An aliased `PIVOT(…) AS p` / `UNPIVOT(…) AS p` relation — the base relation(s) are consumed; this * source exposes the reshaped column set (base passthrough + produced columns) under `p`. Columns are * computed from `base` and applied via applyPivotCols/applyUnpivotCols (schema-fed in qualify). */ | { kind: "pivot"; alias: string; base: ResolvedSource[]; pivot?: PivotInfo; unpivot?: UnpivotInfo; }; export declare function resolveScopes(query: QueryExpr, dialect?: string): ScopeTree; /** The declarations visible to `scope`: walk up to the tree's ROOT, then collect every * `.declarations` in the WHOLE tree rooted there (the root's own, plus every descendant scope's: * a routine's inner statement scopes hang as children of their container's own scope, see * buildQueryScope's `statements` wiring, so this reaches a routine's signature parameters AND * every inner statement's own DECLARE, from anywhere in the body). Declarations are pooled, not * shadowed by nesting/position: a body DECLARE reusing a parameter's (or a sibling statement's) * name becomes a 2-CANDIDATE AMBIGUITY, exactly like two DECLAREs of the same name in one * statement, never silently shadowed (never-wrong's cue to abstain, not this helper's; see the * single-unambiguous-match rule at each call site in infer.ts/symbols.ts). Cross-cell linking (a * DECLARE in an earlier TOP-LEVEL document statement) is still the document layer's job * (src/document/document.ts), not this scope-local walk. */ export declare function rootDeclarations(scope: Scope): readonly VariableDecl[] | undefined; export type ColumnResolution = { kind: "bound"; source: ResolvedSource; column: string; fields: string[]; } | { kind: "alias"; name: string; } | { kind: "ambiguous"; candidates: ResolvedSource[]; } | { kind: "unresolved"; } | { kind: "needs-schema"; }; /** A column reference split into its table qualifier, the column, and struct/field navigation. */ export interface SplitRef { /** The matched source key, when a leading part names a visible source (else unqualified). */ qualifier?: string; /** The column name. */ column: string; /** Struct/map field navigation after the column: `a.b.c` bound to column `a` → ["b","c"]. */ fields: string[]; } /** * Split a (possibly dotted) reference into qualifier / column / field path. A leading part is a * table qualifier only if it names a visible source — otherwise the first part is the column and * the rest is field access (`a.b.c` where `a` is a column → fields b, c). This mirrors Spark's * resolution order (try table-qualified first, then nested field access on a column), so struct * access is no longer mistaken for `table.column`. `isSource` reports whether a key is visible. * Source-map keys are folded (see sourceKey); a raw part matches via either the alias/other fold * or the table fold (these differ only for BigQuery's case-preserving table identifiers). */ export declare function splitColumnRef(parts: string[], isSource: (key: string) => boolean, dialect?: string): SplitRef; /** Split a reference against the sources visible from `scope` (including enclosing scopes). */ export declare function splitColumnRefInScope(scope: Scope, parts: string[]): SplitRef; /** * The sources of ONE scope matching a column reference's QUALIFIER (issue #38) — with the leading * parts VALIDATED, at any depth the dialect's namespace admits: * - a single-part qualifier matches an alias or a source's binding key (today's semantics); * - a multi-part qualifier matches only an UNALIASED table source whose `relation.key` ends with * the folded qualifier parts, ALL of them (an alias supersedes the table name for * qualification, so aliased sources never match multi-part). `wrong.orders.amount` therefore * binds nowhere, where the last-part heuristic used to bind it to any `orders`. * Returns every match: 1 = bound, >1 = a genuinely ambiguous qualifier, 0 = try shorter/elsewhere. */ export declare function sourcesMatchingQualifier(scope: Scope, qualParts: string[]): ResolvedSource[]; /** Clauses where a bare name may reference a SELECT-list alias rather than a source column. Used by * the unified binder (src/sema/resolve.ts) for the projection-alias fallback. */ export declare function aliasVisibleClause(clause: ColumnRef["clause"] | undefined): boolean; export declare function matchesProjectionAlias(scope: Scope, name: string): boolean; export declare function applyPivotCols(base: string[], p: PivotInfo, dialect?: string): string[] | "unknown"; export declare function applyUnpivotCols(base: string[], u: UnpivotInfo, dialect?: string): string[]; /** `UNION BY NAME` output: left columns in order, then right-only columns appended. */ export declare function mergeByName(left: string[] | "unknown", right: string[] | "unknown", dialect?: string): string[] | "unknown"; /** Apply a star node's modifiers to an expansion: EXCLUDE/EXCEPT removes, ILIKE filters by * pattern, RENAME renames (REPLACE keeps name and position — no expansion change). */ export declare function applyStarModifiers(cols: string[], star: { exclude?: string[]; ilike?: string; rename?: { from: string; to: string; }[]; }, dialect?: string): string[]; export { likePatternToRegExp }; /** The columns a resolved source exposes, or "unknown" when it needs a schema (a bare table). * Exported for the unified binder's schema-free path (src/sema/resolve.ts). */ export declare function sourceOutputs(src: ResolvedSource, dialect: string): string[] | "unknown"; /** The reshaped columns of an aliased PIVOT/UNPIVOT source: its base columns (via `cols`) with the * pivot/unpivot transform applied. "unknown" if any base relation's columns need a schema we lack. * Shared by the schema-free scope path and the schema-fed qualify / resolve / lineage passes (each * passes its own column resolver), so an aliased pivot exposes the same reshaped set everywhere. */ export declare function pivotSourceOutputs(src: Extract, cols: (s: ResolvedSource) => string[] | "unknown", dialect: string | undefined): string[] | "unknown";