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