Repo-wide rules are in the root AGENTS.md.
Purpose: turn one parsed Statement into one PhysicalPlan by fixed rules — no statistics, no
cost model, no schema lookup.
Owns the PhysicalPlan tree and its per-statement plan structs (src/plan.rs), the routing rules
from AST shape to operator (src/planner.rs), the static PhysicalPlan::explain() text, and the
plan-time refusals that need only the statement: vector ordering without LIMIT
(UnboundedVectorSearch, UseRankRequiresLimit), USE VECTOR / USE RANK without a vector
ordering, a ROW_VECTOR ordering mixed with other ORDER BY items, graph depth over the engine
cap (BfsDepthExceeded, cap 10, default 5), and a MATCH with no edge.
Must not own: grammar or AST shape (parser); any decision that needs a table's declared schema —
immutability, state machines, which index serves a predicate, vector search mode resolution,
partition scope, validate_sort_key — those run in the engine executor against TableMeta.
Index selection happens in the engine.
Never add a catalog handle here to make a plan "smarter". The planner maps
MAINTAIN COUNT declarations to the engine's sort direction and passes
SET/SHOW VECTOR_SERVING_CACHE as plan variants; the engine owns storage and execution.
- Below:
contextdb-parser(ast::Statement,Expr,SelectBody) andcontextdb-core(Error,Direction,PropagationRule). - Entry point:
contextdb_planner::plan(&Statement) -> Result<PhysicalPlan>. - Above:
contextdb-engineexecutes the plan (executor.rs,executor/bounded.rs) and is the only consumer.HnswSearchexists in the enum but the planner emitsVectorSearch; the engine decides whether a built index answers it.
| Invariant | Guard |
|---|---|
A GRAPH_TABLE ... MATCH CTE plans as GraphBfs |
tests/planner_tests.rs::match_cte_routes_to_graph_bfs |
ORDER BY col <=> $q LIMIT k plans as VectorSearch; a plain predicate plans as Scan |
tests/planner_tests.rs::cosine_order_routes_to_vector_search, ::standard_select_routes_to_scan |
| A CTE the outer query never reads is not planned | tests/planner_tests.rs::unused_graph_cte_is_not_included_in_explain |
| Immutability is a runtime check, never a plan-time one | tests/planner_tests.rs::immutability_checked_at_runtime_not_plan_time |
| A hop count over the engine cap is refused | tests/planner_tests.rs::depth_over_cap_rejected |
PURGE keeps its own plan, never lowered to Delete |
tests/purge_planner_contract_tests.rs::purge_plan_remains_distinct_from_ordinary_delete |
Static explain never prints the key that selects a ROW_VECTOR source |
tests/vector_explain_redaction_contract.rs::static_vector_explain_redacts_every_supported_row_vector_source_key_shape |
USE VECTOR on a query without a vector ordering is refused |
crates/contextdb-engine/tests/vector/vector_partition_query_contract.rs::every_use_vector_mode_refuses_a_non_vector_ordering_query |
USE RANK without a vector ordering, or without LIMIT, is refused |
tests/planner_tests.rs::use_rank_requires_vector_order_and_limit |
A ROW_VECTOR ordering must be the only ORDER BY item |
crates/contextdb-engine/tests/sql_surface/sql_surface_other.rs::prv_24_row_vector_order_by_rejects_unsupported_sort_shapes |
| A declared count remains correct immediately on a populated table | crates/contextdb-engine/tests/engine_seams/declaring_a_count_on_a_table_with_rows_is_atomic_and_charged.rs::declaring_a_count_on_a_table_with_rows_is_correct_immediately |
The serving-cache setting survives reopen and clears on NONE |
crates/contextdb-engine/tests/vector/a_declared_serving_cache_is_honoured.rs::a_declared_serving_cache_survives_a_reopen_and_clears_on_none |
| Change | Where it lands |
|---|---|
| A new statement kind | A PhysicalPlan variant or plan struct in plan.rs, its arm in plan(), its explain() text; then the engine executor arm. The parser adds the Statement first. |
| A new query-shape refusal that needs only the AST | planner.rs, returning a typed contextdb_core::Error variant (add the variant in core). |
| A refusal that needs the table's declaration | Engine executor, not here. |
| New vector query options | VectorSearch fields in plan.rs, filled from the parser's SelectBody. |
MAINTAIN COUNT projection or SET/SHOW VECTOR_SERVING_CACHE routing |
planner.rs maps the parsed declaration or statement; plan.rs carries the resulting plan. |
| Columns or vector sources nested in row expressions | planner.rs recursive expression walkers. |
| Explain wording | PhysicalPlan::explain(); redact anything a bound value could leak. |
cargo test -p contextdb-planner