Skip to main content

uqa_sql/schema/dependencies/
registration.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Bind and rewrite durable routine identities in stored SQL schema expressions.
8use super::{regclass::SchemaReferenceCatalog, walk_schema_expr_mut};
9use crate::schema::{SchemaBindingContext, SchemaExpressionCatalog};
10use crate::semantics::conflict::InferenceBindingScope;
11use crate::{
12    ast::{ColumnDef, Expr},
13    SQLError,
14};
15
16pub struct SchemaDependencyBindingContext<'a> {
17    pub references: &'a dyn SchemaReferenceCatalog,
18    pub schema: &'a dyn SchemaExpressionCatalog,
19    pub bindings: &'a dyn InferenceBindingScope,
20}
21
22pub fn rewrite_schema_routine_references(
23    columns: &mut [crate::ast::ColumnDef],
24    checks: &mut [crate::ast::TableCheck],
25    target: &crate::ast::FunctionBinding,
26    new_name: &str,
27) -> Result<bool, String> {
28    let mut changed = false;
29    for column in columns {
30        for expression in [&mut column.default, &mut column.check]
31            .into_iter()
32            .flatten()
33        {
34            changed |= crate::catalog::stored_ast::rewrite_expression_routine_identity(
35                expression, target, new_name,
36            )
37            .map_err(|error| error.to_string())?;
38        }
39        if let Some(generated) = column.generated.as_mut() {
40            changed |= crate::catalog::stored_ast::rewrite_expression_routine_identity(
41                &mut generated.expression,
42                target,
43                new_name,
44            )
45            .map_err(|error| error.to_string())?;
46            for dependency in &mut generated.function_dependencies {
47                if crate::routines::function_binding_matches(dependency, target) {
48                    dependency.name = new_name.to_string();
49                    changed = true;
50                }
51            }
52        }
53    }
54    for check in checks {
55        changed |= crate::catalog::stored_ast::rewrite_expression_routine_identity(
56            &mut check.expr,
57            target,
58            new_name,
59        )
60        .map_err(|error| error.to_string())?;
61    }
62    Ok(changed)
63}
64
65fn schema_expr_may_require_routine_identity_binding(
66    expression: &crate::ast::Expr,
67) -> Result<bool, String> {
68    let mut expression = expression.clone();
69    let mut legacy = false;
70    walk_schema_expr_mut(&mut expression, &mut |node| {
71        if let crate::ast::Expr::Func { binding, .. } = node {
72            legacy |= binding.as_ref().is_none_or(|binding| {
73                !binding.builtin
74                    && binding.dispatch.is_none()
75                    && binding.resolution_error.is_none()
76                    && binding.object_id.is_none()
77            });
78        }
79        Ok(())
80    })?;
81    Ok(legacy)
82}
83
84pub fn schema_expr_has_legacy_routine_identity(
85    expression: &crate::ast::Expr,
86) -> Result<bool, String> {
87    let mut expression = expression.clone();
88    let mut legacy = false;
89    walk_schema_expr_mut(&mut expression, &mut |node| {
90        if let crate::ast::Expr::Func {
91            binding: Some(binding),
92            ..
93        } = node
94        {
95            legacy |= !binding.builtin
96                && binding.dispatch.is_none()
97                && binding.resolution_error.is_none()
98                && binding.object_id.is_none();
99        }
100        Ok(())
101    })?;
102    Ok(legacy)
103}
104
105pub fn bind_table_schema_routine_identities(
106    context: &SchemaDependencyBindingContext<'_>,
107    table_name: &str,
108    columns: &mut [crate::ast::ColumnDef],
109    checks: &mut [crate::ast::TableCheck],
110) -> Result<bool, String> {
111    let check_columns = columns.to_vec();
112    bind_table_schema_routine_identities_with_check_columns(
113        context,
114        table_name,
115        columns,
116        checks,
117        &check_columns,
118    )
119}
120
121pub fn bind_table_schema_routine_identities_with_check_columns(
122    context: &SchemaDependencyBindingContext<'_>,
123    table_name: &str,
124    columns: &mut [crate::ast::ColumnDef],
125    checks: &mut [crate::ast::TableCheck],
126    check_columns: &[crate::ast::ColumnDef],
127) -> Result<bool, String> {
128    let mut changed = super::regclass::bind_table_schema_regclass_constants(
129        context.references,
130        columns,
131        checks,
132        false,
133    )?;
134    for column in columns {
135        if let Some(default) = &mut column.default {
136            changed |= bind_default_routine_identities(context, table_name, &column.name, default)?;
137        }
138        if let Some(check) = &mut column.check {
139            if schema_expr_may_require_routine_identity_binding(check)? {
140                changed |=
141                    bind_check_routines(context, table_name, table_name, check_columns, check)
142                        .map_err(|error| {
143                            format!(
144                                "bind CHECK routine identities for `{table_name}`.`{}`: {error}",
145                                column.name
146                            )
147                        })?;
148            }
149        }
150    }
151    for check in checks {
152        if schema_expr_may_require_routine_identity_binding(&check.expr)? {
153            changed |= bind_check_routines(
154                context,
155                table_name,
156                table_name,
157                check_columns,
158                &mut check.expr,
159            )
160            .map_err(|error| {
161                format!("bind CHECK routine identities for `{table_name}`: {error}")
162            })?;
163        }
164    }
165    Ok(changed)
166}
167
168pub fn bind_default_routine_identities(
169    context: &SchemaDependencyBindingContext<'_>,
170    table_name: &str,
171    column_name: &str,
172    default: &mut crate::ast::Expr,
173) -> Result<bool, String> {
174    let changed =
175        super::regclass::bind_schema_regclass_constants(context.references, default, false)?;
176    if !schema_expr_may_require_routine_identity_binding(default)? {
177        return Ok(changed);
178    }
179    let bound = bind_default_routines(context, default, default.clone()).map_err(|error| {
180        format!("bind default routine identities for `{table_name}`.`{column_name}`: {error}")
181    })?;
182    Ok(changed || bound)
183}
184
185fn bind_check_routines(
186    context: &SchemaDependencyBindingContext<'_>,
187    table: &str,
188    qualifier: &str,
189    columns: &[ColumnDef],
190    expression: &mut Expr,
191) -> Result<bool, SQLError> {
192    let binding = context.bindings.binding_scope()?;
193    crate::schema::constraints::bind_stored_check_expression_routines(
194        &SchemaBindingContext {
195            catalog: context.schema,
196            binding: &binding.context(),
197        },
198        table,
199        qualifier,
200        columns,
201        expression,
202    )
203}
204fn bind_default_routines(
205    context: &SchemaDependencyBindingContext<'_>,
206    expression: &mut Expr,
207    typed: Expr,
208) -> Result<bool, SQLError> {
209    let binding = context.bindings.binding_scope()?;
210    crate::schema::defaults::bind_stored_schema_expression_routines(
211        &SchemaBindingContext {
212            catalog: context.schema,
213            binding: &binding.context(),
214        },
215        expression,
216        typed,
217    )
218}