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