Skip to main content

uqa_sql/semantics/partition/
identity.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7use super::PartitionCatalog;
8use crate::SQLError;
9use std::collections::BTreeSet;
10pub fn partition_hierarchy_root(
11    catalog: &dyn PartitionCatalog,
12    table: &str,
13) -> Result<Option<String>, SQLError> {
14    let mut current = catalog
15        .try_resolve_table_name(table)
16        .map_err(|error| SQLError::Internal(format!("resolve table `{table}`: {error}")))?
17        .ok_or_else(|| SQLError::UnknownTable(table.to_string()))?;
18    let mut visited = BTreeSet::new();
19    let mut participates = false;
20    loop {
21        if !visited.insert(current.clone()) {
22            return Err(SQLError::Internal(format!(
23                "partition hierarchy cycle reaches `{current}`"
24            )));
25        }
26        let hierarchy = catalog
27            .try_table_hierarchy(&current)
28            .map_err(|error| SQLError::Internal(format!("read partition hierarchy: {error}")))?;
29        participates |= hierarchy.partition_spec.is_some() || hierarchy.is_partition();
30        if !hierarchy.is_partition() {
31            return Ok(participates.then_some(current));
32        }
33        current = hierarchy
34            .parents
35            .first()
36            .cloned()
37            .ok_or_else(|| SQLError::Internal("partition has no parent relation".into()))?;
38    }
39}
40
41fn resolve_table(catalog: &dyn PartitionCatalog, table: &str) -> Result<String, SQLError> {
42    catalog
43        .try_resolve_table_name(table)
44        .map_err(|error| SQLError::Internal(format!("resolve table `{table}`: {error}")))?
45        .ok_or_else(|| SQLError::UnknownTable(table.to_string()))
46}
47
48/// The relation followed by each partitioned table it is a partition of, nearest first. A foreign key that references a table which is not partitioned reads `ONLY` that table, as `PostgreSQL`'s referential queries do, so plain inheritance parents are not among them.
49pub fn partition_ancestor_tables(
50    catalog: &dyn PartitionCatalog,
51    table: &str,
52) -> Result<Vec<String>, SQLError> {
53    let mut current = resolve_table(catalog, table)?;
54    let mut output = Vec::new();
55    let mut visited = BTreeSet::new();
56    loop {
57        if !visited.insert(current.clone()) {
58            return Err(SQLError::Internal(format!(
59                "partition hierarchy cycle reaches `{current}`"
60            )));
61        }
62        let hierarchy = catalog
63            .try_table_hierarchy(&current)
64            .map_err(|error| SQLError::Internal(format!("read partition hierarchy: {error}")))?;
65        output.push(current);
66        if !hierarchy.is_partition() {
67            return Ok(output);
68        }
69        current = hierarchy
70            .parents
71            .first()
72            .cloned()
73            .ok_or_else(|| SQLError::Internal("partition has no parent relation".into()))?;
74    }
75}
76
77/// The relations whose rows a foreign key reads for `table`: the table and, when it is partitioned, every partition below it, each before its own partitions. The rows of a plain inheritance child belong to the child alone, as `PostgreSQL`'s referential queries read `ONLY` a table that is not partitioned.
78pub fn foreign_key_scan_tables(
79    catalog: &dyn PartitionCatalog,
80    table: &str,
81) -> Result<Vec<String>, SQLError> {
82    let mut pending = vec![resolve_table(catalog, table)?];
83    let mut output = Vec::new();
84    let mut visited = BTreeSet::new();
85    while let Some(current) = pending.pop() {
86        if !visited.insert(current.clone()) {
87            return Err(SQLError::Internal(format!(
88                "partition hierarchy cycle reaches `{current}`"
89            )));
90        }
91        let hierarchy = catalog
92            .try_table_hierarchy(&current)
93            .map_err(|error| SQLError::Internal(format!("read partition hierarchy: {error}")))?;
94        if hierarchy.partition_spec.is_some() {
95            let mut partitions = catalog.direct_hierarchy_children(&current)?;
96            partitions.reverse();
97            pending.extend(partitions);
98        }
99        output.push(current);
100    }
101    Ok(output)
102}
103
104/// Return the physical counter owner for legacy auto-increment metadata. Declarative partitions share the top partitioned parent's counter; newly created SERIAL and identity columns use their durable sequence binding instead.
105pub fn partition_identity_owner(
106    catalog: &dyn PartitionCatalog,
107    table: &str,
108) -> Result<String, SQLError> {
109    if let Some(root) = partition_hierarchy_root(catalog, table)? {
110        return Ok(root);
111    }
112    catalog
113        .try_resolve_table_name(table)
114        .map_err(|error| SQLError::Internal(format!("resolve table `{table}`: {error}")))?
115        .ok_or_else(|| SQLError::UnknownTable(table.to_string()))
116}
117
118#[cfg(test)]
119mod tests;