Skip to main content

uqa_sql/ast/
relation_lifecycle.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Relation persistence, view options, and sequence lifecycle nodes.
8
9use super::{AclRoleSpecification, RoleSpecification};
10use serde::{Deserialize, Serialize};
11
12/// `PostgreSQL`'s `pg_class.relpersistence` contract.
13#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
14pub enum RelationPersistence {
15    #[default]
16    Permanent,
17    Unlogged,
18    Temporary,
19}
20
21impl RelationPersistence {
22    #[must_use]
23    pub const fn catalog_code(self) -> &'static str {
24        match self {
25            Self::Permanent => "p",
26            Self::Unlogged => "u",
27            Self::Temporary => "t",
28        }
29    }
30}
31
32/// `ON COMMIT` behavior retained with a temporary table definition.
33#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
34pub enum OnCommitAction {
35    #[default]
36    PreserveRows,
37    DeleteRows,
38    Drop,
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
42pub enum AlterViewKind {
43    View,
44    MaterializedView,
45}
46
47#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
48pub enum AlterViewAction {
49    Set(Vec<(String, String)>),
50    Reset(Vec<String>),
51    OwnerTo(RoleSpecification),
52    RenameTo(String),
53    SetSchema(String),
54}
55
56#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
57pub struct AlterViewStmt {
58    pub name: String,
59    pub kind: AlterViewKind,
60    pub if_exists: bool,
61    pub action: AlterViewAction,
62}
63
64#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
65pub struct DropColumnAction {
66    pub name: String,
67    pub if_exists: bool,
68    pub cascade: bool,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
72pub enum AlterForeignTableAction {
73    DropColumns(Vec<DropColumnAction>),
74    OwnerTo(RoleSpecification),
75    RenameTo(String),
76    SetSchema(String),
77}
78
79#[derive(Debug, Clone, PartialEq, Eq)]
80pub struct AlterForeignTableStmt {
81    pub name: String,
82    pub if_exists: bool,
83    pub action: AlterForeignTableAction,
84}
85
86#[derive(Serialize, Deserialize)]
87struct AlterForeignTableStmtSerde {
88    name: String,
89    if_exists: bool,
90    #[serde(default, skip_serializing_if = "Option::is_none")]
91    owner: Option<RoleSpecification>,
92    #[serde(default, skip_serializing_if = "Option::is_none")]
93    rename_to: Option<String>,
94    #[serde(default, skip_serializing_if = "Option::is_none")]
95    set_schema: Option<String>,
96    #[serde(default, skip_serializing_if = "Option::is_none")]
97    drop_columns: Option<Vec<DropColumnAction>>,
98}
99
100impl Serialize for AlterForeignTableStmt {
101    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
102    where
103        S: serde::Serializer,
104    {
105        let (owner, rename_to, set_schema, drop_columns) = match &self.action {
106            AlterForeignTableAction::OwnerTo(owner) => (Some(owner.clone()), None, None, None),
107            AlterForeignTableAction::RenameTo(name) => (None, Some(name.clone()), None, None),
108            AlterForeignTableAction::SetSchema(schema) => (None, None, Some(schema.clone()), None),
109            AlterForeignTableAction::DropColumns(columns) => {
110                (None, None, None, Some(columns.clone()))
111            }
112        };
113        AlterForeignTableStmtSerde {
114            name: self.name.clone(),
115            if_exists: self.if_exists,
116            owner,
117            rename_to,
118            set_schema,
119            drop_columns,
120        }
121        .serialize(serializer)
122    }
123}
124
125impl<'de> Deserialize<'de> for AlterForeignTableStmt {
126    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
127    where
128        D: serde::Deserializer<'de>,
129    {
130        let value = AlterForeignTableStmtSerde::deserialize(deserializer)?;
131        let action = match (
132            value.owner,
133            value.rename_to,
134            value.set_schema,
135            value.drop_columns,
136        ) {
137            (Some(owner), None, None, None) => AlterForeignTableAction::OwnerTo(owner),
138            (None, Some(name), None, None) => AlterForeignTableAction::RenameTo(name),
139            (None, None, Some(schema), None) => AlterForeignTableAction::SetSchema(schema),
140            (None, None, None, Some(columns)) if !columns.is_empty() => {
141                AlterForeignTableAction::DropColumns(columns)
142            }
143            (Some(_), Some(_), None, None) => {
144                return Err(serde::de::Error::custom(
145                    "ALTER FOREIGN TABLE cannot contain both owner and rename_to",
146                ));
147            }
148            _ => {
149                return Err(serde::de::Error::custom(
150                    "ALTER FOREIGN TABLE requires exactly one of owner, rename_to, set_schema or nonempty drop_columns",
151                ));
152            }
153        };
154        Ok(Self {
155            name: value.name,
156            if_exists: value.if_exists,
157            action,
158        })
159    }
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct CreateSequence {
164    pub name: String,
165    pub if_not_exists: bool,
166    pub start: i64,
167    pub increment: i64,
168    #[serde(default)]
169    pub persistence: RelationPersistence,
170    #[serde(default)]
171    pub data_type: SequenceDataType,
172    /// Concrete bounds are written by current compilers. `None` is retained for backward-compatible plans and means the `PostgreSQL` default for the declared type and increment direction.
173    #[serde(default)]
174    pub min_value: Option<i64>,
175    #[serde(default)]
176    pub max_value: Option<i64>,
177    #[serde(default)]
178    pub cycle: bool,
179    #[serde(default = "default_sequence_cache_size")]
180    pub cache_size: i64,
181    #[serde(default)]
182    pub ownership: SequenceOwnership,
183    /// `RESTART [WITH value]`: the value the first `nextval` returns, instead of the start.
184    #[serde(default)]
185    pub restart: SequenceRestart,
186}
187
188#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
189pub enum SequenceDataType {
190    SmallInt,
191    Integer,
192    #[default]
193    BigInt,
194}
195
196const fn default_sequence_cache_size() -> i64 {
197    1
198}
199
200impl SequenceDataType {
201    #[must_use]
202    pub const fn sql_name(self) -> &'static str {
203        match self {
204            Self::SmallInt => "smallint",
205            Self::Integer => "integer",
206            Self::BigInt => "bigint",
207        }
208    }
209
210    #[must_use]
211    pub const fn bounds(self) -> (i64, i64) {
212        match self {
213            Self::SmallInt => (i16::MIN as i64, i16::MAX as i64),
214            Self::Integer => (i32::MIN as i64, i32::MAX as i64),
215            Self::BigInt => (i64::MIN, i64::MAX),
216        }
217    }
218}
219
220/// Physical restart action carried by `ALTER SEQUENCE`.
221#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
222pub enum SequenceRestart {
223    /// No `RESTART` clause was specified.
224    #[default]
225    Unchanged,
226    /// Bare `RESTART`; allocate the configured start value next.
227    FromStart,
228    /// `RESTART WITH value`; allocate the supplied value next.
229    With(i64),
230}
231
232/// `ALTER SEQUENCE` bound action, distinguishing omission from `NO MINVALUE` or `NO MAXVALUE`.
233#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
234pub enum SequenceBound {
235    #[default]
236    Unchanged,
237    Default,
238    Value(i64),
239}
240
241/// `OWNED BY` action carried by `CREATE SEQUENCE` and `ALTER SEQUENCE`.
242#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
243pub enum SequenceOwnership {
244    /// No ownership clause was specified. On `CREATE SEQUENCE` this creates an unowned sequence; on `ALTER SEQUENCE` it preserves the current dependency.
245    #[default]
246    Unchanged,
247    /// Explicit `OWNED BY NONE`.
248    Unowned,
249    /// A table relation and one of its columns. The engine resolves both names to stable catalog object identities before persisting the dependency.
250    Column { table: String, column: String },
251}
252
253/// Name or namespace lifecycle action carried by `ALTER SEQUENCE`.
254#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
255pub enum SequenceLifecycle {
256    /// No name or namespace change was specified.
257    #[default]
258    Unchanged,
259    RenameTo {
260        name: String,
261    },
262    SetSchema {
263        schema: String,
264    },
265}
266
267fn deserialize_sequence_restart<'de, D>(deserializer: D) -> Result<SequenceRestart, D::Error>
268where
269    D: serde::Deserializer<'de>,
270{
271    #[derive(Deserialize)]
272    enum Current {
273        Unchanged,
274        FromStart,
275        With(i64),
276    }
277
278    #[derive(Deserialize)]
279    #[serde(untagged)]
280    enum Representation {
281        Current(Current),
282        // Before SequenceRestart existed this field was
283        // Option<Option<i64>>, serialized as null or an integer.
284        Legacy(Option<i64>),
285    }
286
287    Ok(match Representation::deserialize(deserializer)? {
288        Representation::Current(Current::Unchanged) | Representation::Legacy(None) => {
289            SequenceRestart::Unchanged
290        }
291        Representation::Current(Current::FromStart) => SequenceRestart::FromStart,
292        Representation::Current(Current::With(value)) | Representation::Legacy(Some(value)) => {
293            SequenceRestart::With(value)
294        }
295    })
296}
297
298#[derive(Debug, Clone, Default, Serialize, Deserialize)]
299pub struct AlterSequence {
300    pub name: String,
301    /// `ALTER SEQUENCE IF EXISTS` suppresses only a missing sequence.
302    #[serde(default)]
303    pub if_exists: bool,
304    /// `RESTART [WITH n]`, preserving omitted, bare, and explicit forms.
305    #[serde(default, deserialize_with = "deserialize_sequence_restart")]
306    pub restart: SequenceRestart,
307    pub increment: Option<i64>,
308    pub start: Option<i64>,
309    #[serde(default)]
310    pub data_type: Option<SequenceDataType>,
311    #[serde(default)]
312    pub min_value: SequenceBound,
313    #[serde(default)]
314    pub max_value: SequenceBound,
315    #[serde(default)]
316    pub cycle: Option<bool>,
317    pub cache_size: Option<i64>,
318    #[serde(default)]
319    pub ownership: SequenceOwnership,
320    /// `SET LOGGED` or `SET UNLOGGED`. Temporary is never a valid requested target state.
321    #[serde(default)]
322    pub persistence: Option<RelationPersistence>,
323    /// `OWNER TO role`, distinct from column ownership expressed by `OWNED BY`.
324    #[serde(default, skip_serializing_if = "Option::is_none")]
325    pub role_owner: Option<RoleSpecification>,
326    /// `RENAME TO` or `SET SCHEMA`, kept distinct from definition changes.
327    #[serde(default)]
328    pub lifecycle: SequenceLifecycle,
329}
330
331/// One requested sequence privilege. Unsupported names survive compilation so execution can preserve `PostgreSQL` target- and role-resolution precedence.
332#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
333pub enum SequencePrivilege {
334    Select,
335    Update,
336    Usage,
337    ColumnsUnsupported,
338    Unsupported(String),
339}
340
341/// One table privilege and its optional column list. Unsupported names and column forms survive compilation so execution can preserve `PostgreSQL` object- and role-resolution precedence.
342#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
343pub struct TablePrivilegeSpec {
344    pub privilege: TablePrivilege,
345    #[serde(default, skip_serializing_if = "Vec::is_empty")]
346    pub columns: Vec<String>,
347}
348
349/// One requested ordinary-table privilege.
350#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
351pub enum TablePrivilege {
352    Select,
353    Insert,
354    Update,
355    Delete,
356    Truncate,
357    References,
358    Trigger,
359    Maintain,
360    Usage,
361    Unsupported(String),
362}
363
364/// Relation targets carried by `GRANT` or `REVOKE` with the `TABLE` object class.
365#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
366pub enum GrantTableTarget {
367    Relations { names: Vec<String> },
368    AllTablesInSchemas { schemas: Vec<String> },
369}
370
371/// Dependency behavior for ordinary-table privilege revocation.
372#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
373pub enum TableRevokeBehavior {
374    #[default]
375    Restrict,
376    Cascade,
377}
378
379/// `GRANT` or `REVOKE` of privileges on ordinary tables. An empty privilege list records `ALL PRIVILEGES` so explicit sequence targets can expand against their own privilege set.
380#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
381pub struct GrantTableStmt {
382    pub is_grant: bool,
383    pub grant_option: bool,
384    pub grant_option_only: bool,
385    pub privileges: Vec<TablePrivilegeSpec>,
386    pub target: GrantTableTarget,
387    pub grantees: Vec<AclRoleSpecification>,
388    #[serde(default, skip_serializing_if = "Option::is_none")]
389    pub grantor: Option<RoleSpecification>,
390    #[serde(default)]
391    pub revoke_behavior: TableRevokeBehavior,
392}
393
394/// Relation targets carried by `GRANT` or `REVOKE` for sequences.
395#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
396pub enum GrantSequenceTarget {
397    Sequences { names: Vec<String> },
398    AllSequencesInSchemas { schemas: Vec<String> },
399}
400
401/// Dependency behavior for sequence privilege revocation.
402#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
403pub enum SequenceRevokeBehavior {
404    #[default]
405    Restrict,
406    Cascade,
407}
408
409/// `GRANT` or `REVOKE` of `USAGE`, `SELECT`, and `UPDATE` on sequences.
410#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
411pub struct GrantSequenceStmt {
412    pub is_grant: bool,
413    pub grant_option: bool,
414    pub grant_option_only: bool,
415    pub privileges: Vec<SequencePrivilege>,
416    pub target: GrantSequenceTarget,
417    pub grantees: Vec<AclRoleSpecification>,
418    #[serde(default, skip_serializing_if = "Option::is_none")]
419    pub grantor: Option<RoleSpecification>,
420    #[serde(default)]
421    pub revoke_behavior: SequenceRevokeBehavior,
422}
423
424/// One requested database privilege. Unsupported names survive compilation so execution can preserve `PostgreSQL` target- and role-resolution precedence.
425#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
426pub enum DatabasePrivilege {
427    Connect,
428    Create,
429    Temporary,
430    Unsupported(String),
431}
432
433/// Dependency behavior for database privilege revocation.
434#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
435pub enum DatabaseRevokeBehavior {
436    #[default]
437    Restrict,
438    Cascade,
439}
440
441/// `GRANT` or `REVOKE` of `CONNECT`, `CREATE`, and `TEMPORARY` on databases.
442#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
443pub struct GrantDatabaseStmt {
444    pub is_grant: bool,
445    pub grant_option: bool,
446    pub grant_option_only: bool,
447    pub privileges: Vec<DatabasePrivilege>,
448    pub databases: Vec<String>,
449    pub grantees: Vec<AclRoleSpecification>,
450    #[serde(default, skip_serializing_if = "Option::is_none")]
451    pub grantor: Option<RoleSpecification>,
452    #[serde(default)]
453    pub revoke_behavior: DatabaseRevokeBehavior,
454}
455
456/// One requested schema privilege. Unsupported names survive compilation so execution can preserve `PostgreSQL` target- and role-resolution precedence.
457#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
458pub enum SchemaPrivilege {
459    Usage,
460    Create,
461    Unsupported(String),
462}
463
464/// Dependency behavior for schema privilege revocation.
465#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
466pub enum SchemaRevokeBehavior {
467    #[default]
468    Restrict,
469    Cascade,
470}
471
472/// `GRANT` or `REVOKE` of `USAGE` and `CREATE` on schemas.
473#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
474pub struct GrantSchemaStmt {
475    pub is_grant: bool,
476    pub grant_option: bool,
477    pub grant_option_only: bool,
478    pub privileges: Vec<SchemaPrivilege>,
479    pub schemas: Vec<String>,
480    pub grantees: Vec<AclRoleSpecification>,
481    #[serde(default, skip_serializing_if = "Option::is_none")]
482    pub grantor: Option<RoleSpecification>,
483    #[serde(default)]
484    pub revoke_behavior: SchemaRevokeBehavior,
485}