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