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    context: &SchemaDependencyBindingContext<'_>,
67    expression: &crate::ast::Expr,
68) -> Result<bool, String> {
69    if crate::type_resolution::composite_rows::expression_requires_binding(
70        expression,
71        context.schema,
72    )
73    .map_err(|error| error.to_string())?
74    {
75        return Ok(true);
76    }
77    let mut expression = expression.clone();
78    let mut legacy = false;
79    walk_schema_expr_mut(&mut expression, &mut |node| {
80        if let crate::ast::Expr::Func { binding, .. } = node {
81            legacy |= binding.as_ref().is_none_or(|binding| {
82                !binding.builtin
83                    && binding.dispatch.is_none()
84                    && binding.resolution_error.is_none()
85                    && binding.object_id.is_none()
86            });
87        }
88        Ok(())
89    })?;
90    Ok(legacy)
91}
92
93pub fn schema_expr_has_legacy_routine_identity(
94    expression: &crate::ast::Expr,
95) -> Result<bool, String> {
96    let mut expression = expression.clone();
97    let mut legacy = false;
98    walk_schema_expr_mut(&mut expression, &mut |node| {
99        if let crate::ast::Expr::Func {
100            binding: Some(binding),
101            ..
102        } = node
103        {
104            legacy |= !binding.builtin
105                && binding.dispatch.is_none()
106                && binding.resolution_error.is_none()
107                && binding.object_id.is_none();
108        }
109        Ok(())
110    })?;
111    Ok(legacy)
112}
113
114pub fn bind_table_schema_routine_identities(
115    context: &SchemaDependencyBindingContext<'_>,
116    table_name: &str,
117    columns: &mut [crate::ast::ColumnDef],
118    checks: &mut [crate::ast::TableCheck],
119) -> Result<bool, String> {
120    let check_columns = columns.to_vec();
121    bind_table_schema_routine_identities_with_check_columns(
122        context,
123        table_name,
124        columns,
125        checks,
126        &check_columns,
127    )
128}
129
130pub fn bind_table_schema_routine_identities_with_check_columns(
131    context: &SchemaDependencyBindingContext<'_>,
132    table_name: &str,
133    columns: &mut [crate::ast::ColumnDef],
134    checks: &mut [crate::ast::TableCheck],
135    check_columns: &[crate::ast::ColumnDef],
136) -> Result<bool, String> {
137    let mut changed = super::regclass::bind_table_schema_regclass_constants(
138        context.references,
139        columns,
140        checks,
141        false,
142    )?;
143    for column in columns {
144        if let Some(default) = &mut column.default {
145            changed |= bind_default_routine_identities(context, table_name, &column.name, default)?;
146        }
147        if let Some(check) = &mut column.check {
148            if schema_expr_may_require_routine_identity_binding(context, check)? {
149                changed |=
150                    bind_check_routines(context, table_name, table_name, check_columns, check)
151                        .map_err(|error| {
152                            format!(
153                                "bind CHECK routine identities for `{table_name}`.`{}`: {error}",
154                                column.name
155                            )
156                        })?;
157            }
158        }
159    }
160    for check in checks {
161        if schema_expr_may_require_routine_identity_binding(context, &check.expr)? {
162            changed |= bind_check_routines(
163                context,
164                table_name,
165                table_name,
166                check_columns,
167                &mut check.expr,
168            )
169            .map_err(|error| {
170                format!("bind CHECK routine identities for `{table_name}`: {error}")
171            })?;
172        }
173    }
174    Ok(changed)
175}
176
177pub fn bind_default_routine_identities(
178    context: &SchemaDependencyBindingContext<'_>,
179    table_name: &str,
180    column_name: &str,
181    default: &mut crate::ast::Expr,
182) -> Result<bool, String> {
183    let changed =
184        super::regclass::bind_schema_regclass_constants(context.references, default, false)?;
185    if !schema_expr_may_require_routine_identity_binding(context, default)? {
186        return Ok(changed);
187    }
188    let bound = bind_default_routines(context, default, default.clone()).map_err(|error| {
189        format!("bind default routine identities for `{table_name}`.`{column_name}`: {error}")
190    })?;
191    Ok(changed || bound)
192}
193
194fn bind_check_routines(
195    context: &SchemaDependencyBindingContext<'_>,
196    table: &str,
197    qualifier: &str,
198    columns: &[ColumnDef],
199    expression: &mut Expr,
200) -> Result<bool, SQLError> {
201    let binding = context.bindings.binding_scope()?;
202    crate::schema::constraints::bind_stored_check_expression(
203        &SchemaBindingContext {
204            catalog: context.schema,
205            binding: &binding.context(),
206        },
207        table,
208        qualifier,
209        columns,
210        expression,
211    )
212}
213fn bind_default_routines(
214    context: &SchemaDependencyBindingContext<'_>,
215    expression: &mut Expr,
216    typed: Expr,
217) -> Result<bool, SQLError> {
218    let binding = context.bindings.binding_scope()?;
219    crate::schema::defaults::bind_stored_schema_expression(
220        &SchemaBindingContext {
221            catalog: context.schema,
222            binding: &binding.context(),
223        },
224        expression,
225        typed,
226    )
227}
228
229/// Upgrade legacy sequence defaults using loaded object identities, then retain
230/// the selected function's other input coercions before the candidate is stored.
231pub fn restore_legacy_default_sequence_inputs(
232    context: &SchemaDependencyBindingContext<'_>,
233    sequences: &dyn crate::schema::sequences::implicit_ownership::StoredSequenceNames,
234    columns: &mut [ColumnDef],
235) -> Result<bool, String> {
236    let mut changed = false;
237    for default in columns
238        .iter_mut()
239        .filter_map(|column| column.default.as_mut())
240    {
241        if super::regclass::bind_legacy_sequence_regclass_constants(
242            context.references,
243            sequences,
244            default,
245        )? {
246            bind_default_routines(context, default, default.clone())
247                .map_err(|error| error.to_string())?;
248            changed = true;
249        }
250    }
251    Ok(changed)
252}