use super::schema::{CheckExpr, Column};
use super::types::ColumnType;
#[derive(Debug, Clone)]
pub enum AlterOp {
AddColumn(Column),
DropColumn {
name: String,
cascade: bool,
},
RenameColumn {
from: String,
to: String,
},
AlterType {
column: String,
new_type: ColumnType,
using: Option<String>,
},
SetNotNull(String),
DropNotNull(String),
SetDefault {
column: String,
expr: String,
},
DropDefault(String),
AddConstraint {
name: String,
constraint: TableConstraint,
},
DropConstraint {
name: String,
cascade: bool,
},
RenameTable(String),
SetSchema(String),
SetRowLevelSecurity(bool),
ForceRowLevelSecurity(bool),
}
#[derive(Debug, Clone)]
pub enum TableConstraint {
PrimaryKey(Vec<String>),
Unique(Vec<String>),
Check(CheckExpr),
ForeignKey {
columns: Vec<String>,
ref_table: String,
ref_columns: Vec<String>,
},
Exclude {
method: String,
elements: Vec<String>,
},
}
#[derive(Debug, Clone)]
pub struct AlterTable {
pub table: String,
pub ops: Vec<AlterOp>,
pub only: bool,
pub if_exists: bool,
}
impl AlterTable {
pub fn new(table: impl Into<String>) -> Self {
Self {
table: table.into(),
ops: Vec::new(),
only: false,
if_exists: false,
}
}
pub fn only(mut self) -> Self {
self.only = true;
self
}
pub fn if_exists(mut self) -> Self {
self.if_exists = true;
self
}
pub fn add_column(mut self, col: Column) -> Self {
self.ops.push(AlterOp::AddColumn(col));
self
}
pub fn drop_column(mut self, name: impl Into<String>) -> Self {
self.ops.push(AlterOp::DropColumn {
name: name.into(),
cascade: false,
});
self
}
pub fn drop_column_cascade(mut self, name: impl Into<String>) -> Self {
self.ops.push(AlterOp::DropColumn {
name: name.into(),
cascade: true,
});
self
}
pub fn rename_column(mut self, from: impl Into<String>, to: impl Into<String>) -> Self {
self.ops.push(AlterOp::RenameColumn {
from: from.into(),
to: to.into(),
});
self
}
pub fn set_type(mut self, column: impl Into<String>, new_type: ColumnType) -> Self {
self.ops.push(AlterOp::AlterType {
column: column.into(),
new_type,
using: None,
});
self
}
pub fn set_type_using(
mut self,
column: impl Into<String>,
new_type: ColumnType,
using: impl Into<String>,
) -> Self {
self.ops.push(AlterOp::AlterType {
column: column.into(),
new_type,
using: Some(using.into()),
});
self
}
pub fn set_not_null(mut self, column: impl Into<String>) -> Self {
self.ops.push(AlterOp::SetNotNull(column.into()));
self
}
pub fn drop_not_null(mut self, column: impl Into<String>) -> Self {
self.ops.push(AlterOp::DropNotNull(column.into()));
self
}
pub fn set_default(mut self, column: impl Into<String>, expr: impl Into<String>) -> Self {
self.ops.push(AlterOp::SetDefault {
column: column.into(),
expr: expr.into(),
});
self
}
pub fn drop_default(mut self, column: impl Into<String>) -> Self {
self.ops.push(AlterOp::DropDefault(column.into()));
self
}
pub fn add_constraint(mut self, name: impl Into<String>, constraint: TableConstraint) -> Self {
self.ops.push(AlterOp::AddConstraint {
name: name.into(),
constraint,
});
self
}
pub fn drop_constraint(mut self, name: impl Into<String>) -> Self {
self.ops.push(AlterOp::DropConstraint {
name: name.into(),
cascade: false,
});
self
}
pub fn drop_constraint_cascade(mut self, name: impl Into<String>) -> Self {
self.ops.push(AlterOp::DropConstraint {
name: name.into(),
cascade: true,
});
self
}
pub fn rename_to(mut self, name: impl Into<String>) -> Self {
self.ops.push(AlterOp::RenameTable(name.into()));
self
}
pub fn set_schema(mut self, schema: impl Into<String>) -> Self {
self.ops.push(AlterOp::SetSchema(schema.into()));
self
}
pub fn enable_rls(mut self) -> Self {
self.ops.push(AlterOp::SetRowLevelSecurity(true));
self
}
pub fn disable_rls(mut self) -> Self {
self.ops.push(AlterOp::SetRowLevelSecurity(false));
self
}
pub fn force_rls(mut self) -> Self {
self.ops.push(AlterOp::ForceRowLevelSecurity(true));
self
}
pub fn no_force_rls(mut self) -> Self {
self.ops.push(AlterOp::ForceRowLevelSecurity(false));
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::migrate::types::ColumnType;
#[test]
fn test_alter_table_builder() {
let alter = AlterTable::new("users")
.add_column(Column::new("bio", ColumnType::Text))
.drop_column("legacy")
.rename_column("username", "handle")
.set_not_null("email");
assert_eq!(alter.table, "users");
assert_eq!(alter.ops.len(), 4);
}
#[test]
fn test_alter_type_with_using() {
let alter = AlterTable::new("users").set_type_using("age", ColumnType::Int, "age::integer");
match &alter.ops[0] {
AlterOp::AlterType { column, using, .. } => {
assert_eq!(column, "age");
assert_eq!(using.as_ref().unwrap(), "age::integer");
}
_ => panic!("Expected AlterType"),
}
}
#[test]
fn test_add_constraint() {
let alter = AlterTable::new("users")
.add_constraint("pk_users", TableConstraint::PrimaryKey(vec!["id".into()]));
assert_eq!(alter.ops.len(), 1);
}
}