Skip to main content

uqa_sql/schema/table_alteration/
syntax.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Validate ALTER TABLE transaction restrictions and bind native relation-kind actions.
8use crate::{
9    ast::{AlterTableAction, AlterTableStmt},
10    SQLError,
11};
12
13pub fn validate_alter_table_transaction(
14    stmt: &AlterTableStmt,
15    in_transaction_block: bool,
16) -> Result<(), SQLError> {
17    if in_transaction_block
18        && stmt.actions.iter().any(|action| {
19            matches!(
20                action,
21                AlterTableAction::DetachPartition {
22                    concurrently: true,
23                    ..
24                }
25            )
26        })
27    {
28        return Err(SQLError::Routine {
29            sqlstate: "25001".into(),
30            message: "ALTER TABLE ... DETACH CONCURRENTLY cannot run inside a transaction block"
31                .into(),
32        });
33    }
34    Ok(())
35}
36
37pub fn alter_sequence_from_table_syntax(
38    canonical: &str,
39    stmt: &AlterTableStmt,
40) -> Result<crate::ast::AlterSequence, SQLError> {
41    let mut alter = crate::ast::AlterSequence {
42        name: canonical.to_string(),
43        if_exists: stmt.if_exists,
44        ..crate::ast::AlterSequence::default()
45    };
46    match stmt.actions.as_slice() {
47        [AlterTableAction::SetPersistence { persistence }] => {
48            alter.persistence = Some(*persistence);
49        }
50        [AlterTableAction::RenameTable { to }] => {
51            alter.lifecycle = crate::ast::SequenceLifecycle::RenameTo { name: to.clone() };
52        }
53        [AlterTableAction::SetSchema { schema }] => {
54            alter.lifecycle = crate::ast::SequenceLifecycle::SetSchema {
55                schema: schema.clone(),
56            };
57        }
58        [AlterTableAction::ChangeOwner { owner }] => {
59            alter.role_owner = Some(owner.clone());
60        }
61        _ => {
62            return Err(SQLError::Routine {
63                sqlstate: "42809".into(),
64                message: format!("ALTER TABLE: relation `{canonical}` is a sequence, not a table"),
65            });
66        }
67    }
68    Ok(alter)
69}
70
71/// Return a native view rename, or None for a valid sequence of view event renames.
72pub fn alter_view_from_table_syntax(
73    canonical: &str,
74    kind: &str,
75    stmt: &AlterTableStmt,
76) -> Result<Option<crate::ast::AlterViewStmt>, SQLError> {
77    if let [AlterTableAction::RenameTable { to }] = stmt.actions.as_slice() {
78        return Ok(Some(crate::ast::AlterViewStmt {
79            name: canonical.to_string(),
80            kind: if kind == "view" {
81                crate::ast::AlterViewKind::View
82            } else {
83                crate::ast::AlterViewKind::MaterializedView
84            },
85            if_exists: stmt.if_exists,
86            action: crate::ast::AlterViewAction::RenameTo(to.clone()),
87        }));
88    }
89    if kind == "view"
90        && stmt.actions.iter().all(|action| {
91            matches!(
92                action,
93                AlterTableAction::RenameRule { .. } | AlterTableAction::RenameTrigger { .. }
94            )
95        })
96    {
97        return Ok(None);
98    }
99    Err(SQLError::Routine {
100        sqlstate: "42809".into(),
101        message: format!("ALTER TABLE: relation `{canonical}` is a {kind}, not a table"),
102    })
103}
104
105/// Return a native foreign-table owner or name change, or None for valid trigger actions.
106pub fn alter_foreign_table_from_table_syntax(
107    canonical: &str,
108    stmt: &AlterTableStmt,
109) -> Result<Option<crate::ast::AlterForeignTableStmt>, SQLError> {
110    if stmt.actions.iter().all(|action| {
111        matches!(
112            action,
113            AlterTableAction::RenameTrigger { .. } | AlterTableAction::SetTriggerEnableMode { .. }
114        )
115    }) {
116        return Ok(None);
117    }
118    let action = match stmt.actions.as_slice() {
119        [AlterTableAction::ChangeOwner { owner }] => {
120            crate::ast::AlterForeignTableAction::OwnerTo(owner.clone())
121        }
122        [AlterTableAction::RenameTable { to }] => {
123            crate::ast::AlterForeignTableAction::RenameTo(to.clone())
124        }
125        _ => {
126            return Err(SQLError::Routine {
127                sqlstate: "42809".into(),
128                message: format!(
129                    "ALTER TABLE: relation `{canonical}` is a foreign table, not a table"
130                ),
131            });
132        }
133    };
134    Ok(Some(crate::ast::AlterForeignTableStmt {
135        name: canonical.to_string(),
136        if_exists: stmt.if_exists,
137        action,
138    }))
139}