Skip to main content

uqa_sql/schema/sequences/
actions.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Validate combinations of SQL sequence actions and persistence changes.
8use crate::ast::{RelationPersistence, SequenceBound, SequenceRestart, TableLockMode};
9use crate::SQLError;
10
11pub fn sequence_alter_lock_mode(alter: &crate::ast::AlterSequence) -> TableLockMode {
12    if alter.role_owner.is_some()
13        || alter.persistence.is_some()
14        || alter.lifecycle != crate::ast::SequenceLifecycle::Unchanged
15    {
16        TableLockMode::AccessExclusive
17    } else {
18        TableLockMode::ShareRowExclusive
19    }
20}
21
22pub fn altered_sequence_persistence(
23    alter: &crate::ast::AlterSequence,
24    current: RelationPersistence,
25    relation_name: &str,
26) -> Result<RelationPersistence, SQLError> {
27    match alter.persistence {
28        None => Ok(current),
29        Some(RelationPersistence::Permanent | RelationPersistence::Unlogged)
30            if current == RelationPersistence::Temporary =>
31        {
32            Err(SQLError::Routine {
33                sqlstate: "42P16".into(),
34                message: format!(
35                    "cannot change logged status of table \"{relation_name}\" because it is temporary"
36                ),
37            })
38        }
39        Some(requested @ (RelationPersistence::Permanent | RelationPersistence::Unlogged)) => {
40            Ok(requested)
41        }
42        Some(RelationPersistence::Temporary) => Err(SQLError::Internal(
43            "ALTER SEQUENCE cannot request temporary persistence".into(),
44        )),
45    }
46}
47
48pub fn sequence_alter_is_persistence_only(alter: &crate::ast::AlterSequence) -> bool {
49    alter.persistence.is_some()
50        && alter.role_owner.is_none()
51        && !sequence_alter_changes_value_parameters(alter)
52        && alter.ownership == crate::ast::SequenceOwnership::Unchanged
53}
54
55/// Explicit value-generation options replace the sequence's allocation generation even when their values are unchanged. Ownership, name and persistence actions are handled separately.
56pub fn sequence_alter_changes_value_parameters(alter: &crate::ast::AlterSequence) -> bool {
57    alter.restart != SequenceRestart::Unchanged
58        || alter.increment.is_some()
59        || alter.start.is_some()
60        || alter.data_type.is_some()
61        || alter.min_value != SequenceBound::Unchanged
62        || alter.max_value != SequenceBound::Unchanged
63        || alter.cycle.is_some()
64        || alter.cache_size.is_some()
65}
66
67pub fn validate_sequence_role_owner_shape(
68    alter: &crate::ast::AlterSequence,
69) -> Result<(), SQLError> {
70    if sequence_alter_changes_value_parameters(alter)
71        || alter.ownership != crate::ast::SequenceOwnership::Unchanged
72        || alter.persistence.is_some()
73        || alter.lifecycle != crate::ast::SequenceLifecycle::Unchanged
74    {
75        return Err(SQLError::Internal(
76            "ALTER SEQUENCE OWNER TO cannot contain another action".into(),
77        ));
78    }
79    Ok(())
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn sequence_definition_and_relation_lifecycle_select_distinct_lock_modes() {
88        for (suffix, mode) in [
89            ("INCREMENT BY 2", TableLockMode::ShareRowExclusive),
90            ("CACHE 1", TableLockMode::ShareRowExclusive),
91            ("RESTART", TableLockMode::ShareRowExclusive),
92            ("OWNED BY NONE", TableLockMode::ShareRowExclusive),
93            ("OWNER TO owner", TableLockMode::AccessExclusive),
94            ("RENAME TO renamed", TableLockMode::AccessExclusive),
95            ("SET SCHEMA archive", TableLockMode::AccessExclusive),
96            ("SET LOGGED", TableLockMode::AccessExclusive),
97            ("SET UNLOGGED", TableLockMode::AccessExclusive),
98        ] {
99            let sql = format!("ALTER SEQUENCE ids {suffix}");
100            let crate::Statement::AlterSequence(alter) = crate::compile(&sql).unwrap().remove(0)
101            else {
102                panic!("expected ALTER SEQUENCE: {sql}");
103            };
104            assert_eq!(sequence_alter_lock_mode(&alter), mode, "{sql}");
105        }
106    }
107
108    #[test]
109    fn sequence_value_generation_distinguishes_explicit_options_from_owner_and_name_changes() {
110        for (suffix, expected) in [
111            ("AS bigint", true),
112            ("INCREMENT BY 1", true),
113            ("START WITH 1", true),
114            ("RESTART", true),
115            ("RESTART WITH 1", true),
116            ("MINVALUE 1", true),
117            ("NO MINVALUE", true),
118            ("MAXVALUE 10", true),
119            ("NO MAXVALUE", true),
120            ("CYCLE", true),
121            ("NO CYCLE", true),
122            ("CACHE 1", true),
123            ("OWNED BY items.id CACHE 3", true),
124            ("OWNED BY NONE", false),
125            ("OWNED BY items.id", false),
126            ("OWNER TO owner", false),
127            ("RENAME TO renamed", false),
128            ("SET SCHEMA archive", false),
129            ("SET LOGGED", false),
130        ] {
131            let sql = format!("ALTER SEQUENCE ids {suffix}");
132            let crate::Statement::AlterSequence(alter) = crate::compile(&sql).unwrap().remove(0)
133            else {
134                panic!("expected an ALTER SEQUENCE statement: {sql}");
135            };
136            assert_eq!(
137                sequence_alter_changes_value_parameters(&alter),
138                expected,
139                "{sql}"
140            );
141        }
142    }
143}