1use crate::schema::Constraint;
2use crate::util::SqlExtension;
3use crate::{Column, Dialect, ToSql, Type};
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub enum AlterColumnAction {
7 SetType { typ: Type, using: Option<String> },
8 SetNullable(bool),
9}
10
11#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum AlterAction {
14 AddColumn {
15 column: Column,
16 },
17 AlterColumn {
18 name: String,
19 action: AlterColumnAction,
20 },
21 AddConstraint {
22 name: String,
23 column: String,
24 constraint: Constraint,
25 },
26}
27
28impl AlterAction {
29 pub fn set_nullable(name: String, nullable: bool) -> Self {
30 Self::AlterColumn {
31 name,
32 action: AlterColumnAction::SetNullable(nullable),
33 }
34 }
35
36 pub fn set_type(name: String, typ: Type) -> Self {
37 Self::AlterColumn {
38 name,
39 action: AlterColumnAction::SetType { typ, using: None },
40 }
41 }
42
43 pub fn add_constraint(table: &str, column: String, constraint: Constraint) -> Self {
44 let name = format!("fk_{table}_{column}");
45 Self::AddConstraint {
46 name,
47 column,
48 constraint,
49 }
50 }
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct AlterTable {
55 pub schema: Option<String>,
56 pub name: String,
57 pub actions: Vec<AlterAction>,
58}
59
60impl ToSql for AlterTable {
61 fn write_sql(&self, buf: &mut String, dialect: Dialect) {
62 #[cfg(feature = "tracing")]
63 tracing::error_span!(
64 "alter-table",
65 table = format!(
66 "{}{}{}",
67 self.schema.as_deref().unwrap_or(""),
68 if self.schema.is_some() { "." } else { "" },
69 self.name
70 )
71 );
72 buf.push_str("ALTER TABLE ");
73 buf.push_table_name(&self.schema, &self.name);
74 buf.push_sql_sequence(&self.actions, ",", dialect);
75 }
76}
77
78impl ToSql for AlterAction {
79 fn write_sql(&self, buf: &mut String, dialect: Dialect) {
80 use AlterAction::*;
81 match self {
82 AddColumn { column } => {
83 buf.push_str(" ADD COLUMN ");
84 buf.push_str(&column.to_sql(dialect));
85 }
86 AlterColumn { name, action } => {
87 use AlterColumnAction::*;
88 buf.push_str(" ALTER COLUMN ");
89 buf.push_quoted(name);
90 match action {
91 SetType { typ, using } => {
92 buf.push_str(" TYPE ");
93 buf.push_sql(typ, dialect);
94 buf.push_str(" USING ");
95 if let Some(using) = using {
96 buf.push_str(&using)
97 } else {
98 buf.push_quoted(name);
99 buf.push_str("::");
100 buf.push_sql(typ, dialect);
101 }
102 }
103 SetNullable(nullable) => {
104 if *nullable {
105 buf.push_str(" DROP NOT NULL");
106 } else {
107 buf.push_str(" SET NOT NULL");
108 }
109 }
110 }
111 }
112 AddConstraint {
113 name,
114 column,
115 constraint,
116 } => {
117 buf.push_str(" ADD CONSTRAINT ");
118 buf.push_quoted(name);
119 buf.push_str(" FOREIGN KEY (");
120 buf.push_quoted(column);
121 buf.push_str(") ");
122 buf.push_sql(constraint, dialect);
123 }
124 }
125 }
126}
127
128#[cfg(test)]
129mod test {
130 use super::*;
131
132 #[test]
133 fn test_alter_action() {
134 let alter = AlterAction::AlterColumn {
135 name: "foo".to_string(),
136 action: AlterColumnAction::SetType {
137 typ: Type::Text,
138 using: None,
139 },
140 };
141 assert_eq!(
142 alter.to_sql(Dialect::Postgres),
143 r#" ALTER COLUMN "foo" TYPE character varying USING "foo"::character varying"#
144 );
145
146 let alter = AlterAction::AlterColumn {
147 name: "foo".to_string(),
148 action: AlterColumnAction::SetType {
149 typ: Type::Text,
150 using: Some("SUBSTRING(foo, 1, 3)".to_string()),
151 },
152 };
153 assert_eq!(
154 alter.to_sql(Dialect::Postgres),
155 r#" ALTER COLUMN "foo" TYPE character varying USING SUBSTRING(foo, 1, 3)"#
156 );
157 }
158}