Skip to main content

uqa_sql/binding/stored_relations/
query.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Query relation and sequence binding against current or already-loaded catalog state.
8
9use super::StoredRelationCatalog;
10use crate::{
11    binding::view_dependencies::{bind_query_plan_relations, bind_query_plan_sequence_references},
12    plan::QueryPlan,
13    SQLError,
14};
15use std::collections::BTreeSet;
16use uqa_core::RelationIdentity;
17
18pub trait StoredQuerySequences {
19    fn query_sequence(&self, reference: &str) -> Result<String, String>;
20    fn loaded_query_sequence(&self, reference: &str) -> Result<String, String>;
21}
22/// Namespace metadata captured at the start of a stored query binding pass.
23pub struct StoredQueryNamespace {
24    pub temporary_schema: String,
25    pub transition_relations: BTreeSet<String>,
26}
27pub struct StoredQueryBindingContext<'a> {
28    pub relations: &'a dyn StoredRelationCatalog,
29    pub sequences: &'a dyn StoredQuerySequences,
30    pub temporary_schema: &'a str,
31    pub transition_relations: &'a BTreeSet<String>,
32}
33
34pub fn resolve_loaded_query_sequence(
35    reference: &str,
36    candidates: Vec<RelationIdentity>,
37    mut contains: impl FnMut(&RelationIdentity) -> bool,
38) -> Result<String, String> {
39    candidates
40        .into_iter()
41        .find(|candidate| contains(candidate))
42        .map(|candidate| candidate.qualified_name())
43        .ok_or_else(|| format!("Sequence `{reference}` does not exist"))
44}
45
46pub fn bind_stored_query_relations(
47    catalog: &StoredQueryBindingContext<'_>,
48    plan: &mut QueryPlan,
49    context: &str,
50    reject_transition_relations: bool,
51    loaded_catalog: bool,
52) -> Result<bool, SQLError> {
53    let mut uses_temporary_relation = false;
54    bind_query_plan_relations(plan, &std::collections::BTreeSet::new(), &mut |reference| {
55        if let Some(canonical) = catalog
56            .relations
57            .resolve_age_label_relation_name(reference)?
58        {
59            return Ok(canonical);
60        }
61        if RelationIdentity::parse_reference(reference)
62            .ok()
63            .is_some_and(|(schema, relation)| {
64                schema.is_none() && catalog.transition_relations.contains(&relation)
65            })
66        {
67            if reject_transition_relations {
68                return Err(SQLError::Routine {
69                    sqlstate: "0A000".into(),
70                    message: "transition tables cannot be referenced in a view definition".into(),
71                });
72            }
73            return Ok(reference.to_string());
74        }
75        let resolved = if loaded_catalog {
76            catalog
77                .relations
78                .resolve_loaded_visible_relation_kind(reference)?
79                .into_found()
80        } else {
81            catalog
82                .relations
83                .resolve_visible_relation_kind(reference)?
84                .into_found()
85        };
86        match resolved {
87            Some((canonical, "table" | "view" | "materialized view" | "foreign table")) => {
88                uses_temporary_relation |= RelationIdentity::from_legacy_name(&canonical)
89                    .is_ok_and(|relation| relation.schema == catalog.temporary_schema);
90                Ok(canonical)
91            }
92            Some((canonical, kind)) => Err(SQLError::Routine {
93                sqlstate: "42809".into(),
94                message: format!(
95                    "{context} source \"{canonical}\" is a {kind}, not a row relation"
96                ),
97            }),
98            None => Err(SQLError::UnknownTable(reference.to_string())),
99        }
100    })?;
101    bind_query_plan_sequence_references(plan, &mut |reference| {
102        let resolved = if loaded_catalog {
103            catalog.sequences.loaded_query_sequence(reference)
104        } else {
105            catalog.sequences.query_sequence(reference)
106        };
107        resolved.map_err(|error| {
108            SQLError::Unsupported(format!(
109                "{context} sequence reference `{reference}`: {error}"
110            ))
111        })
112    })?;
113    Ok(uses_temporary_relation)
114}