Skip to main content

uqa_planner/optimizer/
api.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Optimizer configuration, statistics seam, and public entry points.
8
9use super::{
10    optimize_unified_plan, reorder_unified_plan_joins, AggregateClassifier, JoinGraphError,
11    RelationStats, ScalarExpr, SourcePlan, UnifiedPlan,
12};
13use crate::LocalAccessEstimate;
14
15/// Errors raised while simplifying expressions or choosing a physical join order.
16#[derive(Debug, thiserror::Error)]
17pub enum OptimizerError {
18    #[error(transparent)]
19    Expression(#[from] uqa_sql::SQLError),
20    #[error(transparent)]
21    JoinGraph(#[from] JoinGraphError),
22}
23
24pub type OptimizerResult<T> = Result<T, OptimizerError>;
25
26/// Evaluate a planner-proven constant using the runtime selected by the caller.
27pub type ConstantEvaluator = fn(&ScalarExpr) -> Result<uqa_core::Value, uqa_sql::SQLError>;
28
29/// Plan an already analyzed scalar at its owning command's preparation boundary. This uses the same lazy conditional simplification and constant-error propagation as query planning.
30pub fn optimize_scalar_expression(
31    expression: &mut ScalarExpr,
32    config: &OptimizerConfig,
33) -> Result<(), uqa_sql::SQLError> {
34    super::optimize_scalar_slot(expression, config)
35}
36
37#[derive(Debug, Clone)]
38pub struct OptimizerConfig<'a> {
39    /// CASE/COALESCE result types and arm coercions have already been retained by the caller. Unreachable arms can be discarded without changing subsequent type inference.
40    pub coerced_conditionals: bool,
41    pub enable_filter_pushdown: bool,
42    pub enable_boolean_simplify: bool,
43    pub enable_vector_threshold_merge: bool,
44    pub enable_join_reordering: bool,
45    /// Shared scalar execution supplied by the engine composition boundary.
46    pub constant_evaluator: ConstantEvaluator,
47    /// Retained selected-call authority for immutable evaluation. An absent catalog has no mutable routine ACLs.
48    pub builtin_permissions: Option<
49        std::sync::Arc<dyn uqa_sql::catalog::security::builtin_routines::BuiltinRoutineExecution>,
50    >,
51    /// Selected SQL functions expanded in the caller's plan, never in stored syntax.
52    pub routine_inlining: Option<uqa_sql::routines::inlining::RoutineInliningContext<'a>>,
53    pub(super) active_inline_routines: Vec<[u8; 16]>,
54}
55
56impl OptimizerConfig<'_> {
57    #[must_use]
58    pub const fn new(constant_evaluator: ConstantEvaluator) -> Self {
59        Self {
60            coerced_conditionals: false,
61            enable_filter_pushdown: true,
62            enable_boolean_simplify: true,
63            enable_vector_threshold_merge: true,
64            enable_join_reordering: true,
65            constant_evaluator,
66            builtin_permissions: None,
67            routine_inlining: None,
68            active_inline_routines: Vec::new(),
69        }
70    }
71}
72
73/// Cardinality and column statistics used to cost base relations during
74/// join enumeration. Engines implement this against their live catalogue;
75/// callers without a catalogue still get deterministic DPccp enumeration
76/// from the optimizer's fallback cardinality.
77pub trait SourceStatistics {
78    fn relation_statistics(&self, table: &str) -> Option<RelationStats>;
79
80    /// Estimate a non-table source that can participate as one atom in an
81    /// inner-join region. Returning `None` keeps that region in SQL order.
82    fn source_access_estimate(&self, _source: &SourcePlan) -> Option<LocalAccessEstimate> {
83        None
84    }
85
86    /// Estimate a predicate that references only `table`. Returning `None`
87    /// delegates to the planner's scalar selectivity model.
88    fn local_access_estimate(
89        &self,
90        _table: &str,
91        _predicate: &ScalarExpr,
92    ) -> Option<LocalAccessEstimate> {
93        None
94    }
95}
96
97impl<F> SourceStatistics for F
98where
99    F: Fn(&str) -> Option<RelationStats>,
100{
101    fn relation_statistics(&self, table: &str) -> Option<RelationStats> {
102        self(table)
103    }
104}
105
106struct NoSourceStatistics;
107
108impl SourceStatistics for NoSourceStatistics {
109    fn relation_statistics(&self, _table: &str) -> Option<RelationStats> {
110        None
111    }
112}
113
114struct NoRegisteredAggregates;
115
116impl AggregateClassifier for NoRegisteredAggregates {
117    fn is_registered_aggregate(&self, _name: &str) -> bool {
118        false
119    }
120}
121
122/// Optimize a fully lowered plan using the built-in aggregate catalogue.
123pub fn optimize(plan: UnifiedPlan, config: &OptimizerConfig) -> OptimizerResult<UnifiedPlan> {
124    optimize_with_aggregates_and_statistics(
125        plan,
126        config,
127        &NoRegisteredAggregates,
128        &NoSourceStatistics,
129    )
130}
131
132/// Optimize a fully lowered plan while classifying engine-local aggregates.
133pub fn optimize_with_aggregates(
134    plan: UnifiedPlan,
135    config: &OptimizerConfig,
136    aggregates: &dyn AggregateClassifier,
137) -> OptimizerResult<UnifiedPlan> {
138    optimize_with_aggregates_and_statistics(plan, config, aggregates, &NoSourceStatistics)
139}
140
141/// Optimize a fully lowered plan with caller-provided relation statistics.
142pub fn optimize_with_statistics(
143    plan: UnifiedPlan,
144    config: &OptimizerConfig,
145    statistics: &dyn SourceStatistics,
146) -> OptimizerResult<UnifiedPlan> {
147    optimize_with_aggregates_and_statistics(plan, config, &NoRegisteredAggregates, statistics)
148}
149
150/// Optimize a fully lowered plan while classifying engine-local aggregates
151/// and costing join orders from the engine's relation statistics.
152pub fn optimize_with_aggregates_and_statistics(
153    mut plan: UnifiedPlan,
154    config: &OptimizerConfig,
155    aggregates: &dyn AggregateClassifier,
156    statistics: &dyn SourceStatistics,
157) -> OptimizerResult<UnifiedPlan> {
158    optimize_unified_plan(&mut plan, config, aggregates)?;
159    if config.enable_join_reordering {
160        reorder_unified_plan_joins(&mut plan, statistics)?;
161    }
162    Ok(plan)
163}