Documentation
¶
Overview ¶
Package semantic is the Go port of Java's `com.apple.foundationdb.relational.recordlayer.query.SemanticAnalyzer` plus related Identifier / Expression / reference-resolution helpers.
The Java class is a 1280-line monolith doing identifier normalization, table / CTE / function lookup, index validation, ORDER BY validation, type inference, star expansion, nested-field resolution, and correlated-identifier resolution. The Go port breaks that up across focused files:
- identifier.go — Identifier type + case-folding normalization.
- (future) — table / column / CTE / index lookup, type inference, star expansion.
Currently ships only the identifier machinery; the resolution + type passes are follow-up work.
Index ¶
- func NormalizeString(s string, caseSensitive bool) string
- type AmbiguousColumnError
- type Analyzer
- func (a *Analyzer) BuildScopeFromFromClause(parent *Scope, fromCtx antlrgen.IFromClauseContext) (*Scope, error)
- func (a *Analyzer) CaseSensitive() bool
- func (a *Analyzer) Catalog() Catalog
- func (a *Analyzer) ExpandQualifiedStar(scope *Scope, qualifier Identifier) ([]ExpandedColumn, error)
- func (a *Analyzer) ExpandScopeStar(scope *Scope) []ExpandedColumn
- func (a *Analyzer) ExpandStar(table Table) []Column
- func (a *Analyzer) ResolveColumn(table Table, id Identifier) (Column, error)
- func (a *Analyzer) ResolveColumnRefNested(scope *Scope, qualifier, id Identifier) (Column, ScopeSource, []NestedAccessor, error)
- func (a *Analyzer) ResolveColumnRefPath(scope *Scope, segs []Identifier) (Column, ScopeSource, []NestedAccessor, error)
- func (a *Analyzer) ResolveTable(name QualifiedName) (Table, error)
- func (a *Analyzer) ResolveTableRef(ctx antlrgen.IFullIdContext) (Table, error)
- type Catalog
- type Column
- type ColumnNotFoundError
- type CorrelatedShadowError
- type DuplicateAliasError
- type EnumMember
- type ExpandedColumn
- type FunctionArityError
- type FunctionCatalog
- type FunctionKind
- type FunctionNotFoundError
- type FunctionSpec
- type Identifier
- type InMemoryCatalog
- type NestedAccessor
- type NestedResolutionError
- type QualifiedName
- func (q QualifiedName) EqualsIgnoreQuoting(other QualifiedName) bool
- func (q QualifiedName) IsQualified() bool
- func (q QualifiedName) IsZero() bool
- func (q QualifiedName) LeafIdentifier() Identifier
- func (q QualifiedName) Name() string
- func (q QualifiedName) PrefixedWith(prefix QualifiedName) bool
- func (q QualifiedName) Qualifier() []string
- func (q QualifiedName) Segments() []string
- func (q QualifiedName) String() string
- type Scope
- func (s *Scope) AddSource(src ScopeSource) error
- func (s *Scope) AllSourcesRecursive() []ScopeSource
- func (s *Scope) Parent() *Scope
- func (s *Scope) ResolveColumn(id Identifier) (Column, ScopeSource, error)
- func (s *Scope) ResolvePathAcrossLevels(segs []Identifier) (Column, ScopeSource, []NestedAccessor, error)
- func (s *Scope) ResolvePathNested(segs []Identifier) (Column, ScopeSource, []NestedAccessor, error)
- func (s *Scope) ResolveQualifiedColumn(qualifier, col Identifier) (Column, ScopeSource, error)
- func (s *Scope) ResolveQualifiedColumnNested(qualifier, col Identifier) (Column, ScopeSource, []NestedAccessor, error)
- func (s *Scope) ResolveSourceQualifiedPath(segs []Identifier) (Column, ScopeSource, []NestedAccessor, error)
- func (s *Scope) Sources() []ScopeSource
- func (s *Scope) WithUnnamedSources() *Scope
- type ScopeSource
- type SourceNotFoundError
- type StaticTable
- type Table
- type TableNotFoundError
- type UnresolvableSourceError
- type UnsupportedFromShapeError
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func NormalizeString ¶
NormalizeString is the lower-level helper underlying New. Exposed for call sites that have a raw string and don't need the full Identifier wrapper (e.g. dedup keys in the parser). Mirrors Java's `SemanticAnalyzer.normalizeString`.
- Empty string → empty string.
- Quoted (single or double) → strip quotes verbatim.
- Unquoted + caseSensitive → return unchanged.
- Unquoted + !caseSensitive → upper-case.
Types ¶
type AmbiguousColumnError ¶
type AmbiguousColumnError struct {
Id Identifier
// Qualifier is the reference's qualifier for the QUALIFIED form
// (`a.id` over duplicate aliases both carrying id); zero for a bare
// reference. Callers render Java's exact message from the reference
// as written: `Ambiguous reference A.ID` / `Ambiguous reference ID`.
Qualifier Identifier
// Path is the reference's FULL segment list when it was resolved as a
// dotted path (`a.n.sk` → [A N SK]). Qualifier/Id keep the first and last
// segments so two-segment callers are unaffected, but they cannot render a
// deeper reference AS WRITTEN — and the rendering is the message operand
// Java prints, so a three-segment ambiguity would otherwise report a
// reference the user never typed. Empty for callers that pass no path.
Path []Identifier
// Matches is always equal to len(Sources); exists as a
// convenience accessor for callers who don't need the full
// alias list. Future API tightening may remove it — prefer
// len(Sources) for new code.
Matches int
// Sources is the list of ScopeSource aliases that matched,
// allowing the user-facing message to suggest
// `alias.column` for each candidate.
Sources []Identifier
}
AmbiguousColumnError is returned when a column reference matches multiple sources at the same scope level — bare (two tables expose the name) or qualified (two same-aliased sources both carry the column, Java's per-attribute 42702). Carries the conflicting identifier and the conflicting source aliases so the user knows which tables to qualify against.
func (*AmbiguousColumnError) Error ¶
func (e *AmbiguousColumnError) Error() string
func (*AmbiguousColumnError) Reference ¶
func (e *AmbiguousColumnError) Reference() string
Reference renders the ambiguous reference AS WRITTEN (normalized) — Java's message operand: `A.ID` for a qualified reference, `ID` for a bare one. The callers' user-facing mapping is `Ambiguous reference %s` byte-equal to Java's SemanticAnalyzer text (verified for duplicate AND distinct aliases, bare AND qualified).
type Analyzer ¶
type Analyzer struct {
// contains filtered or unexported fields
}
Analyzer ties Catalog lookups + identifier normalization into the resolution helpers that rule authors / logical-plan builders invoke. Mirrors the instance surface of Java's `SemanticAnalyzer` (the static methods on that class — case folding etc. — live as free functions in this package).
It resolves table and column references (bare, qualified and nested paths through a Scope) and expands `*` and `t.*` (ExpandStar, ExpandScopeStar, ExpandQualifiedStar).
Not safe for concurrent mutation of the underlying Catalog; the Analyzer itself is stateless once constructed.
func NewAnalyzer ¶
NewAnalyzer wires up an Analyzer against the given catalog. A nil catalog is rejected — callers who don't have a real schema yet should use NewInMemoryCatalog() with no tables.
func (*Analyzer) BuildScopeFromFromClause ¶
func (a *Analyzer) BuildScopeFromFromClause(parent *Scope, fromCtx antlrgen.IFromClauseContext) (*Scope, error)
BuildScopeFromFromClause walks a parsed FROM clause and produces a Scope populated with one ScopeSource per TableSource. The analyzer resolves each table via its catalog; missing tables return the first TableNotFoundError encountered.
Supports the simple shape (comma-separated AtomTableItem entries + optional alias); subquery-in-FROM and JOIN clauses are deferred until the join/derived-table resolution passes land. Unsupported shapes return an UnsupportedFromShapeError so callers can fall back to the existing logical-builder path cleanly.
Pass parent=nil for a top-level query; pass the enclosing scope for correlated subqueries.
func (*Analyzer) CaseSensitive ¶
CaseSensitive reports the analyzer's case-sensitivity setting.
func (*Analyzer) Catalog ¶
Catalog returns the underlying Catalog. Exposed so higher-level passes (e.g. LogicalPlan builder) can thread the same catalog through without re-wiring.
func (*Analyzer) ExpandQualifiedStar ¶
func (a *Analyzer) ExpandQualifiedStar(scope *Scope, qualifier Identifier) ([]ExpandedColumn, error)
ExpandQualifiedStar implements `SELECT alias.*` against a Scope: looks up the named source, then its columns. Walks the parent chain for correlated-star references. Returns SourceNotFoundError (with the Available alias list populated from every visible scope for "did you mean?" rendering) when no source matches.
func (*Analyzer) ExpandScopeStar ¶
func (a *Analyzer) ExpandScopeStar(scope *Scope) []ExpandedColumn
ExpandScopeStar implements unqualified `SELECT *` against a Scope: concatenates each source's columns in FROM-order. Ambiguity is NOT flagged here — Java's SQL lets two sources expose same-named columns through `SELECT *` (the output just gets two columns); only bare *references* error. Downstream callers tag each ExpandedColumn with its source so later projection rewrites can qualify.
func (*Analyzer) ExpandStar ¶
ExpandStar implements the `SELECT *` rewrite — returns the full column list of the given table in declared order. Each Column is returned unchanged (same Id, Type, Nullable) so downstream plan builders can wrap each into a ColumnReference / ProjectionItem.
Mirrors the single-qualifier case of Java's `SemanticAnalyzer.expandStar`; ExpandScopeStar and ExpandQualifiedStar cover multi-table and alias-qualified stars.
func (*Analyzer) ResolveColumn ¶
func (a *Analyzer) ResolveColumn(table Table, id Identifier) (Column, error)
ResolveColumn looks up a column by identifier against a resolved table. Mirrors the simple case of Java's resolveIdentifier — qualifier resolution (`t.col` → column on aliased table) comes later with the FROM-clause scope machinery.
One table, so there is nothing to adjudicate across sources and the lookup is the relaxed one: exact spelling first, then case-insensitive. That is what the scope would answer for the same reference against the same single source, and an entry point that answered differently would be a second resolution rule.
func (*Analyzer) ResolveColumnRefNested ¶
func (a *Analyzer) ResolveColumnRefNested(scope *Scope, qualifier, id Identifier) (Column, ScopeSource, []NestedAccessor, error)
ResolveColumnRefNested is the one-shot column-reference resolver: given a qualifier (may be zero) and a column identifier, dispatch to bare or qualified lookup against the provided scope. This is the analyzer's top-level hook for every identifier reference the expression resolver sees.
- qualifier.IsZero() → Scope.ResolveColumn (bare). - qualifier non-zero → Scope.ResolveQualifiedColumnNested.
Returns the same typed errors as the underlying scope methods, plus the accessor chain a reference that descends INTO a struct column resolves to — Java's lookupNestedField result (SemanticAnalyzer.java:578-601). The chain is empty for every reference that addresses a source column directly.
The chain is part of the RESULT, not an optional extra: a caller that mints a value from (Column, ScopeSource) alone and ignores the chain has resolved `struct.member` to the whole struct — a wrong-column read that raises no error. There is deliberately no chain-discarding sibling of this method for a caller to reach for; Java has no such split either, because lookupNestedField fuses the descent onto the value before any caller sees it (SemanticAnalyzer.java:599-600). expr.fuseNestedAccessorsIfAny is the Go side of that fuse.
BE PRECISE ABOUT WHAT THAT BUYS. Removing the discarding sibling RAISED THE COST of the mistake; it did not make it unrepresentable. Each mint still calls the fuse itself, so a NEW mint that resolves through this method and forgets the call is the same silent wrong-column read as before — it just has to be written deliberately rather than by picking the shorter-named function. The change that would remove the shape is a single mint, or the fuse moved inside whatever the mints share; neither exists yet.
The BARE arm never descends, and that is Java's rule, not an omission: lookupNestedField returns empty immediately when the requested identifier has one segment (SemanticAnalyzer.java:557-559), because a descent needs a prefix to consume before there is anything left to walk into.
func (*Analyzer) ResolveColumnRefPath ¶
func (a *Analyzer) ResolveColumnRefPath(scope *Scope, segs []Identifier) (Column, ScopeSource, []NestedAccessor, error)
ResolveColumnRefPath is ResolveColumnRefNested for a reference of ARBITRARY segment depth — `a.n.sk` and deeper. Java has no arity cap on this path (`fullId : uid (DOT uid)*` in the grammar, an unbounded remainingPath loop in SemanticAnalyzer.lookupNestedField), so neither does this.
A single segment takes the BARE arm, which never descends: Java's lookupNestedField returns empty immediately for a one-segment identifier (SemanticAnalyzer.java:557-559), because a descent needs a prefix to consume before there is anything left to walk into.
func (*Analyzer) ResolveTable ¶
func (a *Analyzer) ResolveTable(name QualifiedName) (Table, error)
ResolveTable looks up a table by qualified name. Returns a typed error when the name is missing so callers can wrap it into the API-level error shape without string-matching.
func (*Analyzer) ResolveTableRef ¶
func (a *Analyzer) ResolveTableRef(ctx antlrgen.IFullIdContext) (Table, error)
ResolveTableRef is the parse-tree convenience wrapper over ResolveTable. Reads the IFullIdContext (ANTLR's table reference node), builds a QualifiedName with the analyzer's case-sensitivity, then looks it up in the catalog.
Returns TableNotFoundError with the QualifiedName the caller requested; callers preserve user-facing names through `err.Name.String()`.
type Catalog ¶
type Catalog interface {
// LookupTable returns a Table handle for the given qualified
// name, or (nil, false) if no such table exists. Name
// normalization is the caller's job — QualifiedName already
// carries the case-folded form.
LookupTable(name QualifiedName) (Table, bool)
// TableExists reports whether a Table with the given name is
// registered. Equivalent to LookupTable's second return value;
// surfaced separately because it's the common fast-path check
// in existence-gating rules.
TableExists(name QualifiedName) bool
// AllTableNames returns every registered table's qualified
// name. Order is unspecified; callers that need deterministic
// ordering should sort. Used for INFORMATION_SCHEMA-style
// reflection and for error messages that enumerate candidate
// tables.
AllTableNames() []QualifiedName
}
Catalog is the semantic analyzer's view of the schema. The analyzer looks up tables, columns, and indexes through this interface; concrete implementations bridge to `RecordMetaData` (for the embedded engine) or to test fixtures.
Mirrors the subset of Java's `SchemaTemplate` that the Go SemanticAnalyzer port needs. Keeping it narrow so the seed doesn't drag in the full RecordLayer metadata surface — callers who need more adapter methods can extend the Table interface later.
All lookups take QualifiedName so the analyzer can handle template-qualified references uniformly; concrete impls decide how to resolve un-qualified names (walk the search path, default schema, etc.).
type Column ¶
type Column struct {
// Id is the column name.
Id Identifier
// Type is the column's SQL-ish data type as a string (e.g.
// "INT", "STRING", "BYTES"). Will be replaced with a richer
// `DataType` once the Type hierarchy is ported.
Type string
// Vector shape accompanies the VECTOR scalar or array element kind.
VectorPrecision int
VectorDimensions int
// Nullable reports whether the column allows NULL values.
// Matters for NOT-NULL-gated simplifications (x = x → TRUE).
Nullable bool
// IsArray reports whether the column is an ARRAY (a repeated proto
// field). The placeholder Type string carries only the scalar/element
// kind, so this is the array signal callers need to type the resolved
// column Value as an ArrayType — e.g. CARDINALITY()'s isArray() check.
// When true, Type is the ELEMENT type string.
IsArray bool
// ElementNullable is an ARRAY whose element may be NULL (ARRAY_AGG under
// RESPECT NULLS); a DDL array column's element never is.
ElementNullable bool
// Ephemeral marks a column that is resolvable BY NAME but invisible to
// star expansion — Java's Expression.asEphemeral() attribute, applied to
// the __ROW_VERSION pseudo-field appended by generateTableAccess
// (LogicalOperator.java:296-301); star expansion consumes
// nonEphemeralVisible() (SemanticAnalyzer.java:346-348). An ephemeral
// column still occupies its trailing slot in the flowed row layout, so
// ordinal binding (sourceRowType) keeps it.
Ephemeral bool
// UnqualifiedOutput preserves an attribute introduced without an SQL
// qualifier even inside a named operator, such as a whole-struct unnest
// alias. Visibility (Ephemeral) and qualification are independent.
UnqualifiedOutput bool
// SQLAuthored marks a column whose NAME the statement's own SQL text wrote
// — an unnest's AS or AT alias, a SELECT-list `AS` a derived table or CTE
// publishes, a CTE column list — rather than one read from a descriptor.
// Resolution compares such a name exactly in both passes: the relaxed
// pass repairs descriptor spellings (see relaxedPass), and an authored
// name is already the normalized SQL spelling, so folding it only answers
// what Java refuses (`FROM t, t.arr AS "e" WHERE E = 1` is 42703 there).
SQLAuthored bool
// StructFields is the DECLARED field list of a STRUCT column or an ARRAY's
// STRUCT element (Type "RECORD"), in declared order. IsArray distinguishes
// the container; its element metadata does not authorize field descent. It is
// the thing Java's lookupNestedField scans when it turns the path
// segments left over after the matched attribute prefix into
// FieldValue.Accessor(name, ordinal) entries (SemanticAnalyzer.java:
// 578-597, `((DataType.StructType) type).getFields()`).
//
// The ORDINAL a nested accessor carries is the field's POSITION IN THIS
// SLICE, which is why declared order is part of the contract and not an
// incidental property of how the slice was built: Java resolves the same
// accessor against Type.Record's field list and stores that list position
// (FieldValue.java:288-295 via getFieldNameToOrdinalMap), so the two
// agree only while both lists are the descriptor's own field order.
//
// Recursive by construction — a nested struct field carries its own
// StructFields — because a path may descend more than one level.
StructFields []Column
// StructTypeName is the exact declared identity of a STRUCT column's
// record type. Type remains the SQL kind "RECORD" so existing semantic
// kind checks stay stable; this companion carries the descriptor full name
// needed when the resolver mints an executable values.RecordType. Without
// it, every distinct STRUCT became a record literally named "RECORD", so a
// resolved nested FieldValue and the scan edge for the same proto field had
// different exact types.
//
// Empty for non-STRUCT columns and for synthetic catalog fixtures that do
// not declare a nominal record identity.
StructTypeName string
// EnumTypeName and EnumMembers carry the complete declaration when Type
// is ENUM (also for an enum array's element). A kind string alone cannot
// distinguish an enum from a STRING or reconstruct its stored numbers.
EnumTypeName string
EnumMembers []EnumMember
}
Column is the analyzer's view of a table column. Type is a placeholder string until the DataType / Type hierarchy port lands (Phase 4.0 continuation).
func LookupColumnRelaxed ¶
func LookupColumnRelaxed(tbl Table, id Identifier) (Column, bool)
LookupColumnRelaxed asks ONE table whether it declares a column the given reference could name: the exact spelling first, then a case-insensitive match — the same two steps Scope's relaxed pass applies.
It exists for the SINGLE-SOURCE questions that are not resolutions: "does this leg still carry the column the outer reference named?", "does the CTE body project this name?". Those have nothing to adjudicate across sources, which is the whole reason Table.LookupColumn itself stays exact — a table that relaxed on its own would let one source's loose match compete with another source's exact one. Do NOT use this to resolve a reference; use the Scope methods, which run the two passes level by level and count candidates.
func NonEphemeral ¶
NonEphemeral filters ephemeral columns out of a column list — the star visibility rule (Java's Expressions.nonEphemeralVisible, consumed by SemanticAnalyzer.expandStar at SemanticAnalyzer.java:346-348). Resolution by NAME keeps seeing ephemeral columns (LookupColumn is unfiltered).
func (Column) LookupStructField ¶
func (c Column) LookupStructField(id Identifier) (Column, int, bool)
LookupStructField scans a STRUCT column's declared fields for one named by id and returns it with its ORDINAL (position in StructFields).
This is Java's lookupNestedField inner loop (SemanticAnalyzer.java:584-593): a linear scan, FIRST name match wins, and — the load-bearing part — a MISS is not an error. Java returns Optional.empty() both for "this is not a struct" (:581-583) and for "no field of that name" (:594-596); the failure surfaces later and generically as UNDEFINED_COLUMN once every attribute of every operator has declined. Reporting a bespoke "no such field" here would turn a candidate that merely lost into a hard error, and a reference that Java resolves against a LATER source would die on an EARLIER one.
type ColumnNotFoundError ¶
type ColumnNotFoundError struct {
TableName QualifiedName
Id Identifier
// Path retains the normalized reference, not the resolved table name: a
// source alias and nested segments must survive computed-expression errors.
Path []Identifier
}
ColumnNotFoundError is returned when ResolveColumn can't find a column on the given table.
func (*ColumnNotFoundError) Error ¶
func (e *ColumnNotFoundError) Error() string
func (*ColumnNotFoundError) Reference ¶
func (e *ColumnNotFoundError) Reference() string
Reference renders the reference retained by path resolution. Direct column lookups carry only Id, as they do not have a source-qualified reference.
type CorrelatedShadowError ¶
type CorrelatedShadowError struct {
}
CorrelatedShadowError is returned when a qualified reference resolves to a PARENT-scope source (Java's zero-match fallthrough) whose correlation name is SHADOWED by a local FROM source that lacks the column — an emitted-uncorrelatable case. Emitting QOV(correlation) would bind the local (inner) leg's quantifier, so resolution declines LOUDLY (never wrong rows); this matches Java (unique quantifier ids) and will flip once cross-scope binding ids are supported. Carries the reference as written for the surfaced message.
func (*CorrelatedShadowError) Error ¶
func (e *CorrelatedShadowError) Error() string
type DuplicateAliasError ¶
type DuplicateAliasError struct {
Alias Identifier
}
DuplicateAliasError is returned by AddSource when the same alias is already registered at this scope level.
func (*DuplicateAliasError) Error ¶
func (e *DuplicateAliasError) Error() string
type EnumMember ¶
EnumMember is one declared enum member, in declaration order. Numbers are protobuf values, not positions in the member list.
type ExpandedColumn ¶
type ExpandedColumn struct {
Column Column
Source ScopeSource
}
ExpandedColumn pairs a Column with the ScopeSource it came from. The scope-aware star expander / qualified-star expander returns these so downstream plan builders know which FROM source to attribute each projected column to.
type FunctionArityError ¶
FunctionArityError signals too few / too many arguments.
func (*FunctionArityError) Error ¶
func (e *FunctionArityError) Error() string
type FunctionCatalog ¶
type FunctionCatalog struct {
// contains filtered or unexported fields
}
FunctionCatalog holds the set of functions the analyzer recognizes. Seed ships the core SQL aggregates; scalar function catalogues come as the embedded engine's scalar-function library gets ported.
func NewFunctionCatalog ¶
func NewFunctionCatalog() *FunctionCatalog
NewFunctionCatalog builds an empty catalog. Use RegisterDefaults or Register to populate.
func (*FunctionCatalog) Contains ¶
func (c *FunctionCatalog) Contains(name Identifier) bool
Contains reports whether a function with the given identifier is registered. Equivalent to Lookup's second return.
func (*FunctionCatalog) Lookup ¶
func (c *FunctionCatalog) Lookup(name Identifier) (FunctionSpec, bool)
Lookup returns the FunctionSpec for name (case-insensitive), or (_, false) when not registered.
func (*FunctionCatalog) Register ¶
func (c *FunctionCatalog) Register(spec FunctionSpec) error
Register adds a FunctionSpec. Returns an error on duplicate name; caller can ignore when registering a known stable set or bubble up when the registry is built from user extensions.
func (*FunctionCatalog) RegisterDefaults ¶
func (c *FunctionCatalog) RegisterDefaults()
RegisterDefaults populates the catalog with the standard SQL aggregate functions. Scalar functions are not seeded — they come from the dedicated scalar-function catalogue once that's ported.
type FunctionKind ¶
type FunctionKind int
FunctionKind enumerates the classes of function the analyzer supports.
Values are assigned explicitly (not via `iota`) so inserting a new kind between existing ones doesn't renumber anything — future serialized-plan formats can assume these values are stable.
const ( // FunctionScalar: per-row function (UPPER, LOWER, ABS, etc.). FunctionScalar FunctionKind = 1 // FunctionAggregate: spans multiple rows (COUNT, SUM, MIN, MAX, AVG). FunctionAggregate FunctionKind = 2 )
func (FunctionKind) String ¶
func (k FunctionKind) String() string
String returns the kind as a debug-friendly string.
type FunctionNotFoundError ¶
type FunctionNotFoundError struct {
Name Identifier
}
FunctionNotFoundError signals a lookup miss — the function name isn't registered in the catalogue.
func (*FunctionNotFoundError) Error ¶
func (e *FunctionNotFoundError) Error() string
type FunctionSpec ¶
type FunctionSpec struct {
// Name is the canonical, case-folded function name (e.g. "COUNT").
Name string
// Kind classifies the function — scalar or aggregate (window
// functions come later).
Kind FunctionKind
// MinArgs / MaxArgs bound accepted arity. A MaxArgs of -1 means
// no upper bound (variadic).
MinArgs int
MaxArgs int
// AllowsStar reports whether the function accepts `*` as its
// argument (currently only COUNT does).
AllowsStar bool
// AllowsDistinct reports whether the function accepts a leading
// DISTINCT modifier — e.g. `COUNT(DISTINCT col)`. All aggregates
// in the SQL standard accept DISTINCT; the flag exists here so
// future scalar extensions can opt out.
AllowsDistinct bool
}
FunctionSpec describes a SQL-visible function the analyzer can resolve. The seed differentiates scalar vs aggregate functions since rule-matching / plan-building treats them differently.
func (FunctionSpec) ValidateArity ¶
func (spec FunctionSpec) ValidateArity(argCount int) error
ValidateArity reports whether argCount is acceptable for spec. Zero = no arguments. A MaxArgs of -1 is treated as "no upper bound". Returns a typed error on mismatch so callers can build user-facing messages without string-matching.
Callers handling `*` (star-argument) functions should check `AllowsStar` BEFORE calling ValidateArity and skip the arity check for the star case. The star is syntactically 0 arguments, but COUNT(*) is legal despite COUNT's MinArgs=1 — that's not a contradiction the arity check should reason about.
type Identifier ¶
type Identifier struct {
// contains filtered or unexported fields
}
Identifier is a SQL identifier — a table, column, alias, or function name. Carries the normalized form (case-folded per SQL rules) so Identifiers can live in maps and compare by `==` directly. The original source text is the caller's responsibility (store it alongside via the parse tree's token stream when you need user-spelling-preserving error messages).
Mirrors Java's `com.apple.foundationdb.relational.recordlayer. query.Identifier`, trimmed to the essential equality surface.
func FromNormalized ¶
func FromNormalized(name string) Identifier
FromNormalized wraps a string that was ALREADY normalized by the parse capture (functions.NormalizeIdentifier: unquoted segments folded UPPER, quoted segments verbatim with the quotes removed). Unlike NewUnquoted it performs NO re-normalization — re-folding a captured quoted-verbatim alias (`AS "q$1"` → captured `q$1`) would corrupt it to `Q$1` and split resolution across positions (the FROM registration and the quote-aware reference channel disagreeing on the same alias). The quoting FLAG is not recoverable from the captured string, which is fine everywhere this is used: resolution equality ignores the flag (EqualsIgnoreQuoting) and only the normalized text matters.
func FromUidContext ¶
func FromUidContext(ctx antlrgen.IUidContext, caseSensitive bool) Identifier
FromUidContext converts a single IUidContext to an Identifier. Used for unqualified references (column aliases, CTE names, etc.) where the full-id shape is overkill.
func New ¶
func New(raw string, caseSensitive bool) Identifier
New constructs an Identifier by normalizing raw per SQL rules. When caseSensitive is true, unquoted identifiers retain their source casing; otherwise they're upper-cased. Quoted identifiers always retain case regardless of caseSensitive.
func NewUnquoted ¶
func NewUnquoted(raw string) Identifier
NewUnquoted is the common path — a case-insensitive bare identifier. Equivalent to New(raw, false).
func (Identifier) EqualsIgnoreQuoting ¶
func (i Identifier) EqualsIgnoreQuoting(other Identifier) bool
EqualsIgnoreQuoting compares by Name only, ignoring the quoting flag. For most lookups a quoted-vs-unquoted distinction doesn't matter — e.g. an `ORDER BY` clause referring to `"age"` still targets the same column as a SELECT projecting `age`. Use `==` on Identifier when the quoting distinction matters (resolving against a reserved-word shadow).
func (Identifier) IsZero ¶
func (i Identifier) IsZero() bool
IsZero reports whether i is the zero-value Identifier (empty). Useful for nil-check replacement since Identifier is a value type.
func (Identifier) Name ¶
func (i Identifier) Name() string
Name returns the normalized identifier text. Two Identifiers with the same Name AND same WasQuoted are equal via `==`.
func (Identifier) WasQuoted ¶
func (i Identifier) WasQuoted() bool
WasQuoted reports whether the source text was quoted. Callers that need to preserve user intent (e.g. "keyword" vs keyword) check this flag.
type InMemoryCatalog ¶
type InMemoryCatalog struct {
// contains filtered or unexported fields
}
InMemoryCatalog is a test-friendly Catalog built from a fixed list of tables. Keeps the test surface small — production impls bridge to RecordMetaData; tests construct one of these in a line or two.
func NewInMemoryCatalog ¶
func NewInMemoryCatalog(tables ...Table) *InMemoryCatalog
NewInMemoryCatalog builds a Catalog from the given tables. Table names key the map by their canonical String() form so lookups are O(1).
func (*InMemoryCatalog) AllTableNames ¶
func (c *InMemoryCatalog) AllTableNames() []QualifiedName
AllTableNames implements Catalog. Iterates the map — order is unspecified.
func (*InMemoryCatalog) LookupTable ¶
func (c *InMemoryCatalog) LookupTable(name QualifiedName) (Table, bool)
LookupTable implements Catalog.
func (*InMemoryCatalog) TableExists ¶
func (c *InMemoryCatalog) TableExists(name QualifiedName) bool
TableExists implements Catalog.
type NestedAccessor ¶
type NestedAccessor struct {
// Name is the struct field's declared name.
Name string
// Ordinal is its position in the enclosing struct's field list.
Ordinal int
// Col is the field's own column view, so a consumer can type the result
// and a deeper descent can continue from it.
Col Column
}
NestedAccessor is one resolved step of a descent INTO a struct column — Java's FieldValue.Accessor(name, ordinal) as lookupNestedField mints it (SemanticAnalyzer.java:586-588). Ordinal is the field's position in the enclosing struct's declared field list, which is the ordinal Java's resolveFieldPath ends up storing (FieldValue.java:288-295).
type NestedResolutionError ¶
type NestedResolutionError struct {
Qualifier Identifier
Id Identifier
}
NestedResolutionError reports a reference that resolved by DESCENDING into a struct column, asked of a lookup form that cannot express the descent. It is never a user-facing error: every SQL path resolves through ResolveQualifiedColumnNested. It exists so the chain-free form fails LOUDLY instead of answering with the struct root.
func (*NestedResolutionError) Error ¶
func (e *NestedResolutionError) Error() string
type QualifiedName ¶
type QualifiedName struct {
// contains filtered or unexported fields
}
QualifiedName is a dot-separated SQL name like `schema.table.col`. The leaf (last segment) is the "simple name"; the preceding segments are the "qualifier". Both normalized together — callers never see a QualifiedName with one segment upper-cased and another in source case.
Mirrors Java's Identifier-with-qualifier shape. The Go port breaks it off into its own type because:
- Slices aren't comparable, so bundling segments into Identifier would lose the map-as-key ergonomics.
- Most callsites touch only the leaf — an unqualified Identifier stays simple.
Use QualifiedName when you need to preserve the qualifier chain (table aliases, schema-scoped tables). Use Identifier for bare column / alias references.
func FromFullIdContext ¶
func FromFullIdContext(ctx antlrgen.IFullIdContext, caseSensitive bool) QualifiedName
FromFullIdContext converts an ANTLR IFullIdContext parse-tree node to a QualifiedName. Each Uid segment is read via GetText() (which preserves source casing), then normalized per caseSensitive.
The typical call site is table-name resolution:
tbl := semantic.FromFullIdContext(tblCtx.FullId(), false)
if resolved, err := catalog.LookupTable(tbl); err != nil { ... }
Returns the zero QualifiedName when ctx is nil or has no Uid children — callers should test IsZero before trusting the result.
func FromSegments ¶
func FromSegments(segments []string, caseSensitive bool) QualifiedName
FromSegments builds a QualifiedName from already-normalized segments (each passed through NormalizeString or equivalent). Empty input returns the zero value.
func ParseQualifiedName ¶
func ParseQualifiedName(raw string, caseSensitive bool) QualifiedName
ParseQualifiedName splits a raw dotted string into a QualifiedName. Each segment is normalized independently (stripped of its own surrounding quotes, case-folded unless caseSensitive or quoted).
Semantics match Java's token-per-segment handling: `t."X"` parses as qualifier=[T], name="X" (first segment upper-cased because unquoted, second preserved because quoted).
Quote-embedded dots are NOT handled (e.g. `"a.b".c` still splits on every dot). The ANTLR parser already tokenises individual identifiers, so callers feeding us pre-tokenised segments won't hit this edge — see FromSegments for that path.
func (QualifiedName) EqualsIgnoreQuoting ¶
func (q QualifiedName) EqualsIgnoreQuoting(other QualifiedName) bool
EqualsIgnoreQuoting compares segment-by-segment by normalized text only, ignoring per-segment quoting flags. This is the common "these target the same database object" semantics that Java's Identifier.equals uses.
func (QualifiedName) IsQualified ¶
func (q QualifiedName) IsQualified() bool
IsQualified reports whether the name has at least one qualifier segment preceding the leaf.
func (QualifiedName) IsZero ¶
func (q QualifiedName) IsZero() bool
IsZero reports whether q is the zero-value (empty) QualifiedName.
func (QualifiedName) LeafIdentifier ¶
func (q QualifiedName) LeafIdentifier() Identifier
LeafIdentifier returns the leaf segment wrapped as an Identifier (preserving the leaf's wasQuoted flag). Useful when a caller resolved a qualified name and now wants to work with just the column name.
func (QualifiedName) Name ¶
func (q QualifiedName) Name() string
Name returns the leaf (last) segment. For `schema.table.col` this is `col`. Zero QualifiedName returns the empty string.
func (QualifiedName) PrefixedWith ¶
func (q QualifiedName) PrefixedWith(prefix QualifiedName) bool
PrefixedWith reports whether q starts with prefix's segments. `schema.table.col` is prefixed with `schema.table`. Useful for resolving a bare column against a set of alias-qualified candidates.
func (QualifiedName) Qualifier ¶
func (q QualifiedName) Qualifier() []string
Qualifier returns the segments before the leaf. For `schema.table.col` this returns [schema, table]. An unqualified name returns an empty slice (never nil).
func (QualifiedName) Segments ¶
func (q QualifiedName) Segments() []string
Segments returns all segments, leaf-last. Zero QualifiedName returns nil; otherwise a defensive copy.
func (QualifiedName) String ¶
func (q QualifiedName) String() string
String returns the canonical dotted representation. Suitable for map keys (two QualifiedNames with the same String value are equal under EqualsIgnoreQuoting).
type Scope ¶
type Scope struct {
// contains filtered or unexported fields
}
Scope is the set of named resolutions visible at a point during query analysis. A scope knows about the FROM-clause sources (tables + their aliases) at that level and, via parent-chain, inherits correlated sources from enclosing scopes (for nested subqueries).
Mirrors the subset of Java's `LogicalPlanFragment` + scope chain the analyzer uses for identifier resolution.
Construction: start with NewScope(parent) — parent nil means the outermost query. Call AddSource to push each FROM source as the analyzer walks FROM clauses left-to-right.
Not concurrency-safe; the analyzer is single-threaded per query.
func NewScope ¶
NewScope constructs a Scope inheriting from parent. parent may be nil for the outermost query.
func (*Scope) AddSource ¶
func (s *Scope) AddSource(src ScopeSource) error
AddSource appends a FROM-clause source. SQL aliases may repeat, including lateral shadowing sources; references are adjudicated per attribute. Explicit runtime correlation identities must remain unique, independent of alias text.
func (*Scope) AllSourcesRecursive ¶
func (s *Scope) AllSourcesRecursive() []ScopeSource
AllSourcesRecursive returns sources from this scope and every ancestor, inner-first. Useful for "did you mean?" error suggestions when a qualifier misses — callers can enumerate all visible aliases and suggest the closest.
func (*Scope) ResolveColumn ¶
func (s *Scope) ResolveColumn(id Identifier) (Column, ScopeSource, error)
ResolveColumn looks up a bare column reference (no qualifier) against the scope's sources, following the parent chain if no local match. Ambiguous matches within a single scope level (multiple tables with a column of this name) return an error — the caller should instruct the user to qualify.
Mirrors Java's resolution: inner scopes shadow outer; within a scope, ambiguity is a hard error.
func (*Scope) ResolvePathAcrossLevels ¶
func (s *Scope) ResolvePathAcrossLevels(segs []Identifier) (Column, ScopeSource, []NestedAccessor, error)
ResolvePathAcrossLevels resolves a FROM item's correlated path as Java does: resolveCorrelatedIdentifier's resolveIdentifier over the operators of this query block and every enclosing one as ONE list (LogicalPlanFragment.getLogicalOperatorsIncludingOuter). Java looks up that list twice (SemanticAnalyzer.resolveIdentifier): the qualified-only lookup first, where more than one answer is ambiguous, and the struct-relative lookup only when the first finds nothing. So a qualified reading at one level wins over a struct-relative one at another — `EXISTS (SELECT 1 FROM w AS n, n.arr AS x)` under an outer t2 whose struct column n holds arr is the inner N.ARR (measured) — and two qualified readings at different levels are 42702 (`EXISTS (SELECT 1 FROM w, w.arr AS v)` under an outer w, measured).
Each level contributes the candidates ResolvePathNested reads there (levelPathCandidates), under the strict pass and then Go's relaxed pass, which folds case; the relaxed pass runs only when the strict one answers nothing at any level. A single-segment path is not a correlated identifier (Java refuses it as an unknown table before any lookup); it keeps ResolvePathNested's innermost-level reading.
func (*Scope) ResolvePathNested ¶
func (s *Scope) ResolvePathNested(segs []Identifier) (Column, ScopeSource, []NestedAccessor, error)
ResolvePathNested resolves a dotted reference of ARBITRARY depth — the shape Java resolves natively because its Identifier carries `name` plus a `List<String> qualifier` and every rule reasons over the joined `fullyQualifiedName()` list (IdentifierVisitor.java:56-64 builds it segment by segment; SemanticAnalyzer.lookupNestedField consumes a matched PREFIX and walks whatever remains). Go's two-argument (qualifier, column) shape could express only the first two segments, so `a.n.sk` was flattened into a qualifier string "A.N" that names neither a source nor a struct column and the reference died as UNDEFINED_COLUMN.
Two candidate kinds, in Java's order (SemanticAnalyzer.resolveIdentifierMaybe runs lookup(…, matchQualifiedOnly=true) and only when that finds nothing lookup(…, false)):
- QUALIFIED, first: the leading segments name the source (its alias, or the whole NamePath), the next segment one of its columns, and the rest descend into that column (`a.id`, `a.n.sk`, `T.W.id`); or Java's doubled qualifier names a top-level column (doubledQualifierColumn, `t.t.c`). Every such candidate at the level counts, so two are an ambiguity.
- STRUCT-RELATIVE, only when no qualified candidate exists: segs[0] names a column of the source and segs[1:] descend into it (`n.sk`, `n.inner.leaf`). It carries no source qualifier, so it is tried against EVERY source in scope. Java's qualified lookup cannot produce it (a named operator's attributes carry the operator's name, and a reference without it is no prefix of theirs), so `x.f` over a table x with a column f and a struct column x holding f is x's f, as Java answers (measured).
The qualified arm is what keeps two sources declaring the same struct apart: `a.n.sk` and `b.n.sk` each match exactly one source because the leading segment is compared against the source ALIAS, not discarded. The RELAXED pass (the declared case over-resolution) takes both kinds together, so a fold that reaches a field by both readings is ambiguous rather than the first reading's, as resolveUpdateColumn's fold is.
The scope-chain walk is unchanged from the two-segment form it subsumes: ambiguity at a level is terminal, a zero-match level falls through to the parent, and exhaustion reports ColumnNotFound when some alias matched somewhere and SourceNotFound when nothing did.
func (*Scope) ResolveQualifiedColumn ¶
func (s *Scope) ResolveQualifiedColumn(qualifier, col Identifier) (Column, ScopeSource, error)
ResolveQualifiedColumn handles `alias.col` with Java's PER-ATTRIBUTE semantics (SemanticAnalyzer.resolveIdentifierMaybe + resolveAcrossFragments):
- ALL alias-matching sources at a level are candidates; >1 carrying the column is ambiguous (42702 at the caller) — ambiguity is TERMINAL, it never falls through to a parent scope;
- ZERO matches at a level fall through to the PARENT — even when the alias exists locally without the column (the correlated shadow shape: `SELECT p.v FROM t1 AS p WHERE EXISTS(SELECT 1 FROM t2 AS p WHERE p.v = 10)` ANSWERS in Java, live-verified);
- at chain exhaustion: ColumnNotFoundError when some alias-matching source existed anywhere on the chain (named after the innermost one), SourceNotFoundError when the qualifier matched nothing.
This is the chain-free form, for callers that need only the resolved (column, source) IDENTITY. A resolution that DESCENDED into a struct column has no identity to report without its chain — the Column it would hand back is the struct ROOT, not the field the reference named — so this form DECLINES it with NestedResolutionError rather than returning the root. Returning the root is a wrong-column answer that no caller can detect, and a caller that wants the descent already has ResolveQualifiedColumnNested.
func (*Scope) ResolveQualifiedColumnNested ¶
func (s *Scope) ResolveQualifiedColumnNested(qualifier, col Identifier) (Column, ScopeSource, []NestedAccessor, error)
ResolveQualifiedColumnNested is ResolveQualifiedColumn plus Java's lookupNestedField rule (SemanticAnalyzer.java:481-488 — the fifth and last matching rule `lookup` applies per output attribute).
Two candidate kinds, in Java's order (ResolvePathNested):
- a DIRECT match: `qualifier` names a FROM source and `col` is one of its columns. This is the shape every reference had before struct columns existed.
- a NESTED match: `qualifier` names a STRUCT COLUMN of some source in scope and `col` is one of that struct's fields, so the reference `home_address.city` descends rather than addressing a source.
Java 4.14.2.0's resolveIdentifierMaybe looks the direct (qualified) match up first and the nested one only when that finds nothing, so a source's column outranks a same-named struct column's field (measured: `SELECT h.f FROM y, h` is table h's f). Two candidates of one kind at a level are an ambiguity ("Ambiguous reference %s", ErrorCode.AMBIGUOUS_COLUMN).
Everything else about the level walk is unchanged, including the property that a zero-match level falls through to the parent.
func (*Scope) ResolveSourceQualifiedPath ¶
func (s *Scope) ResolveSourceQualifiedPath(segs []Identifier) (Column, ScopeSource, []NestedAccessor, error)
ResolveSourceQualifiedPath resolves a path whose leading segment is already proven by the grammar/caller to name a FROM source. Unlike ResolvePathNested, it does not also consider the leading segment as a struct column, which a FROM item's path never is once the FROM-item classifier has established that its first segment names a preceding source (`FROM t, t.records AS item, item.item AS leaf`). Nor does it take the doubled reading: its callers resolve Go's own expansion of a FROM item's path through a prior unnest's whole element (`item.m` read as `item.item.m`, unnestSemanticPath), never a path a user spelled, whose doubled reading ResolvePathNested and ResolvePathAcrossLevels take (`FROM w, w.w.arr AS x` unnests w.arr, measured).
Duplicate source aliases retain Java's per-attribute ambiguity rule: all alias-matching sources at one scope level are considered, and more than one complete match is loud. Zero matches fall through to the parent exactly as ResolvePathNested does. An ordinary expression resolves through ResolvePathNested, which weighs the source-qualified reading against the struct-relative and doubled ones as Java's lookup does.
func (*Scope) Sources ¶
func (s *Scope) Sources() []ScopeSource
Sources returns the FROM-clause sources at this scope level (defensive copy, does NOT include parent sources).
func (*Scope) WithUnnamedSources ¶
WithUnnamedSources returns this level with every source Unnamed, under the same parent: the level an aggregate query block's select list, GROUP BY, HAVING and ORDER BY resolve against, Java's generateSelectWhere operator over the block's sources. The receiver is unchanged.
type ScopeSource ¶
type ScopeSource struct {
CTE *logical.CTEProducer
// Table is the resolved schema-level table.
Table Table
// Alias is the name used to reference this source in the
// enclosing query (column qualifier). For `FROM t AS x` → Alias
// is `x`; for `FROM t` with no alias → Alias is `t`.
Alias Identifier
// UnqualifiedOutput distinguishes unnamed SQL operators from their private
// runtime correlation aliases. Their inherited output names remain eligible
// for SELECT alias lookup; named operators qualify those same attributes.
UnqualifiedOutput bool
// CorrelationName is the identifier the analyzer uses to tie
// this source back to a Quantifier when building
// QuantifiedObjectValue / FieldValue trees that reference it.
// Stored as a string so the semantic package doesn't take a
// dependency on cascades/values/CorrelationIdentifier — callers wrap
// this into a cascades.values.CorrelationIdentifier themselves.
CorrelationName string
// NamePath, when it has two or more segments, is the name SQL references
// this source by in place of Alias: an unaliased table qualified by its
// schema template's name (`FROM T.W`) is named by the whole qualified
// identifier, as Java names the table operator by it
// (LogicalOperator.generateTableAccess with no alias), so `T.W.col`
// qualifies its columns and `W.col` does not. Alias then carries only the
// last segment, for messages; CorrelationName is unaffected.
NamePath []Identifier
// AdditionalQualifiers are query-block-local spellings that may qualify
// this source without changing its runtime correlation identity. The narrow
// live use is Java's table-first alias/schema collision: in
// `FROM PA AS s, s.PB AS B`, where `s` is also the schema template's name
// (the qualifier a table name may carry), `PA.ID`
// continues to address the PA source even though its range alias is `s`.
// Ordinary aliased sources leave this empty, so SQL's usual alias-hides-table
// rule and the correlation mint remain unchanged.
AdditionalQualifiers []Identifier
// Unnamed marks a source this scope level reads through an operator Java
// builds with preserved expression names and no name
// (LogicalOperator.newOperatorWithPreservedExpressionNames): an aggregate
// query block's select-where operator, which its select list, GROUP BY,
// HAVING and ORDER BY resolve against (QueryVisitor.visitSimpleTable), and
// an outer join's operator, which replaces the sources it joins for every
// clause after it (QueryVisitor.wrapOperandsForOuterJoin, and
// collapseLeftSideOperators before an outer join's ON when two or more
// sources precede it). Each attribute keeps its qualified name, so `x.f`
// still names x's column f, but SemanticAnalyzer.lookup's doubled reading
// needs the operator's name, and lookupNestedField clears an unnamed
// operator's qualifier: a struct column's field is reached by the column's
// bare name in the qualified pass too, counted beside the qualified
// reading, and a qualified path through a column is tried only when the
// path does not begin with that column's bare name. Measured in
// conformance/ws_f_table_qualifier_conformance_test.go.
Unnamed bool
// Shadowing marks a virtual AS/AT source whose value construction must keep
// the exact element/ordinal correlation. It grants no lookup precedence:
// every visible matching attribute participates in ordinary ambiguity.
Shadowing bool
// FlowedColumns is the exact row layout carried by this source's quantified
// object when that layout differs from the columns exposed for SQL name
// resolution. It is nil for ordinary tables and for a scalar lateral-unnest
// element (whose quantified object is the whole element). WITH ORDINALITY is
// the motivating row-valued virtual source: SQL exposes the AS/AT aliases,
// while an AT-only source still physically carries the unexposed element in
// slot 0 and the ordinal in slot 1.
FlowedColumns []Column
// FlowedObject is the exact whole value carried by a non-row virtual
// source. A record element publishes its fields and an ephemeral whole
// alias while the quantifier still flows the original nominal record type;
// neither the extra alias nor its SQL position is a physical row slot.
FlowedObject *Column
// ColumnOrdinals maps SQL attribute positions to physical row slots when
// they are not parallel to FlowedColumns (for example, AT without AS).
// A whole-object attribute uses -1 instead of a fabricated row slot.
ColumnOrdinals []int
// FlowedNullable is the record-level nullability of FlowedColumns. It is
// meaningful only when FlowedColumns is non-empty.
FlowedNullable bool
// HiddenColumns names columns of this source that UNQUALIFIED
// references skip — Java's Expression visibility
// (SemanticAnalyzer.java:468: an unqualified reference ignores a
// non-visible attribute; a qualified reference still binds it). A
// JOIN … USING marks the RIGHT side's copy of each USING column
// hidden (QueryVisitor.resolveJoinUsingClause → asHidden), which is
// what makes a bare reference to the USING column resolve the LEFT
// copy instead of being ambiguous. Keys are UPPER-folded bare names.
HiddenColumns map[string]struct{}
}
ScopeSource is one FROM-clause entry: a resolved Table plus the alias it's visible under. Alias is always non-zero — when the user doesn't write AS, the Table's own name fills in.
func (ScopeSource) NamedBy ¶
func (s ScopeSource) NamedBy(q Identifier) bool
NamedBy reports whether SQL names this source by the single identifier q: its alias, or its table's name when unaliased. A source named by a qualified path (NamePath) is never named by one identifier, its last segment included. AdditionalQualifiers are not consulted: they qualify columns, they do not name the source.
type SourceNotFoundError ¶
type SourceNotFoundError struct {
Alias Identifier
Available []Identifier
// Path is the column reference as written (`Q.ID`) when a column
// lookup, not a star expansion, missed the qualifier.
Path []Identifier
}
SourceNotFoundError is returned when a qualifier doesn't match any FROM-clause alias in the scope chain. Carries the list of available aliases (inner-first) so callers can render a "did you mean?" suggestion.
func (*SourceNotFoundError) Error ¶
func (e *SourceNotFoundError) Error() string
func (*SourceNotFoundError) Reference ¶
func (e *SourceNotFoundError) Reference() string
Reference renders the reference as written: the column path when a column lookup missed its qualifier, else the qualifier alone.
type StaticTable ¶
type StaticTable struct {
TableName QualifiedName
TableColumns []Column
TableIndexes []string
}
StaticTable is a test-friendly Table impl backing InMemoryCatalog. Production code should implement Table directly (bridging to RecordType) rather than use this value-type.
func (*StaticTable) Columns ¶
func (t *StaticTable) Columns() []Column
Columns implements Table; returns a defensive copy so callers can't mutate the backing slice.
func (*StaticTable) LookupColumn ¶
func (t *StaticTable) LookupColumn(id Identifier) (Column, bool)
LookupColumn implements Table — EXACT-name match on Identifier.Name (EqualsIgnoreQuoting ignores only the quoting FLAG, never case). Unquoted lookups arrive pre-folded by the identifier constructor, so folded registrations match; a case-preserved quoted name matches only its exact spelling.
type Table ¶
type Table interface {
// Name returns the qualified table name.
Name() QualifiedName
// Columns returns the table's column definitions in declared
// order. Empty slice if the table has no columns (a valid state
// for views / CTEs). Never nil.
Columns() []Column
// LookupColumn returns a Column by identifier, matching
// case-insensitively under SQL rules (the Identifier's
// normalized form). Returns (Column{}, false) if no match.
LookupColumn(id Identifier) (Column, bool)
// Indexes returns the index names defined on this table. The
// seed returns just names; richer IndexInfo follows once
// index-pushdown rules need per-index metadata.
Indexes() []string
}
Table is the analyzer's view of a single SQL table. Minimal for the seed — Name + Columns + Indexes. Richer methods (PK shape, fields-of-interest, RecordType bridging) land as the analyzer grows.
type TableNotFoundError ¶
type TableNotFoundError struct {
Name QualifiedName
}
TableNotFoundError is returned when ResolveTable can't find a table. Carries the qualified name the caller requested; follows the error-type pattern from CLAUDE.md (Java exception = Go error struct).
func (*TableNotFoundError) Error ¶
func (e *TableNotFoundError) Error() string
type UnresolvableSourceError ¶
type UnresolvableSourceError struct {
Alias Identifier
}
UnresolvableSourceError reports an attempt to add a scope source with no Table — the declared-but-underivable CTE tombstone reaching a resolver.
func (*UnresolvableSourceError) Error ¶
func (e *UnresolvableSourceError) Error() string
type UnsupportedFromShapeError ¶
type UnsupportedFromShapeError struct {
Shape string
}
UnsupportedFromShapeError signals a FROM-clause shape the seed analyzer doesn't handle yet. Carried up so callers can fall back to the existing logical-builder path rather than erroring out at the SQL level.
func (*UnsupportedFromShapeError) Error ¶
func (e *UnsupportedFromShapeError) Error() string