Skip to main content

uqa_sql/schema/table_alteration/
targets.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Resolve ALTER TABLE names before locking and lower the revalidated relation kind afterward.
8use super::syntax::{
9    alter_foreign_table_from_table_syntax, alter_sequence_from_table_syntax,
10    alter_view_from_table_syntax,
11};
12use crate::{
13    ast::{AlterForeignTableStmt, AlterSequence, AlterTableAction, AlterTableStmt, AlterViewStmt},
14    catalog::resolution::RelationResolution,
15    schema::relation_alteration::RelationAlterTarget,
16    SQLError,
17};
18
19pub enum BoundTableAlteration {
20    Table(AlterTableStmt),
21    Sequence(AlterSequence),
22    View(AlterViewStmt),
23    ForeignTable(AlterForeignTableStmt),
24    IndexRename {
25        name: String,
26        new_name: String,
27    },
28    ViewEvents {
29        name: String,
30        actions: Vec<AlterTableAction>,
31    },
32    ForeignTableEvents {
33        name: String,
34        actions: Vec<AlterTableAction>,
35    },
36}
37
38pub fn table_alter_target(
39    resolution: RelationResolution,
40    statement: &AlterTableStmt,
41    notice: &mut dyn FnMut(&str),
42) -> Result<Option<RelationAlterTarget>, SQLError> {
43    RelationAlterTarget::resolve(resolution, &statement.table, statement.if_exists, notice)
44}
45
46pub fn bind_table_alteration(
47    target: RelationAlterTarget,
48    mut statement: AlterTableStmt,
49) -> Result<BoundTableAlteration, SQLError> {
50    let RelationAlterTarget {
51        canonical,
52        relation,
53        kind,
54    } = target;
55    let bound = match kind {
56        "index"
57            if matches!(
58                statement.actions.as_slice(),
59                [AlterTableAction::RenameTable { .. }]
60            ) =>
61        {
62            let AlterTableAction::RenameTable { to } = &statement.actions[0] else {
63                unreachable!();
64            };
65            BoundTableAlteration::IndexRename {
66                name: canonical,
67                new_name: to.clone(),
68            }
69        }
70        "table" => {
71            statement.table = canonical;
72            BoundTableAlteration::Table(statement)
73        }
74        "sequence" => BoundTableAlteration::Sequence(alter_sequence_from_table_syntax(
75            &canonical, &statement,
76        )?),
77        "foreign table" => match alter_foreign_table_from_table_syntax(&canonical, &statement)? {
78            Some(change) => BoundTableAlteration::ForeignTable(change),
79            None => BoundTableAlteration::ForeignTableEvents {
80                name: canonical,
81                actions: statement.actions,
82            },
83        },
84        "view" | "materialized view" => {
85            match alter_view_from_table_syntax(&canonical, &relation.name, kind, &statement)? {
86                Some(change) => BoundTableAlteration::View(change),
87                None => BoundTableAlteration::ViewEvents {
88                    name: canonical,
89                    actions: statement.actions,
90                },
91            }
92        }
93        // `RangeVarCallbackForAlterRelation` wants composite types altered through ALTER TYPE.
94        "composite type" => {
95            let local = uqa_core::RelationIdentity::from_legacy_name(&canonical)
96                .map_or_else(|_| canonical.clone(), |relation| relation.name);
97            return Err(SQLError::Diagnostic {
98                sqlstate: "42809".into(),
99                message: format!("\"{local}\" is a composite type"),
100                detail: None,
101                hint: Some("Use ALTER TYPE instead.".into()),
102            });
103        }
104        _ => {
105            return Err(SQLError::Routine {
106                sqlstate: "42809".into(),
107                message: format!("ALTER TABLE: relation `{canonical}` is a {kind}, not a table"),
108            });
109        }
110    };
111    Ok(bound)
112}
113
114#[cfg(test)]
115mod tests;