Documentation
¶
Overview ¶
Package semcheck provides Tier 1 (zero-config) semantic checks that operate on parsed ASTs without a schema catalog or cluster connection. Today there are two zero-config phases: function-name resolution (CheckFunctionNames, which surfaces 42883 typos with "did you mean?" suggestions) and expression type checking (CheckExprTypes, which detects type mismatches in literal expressions and builtin function calls using MakeSemaContext(nil)). Builtin function metadata is registered at startup via internal/builtinstubs, enabling both phases to operate without a live database.
Expressions that reference columns, subqueries, placeholders, or FuncExprs whose bare names are unknown to the builtin registry are silently skipped by CheckExprTypes. Columns/subqueries/placeholders are skipped because resolving them requires a catalog (Tier 2) or connection (Tier 3); unknown function names are skipped to avoid double-reporting the structured 42883 already produced by CheckFunctionNames.
Index ¶
- func CheckColumnNames(stmts statements.Statements, fullSQL string, cat *catalog.Catalog) []output.Error
- func CheckExprTypes(stmts statements.Statements, fullSQL string) []output.Error
- func CheckFunctionNames(stmts statements.Statements, fullSQL string) []output.Error
- func CheckTableNames(stmts statements.Statements, fullSQL string, cat *catalog.Catalog) []output.Error
- type Result
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CheckColumnNames ¶
func CheckColumnNames(stmts statements.Statements, fullSQL string, cat *catalog.Catalog) []output.Error
CheckColumnNames walks every statement in stmts and reports column references that do not resolve in the per-statement table scope.
Each query block (SELECT/INSERT/UPDATE/DELETE) gets a scope built from its FROM/JOIN/USING/INSERT-target/UPDATE-target sources. Every source is one of:
- a catalog table — its columns are known and refs against it are checked;
- a derived source (CTE body or subquery in FROM) — columns are inferred from the body's projection list (or taken from an explicit `AS x(a, b)` alias list when present) and refs are checked against that. When inference is defeated (SELECT *, VALUES, an opaque expression shape) the source is recorded with columns=nil and refs are silently skipped;
- an unresolved table (CheckTableNames already flagged it), numeric TableRef, ROWS FROM, or other opaque shape — recorded with columns=nil so refs against it are silently skipped, avoiding a cascade of false-positive unknown_column errors.
Subqueries appearing in expressions (WHERE, SELECT-list, HAVING, ...) push a child scope so correlated refs to the outer scope still resolve.
Errors are deduplicated by (qualifier, column) across the whole input — the same missing column referenced from two statements produces one error, not two. The first reference's source position is reported. fullSQL is the original SQL text used to compute 1-based line/column positions. A nil cat or empty stmts returns nil with no work; the catalog-required wiring lives in the caller (cmd/validate.go), which only invokes this when --schema was supplied.
func CheckExprTypes ¶
func CheckExprTypes(stmts statements.Statements, fullSQL string) []output.Error
CheckExprTypes walks every expression in stmts and type-checks variable-free sub-trees using MakeSemaContext(nil). Expressions containing column references, subqueries, or placeholders are skipped. Builtin function calls are resolved against the registered stubs. Returns one output.Error per type-check failure; the slice is nil when all checked expressions are valid.
func CheckFunctionNames ¶
func CheckFunctionNames(stmts statements.Statements, fullSQL string) []output.Error
CheckFunctionNames walks every statement in stmts and reports calls to function names that do not exist in the builtin registry (tree.FunDefs, populated by internal/builtinstubs at process start).
Errors are deduplicated by lowercased function name across the whole input — a single missing name referenced from two statements produces one error, not two. The first reference's source position is reported.
Schema-qualified calls (e.g. pg_catalog.foo()) are intentionally skipped: this check is name-existence only, and a meaningful schema-qualified lookup needs the per-search-path resolver that lives behind a catalog (out of scope for issue #107).
fullSQL is the original SQL text used to compute 1-based line/column positions and to feed diag.Suggest's byte range. Returns nil when stmts is empty or every call resolves.
func CheckTableNames ¶
func CheckTableNames(stmts statements.Statements, fullSQL string, cat *catalog.Catalog) []output.Error
CheckTableNames walks every statement in stmts and reports table references that do not resolve in cat. CTE names from WITH clauses and table aliases are added to a per-statement scope so they are not flagged.
Errors are deduplicated by name across the whole input — a single missing table referenced from two statements produces one error, not two. The first reference's source position is reported.
fullSQL is the original SQL text used to compute 1-based line/column positions. A nil cat or empty stmts returns nil with no work; the catalog-required wiring lives in the caller (cmd/validate.go), which only invokes this when --schema was supplied.
Types ¶
type Result ¶
type Result struct {
FunctionResolution validateresult.CheckStatus
TypeCheck validateresult.CheckStatus
NameResolution validateresult.CheckStatus
}
Result reports the per-phase outcome of a Run. The fields mirror validateresult.Checks (minus Syntax, which is the parser's responsibility) so callers can copy directly into the envelope's Checks payload without translation. Field declaration order matches Run's phase-execution order.
func Run ¶
func Run(stmts statements.Statements, sql string, cat *catalog.Catalog) (Result, []output.Error)
Run executes every semantic check against the parsed input and returns the per-phase Result together with the accumulated diagnostics in a stable phase order: function-name errors, then type errors, then table-name errors, then column-name errors. Errors from later phases are not suppressed by errors in earlier phases — the runner exists so a single invocation surfaces every diagnostic the user could fix in one editing pass.
Function-name resolution runs first because its diagnostics are the most actionable output for the common typo case, and because CheckExprTypes deliberately skips FuncExprs whose bare names are unresolved (see containsUnknownFunc in expr.go) so the user sees exactly one structured 42883 per typo rather than a duplicate from the type checker.
Cascade suppression for downstream column references against an unresolved table is the responsibility of CheckColumnNames (see unknownSource in names.go), not the runner; Run simply chains the existing checkers.
cat may be nil. When nil, name resolution is skipped and NameResolution is reported as CheckSkipped — the caller decides whether to also append a capability_required warning to the envelope (today only the CLI's --schema-less single-input path does this). Function-name resolution does not need a catalog (the builtin registry is process-global) and always runs.
Inputs are not copied: stmts and sql are read by the underlying checkers and must remain valid for the duration of the call.