Skip to main content

uqa_sql/prepared/
dependencies.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Catalog identities selected by parse analysis, before executable-plan dependencies are introduced.
8
9use crate::{ast::FunctionBinding, ColumnType, ScalarExpr};
10use std::{
11    any::Any,
12    collections::{BTreeMap, BTreeSet},
13    fmt,
14    sync::Arc,
15};
16use uqa_core::Value;
17
18/// Relations and selected routines used by the analyzed statement. Domain constraint dependencies introduced by optimization do not invalidate the statement's already-read input constants.
19#[derive(Clone, Debug, Default, PartialEq, Eq)]
20pub struct PreparedAnalysisDependencies {
21    pub relations: BTreeSet<u32>,
22    pub routines: BTreeSet<[u8; 16]>,
23}
24
25/// An exact provider-owned revision whose representation and retained resources stay opaque to SQL. Equality invokes the original type's equality; no serialization, hash or pointer-to-integer conversion discards its identity lifetime.
26#[derive(Clone)]
27pub struct PreparedDependencyRevision(Arc<dyn RevisionEquality>);
28
29trait RevisionEquality: Send + Sync {
30    fn as_any(&self) -> &dyn Any;
31    fn equals(&self, other: &dyn RevisionEquality) -> bool;
32}
33
34impl<T: Eq + Send + Sync + 'static> RevisionEquality for T {
35    fn as_any(&self) -> &dyn Any {
36        self
37    }
38
39    fn equals(&self, other: &dyn RevisionEquality) -> bool {
40        other.as_any().downcast_ref::<Self>() == Some(self)
41    }
42}
43
44impl PreparedDependencyRevision {
45    #[must_use]
46    pub fn new<T: Eq + Send + Sync + 'static>(revision: T) -> Self {
47        Self(Arc::new(revision))
48    }
49}
50
51impl PartialEq for PreparedDependencyRevision {
52    fn eq(&self, other: &Self) -> bool {
53        Arc::ptr_eq(&self.0, &other.0) || self.0.equals(other.0.as_ref())
54    }
55}
56
57impl Eq for PreparedDependencyRevision {}
58
59impl fmt::Debug for PreparedDependencyRevision {
60    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
61        formatter
62            .debug_struct("PreparedDependencyRevision")
63            .finish_non_exhaustive()
64    }
65}
66
67/// Revisions read from the same immutable catalog scope that resolved the identities. A missing map value represents an object that no longer exists, rather than an unsupported snapshot capability.
68#[derive(Clone, Debug, Default, PartialEq, Eq)]
69pub struct PreparedDependencySnapshot {
70    /// Global parse-analysis invalidations, such as replacement of a namespace catalog tuple.
71    pub global_catalog: Option<PreparedDependencyRevision>,
72    pub relations: BTreeMap<u32, Option<PreparedDependencyRevision>>,
73    pub routines: BTreeMap<[u8; 16], Option<PreparedDependencyRevision>>,
74}
75
76impl PreparedAnalysisDependencies {
77    pub(crate) fn include_routine(&mut self, binding: &FunctionBinding) {
78        if !binding.builtin {
79            if let Some(identity) = binding.object_id {
80                self.routines.insert(identity);
81            }
82        }
83    }
84
85    /// `PostgreSQL`'s `ISREGCLASSCONST` includes non-null scalar regclass and oid constants. A whole array constant contributes no scalar relation identity; an array expression's scalar children do.
86    pub(crate) fn include_expression(&mut self, expression: &ScalarExpr) {
87        expression.visit(&mut |expression| match expression {
88            ScalarExpr::TypedLiteral {
89                value: Value::Int(oid),
90                ty,
91                bound_type,
92                parameter_index: None,
93            } => {
94                let parsed;
95                let ty = if let Some(ty) = bound_type.as_ref() {
96                    Some(ty)
97                } else {
98                    parsed = ColumnType::from_sql_name(ty).ok();
99                    parsed.as_ref()
100                };
101                if matches!(ty, Some(ColumnType::Regclass | ColumnType::Oid)) {
102                    if let Ok(oid) = u32::try_from(*oid) {
103                        self.relations.insert(oid);
104                    }
105                }
106            }
107            ScalarExpr::Func {
108                binding: Some(binding),
109                ..
110            } => self.include_routine(binding),
111            _ => {}
112        });
113    }
114}
115
116#[cfg(test)]
117mod tests;