Skip to main content

uqa_sql/binding/statements/
procedural.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Retained procedural input analysis and its selected catalog dependencies.
8
9use super::{AnalyzedResult, BindingContext, StatementAnalysisContext};
10use crate::{
11    catalog::resolution::EffectiveSearchPath,
12    plan::{CommandPlan, UnifiedPlan},
13    prepared::dependencies::{PreparedAnalysisDependencies, PreparedDependencySnapshot},
14    routines::{declaration::RoutineTypeCatalog, resolution::RoutineOverloadContext},
15    SQLError, SQLParam,
16};
17
18#[derive(Debug)]
19pub struct ProceduralPlanAnalysis {
20    pub composite_inputs: crate::prepared::composites::CompositeInputs,
21    pub result: AnalyzedResult,
22    pub dependencies: PreparedAnalysisDependencies,
23    pub effective_search_path: Option<EffectiveSearchPath>,
24    pub dependency_snapshot: Option<PreparedDependencySnapshot>,
25}
26
27impl ProceduralPlanAnalysis {
28    /// Publish executable variants from the already successful procedural analysis.
29    pub fn prepared_definition(
30        &self,
31        plan: &UnifiedPlan,
32        params: &[SQLParam],
33    ) -> Result<crate::prepared::definition::PreparedDefinition, SQLError> {
34        let parameter_types = (1..=params.len())
35            .map(|index| {
36                crate::type_resolution::scalar_type(
37                    &crate::ScalarExpr::Param(index),
38                    &crate::RowSchema::new(Vec::new()),
39                    params,
40                )
41            })
42            .collect::<Result<Vec<_>, _>>()?;
43        let result_schema = match &self.result {
44            AnalyzedResult::Command => None,
45            AnalyzedResult::Schema(schema) => Some(schema.clone()),
46            AnalyzedResult::Rows(types) => Some(crate::RowSchema::with_types(
47                vec![String::new(); types.len()],
48                types.clone(),
49            )),
50        };
51        Ok(crate::prepared::definition::PreparedDefinition {
52            composite_inputs: self.composite_inputs.clone(),
53            logical_plan: plan.clone(),
54            parameter_types,
55            result_schema,
56            effective_search_path: self.effective_search_path.clone(),
57            dependencies: self.dependencies.clone(),
58            dependency_snapshot: self.dependency_snapshot.clone(),
59        })
60    }
61}
62
63/// Analyze a reached SQL occurrence once in its retained catalog scope. CALL
64/// resolves and converts its selected inputs before successful publication too.
65pub fn analyze_procedural_plan(
66    context: &StatementAnalysisContext<'_>,
67    overloads: &RoutineOverloadContext<'_>,
68    types: &dyn RoutineTypeCatalog,
69    plan: &mut UnifiedPlan,
70    params: &[SQLParam],
71) -> Result<ProceduralPlanAnalysis, SQLError> {
72    let mut analyzed = None;
73    context.scopes.with_scope(&mut |scope| {
74        let binding = scope.binding_context()?;
75        let (result, dependencies) =
76            analyze_inputs(context, overloads, types, plan, params, &binding)?;
77        crate::routines::compilation::bind_analyzed_sql_body_types(types, plan)?;
78        super::super::composite_inputs::retain_composite_inputs(
79            context.routines,
80            plan,
81            params,
82            &binding,
83        )?;
84        let composite_inputs =
85            crate::prepared::composites::CompositeInputs::capture(plan, context.routines)?;
86        let effective_search_path = binding.catalog.effective_search_path(&binding.resolution)?;
87        let dependency_snapshot = binding
88            .catalog
89            .prepared_dependency_snapshot(&dependencies)?;
90        analyzed = Some(ProceduralPlanAnalysis {
91            composite_inputs,
92            result,
93            dependencies,
94            effective_search_path,
95            dependency_snapshot,
96        });
97        Ok(())
98    })?;
99    analyzed.ok_or_else(|| SQLError::Internal("procedural analysis scope did not run".into()))
100}
101
102fn analyze_inputs(
103    context: &StatementAnalysisContext<'_>,
104    overloads: &RoutineOverloadContext<'_>,
105    types: &dyn RoutineTypeCatalog,
106    plan: &mut UnifiedPlan,
107    params: &[SQLParam],
108    binding: &BindingContext<'_>,
109) -> Result<(AnalyzedResult, PreparedAnalysisDependencies), SQLError> {
110    if let UnifiedPlan::Command(command) = plan {
111        match command.as_mut() {
112            CommandPlan::Call { .. } => {
113                return crate::binding::preparation::prepare_procedure_call(
114                    context, overloads, types, plan, params, binding,
115                );
116            }
117            CommandPlan::Explain { body, .. } => {
118                let (_, dependencies) =
119                    analyze_inputs(context, overloads, types, body, params, binding)?;
120                return Ok((AnalyzedResult::Command, dependencies));
121            }
122            _ => {}
123        }
124    }
125    let dependencies = crate::binding::preparation::read_procedural_inputs(
126        context.routines,
127        plan,
128        params,
129        binding,
130        context.aliases,
131    )?;
132    let result = super::analyze_bound_result(context.routines, plan, params, binding)?;
133    Ok((result, dependencies))
134}