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 uqa_core::RelationIdentity::parse_reference(&requested)
126 .ok()
127 .is_some_and(|(schema, relation)| {
128 schema.is_none()
129 && inputs
130 .transitions
131 .active_transition_relation_names()
132 .contains(&relation)
133 })
134 {
135 return Ok(());
136 }
137 if let Some(canonical) = inputs.catalog.resolve_age_label_relation_name(&requested)? {
138 *name = canonical;
139 return Ok(());
140 }
141 match inputs
142 .catalog
143 .try_resolve_visible_relation_kind(&requested)?
144 {
145 Some((canonical, _)) => *name = canonical,
146 None => return Err(SQLError::UnknownTable(requested)),
147 }
148 Ok(())
149}
150
151pub fn bind_session_portal_function_relations(
152 inputs: &PortalBindingContext<'_>,
153 relations: &mut Option<OperatorJoinRelations>,
154) -> Result<(), SQLError> {
155 let Some(relations) = relations else {
156 return Ok(());
157 };
158 for relation in [&mut relations.left, &mut relations.right] {
159 let requested = relation.clone();
160 match inputs
161 .catalog
162 .try_resolve_visible_relation_kind(&requested)?
163 {
164 Some((canonical, "table")) => *relation = canonical,
165 Some((canonical, kind)) => {
166 return Err(SQLError::Routine {
167 sqlstate: "42809".into(),
168 message: format!(
169 "cursor table-function relation \"{canonical}\" is a {kind}, not a table"
170 ),
171 });
172 }
173 None => return Err(SQLError::UnknownTable(requested)),
174 }
175 }
176 Ok(())
177}