Skip to main content

uqa_sql/binding/
stored_relations.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Bind durable statement relations while preserving namespace, catalog, and error ordering.
8
9mod query;
10pub use query::{
11    bind_stored_query_relations, resolve_loaded_query_sequence, StoredQueryBindingContext,
12    StoredQueryNamespace, StoredQuerySequences,
13};
14
15use crate::{
16    ast::{Expr, Statement},
17    catalog::{
18        events::RuleDependencies,
19        resolution::{RelationLookupMode, RelationResolution},
20        stored_ast::StoredAstVisitor,
21    },
22    SQLError,
23};
24use std::collections::BTreeSet;
25use uqa_core::RelationIdentity;
26
27pub trait StoredRelationCatalog {
28    fn resolve_age_label_relation_name(&self, reference: &str) -> Result<Option<String>, SQLError>;
29    fn resolve_visible_relation_kind(
30        &self,
31        reference: &str,
32    ) -> Result<RelationResolution, SQLError>;
33    fn resolve_loaded_visible_relation_kind(
34        &self,
35        reference: &str,
36    ) -> Result<RelationResolution, SQLError>;
37    fn resolve_bound_relation_kind(&self, reference: &str) -> Result<RelationResolution, SQLError>;
38}
39
40fn bind_catalog_relation_reference(
41    catalog: &dyn StoredRelationCatalog,
42    reference: &mut String,
43    lookup_mode: RelationLookupMode,
44    loaded_catalog: bool,
45    context: &str,
46    dependencies: &mut BTreeSet<RelationIdentity>,
47) -> Result<(), SQLError> {
48    if let Some(canonical) =
49        crate::binding::view_dependencies::canonical_virtual_relation_reference(reference)
50    {
51        *reference = canonical;
52        return Ok(());
53    }
54    if lookup_mode == RelationLookupMode::Dynamic {
55        if let Some(canonical) = catalog.resolve_age_label_relation_name(reference)? {
56            let relation = RelationIdentity::from_legacy_name(&canonical).map_err(|error| {
57                SQLError::Internal(format!("decode bound rule source `{canonical}`: {error}"))
58            })?;
59            *reference = canonical;
60            dependencies.insert(relation);
61            return Ok(());
62        }
63    }
64    let resolution = match (lookup_mode, loaded_catalog) {
65        (RelationLookupMode::Dynamic, true) => {
66            catalog.resolve_loaded_visible_relation_kind(reference)?
67        }
68        (RelationLookupMode::Dynamic, false) => catalog.resolve_visible_relation_kind(reference)?,
69        (RelationLookupMode::Bound, _) => catalog.resolve_bound_relation_kind(reference)?,
70    };
71    let canonical = match resolution {
72        RelationResolution::Found(
73            canonical,
74            "table" | "view" | "materialized view" | "foreign table" | "sequence",
75        ) => canonical,
76        RelationResolution::Found(canonical, kind) => {
77            return Err(SQLError::Routine {
78                sqlstate: "42809".into(),
79                message: format!(
80                    "{context} source \"{canonical}\" is a {kind}, not a row relation"
81                ),
82            });
83        }
84        RelationResolution::MissingSchema(schema) => {
85            return Err(SQLError::Routine {
86                sqlstate: "3F000".into(),
87                message: format!("schema \"{schema}\" does not exist"),
88            });
89        }
90        RelationResolution::MissingRelation => {
91            return Err(SQLError::UnknownTable(reference.clone()));
92        }
93    };
94    let relation = RelationIdentity::from_legacy_name(&canonical).map_err(|error| {
95        SQLError::Internal(format!("decode bound rule source `{canonical}`: {error}"))
96    })?;
97    *reference = canonical;
98    dependencies.insert(relation);
99    Ok(())
100}
101
102pub fn bind_rule_action_relation_dependencies(
103    catalog: &dyn StoredRelationCatalog,
104    statement: &mut Statement,
105    lookup_mode: RelationLookupMode,
106) -> Result<RuleDependencies, SQLError> {
107    let mut dependencies = BTreeSet::new();
108    let mut bind = |reference: &mut String| {
109        bind_catalog_relation_reference(
110            catalog,
111            reference,
112            lookup_mode,
113            false,
114            "CREATE RULE",
115            &mut dependencies,
116        )
117    };
118    let mut ignore_routine = |_: &mut String,
119                              _: Option<&mut Option<crate::ast::FunctionBinding>>|
120     -> Result<(), SQLError> { Ok(()) };
121    StoredAstVisitor {
122        source: None,
123        merge: None,
124        expression: None,
125        ty: None,
126        relation: &mut bind,
127        routine: &mut ignore_routine,
128    }
129    .bind_statement(statement)?;
130    Ok(RuleDependencies {
131        relations: dependencies,
132        columns: BTreeSet::new(),
133        routines: BTreeSet::new(),
134    })
135}
136
137pub fn bind_rule_condition_relation_dependencies(
138    catalog: &dyn StoredRelationCatalog,
139    expression: &mut Expr,
140    lookup_mode: RelationLookupMode,
141) -> Result<RuleDependencies, SQLError> {
142    let mut dependencies = BTreeSet::new();
143    let mut bind = |reference: &mut String| {
144        bind_catalog_relation_reference(
145            catalog,
146            reference,
147            lookup_mode,
148            false,
149            "CREATE RULE",
150            &mut dependencies,
151        )
152    };
153    let mut ignore_routine = |_: &mut String,
154                              _: Option<&mut Option<crate::ast::FunctionBinding>>|
155     -> Result<(), SQLError> { Ok(()) };
156    StoredAstVisitor {
157        source: None,
158        merge: None,
159        expression: None,
160        ty: None,
161        relation: &mut bind,
162        routine: &mut ignore_routine,
163    }
164    .bind_expr(expression, &BTreeSet::new())?;
165    Ok(RuleDependencies {
166        relations: dependencies,
167        columns: BTreeSet::new(),
168        routines: BTreeSet::new(),
169    })
170}
171
172pub fn bind_stored_statement_relations(
173    catalog: &dyn StoredRelationCatalog,
174    statement: &mut Statement,
175    lookup_mode: RelationLookupMode,
176    loaded_catalog: bool,
177    context: &str,
178) -> Result<bool, SQLError> {
179    let mut dependencies = BTreeSet::new();
180    let mut changed = false;
181    let mut bind = |reference: &mut String| {
182        let previous = reference.clone();
183        bind_catalog_relation_reference(
184            catalog,
185            reference,
186            lookup_mode,
187            loaded_catalog,
188            context,
189            &mut dependencies,
190        )?;
191        changed |= reference != &previous;
192        Ok(())
193    };
194    let mut ignore_routine = |_: &mut String,
195                              _: Option<&mut Option<crate::ast::FunctionBinding>>|
196     -> Result<(), SQLError> { Ok(()) };
197    StoredAstVisitor {
198        source: None,
199        merge: None,
200        expression: None,
201        ty: None,
202        relation: &mut bind,
203        routine: &mut ignore_routine,
204    }
205    .bind_statement(statement)?;
206    match statement {
207        Statement::Insert(insert) => {
208            changed |= !insert.target_relation_bound;
209            insert.target_relation_bound = true;
210        }
211        Statement::Update(update) => {
212            changed |= !update.target_relation_bound;
213            update.target_relation_bound = true;
214        }
215        Statement::Delete(delete) => {
216            changed |= !delete.target_relation_bound;
217            delete.target_relation_bound = true;
218        }
219        _ => {}
220    }
221    Ok(changed)
222}