semantic

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 4, 2026 License: Apache-2.0 Imports: 5 Imported by: 0

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

Constants

This section is empty.

Variables

This section is empty.

Functions

func NormalizeString

func NormalizeString(s string, caseSensitive bool) string

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

func NewAnalyzer(catalog Catalog, caseSensitive bool) *Analyzer

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

func (a *Analyzer) CaseSensitive() bool

CaseSensitive reports the analyzer's case-sensitivity setting.

func (*Analyzer) Catalog

func (a *Analyzer) Catalog() 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

func (a *Analyzer) ExpandStar(table Table) []Column

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

func NonEphemeral(cols []Column) []Column

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 {
	Qualifier string
	Field     string
}

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

type EnumMember struct {
	Name   string
	Number int32
}

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

type FunctionArityError struct {
	Function string
	Got      int
	Min      int
	Max      int
}

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) String

func (i Identifier) String() string

String implements fmt.Stringer.

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

func NewScope(parent *Scope) *Scope

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) Parent

func (s *Scope) Parent() *Scope

Parent returns the enclosing scope, or nil if this is the outermost.

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

func (s *Scope) WithUnnamedSources() *Scope

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) Indexes

func (t *StaticTable) Indexes() []string

Indexes implements Table.

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.

func (*StaticTable) Name

func (t *StaticTable) Name() QualifiedName

Name implements Table.

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

Directories

Path Synopsis
Package rlcatalog adapts the Record Layer's `RecordMetaData` into the `semantic.Catalog` interface.
Package rlcatalog adapts the Record Layer's `RecordMetaData` into the `semantic.Catalog` interface.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL