use super::{
optimize_unified_plan, reorder_unified_plan_joins, AggregateClassifier, JoinGraphError,
RelationStats, ScalarExpr, SourcePlan, UnifiedPlan,
};
use crate::LocalAccessEstimate;
#[derive(Debug, thiserror::Error)]
pub enum OptimizerError {
#[error(transparent)]
Expression(#[from] uqa_sql::SQLError),
#[error(transparent)]
JoinGraph(#[from] JoinGraphError),
}
pub type OptimizerResult<T> = Result<T, OptimizerError>;
pub type ConstantEvaluator = fn(&ScalarExpr) -> Result<uqa_core::Value, uqa_sql::SQLError>;
pub fn optimize_scalar_expression(
expression: &mut ScalarExpr,
config: &OptimizerConfig,
) -> Result<(), uqa_sql::SQLError> {
super::optimize_scalar_slot(expression, config)
}
#[derive(Debug, Clone)]
pub struct OptimizerConfig<'a> {
pub coerced_conditionals: bool,
pub enable_filter_pushdown: bool,
pub enable_boolean_simplify: bool,
pub enable_vector_threshold_merge: bool,
pub enable_join_reordering: bool,
pub constant_evaluator: ConstantEvaluator,
pub builtin_permissions: Option<
std::sync::Arc<dyn uqa_sql::catalog::security::builtin_routines::BuiltinRoutineExecution>,
>,
pub routine_inlining: Option<uqa_sql::routines::inlining::RoutineInliningContext<'a>>,
pub(super) active_inline_routines: Vec<[u8; 16]>,
}
impl OptimizerConfig<'_> {
#[must_use]
pub const fn new(constant_evaluator: ConstantEvaluator) -> Self {
Self {
coerced_conditionals: false,
enable_filter_pushdown: true,
enable_boolean_simplify: true,
enable_vector_threshold_merge: true,
enable_join_reordering: true,
constant_evaluator,
builtin_permissions: None,
routine_inlining: None,
active_inline_routines: Vec::new(),
}
}
}
pub trait SourceStatistics {
fn relation_statistics(&self, table: &str) -> Option<RelationStats>;
fn source_access_estimate(&self, _source: &SourcePlan) -> Option<LocalAccessEstimate> {
None
}
fn local_access_estimate(
&self,
_table: &str,
_predicate: &ScalarExpr,
) -> Option<LocalAccessEstimate> {
None
}
}
impl<F> SourceStatistics for F
where
F: Fn(&str) -> Option<RelationStats>,
{
fn relation_statistics(&self, table: &str) -> Option<RelationStats> {
self(table)
}
}
struct NoSourceStatistics;
impl SourceStatistics for NoSourceStatistics {
fn relation_statistics(&self, _table: &str) -> Option<RelationStats> {
None
}
}
struct NoRegisteredAggregates;
impl AggregateClassifier for NoRegisteredAggregates {
fn is_registered_aggregate(&self, _name: &str) -> bool {
false
}
}
pub fn optimize(plan: UnifiedPlan, config: &OptimizerConfig) -> OptimizerResult<UnifiedPlan> {
optimize_with_aggregates_and_statistics(
plan,
config,
&NoRegisteredAggregates,
&NoSourceStatistics,
)
}
pub fn optimize_with_aggregates(
plan: UnifiedPlan,
config: &OptimizerConfig,
aggregates: &dyn AggregateClassifier,
) -> OptimizerResult<UnifiedPlan> {
optimize_with_aggregates_and_statistics(plan, config, aggregates, &NoSourceStatistics)
}
pub fn optimize_with_statistics(
plan: UnifiedPlan,
config: &OptimizerConfig,
statistics: &dyn SourceStatistics,
) -> OptimizerResult<UnifiedPlan> {
optimize_with_aggregates_and_statistics(plan, config, &NoRegisteredAggregates, statistics)
}
pub fn optimize_with_aggregates_and_statistics(
mut plan: UnifiedPlan,
config: &OptimizerConfig,
aggregates: &dyn AggregateClassifier,
statistics: &dyn SourceStatistics,
) -> OptimizerResult<UnifiedPlan> {
optimize_unified_plan(&mut plan, config, aggregates)?;
if config.enable_join_reordering {
reorder_unified_plan_joins(&mut plan, statistics)?;
}
Ok(plan)
}