1mod 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}