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