Skip to main content

uqa_sql/binding/portals/
relations.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Bind cursor relation names while preserving CTE and transition-relation visibility.
8
9use 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}