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