uqa_sql/binding/portals/
relations.rs1use super::PortalBindingContext;
10use crate::{
11 ast::OperatorJoinRelations,
12 plan::{QueryPlan, RelationalPlan, SourcePlan},
13 SQLError,
14};
15
16pub fn bind_session_portal_query_relations(
17 inputs: &PortalBindingContext<'_>,
18 query: &mut QueryPlan,
19 inherited_ctes: &std::collections::BTreeSet<String>,
20) -> Result<(), SQLError> {
21 let mut visible_ctes = inherited_ctes.clone();
22 for cte in &mut query.ctes {
23 let mut definition_scope = visible_ctes.clone();
24 if cte.recursive {
25 definition_scope.insert(cte.name.clone());
26 }
27 crate::binding::view_dependencies::bind_cte_plan_relations(
28 &mut cte.body,
29 &definition_scope,
30 &mut |name| {
31 let mut name = name.to_string();
32 bind_session_portal_relation_reference(
33 inputs,
34 &mut name,
35 &std::collections::BTreeSet::new(),
36 )?;
37 Ok::<_, SQLError>(name)
38 },
39 )?;
40 visible_ctes.insert(cte.name.clone());
41 }
42 bind_session_portal_relational_plan(inputs, &mut query.root, &visible_ctes)?;
43 query.relations_bound = true;
44 Ok(())
45}
46
47pub fn bind_session_portal_relational_plan(
48 inputs: &PortalBindingContext<'_>,
49 plan: &mut RelationalPlan,
50 visible_ctes: &std::collections::BTreeSet<String>,
51) -> Result<(), SQLError> {
52 match plan {
53 RelationalPlan::QueryBlock(block) => {
54 if let Some(source) = block.from.as_mut() {
55 bind_session_portal_source_plan(inputs, source, visible_ctes)?;
56 }
57 for subquery in &mut block.subqueries {
58 bind_session_portal_query_relations(inputs, subquery, visible_ctes)?;
59 }
60 Ok(())
61 }
62 RelationalPlan::SetOp {
63 left,
64 right,
65 subqueries,
66 ..
67 } => {
68 bind_session_portal_query_relations(inputs, left, visible_ctes)?;
69 bind_session_portal_query_relations(inputs, right, visible_ctes)?;
70 for subquery in subqueries {
71 bind_session_portal_query_relations(inputs, subquery, visible_ctes)?;
72 }
73 Ok(())
74 }
75 RelationalPlan::Values { subqueries, .. } => {
76 for subquery in subqueries {
77 bind_session_portal_query_relations(inputs, subquery, visible_ctes)?;
78 }
79 Ok(())
80 }
81 }
82}
83
84pub fn bind_session_portal_source_plan(
85 inputs: &PortalBindingContext<'_>,
86 source: &mut SourcePlan,
87 visible_ctes: &std::collections::BTreeSet<String>,
88) -> Result<(), SQLError> {
89 match source {
90 SourcePlan::Table { name, .. } => {
91 bind_session_portal_relation_reference(inputs, name, visible_ctes)
92 }
93 SourcePlan::Join { left, right, .. } => {
94 bind_session_portal_source_plan(inputs, left, visible_ctes)?;
95 bind_session_portal_source_plan(inputs, right, visible_ctes)
96 }
97 SourcePlan::Subquery { body, .. } => {
98 bind_session_portal_query_relations(inputs, body, visible_ctes)
99 }
100 SourcePlan::Function { relations, .. } => {
101 bind_session_portal_function_relations(inputs, relations)
102 }
103 SourcePlan::FunctionGroup { functions, .. } => {
104 for function in functions {
105 bind_session_portal_function_relations(inputs, &mut function.relations)?;
106 }
107 Ok(())
108 }
109 SourcePlan::Values { .. } => Ok(()),
110 }
111}
112
113fn bind_session_portal_relation_reference(
114 inputs: &PortalBindingContext<'_>,
115 name: &mut String,
116 visible_ctes: &std::collections::BTreeSet<String>,
117) -> Result<(), SQLError> {
118 if uqa_core::RelationIdentity::parse_reference(name)
119 .ok()
120 .is_some_and(|(schema, name)| schema.is_none() && visible_ctes.contains(&name))
121 {
122 return Ok(());
123 }
124 let requested = name.clone();
125 if let Some(canonical) =
126 crate::binding::view_dependencies::canonical_virtual_relation_reference(&requested)
127 {
128 *name = canonical;
129 return Ok(());
130 }
131 if uqa_core::RelationIdentity::parse_reference(&requested)
132 .ok()
133 .is_some_and(|(schema, relation)| {
134 schema.is_none()
135 && inputs
136 .transitions
137 .active_transition_relation_names()
138 .contains(&relation)
139 })
140 {
141 return Ok(());
142 }
143 if let Some(canonical) = inputs.catalog.resolve_age_label_relation_name(&requested)? {
144 *name = canonical;
145 return Ok(());
146 }
147 match inputs
148 .catalog
149 .try_resolve_visible_relation_kind(&requested)?
150 {
151 Some((canonical, _)) => *name = canonical,
152 None => return Err(SQLError::UnknownTable(requested)),
153 }
154 Ok(())
155}
156
157pub fn bind_session_portal_function_relations(
158 inputs: &PortalBindingContext<'_>,
159 relations: &mut Option<OperatorJoinRelations>,
160) -> Result<(), SQLError> {
161 let Some(relations) = relations else {
162 return Ok(());
163 };
164 for relation in [&mut relations.left, &mut relations.right] {
165 let requested = relation.clone();
166 match inputs
167 .catalog
168 .try_resolve_visible_relation_kind(&requested)?
169 {
170 Some((canonical, "table")) => *relation = canonical,
171 Some((canonical, kind)) => {
172 return Err(SQLError::Routine {
173 sqlstate: "42809".into(),
174 message: format!(
175 "cursor table-function relation \"{canonical}\" is a {kind}, not a table"
176 ),
177 });
178 }
179 None => return Err(SQLError::UnknownTable(requested)),
180 }
181 }
182 Ok(())
183}