Skip to main content

uqa_sql/binding/
portals.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Cursor binding and dependency metadata consumed before snapshot registration.
8
9pub mod dependencies;
10pub mod relations;
11
12#[cfg(test)]
13mod tests;
14
15use crate::{plan::QueryPlan, routines::RoutineResolution, SQLError};
16use std::collections::BTreeSet;
17use uqa_core::RelationIdentity;
18
19pub trait PortalRelationCatalog {
20    fn try_resolve_table_name(&self, name: &str) -> Result<Option<String>, String>;
21    fn hierarchy_scan_tables(
22        &self,
23        table: &str,
24        include_descendants: bool,
25    ) -> Result<Vec<String>, SQLError>;
26    fn view_plan(&self, name: &str) -> Result<Option<QueryPlan>, SQLError>;
27    fn try_resolve_visible_relation_kind(
28        &self,
29        name: &str,
30    ) -> Result<Option<(String, &'static str)>, SQLError>;
31    fn resolve_age_label_relation_name(&self, name: &str) -> Result<Option<String>, SQLError>;
32    fn try_resolve_sequence_reference(&self, name: &str) -> Result<Option<String>, String>;
33}
34
35pub trait PortalTransitionRelations {
36    fn active_transition_relation_names(&self) -> BTreeSet<String>;
37}
38
39pub struct PortalBindingContext<'a> {
40    pub catalog: &'a dyn PortalRelationCatalog,
41    pub routines: &'a dyn RoutineResolution,
42    pub transitions: &'a dyn PortalTransitionRelations,
43}
44
45pub struct SessionPortalTableDependencies {
46    pub tables: Option<std::collections::BTreeSet<RelationIdentity>>,
47    pub graphs: Option<std::collections::BTreeSet<String>>,
48    pub graph_catalog: bool,
49}
50
51impl SessionPortalTableDependencies {
52    pub fn empty() -> Self {
53        Self {
54            tables: Some(std::collections::BTreeSet::new()),
55            graphs: Some(std::collections::BTreeSet::new()),
56            graph_catalog: false,
57        }
58    }
59
60    pub fn all() -> Self {
61        Self {
62            tables: None,
63            graphs: None,
64            graph_catalog: true,
65        }
66    }
67
68    pub fn is_all(&self) -> bool {
69        self.tables.is_none() && self.graphs.is_none()
70    }
71
72    pub fn includes(&self, relation: &RelationIdentity) -> bool {
73        self.tables
74            .as_ref()
75            .is_none_or(|tables| tables.contains(relation))
76    }
77
78    pub fn insert(&mut self, relation: RelationIdentity) {
79        if let Some(relations) = &mut self.tables {
80            relations.insert(relation);
81        }
82    }
83
84    pub fn insert_graph(&mut self, graph: String) {
85        self.graph_catalog = true;
86        if let Some(graphs) = &mut self.graphs {
87            graphs.insert(graph);
88        }
89    }
90}
91
92pub fn prepare_query(
93    inputs: &PortalBindingContext<'_>,
94    query: &mut QueryPlan,
95) -> Result<SessionPortalTableDependencies, SQLError> {
96    relations::bind_session_portal_query_relations(
97        inputs,
98        query,
99        &std::collections::BTreeSet::new(),
100    )?;
101    super::view_dependencies::bind_query_plan_sequence_references(query, &mut |reference| {
102        inputs
103            .catalog
104            .try_resolve_sequence_reference(reference)
105            .map_err(|error| {
106                SQLError::Internal(format!(
107                    "bind cursor sequence `{reference}` at DECLARE: {error}"
108                ))
109            })
110            .and_then(|bound| {
111                bound.ok_or_else(|| SQLError::Routine {
112                    sqlstate: "42P01".into(),
113                    message: format!("relation \"{reference}\" does not exist"),
114                })
115            })
116    })?;
117    let table_dependencies = dependencies::session_portal_table_dependencies(inputs, query)?;
118    Ok(table_dependencies)
119}