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, TableLockMode},
10    SQLError,
11};
12
13pub fn table_alter_lock_mode(statement: &AlterTableStmt) -> TableLockMode {
14    let mut mode = TableLockMode::ShareUpdateExclusive;
15    for action in &statement.actions {
16        match action {
17            AlterTableAction::SetTriggerEnableMode { .. }
18            | AlterTableAction::AddForeignKeyConstraint { .. } => {
19                mode = TableLockMode::ShareRowExclusive;
20            }
21            AlterTableAction::ValidateConstraint { .. }
22            | AlterTableAction::AttachPartition { .. }
23            | AlterTableAction::DetachPartition {
24                concurrently: true, ..
25            }
26            | AlterTableAction::DetachPartition { finalize: true, .. } => {}
27            _ => return TableLockMode::AccessExclusive,
28        }
29    }
30    mode
31}
32
33pub fn validate_alter_table_transaction(
34    stmt: &AlterTableStmt,
35    in_transaction_block: bool,
36) -> Result<(), SQLError> {
37    if in_transaction_block
38        && stmt.actions.iter().any(|action| {
39            matches!(
40                action,
41                AlterTableAction::DetachPartition {
42                    concurrently: true,
43                    ..
44                }
45            )
46        })
47    {
48        return Err(SQLError::Routine {
49            sqlstate: "25001".into(),
50            message: "ALTER TABLE ... DETACH CONCURRENTLY cannot run inside a transaction block"
51                .into(),
52        });
53    }
54    Ok(())
55}
56
57pub fn alter_sequence_from_table_syntax(
58    canonical: &str,
59    stmt: &AlterTableStmt,
60) -> Result<crate::ast::AlterSequence, SQLError> {
61    let mut alter = crate::ast::AlterSequence {
62        name: canonical.to_string(),
63        if_exists: stmt.if_exists,
64        ..crate::ast::AlterSequence::default()
65    };
66    match stmt.actions.as_slice() {
67        [AlterTableAction::SetPersistence { persistence }] => {
68            alter.persistence = Some(*persistence);
69        }
70        [AlterTableAction::RenameTable { to }] => {
71            alter.lifecycle = crate::ast::SequenceLifecycle::RenameTo { name: to.clone() };
72        }
73        [AlterTableAction::SetSchema { schema }] => {
74            alter.lifecycle = crate::ast::SequenceLifecycle::SetSchema {
75                schema: schema.clone(),
76            };
77        }
78        [AlterTableAction::ChangeOwner { owner }] => {
79            alter.role_owner = Some(owner.clone());
80        }
81        _ => {
82            return Err(SQLError::Routine {
83                sqlstate: "42809".into(),
84                message: format!("ALTER TABLE: relation `{canonical}` is a sequence, not a table"),
85            });
86        }
87    }
88    Ok(alter)
89}
90
91/// Return a native view owner or name change, or None for a valid sequence of view event renames.
92pub fn alter_view_from_table_syntax(
93    canonical: &str,
94    kind: &str,
95    stmt: &AlterTableStmt,
96) -> Result<Option<crate::ast::AlterViewStmt>, SQLError> {
97    let action = match stmt.actions.as_slice() {
98        [AlterTableAction::RenameTable { to }] => {
99            Some(crate::ast::AlterViewAction::RenameTo(to.clone()))
100        }
101        [AlterTableAction::ChangeOwner { owner }] => {
102            Some(crate::ast::AlterViewAction::OwnerTo(owner.clone()))
103        }
104        _ => None,
105    };
106    if let Some(action) = action {
107        return Ok(Some(crate::ast::AlterViewStmt {
108            name: canonical.to_string(),
109            kind: if kind == "view" {
110                crate::ast::AlterViewKind::View
111            } else {
112                crate::ast::AlterViewKind::MaterializedView
113            },
114            if_exists: stmt.if_exists,
115            action,
116        }));
117    }
118    if kind == "view"
119        && stmt.actions.iter().all(|action| {
120            matches!(
121                action,
122                AlterTableAction::RenameRule { .. } | AlterTableAction::RenameTrigger { .. }
123            )
124        })
125    {
126        return Ok(None);
127    }
128    Err(SQLError::Routine {
129        sqlstate: "42809".into(),
130        message: format!("ALTER TABLE: relation `{canonical}` is a {kind}, not a table"),
131    })
132}
133
134/// Return a native foreign-table owner or name change, or None for valid trigger actions.
135pub fn alter_foreign_table_from_table_syntax(
136    canonical: &str,
137    stmt: &AlterTableStmt,
138) -> Result<Option<crate::ast::AlterForeignTableStmt>, SQLError> {
139    if stmt.actions.iter().all(|action| {
140        matches!(
141            action,
142            AlterTableAction::RenameTrigger { .. } | AlterTableAction::SetTriggerEnableMode { .. }
143        )
144    }) {
145        return Ok(None);
146    }
147    let action = match stmt.actions.as_slice() {
148        [AlterTableAction::ChangeOwner { owner }] => {
149            crate::ast::AlterForeignTableAction::OwnerTo(owner.clone())
150        }
151        [AlterTableAction::RenameTable { to }] => {
152            crate::ast::AlterForeignTableAction::RenameTo(to.clone())
153        }
154        _ => {
155            return Err(SQLError::Routine {
156                sqlstate: "42809".into(),
157                message: format!(
158                    "ALTER TABLE: relation `{canonical}` is a foreign table, not a table"
159                ),
160            });
161        }
162    };
163    Ok(Some(crate::ast::AlterForeignTableStmt {
164        name: canonical.to_string(),
165        if_exists: stmt.if_exists,
166        action,
167    }))
168}
169
170#[cfg(test)]
171mod tests;