Skip to main content

uqa_planner/
statement_planning.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Statement optimization and prepared-plan estimates over explicit catalog and retrieval inputs.
8use crate::UnifiedPlan;
9use statistics::CatalogSourceStatistics;
10pub use statistics::{
11    PlannerStatisticsCatalog, RetrievalSourceCosting, StatementStatisticsContext,
12    StatisticsTableState,
13};
14use uqa_sql::SQLError;
15mod parameterized;
16pub mod rule_inputs;
17mod statistics;
18
19#[cfg(test)]
20mod tests;
21
22pub fn estimate_plan(
23    context: StatementStatisticsContext<'_>,
24    plan: &UnifiedPlan,
25) -> Result<crate::plan_cost::PlanCost, SQLError> {
26    let error = std::cell::RefCell::new(None);
27    let statistics = CatalogSourceStatistics {
28        context,
29        error: &error,
30    };
31    let estimate = crate::plan_cost::PlanCostEstimator::new(&statistics).estimate(plan);
32    if let Some(error) = error.into_inner() {
33        return Err(error);
34    }
35    if !estimate.execution.is_finite()
36        || estimate.execution < 0.0
37        || !estimate.rows.is_finite()
38        || estimate.rows < 0.0
39    {
40        return Err(SQLError::Internal(
41            "prepared plan produced an invalid cost estimate".into(),
42        ));
43    }
44    Ok(estimate)
45}
46
47pub fn optimize_plan(
48    context: StatementStatisticsContext<'_>,
49    rules: &rule_inputs::RuleInputPlanningContext<'_>,
50    aggregates: &dyn crate::AggregateClassifier,
51    constant_evaluator: crate::ConstantEvaluator,
52    mut plan: UnifiedPlan,
53) -> Result<UnifiedPlan, SQLError> {
54    rule_inputs::rewrite_plan(rules, &mut plan)?;
55    let callback_error = std::cell::RefCell::new(None);
56    let statistics = CatalogSourceStatistics {
57        context,
58        error: &callback_error,
59    };
60    let optimized = optimize_plan_with_statistics(
61        context.volatility,
62        aggregates,
63        constant_evaluator,
64        plan,
65        &statistics,
66    );
67    if let Some(error) = callback_error.into_inner() {
68        return Err(error);
69    }
70    optimized
71}
72fn optimize_plan_with_statistics(
73    volatility_catalog: &dyn uqa_sql::semantics::volatility::VolatilityCatalog,
74    aggregates: &dyn crate::AggregateClassifier,
75    constant_evaluator: crate::ConstantEvaluator,
76    plan: crate::UnifiedPlan,
77    statistics: &dyn crate::SourceStatistics,
78) -> Result<crate::UnifiedPlan, SQLError> {
79    let mut optimizer_config = crate::optimizer::OptimizerConfig::new(constant_evaluator);
80    if uqa_sql::semantics::volatility::unified_plan_contains_volatile_function(
81        volatility_catalog,
82        &plan,
83    ) {
84        // Predicate prioritization and DPccp both move expressions across
85        // physical evaluation boundaries.  A VOLATILE callback may observe
86        // or mutate state on every call, so even a logically equivalent join
87        // order can change SQL-visible behavior by changing its call count.
88        optimizer_config.enable_filter_pushdown = false;
89        optimizer_config.enable_join_reordering = false;
90    }
91    let optimized = crate::optimizer::optimize_with_aggregates_and_statistics(
92        plan,
93        &optimizer_config,
94        aggregates,
95        statistics,
96    );
97    optimized.map_err(|error| match error {
98        crate::optimizer::OptimizerError::Expression(error) => error,
99        crate::optimizer::OptimizerError::JoinGraph(error) => {
100            SQLError::Internal(format!("optimize SQL join order: {error}"))
101        }
102    })
103}
104
105pub mod prepared;
106
107pub mod executable;