Skip to main content

uqa_sql/schema/sequences/
lifecycle.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Sequence rename and schema-move declaration rules.
8use crate::{
9    ast::{RelationPersistence, SequenceBound, SequenceLifecycle, SequenceOwnership},
10    SQLError,
11};
12use uqa_core::RelationIdentity;
13/// Namespace and ownership metadata needed to bind one sequence lifecycle target.
14pub trait SequenceLifecycleCatalog {
15    fn temporary_schema_name(&self) -> String;
16    fn sequence_is_owned(&self, relation: &RelationIdentity) -> bool;
17    fn schema_exists(&self, schema: &str) -> bool;
18    fn current_user_name(&self) -> String;
19    fn require_schema_create(&self, schema: &str, role: &str) -> Result<(), SQLError>;
20    fn relation_kind_at(&self, name: &str) -> Result<Option<&'static str>, String>;
21}
22pub fn validate_sequence_lifecycle_shape(
23    alter: &crate::ast::AlterSequence,
24) -> Result<(), SQLError> {
25    if alter.restart != crate::ast::SequenceRestart::Unchanged
26        || alter.increment.is_some()
27        || alter.start.is_some()
28        || alter.data_type.is_some()
29        || alter.min_value != SequenceBound::Unchanged
30        || alter.max_value != SequenceBound::Unchanged
31        || alter.cycle.is_some()
32        || alter.cache_size.is_some()
33        || alter.ownership != SequenceOwnership::Unchanged
34        || alter.persistence.is_some()
35        || alter.role_owner.is_some()
36    {
37        return Err(SQLError::Internal(
38            "ALTER SEQUENCE name lifecycle cannot contain definition changes".into(),
39        ));
40    }
41    Ok(())
42}
43
44pub fn sequence_lifecycle_target(
45    catalog: &dyn SequenceLifecycleCatalog,
46    source: &RelationIdentity,
47    persistence: RelationPersistence,
48    lifecycle: &SequenceLifecycle,
49) -> Result<Option<RelationIdentity>, SQLError> {
50    match lifecycle {
51        SequenceLifecycle::Unchanged => Err(SQLError::Internal(
52            "sequence lifecycle executor received no action".into(),
53        )),
54        SequenceLifecycle::RenameTo { name } => {
55            let (schema, target_name) = RelationIdentity::parse_reference(name)
56                .map_err(|error| SQLError::Internal(format!("invalid sequence name: {error}")))?;
57            if schema.is_some() {
58                return Err(SQLError::Internal(
59                    "ALTER SEQUENCE RENAME TO produced a qualified target".into(),
60                ));
61            }
62            let target = RelationIdentity::new(&source.schema, target_name);
63            reject_sequence_lifecycle_collision(catalog, source, &target, true)?;
64            Ok(Some(target))
65        }
66        SequenceLifecycle::SetSchema { schema } => {
67            let (qualifier, mut target_schema) = RelationIdentity::parse_reference(schema)
68                .map_err(|error| SQLError::Internal(format!("invalid schema name: {error}")))?;
69            if qualifier.is_some() {
70                return Err(SQLError::Internal(
71                    "ALTER SEQUENCE SET SCHEMA produced a qualified schema".into(),
72                ));
73            }
74            let temporary_schema = catalog.temporary_schema_name();
75            if schema == "pg_temp" {
76                target_schema.clone_from(&temporary_schema);
77            }
78            if persistence == RelationPersistence::Temporary || target_schema == temporary_schema {
79                return Err(SQLError::Routine {
80                    sqlstate: "0A000".into(),
81                    message: "cannot move objects into or out of temporary schemas".into(),
82                });
83            }
84            if catalog.sequence_is_owned(source) {
85                return Err(SQLError::Routine {
86                    sqlstate: "0A000".into(),
87                    message: "cannot move an owned sequence into another schema".into(),
88                });
89            }
90            if !catalog.schema_exists(&target_schema) {
91                return Err(SQLError::Routine {
92                    sqlstate: "3F000".into(),
93                    message: format!("schema \"{target_schema}\" does not exist"),
94                });
95            }
96            let current_user = catalog.current_user_name();
97            catalog.require_schema_create(&target_schema, &current_user)?;
98            let target = RelationIdentity::new(target_schema, &source.name);
99            if target == *source {
100                return Ok(None);
101            }
102            reject_sequence_lifecycle_collision(catalog, source, &target, false)?;
103            Ok(Some(target))
104        }
105    }
106}
107
108fn reject_sequence_lifecycle_collision(
109    catalog: &dyn SequenceLifecycleCatalog,
110    source: &RelationIdentity,
111    target: &RelationIdentity,
112    rename: bool,
113) -> Result<(), SQLError> {
114    if target == source
115        || catalog
116            .relation_kind_at(&target.qualified_name())
117            .map_err(|error| {
118                SQLError::Internal(format!(
119                    "check sequence lifecycle target `{}`: {error}",
120                    target.qualified_name()
121                ))
122            })?
123            .is_some()
124    {
125        return Err(SQLError::Routine {
126            sqlstate: "42P07".into(),
127            message: if rename {
128                format!("relation \"{}\" already exists", target.name)
129            } else {
130                format!(
131                    "relation \"{}\" already exists in schema \"{}\"",
132                    target.name, target.schema
133                )
134            },
135        });
136    }
137    Ok(())
138}
139
140use crate::catalog::resolution::RelationResolution;
141
142pub fn alter_sequence_target_name(
143    resolution: crate::catalog::resolution::RelationResolution,
144    alter: &crate::ast::AlterSequence,
145) -> Result<Option<String>, SQLError> {
146    match resolution {
147        RelationResolution::Found(name, "sequence") => Ok(Some(name)),
148        RelationResolution::Found(_name, _kind) => Err(SQLError::Routine {
149            sqlstate: "42809".into(),
150            message: format!("\"{}\" is not a sequence", alter.name),
151        }),
152        RelationResolution::MissingRelation | RelationResolution::MissingSchema(_)
153            if alter.if_exists =>
154        {
155            Ok(None)
156        }
157        RelationResolution::MissingSchema(schema) => Err(SQLError::Routine {
158            sqlstate: "3F000".into(),
159            message: format!("schema \"{schema}\" does not exist"),
160        }),
161        RelationResolution::MissingRelation => Err(SQLError::Routine {
162            sqlstate: "42P01".into(),
163            message: format!("relation \"{}\" does not exist", alter.name),
164        }),
165    }
166}