uqa_sql/binding/stored_relations/
query.rs1use super::StoredRelationCatalog;
10use crate::{
11 binding::view_dependencies::{bind_query_plan_relations, bind_query_plan_sequence_references},
12 catalog::resolution::RelationLookupMode,
13 plan::QueryPlan,
14 SQLError,
15};
16use std::collections::BTreeSet;
17use uqa_core::RelationIdentity;
18
19pub trait StoredQuerySequences {
20 fn query_sequence(&self, reference: &str) -> Result<String, String>;
21 fn loaded_query_sequence(&self, reference: &str) -> Result<String, String>;
22}
23pub struct StoredQueryNamespace {
25 pub temporary_schema: String,
26 pub transition_relations: BTreeSet<String>,
27}
28pub struct StoredQueryBindingContext<'a> {
29 pub relations: &'a dyn StoredRelationCatalog,
30 pub lookup_mode: RelationLookupMode,
31 pub sequences: &'a dyn StoredQuerySequences,
32 pub temporary_schema: &'a str,
33 pub transition_relations: &'a BTreeSet<String>,
34}
35
36pub fn resolve_loaded_query_sequence(
37 reference: &str,
38 candidates: Vec<RelationIdentity>,
39 mut contains: impl FnMut(&RelationIdentity) -> bool,
40) -> Result<String, String> {
41 candidates
42 .into_iter()
43 .find(|candidate| contains(candidate))
44 .map(|candidate| candidate.qualified_name())
45 .ok_or_else(|| format!("Sequence `{reference}` does not exist"))
46}
47
48pub fn bind_stored_query_relations(
49 catalog: &StoredQueryBindingContext<'_>,
50 plan: &mut QueryPlan,
51 context: &str,
52 reject_transition_relations: bool,
53 loaded_catalog: bool,
54) -> Result<bool, SQLError> {
55 let mut uses_temporary_relation = false;
56 bind_query_plan_relations(plan, &std::collections::BTreeSet::new(), &mut |reference| {
57 if catalog.lookup_mode == RelationLookupMode::Dynamic {
58 if let Some(canonical) = catalog
59 .relations
60 .resolve_age_label_relation_name(reference)?
61 {
62 return Ok(canonical);
63 }
64 }
65 if RelationIdentity::parse_reference(reference)
66 .ok()
67 .is_some_and(|(schema, relation)| {
68 schema.is_none() && catalog.transition_relations.contains(&relation)
69 })
70 {
71 if reject_transition_relations {
72 return Err(SQLError::Routine {
73 sqlstate: "0A000".into(),
74 message: "transition tables cannot be referenced in a view definition".into(),
75 });
76 }
77 return Ok(reference.to_string());
78 }
79 let resolved = match (catalog.lookup_mode, loaded_catalog) {
80 (RelationLookupMode::Bound, _) => {
81 catalog.relations.resolve_bound_relation_kind(reference)?
82 }
83 (RelationLookupMode::Dynamic, true) => catalog
84 .relations
85 .resolve_loaded_visible_relation_kind(reference)?,
86 (RelationLookupMode::Dynamic, false) => {
87 catalog.relations.resolve_visible_relation_kind(reference)?
88 }
89 }
90 .into_found();
91 match resolved {
92 Some((canonical, "table" | "view" | "materialized view" | "foreign table")) => {
93 uses_temporary_relation |= RelationIdentity::from_legacy_name(&canonical)
94 .is_ok_and(|relation| relation.schema == catalog.temporary_schema);
95 Ok(canonical)
96 }
97 Some((canonical, "composite type")) => Err(composite_relation_error(&canonical)),
98 Some((canonical, kind)) => Err(SQLError::Routine {
99 sqlstate: "42809".into(),
100 message: format!(
101 "{context} source \"{canonical}\" is a {kind}, not a row relation"
102 ),
103 }),
104 None => Err(SQLError::UnknownTable(reference.to_string())),
105 }
106 })?;
107 bind_query_plan_sequence_references(plan, &mut |reference| {
108 let resolved = if loaded_catalog {
109 catalog.sequences.loaded_query_sequence(reference)
110 } else {
111 catalog.sequences.query_sequence(reference)
112 };
113 resolved.map_err(|error| {
114 SQLError::Unsupported(format!(
115 "{context} sequence reference `{reference}`: {error}"
116 ))
117 })
118 })?;
119 Ok(uses_temporary_relation)
120}
121
122pub(super) fn composite_relation_error(canonical: &str) -> SQLError {
124 crate::catalog::analysis::UnopenableRelation {
125 name: RelationIdentity::from_legacy_name(canonical)
126 .map_or_else(|_| canonical.to_string(), |relation| relation.name),
127 kinds: "composite types",
128 }
129 .error()
130}
131
132#[cfg(test)]
133mod tests;