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