Skip to main content

uqa_sql/catalog/
relation_oids.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! The public OIDs of one relation incarnation: its `pg_class` row, the row type and array type `heap_create_with_catalog` creates with it, and a view's `_RETURN` rule.
8
9use serde::{Deserialize, Serialize};
10
11/// The kinds of relation whose OIDs differ in which companion objects exist.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum RelationOidKind {
14    /// Tables and partitioned tables: a row type and its array type.
15    Table,
16    /// Views and materialized views: a row type, its array type and the `_RETURN` rule.
17    View,
18    /// Foreign tables: a row type and its array type.
19    ForeignTable,
20    /// Sequences have no row type.
21    Sequence,
22    /// Standalone composite types: the composite relation, its row type and the row type's array type.
23    CompositeType,
24}
25
26/// Relation OIDs as the database's counter allocated them, in `PostgreSQL`'s order: the relation, the array type, the row type and then a view's rule. Relations created before OIDs were recorded derive them from their identity instead, and never change them.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
28#[serde(deny_unknown_fields)]
29pub struct RelationCatalogOids {
30    pub relation: u32,
31    #[serde(default, skip_serializing_if = "Option::is_none")]
32    pub row_type: Option<u32>,
33    #[serde(default, skip_serializing_if = "Option::is_none")]
34    pub array_type: Option<u32>,
35    #[serde(default, skip_serializing_if = "Option::is_none")]
36    pub rule: Option<u32>,
37}
38
39impl RelationCatalogOids {
40    /// Construct the named row type from these exact relation identities, including a newly allocated relation that is not published yet.
41    #[must_use]
42    pub fn row_type_reference(
43        &self,
44        relation: &uqa_core::RelationIdentity,
45    ) -> Option<crate::ast::CompositeTypeReference> {
46        Some(crate::ast::CompositeTypeReference {
47            schema: relation.schema.clone(),
48            name: relation.name.clone(),
49            oid: self.row_type?,
50            array_oid: self.array_type.unwrap_or(0),
51            relation_oid: self.relation,
52        })
53    }
54
55    /// The OIDs a relation created before OIDs were recorded derives from its identity. Such relations have no recorded array type.
56    #[must_use]
57    pub fn legacy(kind: RelationOidKind, object_id: &[u8; 16]) -> Self {
58        let derive = |label| {
59            u32::try_from(super::oids::stable_object_oid(label, object_id))
60                .expect("derived catalog OIDs fit in u32")
61        };
62        Self {
63            relation: derive("relation"),
64            row_type: (kind != RelationOidKind::Sequence).then(|| derive("rowtype")),
65            array_type: None,
66            rule: (kind == RelationOidKind::View).then(|| derive("view-rule")),
67        }
68    }
69
70    /// The row type OID `pg_class.reltype` reports: zero for relations without one.
71    #[must_use]
72    pub fn reltype(&self) -> u32 {
73        self.row_type.unwrap_or(0)
74    }
75
76    /// Every OID the relation claims, for occupancy checks.
77    pub fn claimed(&self) -> impl Iterator<Item = u32> {
78        [
79            Some(self.relation),
80            self.row_type,
81            self.array_type,
82            self.rule,
83        ]
84        .into_iter()
85        .flatten()
86    }
87
88    /// Whether the OIDs are well formed for the kind and object identity. Newly allocated OIDs use the normal user range; a legacy relation may retain its exact identity-derived companions below that range. Its array OID is always newly allocated.
89    #[must_use]
90    pub fn is_valid_for(&self, kind: RelationOidKind, object_id: &[u8; 16]) -> bool {
91        let user = |oid: u32| oid >= super::oids::FIRST_NORMAL_OBJECT_ID;
92        let has_row_type = kind != RelationOidKind::Sequence;
93        if self.row_type.is_some() != has_row_type
94            || self.array_type.is_some() != has_row_type
95            || self.rule.is_some() != (kind == RelationOidKind::View)
96            || !self.array_type.is_none_or(user)
97        {
98            return false;
99        }
100        if self.claimed().all(user) {
101            return true;
102        }
103        let legacy = Self::legacy(kind, object_id);
104        self.relation == legacy.relation
105            && self.row_type == legacy.row_type
106            && self.rule == legacy.rule
107    }
108}
109
110#[cfg(test)]
111mod tests;