Skip to main content

uqa_sql/routines/
dependencies.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Bind stored routine relation, column, and routine identities against fresh catalog inputs.
8
9use super::{compilation::RoutineCompilationContext, CompiledFunctionBody};
10use crate::{
11    ast::{CreateFunction, FunctionBody},
12    binding::{
13        bind_expression_plan_routines_for_storage,
14        stored_columns::StoredSourceCatalog,
15        stored_relations::bind_stored_statement_relations,
16        stored_routines::{
17            bind_catalog_statement_routines, collect_expression_routine_references,
18            CatalogRoutineContext,
19        },
20    },
21    catalog::{resolution::RelationLookupMode, stored_ast},
22    plan::ExpressionPlan,
23    RowSchema, SQLError,
24};
25
26#[derive(Clone, Copy)]
27pub enum RoutineCompilationMode {
28    Definition,
29    Persisted,
30}
31
32pub fn bind_routine_definition_dependencies(
33    context: &RoutineCompilationContext<'_>,
34    sources: &dyn StoredSourceCatalog,
35    def: &mut CreateFunction,
36    mode: RoutineCompilationMode,
37) -> Result<bool, SQLError> {
38    let mut changed = bind_sql_standard_body_relations(context, sources, def, mode)?;
39    for parameter in &mut def.params {
40        let Some(default) = &mut parameter.default else {
41            continue;
42        };
43        let mut plan = ExpressionPlan::lower_with(default.clone(), &|name: &str| {
44            context.catalog.has_registered_aggregate_function(name)
45        });
46        let binding = context.catalog.binding_snapshot()?;
47        bind_expression_plan_routines_for_storage(
48            context.routines,
49            &mut plan,
50            &[],
51            &binding.context(),
52            &RowSchema::default(),
53        )?;
54        let references = collect_expression_routine_references(&plan)?;
55        changed |= stored_ast::bind_stored_expression_routines(default, &references)?;
56    }
57    Ok(changed)
58}
59
60fn bind_sql_standard_body_relations(
61    context: &RoutineCompilationContext<'_>,
62    sources: &dyn StoredSourceCatalog,
63    def: &mut CreateFunction,
64    mode: RoutineCompilationMode,
65) -> Result<bool, SQLError> {
66    let FunctionBody::Statements(statements) = &mut def.body else {
67        return Ok(false);
68    };
69    let mut changed = false;
70    for statement in statements {
71        changed |= bind_stored_statement_relations(
72            context.relations,
73            statement,
74            RelationLookupMode::Dynamic,
75            matches!(mode, RoutineCompilationMode::Persisted),
76            "SQL routine body",
77        )?;
78        changed |=
79            super::merge_columns::bind_stored_merge_target_columns(context.merge, statement)?;
80        changed |= sources.stored_source_columns().bind_statement(statement)?;
81    }
82    Ok(changed)
83}
84
85pub fn bind_sql_standard_body_routines(
86    context: &RoutineCompilationContext<'_>,
87    def: &mut CreateFunction,
88    compiled: &CompiledFunctionBody,
89) -> Result<bool, SQLError> {
90    let FunctionBody::Statements(statements) = &mut def.body else {
91        return Ok(false);
92    };
93    let CompiledFunctionBody::SQL(plans) = compiled else {
94        return Err(SQLError::Internal(format!(
95            "SQL-standard routine `{}` did not compile to SQL plans",
96            def.name
97        )));
98    };
99    if statements.len() != plans.len() {
100        return Err(SQLError::Internal(format!(
101            "SQL-standard routine `{}` has {} statements but {} plans",
102            def.name,
103            statements.len(),
104            plans.len()
105        )));
106    }
107    let mut changed = false;
108    for (statement, plan) in statements.iter_mut().zip(plans) {
109        let binding = context.catalog.binding_snapshot()?;
110        let routines = bind_catalog_statement_routines(
111            &CatalogRoutineContext {
112                routines: context.routines,
113                binding: &binding.context(),
114            },
115            plan,
116        )?;
117        changed |= stored_ast::bind_stored_statement_routines(statement, &routines.references)?;
118    }
119    Ok(changed)
120}