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