Skip to main content

uqa_sql/binding/
statements.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Executable statement schemas and mutation parameter analysis.
8
9use super::BindingContext;
10use crate::{
11    plan::{CommandPlan, UnifiedPlan},
12    routines::RoutineResolution,
13    RowSchema, SQLError, SQLParam, ScalarExpr,
14};
15use uqa_core::Value;
16
17/// Borrow binding inputs only when the statement's semantic branch requires them.
18pub trait StatementBindingScope {
19    fn binding_context(&self) -> Result<BindingContext<'_>, SQLError>;
20}
21pub type StatementAnalysisOperation<'a> =
22    &'a mut dyn FnMut(&dyn StatementBindingScope) -> Result<(), SQLError>;
23
24/// Retain a fresh catalog, namespace and transition scope for each statement analysis.
25pub trait StatementAnalysisScopes {
26    fn with_scope(&self, analyze: StatementAnalysisOperation<'_>) -> Result<(), SQLError>;
27}
28pub struct StatementAnalysisContext<'a> {
29    pub scopes: &'a dyn StatementAnalysisScopes,
30    pub routines: &'a dyn RoutineResolution,
31}
32
33pub fn analyze_executable_plan(
34    context: &StatementAnalysisContext<'_>,
35    plan: &UnifiedPlan,
36    params: &[SQLParam],
37) -> Result<(), SQLError> {
38    context.scopes.with_scope(&mut |scope| {
39        match plan {
40            UnifiedPlan::Query(query) => {
41                super::analyze_query_plan_schema(
42                    context.routines,
43                    query,
44                    params,
45                    &scope.binding_context()?,
46                    None,
47                )?;
48            }
49            UnifiedPlan::Command(command) => match command.as_ref() {
50                CommandPlan::Explain { body, .. } => {
51                    analyze_executable_plan(context, body, params)?;
52                }
53                CommandPlan::CreateTableAs { query, .. }
54                | CommandPlan::CreateMaterializedView { query, .. }
55                | CommandPlan::DeclareCursor { query, .. } => {
56                    super::analyze_query_plan_schema(
57                        context.routines,
58                        query,
59                        params,
60                        &scope.binding_context()?,
61                        None,
62                    )?;
63                }
64                _ => {
65                    if command.mutation_target().is_some() {
66                        analyze_command_parameters(context.routines, command, params, scope)?;
67                    }
68                    super::analyze_prepared_command_schema(
69                        context.routines,
70                        command,
71                        params,
72                        &scope.binding_context()?,
73                    )?;
74                }
75            },
76        }
77        Ok(())
78    })
79}
80
81pub fn analyze_command_parameters(
82    routines: &dyn RoutineResolution,
83    command: &CommandPlan,
84    params: &[SQLParam],
85    scope: &dyn StatementBindingScope,
86) -> Result<(), SQLError> {
87    let schema = RowSchema::default();
88    let declared = (1..=params.len())
89        .map(|index| match &params[index - 1] {
90            SQLParam::Scalar(Value::Str(_) | Value::Null) => Ok(None),
91            _ => crate::scalar_type(&ScalarExpr::Param(index), &schema, params),
92        })
93        .collect::<Result<Vec<_>, _>>()?;
94    super::infer_prepared_parameter_types(
95        routines,
96        &UnifiedPlan::Command(Box::new(command.clone())),
97        &declared,
98        &scope.binding_context()?,
99    )?;
100    Ok(())
101}
102
103#[cfg(test)]
104mod tests;