Skip to main content

uqa_sql/schema/sequences/
ownership.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Bind sequence ownership to stable table and column identities.
8use crate::ast::{ColumnDef, SequenceOwnership};
9use crate::SQLError;
10use uqa_core::RelationIdentity;
11
12pub type SequenceOwnerColumns = ([u8; 16], Vec<ColumnDef>);
13
14pub trait SequenceOwnerCatalog {
15    fn resolve_owner_relation(
16        &self,
17        name: &str,
18    ) -> Result<Option<(String, &'static str)>, SQLError>;
19    fn owner_table_columns(
20        &self,
21        canonical: &str,
22    ) -> Result<Option<SequenceOwnerColumns>, SQLError>;
23    fn owner_foreign_columns(&self, relation: &RelationIdentity) -> Option<SequenceOwnerColumns>;
24}
25
26#[derive(Debug, Clone, Copy)]
27pub struct SequenceOwnerColumnIdentity {
28    pub table_object_id: [u8; 16],
29    pub column_object_id: [u8; 16],
30}
31
32pub fn sequence_owner_column_identity(
33    catalog: &dyn SequenceOwnerCatalog,
34    canonical: &str,
35    relation_kind: &str,
36    column_name: &str,
37) -> Result<Option<SequenceOwnerColumnIdentity>, SQLError> {
38    let relation = RelationIdentity::from_legacy_name(canonical).map_err(|error| {
39        SQLError::Internal(format!("resolve sequence owner `{canonical}`: {error}"))
40    })?;
41    let columns = match relation_kind {
42        "table" => catalog
43            .owner_table_columns(canonical)?
44            .ok_or_else(|| SQLError::Internal(format!("table `{canonical}` disappeared")))?,
45        "foreign table" => catalog.owner_foreign_columns(&relation).ok_or_else(|| {
46            SQLError::Internal(format!("foreign table `{canonical}` disappeared"))
47        })?,
48        _ => return Ok(None),
49    };
50    let column_object_id = columns
51        .1
52        .iter()
53        .find(|column| column.name == column_name)
54        .ok_or_else(|| SQLError::Routine {
55            sqlstate: "42703".into(),
56            message: format!(
57                "column \"{column_name}\" of relation \"{}\" does not exist",
58                relation.name
59            ),
60        })?
61        .object_id
62        .ok_or_else(|| {
63            SQLError::Internal(format!(
64                "column `{canonical}`.`{column_name}` has no object identity"
65            ))
66        })?;
67    Ok(Some(SequenceOwnerColumnIdentity {
68        table_object_id: columns.0,
69        column_object_id,
70    }))
71}
72
73pub fn bind_sequence_owner(
74    catalog: &dyn SequenceOwnerCatalog,
75    sequence_name: &str,
76    ownership: &SequenceOwnership,
77) -> Result<Option<SequenceOwnerColumnIdentity>, SQLError> {
78    let SequenceOwnership::Column { table, column } = ownership else {
79        return Ok(None);
80    };
81    let (table_name, kind) =
82        catalog
83            .resolve_owner_relation(table)?
84            .ok_or_else(|| SQLError::Routine {
85                sqlstate: "42P01".into(),
86                message: format!("relation \"{table}\" does not exist"),
87            })?;
88    let Some(owner_column) = sequence_owner_column_identity(catalog, &table_name, kind, column)?
89    else {
90        let relation =
91            RelationIdentity::from_legacy_name(&table_name).map_err(SQLError::Internal)?;
92        return Err(SQLError::Diagnostic {
93            sqlstate: "42809".into(),
94            message: format!("sequence cannot be owned by relation \"{}\"", relation.name),
95            detail: crate::catalog::analysis::relkind_not_supported_detail(kind),
96            hint: None,
97        });
98    };
99    let sequence_relation = RelationIdentity::from_legacy_name(sequence_name).map_err(|error| {
100        SQLError::Internal(format!(
101            "resolve sequence `{sequence_name}` ownership: {error}"
102        ))
103    })?;
104    let table_relation = RelationIdentity::from_legacy_name(&table_name).map_err(|error| {
105        SQLError::Internal(format!("resolve table `{table_name}` ownership: {error}"))
106    })?;
107    if sequence_relation.schema != table_relation.schema {
108        return Err(SQLError::Routine {
109            sqlstate: "55000".into(),
110            message: "sequence must be in same schema as table it is linked to".into(),
111        });
112    }
113    Ok(Some(owner_column))
114}
115
116pub fn require_sequence_ownership(
117    local_name: &str,
118    has_owner_privileges: bool,
119) -> Result<(), SQLError> {
120    crate::catalog::security::ownership::require_relation_ownership(
121        local_name,
122        "sequence",
123        has_owner_privileges,
124    )
125}
126pub fn reject_owned_sequence_role_change(
127    local_name: &str,
128    has_column_owner: bool,
129) -> Result<(), SQLError> {
130    if !has_column_owner {
131        return Ok(());
132    }
133    Err(SQLError::Routine {
134        sqlstate: "0A000".into(),
135        message: format!("cannot change owner of sequence \"{local_name}\""),
136    })
137}