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