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