Skip to content

LogicalValidatorNode

Overview

  • Validates the AST plan (PlanModel) against schema and RBAC policies.
  • Exists to enforce correctness and security before SQL generation.
  • Sits between ASTPlannerNode and GeneratorNode in 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)
  • errors and reasoning

Side effects:

  • None beyond in‑memory validation.

Internal Flow (Step-by-Step)

  1. If plan is missing, emit MISSING_PLAN and stop.
  2. Run _validate_static() for structural checks:
  3. _resolve_plan_tables() maps each plan alias to its schema columns and emits TABLE_NOT_FOUND for unknown tables. This stays hand-written because qualify() silently ignores relations absent from its schema.
  4. _validate_columns() builds the plan's sqlglot tree and runs qualify(..., 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.
  5. Run _validate_policy() for RBAC enforcement. This always runs, even when static validation failed or raised — the security check is never skipped.
  6. If any errors are ERROR/CRITICAL, return with errors.
  7. Otherwise return success reasoning.
  8. On exception, emit VALIDATOR_CRASH.

Contracts & Interfaces

Implements a LangGraph node callable:

def __call__(self, state: SubgraphExecutionState) -> Dict[str, Any]

Key contracts:

  • LogicalValidatorResponse
  • PipelineError

Determinism Guarantees

  • Deterministic for a fixed plan and schema snapshot.
  • No randomness in validation logic.

Error Handling

Emits PipelineError with:

  • MISSING_PLAN
  • INVALID_PLAN_STRUCTURE
  • SECURITY_VIOLATION
  • TABLE_NOT_FOUND, COLUMN_NOT_FOUND
  • JOIN_TABLE_NOT_IN_PLAN
  • VALIDATOR_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_columns controls 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 to qualify(), 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.

  • packages/nl2sql/src/nl2sql/pipeline/nodes/validator/node.py
  • packages/nl2sql/src/nl2sql/pipeline/nodes/generator/node.py (SqlVisitor, reused to build the validation tree)