LogicalValidatorNode
Overview
- Validates the AST plan (
PlanModel) against schema and RBAC policies. - Exists to enforce correctness and security before SQL generation.
- Sits between
ASTPlannerNodeandGeneratorNodein the SQL agent subgraph. - Class:
LogicalValidatorNode - Source:
packages/nl2sql/src/nl2sql/pipeline/nodes/validator/node.py
Responsibilities
- Validate query type (READ‑only).
- Validate ordinals, aliases, joins, and column references.
- Enforce RBAC table access using strict datasource namespacing.
- Validate literal values against column stats (when available).
Column resolution is delegated to sqlglot's optimizer. The node converts the
plan into a throw‑away sqlglot expression tree (reusing the generator's
SqlVisitor) and runs sqlglot.optimizer.qualify.qualify() against a schema
built from relevant_tables. The LLM contract is unchanged: the planner still
emits a PlanModel, never SQL.
Position in Execution Graph
Upstream:
- ASTPlannerNode
Downstream:
- GeneratorNode on success
- retry_handler on retryable errors
Trigger conditions: - Executed when an AST plan is present.
flowchart LR
Planner[ASTPlannerNode] --> Validator[LogicalValidatorNode] --> Generator[GeneratorNode]
Validator --> RetryHandler[retry_handler]
Inputs
From SubgraphExecutionState:
ast_planner_response.plan(required)relevant_tables(required)user_context(required for RBAC)sub_query.datasource_id(required for namespacing)sub_query.expected_schema(optional validation)
Validation performed:
- Query type must be
READ. - Ordinals must be contiguous.
- Aliases must be unique.
- Joins must match known relationships.
- Column references must exist and be unambiguous.
Outputs
Mutations to SubgraphExecutionState:
logical_validator_response(LogicalValidatorResponse)errorsandreasoning
Side effects:
- None beyond in‑memory validation.
Internal Flow (Step-by-Step)
- If plan is missing, emit
MISSING_PLANand stop. - Run
_validate_static()for structural checks: _resolve_plan_tables()maps each plan alias to its schema columns and emitsTABLE_NOT_FOUNDfor unknown tables. This stays hand-written becausequalify()silently ignores relations absent from its schema._validate_columns()builds the plan'ssqlglottree and runsqualify(..., validate_qualify_columns=True). On failure each distinct column reference is re-probed so every bad reference is reported, and_describe_column_failure()rewrites the optimizer's SQL-oriented text into plan-oriented feedback the refiner can act on.- Run
_validate_policy()for RBAC enforcement. This always runs, even when static validation failed or raised — the security check is never skipped. - If any errors are
ERROR/CRITICAL, return with errors. - Otherwise return success reasoning.
- On exception, emit
VALIDATOR_CRASH.
Contracts & Interfaces
Implements a LangGraph node callable:
Key contracts:
LogicalValidatorResponsePipelineError
Determinism Guarantees
- Deterministic for a fixed plan and schema snapshot.
- No randomness in validation logic.
Error Handling
Emits PipelineError with:
MISSING_PLANINVALID_PLAN_STRUCTURESECURITY_VIOLATIONTABLE_NOT_FOUND,COLUMN_NOT_FOUNDJOIN_TABLE_NOT_IN_PLANVALIDATOR_CRASH
Retry + Idempotency
- No internal retry logic.
- Retry decisions are made by the subgraph router based on error severity.
Performance Characteristics
- In‑memory validation over AST and schema context.
- Complexity grows with AST size and number of tables/joins.
Observability
- Logger:
logical_validator - Emits reasoning entries and logs debug traces.
Configuration
settings.logical_validator_strict_columnscontrols severity of missing column errors.
Extension Points
- Extend validation rules by modifying
_validate_static()or_validate_policy(). - Column-resolution semantics follow
sqlglot's optimizer; changing them means changing the schema handed toqualify(), not walking the AST by hand. - Replace node in
build_sql_agent_graph()for custom validation.
Known Limitations
- Validation relies on retrieved schema context; missing tables can cause false negatives.
- No cross‑schema disambiguation beyond plan inputs.
Related Code
packages/nl2sql/src/nl2sql/pipeline/nodes/validator/node.pypackages/nl2sql/src/nl2sql/pipeline/nodes/generator/node.py(SqlVisitor, reused to build the validation tree)