Skip to main content

uqa_sql/binding/statements/
cache.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Reusable SQL analysis keeps parameter types and converted immutable inputs, never invocation values or optimizer choices.
8
9use super::{analyze_reusable_inputs, StatementAnalysisContext};
10use crate::{plan::UnifiedPlan, ColumnType, RowSchema, SQLError, SQLParam, ScalarExpr};
11
12#[derive(Clone, PartialEq)]
13struct ParameterShape {
14    declared: bool,
15    resolved: Option<ColumnType>,
16}
17
18fn parameter_shapes(params: &[SQLParam]) -> Option<Vec<ParameterShape>> {
19    let schema = RowSchema::default();
20    params
21        .iter()
22        .enumerate()
23        .map(|(index, parameter)| {
24            Some(ParameterShape {
25                declared: parameter.declared_scalar_type().is_some(),
26                resolved: crate::scalar_type(&ScalarExpr::Param(index + 1), &schema, params)
27                    .ok()?,
28            })
29        })
30        .collect()
31}
32
33/// Analyzed syntax with immutable input conversions and no optimized access paths. A caller must validate the original statement, catalog, namespace, authority and parser settings in the selected statement snapshot before offering this entry for reuse. Parameter values remain supplied by each execution; their inferred types and explicit declarations must still match.
34#[derive(Clone)]
35pub struct AnalyzedStatement {
36    plan: UnifiedPlan,
37    parameters: Vec<ParameterShape>,
38}
39
40impl AnalyzedStatement {
41    pub fn plan_for(&self, params: &[SQLParam]) -> Option<&UnifiedPlan> {
42        (parameter_shapes(params).as_ref() == Some(&self.parameters)).then_some(&self.plan)
43    }
44}
45
46/// Analyze under the current caller's scope, then retain only inputs whose conversion lifetime spans ordinary messages. A parameter whose shape cannot be captured disables reuse without changing the original analysis's result or diagnostic order.
47pub fn analyze_for_statement_reuse(
48    context: &StatementAnalysisContext<'_>,
49    mut plan: UnifiedPlan,
50    params: &[SQLParam],
51) -> Result<(UnifiedPlan, Option<AnalyzedStatement>), SQLError> {
52    let (_, reusable) = analyze_reusable_inputs(context, &mut plan, params)?;
53    let retained = reusable
54        .then(|| parameter_shapes(params))
55        .flatten()
56        .map(|parameters| AnalyzedStatement {
57            plan: plan.clone(),
58            parameters,
59        });
60    Ok((plan, retained))
61}