PlanModel) against schema and RBAC policies.ASTPlannerNode and GeneratorNode in the SQL agent subgraph.LogicalValidatorNodepackages/nl2sql/src/nl2sql/pipeline/nodes/validator/node.pyColumn 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.
Upstream:
ASTPlannerNodeDownstream:
GeneratorNode on successretry_handler on retryable errorsTrigger conditions:
flowchart LR
Planner[ASTPlannerNode] --> Validator[LogicalValidatorNode] --> Generator[GeneratorNode]
Validator --> RetryHandler[retry_handler]
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:
READ.Mutations to SubgraphExecutionState:
logical_validator_response (LogicalValidatorResponse)errors and reasoningSide effects:
MISSING_PLAN and stop._validate_static() for structural checks:
_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._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._validate_policy() for RBAC enforcement. This always runs, even when
static validation failed or raised — the security check is never skipped.ERROR/CRITICAL, return with errors.VALIDATOR_CRASH.Implements a LangGraph node callable:
def __call__(self, state: SubgraphExecutionState) -> Dict[str, Any]
Key contracts:
LogicalValidatorResponsePipelineErrorEmits PipelineError with:
MISSING_PLANINVALID_PLAN_STRUCTURESECURITY_VIOLATIONTABLE_NOT_FOUND, COLUMN_NOT_FOUNDJOIN_TABLE_NOT_IN_PLANVALIDATOR_CRASHlogical_validatorsettings.logical_validator_strict_columns controls severity of missing column errors._validate_static() or _validate_policy().sqlglot’s optimizer; changing them means
changing the schema handed to qualify(), not walking the AST by hand.build_sql_agent_graph() for custom validation.packages/nl2sql/src/nl2sql/pipeline/nodes/validator/node.pypackages/nl2sql/src/nl2sql/pipeline/nodes/generator/node.py (SqlVisitor, reused to build the validation tree)