uqa_sql/schema/sequences/
lifecycle.rs1use crate::{
9 ast::{RelationPersistence, SequenceBound, SequenceLifecycle, SequenceOwnership},
10 SQLError,
11};
12use uqa_core::RelationIdentity;
13pub trait SequenceLifecycleCatalog {
15 fn temporary_schema_name(&self) -> String;
16 fn sequence_is_owned(&self, relation: &RelationIdentity) -> bool;
17 fn relation_kind_at(&self, name: &str) -> Result<Option<&'static str>, String>;
18}
19pub fn validate_sequence_lifecycle_shape(
20 alter: &crate::ast::AlterSequence,
21) -> Result<(), SQLError> {
22 if alter.restart != crate::ast::SequenceRestart::Unchanged
23 || alter.increment.is_some()
24 || alter.start.is_some()
25 || alter.data_type.is_some()
26 || alter.min_value != SequenceBound::Unchanged
27 || alter.max_value != SequenceBound::Unchanged
28 || alter.cycle.is_some()
29 || alter.cache_size.is_some()
30 || alter.ownership != SequenceOwnership::Unchanged
31 || alter.persistence.is_some()
32 || alter.role_owner.is_some()
33 {
34 return Err(SQLError::Internal(
35 "ALTER SEQUENCE name lifecycle cannot contain definition changes".into(),
36 ));
37 }
38 Ok(())
39}
40
41pub fn sequence_lifecycle_target(
43 catalog: &dyn SequenceLifecycleCatalog,
44 source: &RelationIdentity,
45 lifecycle: &SequenceLifecycle,
46) -> Result<RelationIdentity, SQLError> {
47 match lifecycle {
48 SequenceLifecycle::Unchanged => Err(SQLError::Internal(
49 "sequence lifecycle executor received no action".into(),
50 )),
51 SequenceLifecycle::RenameTo { name } => {
52 let (schema, target_name) = RelationIdentity::parse_reference(name)
53 .map_err(|error| SQLError::Internal(format!("invalid sequence name: {error}")))?;
54 if schema.is_some() {
55 return Err(SQLError::Internal(
56 "ALTER SEQUENCE RENAME TO produced a qualified target".into(),
57 ));
58 }
59 Ok(RelationIdentity::new(&source.schema, target_name))
60 }
61 SequenceLifecycle::SetSchema { schema } => {
62 let (qualifier, target_schema) = RelationIdentity::parse_reference(schema)
63 .map_err(|error| SQLError::Internal(format!("invalid schema name: {error}")))?;
64 if qualifier.is_some() {
65 return Err(SQLError::Internal(
66 "ALTER SEQUENCE SET SCHEMA produced a qualified schema".into(),
67 ));
68 }
69 if catalog.sequence_is_owned(source) {
70 return Err(SQLError::Routine {
71 sqlstate: "0A000".into(),
72 message: "cannot move an owned sequence into another schema".into(),
73 });
74 }
75 Ok(RelationIdentity::new(target_schema, &source.name))
76 }
77 }
78}
79
80pub fn validate_sequence_lifecycle_target(
82 catalog: &dyn SequenceLifecycleCatalog,
83 source: &RelationIdentity,
84 target: &RelationIdentity,
85 persistence: RelationPersistence,
86 lifecycle: &SequenceLifecycle,
87) -> Result<bool, SQLError> {
88 let rename = matches!(lifecycle, SequenceLifecycle::RenameTo { .. });
89 if !rename {
90 if persistence == RelationPersistence::Temporary
91 || target.schema == catalog.temporary_schema_name()
92 {
93 return Err(SQLError::Routine {
94 sqlstate: "0A000".into(),
95 message: "cannot move objects into or out of temporary schemas".into(),
96 });
97 }
98 if source.schema == "pg_toast" || target.schema == "pg_toast" {
99 return Err(SQLError::Routine {
100 sqlstate: "0A000".into(),
101 message: "cannot move objects into or out of TOAST schema".into(),
102 });
103 }
104 if target == source {
105 return Ok(false);
106 }
107 }
108 reject_sequence_lifecycle_collision(catalog, source, target, rename)?;
109 Ok(true)
110}
111
112fn reject_sequence_lifecycle_collision(
113 catalog: &dyn SequenceLifecycleCatalog,
114 source: &RelationIdentity,
115 target: &RelationIdentity,
116 rename: bool,
117) -> Result<(), SQLError> {
118 if target == source
119 || catalog
120 .relation_kind_at(&target.qualified_name())
121 .map_err(|error| {
122 SQLError::Internal(format!(
123 "check sequence lifecycle target `{}`: {error}",
124 target.qualified_name()
125 ))
126 })?
127 .is_some()
128 {
129 return Err(SQLError::Routine {
130 sqlstate: "42P07".into(),
131 message: if rename {
132 format!("relation \"{}\" already exists", target.name)
133 } else {
134 format!(
135 "relation \"{}\" already exists in schema \"{}\"",
136 target.name, target.schema
137 )
138 },
139 });
140 }
141 Ok(())
142}
143
144use crate::catalog::resolution::RelationResolution;
145
146pub fn sequence_alter_relation(
148 resolution: crate::catalog::resolution::RelationResolution,
149 alter: &crate::ast::AlterSequence,
150) -> Result<Option<(String, &'static str)>, SQLError> {
151 match resolution {
152 RelationResolution::Found(name, kind) => Ok(Some((name, kind))),
153 RelationResolution::MissingRelation | RelationResolution::MissingSchema(_)
154 if alter.if_exists =>
155 {
156 Ok(None)
157 }
158 RelationResolution::MissingSchema(schema) => Err(SQLError::Routine {
159 sqlstate: "3F000".into(),
160 message: format!("schema \"{schema}\" does not exist"),
161 }),
162 RelationResolution::MissingRelation => Err(SQLError::Routine {
163 sqlstate: "42P01".into(),
164 message: format!("relation \"{}\" does not exist", alter.name),
165 }),
166 }
167}
168
169pub fn validate_sequence_alter_kind(
170 alter: &crate::ast::AlterSequence,
171 kind: &str,
172 local_name: &str,
173) -> Result<(), SQLError> {
174 if kind == "sequence" {
175 return Ok(());
176 }
177 let definition = alter.lifecycle == SequenceLifecycle::Unchanged
178 && alter.role_owner.is_none()
179 && alter.persistence.is_none();
180 Err(SQLError::Routine {
181 sqlstate: "42809".into(),
182 message: if definition {
183 format!("cannot open relation \"{local_name}\"")
184 } else {
185 format!("\"{local_name}\" is not a sequence")
186 },
187 })
188}
189
190#[cfg(test)]
191mod tests;