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