use drizzle_migrations::postgres::{
PostgresDDL,
collection::diff_ddl,
ddl::{
Column, ForeignKey, Generated, GeneratedType, Identity, Index, IndexColumn, PrimaryKey,
Table, UniqueConstraint,
},
statements::Generator as PostgresGenerator,
};
use std::borrow::Cow;
fn diff_to_sql(from: &PostgresDDL, to: &PostgresDDL) -> Vec<String> {
let diffs = diff_ddl(from, to);
let generator = PostgresGenerator::new().with_breakpoints(false);
generator.generate(&diffs)
}
fn normalize_create_table(sql: &str) -> String {
let Some(paren_start) = sql.find("(\n\t") else {
return sql.to_string();
};
let header = &sql[..paren_start + 1];
let body = &sql[paren_start + 3..sql.len() - 3];
let mut lines: Vec<&str> = body.split(",\n\t").collect();
lines.sort();
format!("{}\n\t{}\n);", header, lines.join(",\n\t"))
}
fn column(table: &str, name: &str, sql_type: &str) -> Column {
Column {
schema: Cow::Borrowed("public"),
table: Cow::Owned(table.to_string()),
name: Cow::Owned(name.to_string()),
sql_type: Cow::Owned(sql_type.to_string()),
type_schema: None,
not_null: false,
default: None,
generated: None,
identity: None,
dimensions: None,
comment: None,
collate: None,
ordinal_position: None,
}
}
fn column_not_null(table: &str, name: &str, sql_type: &str) -> Column {
Column {
not_null: true,
..column(table, name, sql_type)
}
}
fn column_default(table: &str, name: &str, sql_type: &str, default: &str) -> Column {
Column {
default: Some(Cow::Owned(default.to_string())),
..column(table, name, sql_type)
}
}
fn table(name: &str) -> Table {
Table {
schema: Cow::Borrowed("public"),
name: Cow::Owned(name.to_string()),
is_unlogged: None,
is_temporary: None,
inherits: None,
tablespace: None,
is_rls_enabled: None,
comment: None,
}
}
fn primary_key(table_name: &str, columns: Vec<&str>) -> PrimaryKey {
let pk_name = format!("{}_pkey", table_name);
PrimaryKey::from_strings(
"public".to_string(),
table_name.to_string(),
pk_name,
columns.into_iter().map(|s| s.to_string()).collect(),
)
}
fn foreign_key(
table_name: &str,
name: &str,
columns: Vec<&str>,
ref_table: &str,
ref_columns: Vec<&str>,
) -> ForeignKey {
ForeignKey::from_strings(
"public".to_string(),
table_name.to_string(),
name.to_string(),
columns.into_iter().map(|s| s.to_string()).collect(),
"public".to_string(),
ref_table.to_string(),
ref_columns.into_iter().map(|s| s.to_string()).collect(),
)
}
fn index(table_name: &str, name: &str, columns: Vec<&str>) -> Index {
Index {
schema: Cow::Borrowed("public"),
table: Cow::Owned(table_name.to_string()),
name: Cow::Owned(name.to_string()),
name_explicit: false,
columns: columns
.into_iter()
.map(|c| IndexColumn {
value: Cow::Owned(c.to_string()),
is_expression: false,
asc: true,
nulls_first: false,
opclass: None,
})
.collect(),
method: None,
is_unique: false,
concurrently: false,
where_clause: None,
with: None,
}
}
fn unique_constraint(table_name: &str, name: &str, columns: Vec<&str>) -> UniqueConstraint {
UniqueConstraint::from_strings(
"public".to_string(),
table_name.to_string(),
name.to_string(),
columns.into_iter().map(|s| s.to_string()).collect(),
)
}
#[test]
fn test_add_column_not_null() {
let mut from = PostgresDDL::new();
from.tables.push(table("users"));
from.columns.push(column_not_null("users", "id", "integer"));
from.pks.push(primary_key("users", vec!["id"]));
let mut to = PostgresDDL::new();
to.tables.push(table("users"));
to.columns.push(column_not_null("users", "id", "integer"));
to.columns.push(column_not_null("users", "name", "text"));
to.pks.push(primary_key("users", vec!["id"]));
let sql = diff_to_sql(&from, &to);
assert_eq!(sql.len(), 1, "Expected 1 SQL statement, got: {:?}", sql);
assert_eq!(
sql[0],
"ALTER TABLE \"users\" ADD COLUMN \"name\" text NOT NULL;"
);
}
#[test]
fn test_add_multiple_columns() {
let mut from = PostgresDDL::new();
from.tables.push(table("users"));
from.columns.push(column_not_null("users", "id", "integer"));
from.pks.push(primary_key("users", vec!["id"]));
let mut to = PostgresDDL::new();
to.tables.push(table("users"));
to.columns.push(column_not_null("users", "id", "integer"));
to.columns.push(column("users", "name", "text"));
to.columns.push(column("users", "email", "text"));
to.pks.push(primary_key("users", vec!["id"]));
let sql = diff_to_sql(&from, &to);
assert_eq!(sql.len(), 2, "Expected 2 SQL statements, got: {:?}", sql);
let mut sorted = sql.clone();
sorted.sort();
assert_eq!(
sorted,
vec![
"ALTER TABLE \"users\" ADD COLUMN \"email\" text;",
"ALTER TABLE \"users\" ADD COLUMN \"name\" text;",
],
"Unexpected ADD COLUMN statements: {:?}",
sql
);
}
#[test]
fn test_add_column_with_default() {
let mut from = PostgresDDL::new();
from.tables.push(table("users"));
from.columns.push(column_not_null("users", "id", "integer"));
from.pks.push(primary_key("users", vec!["id"]));
let mut to = PostgresDDL::new();
to.tables.push(table("users"));
to.columns.push(column_not_null("users", "id", "integer"));
to.columns
.push(column_default("users", "status", "text", "'active'"));
to.pks.push(primary_key("users", vec!["id"]));
let sql = diff_to_sql(&from, &to);
assert_eq!(sql.len(), 1, "Expected 1 SQL statement, got: {:?}", sql);
assert_eq!(
sql[0],
"ALTER TABLE \"users\" ADD COLUMN \"status\" text DEFAULT 'active';"
);
}
#[test]
fn test_drop_column() {
let mut from = PostgresDDL::new();
from.tables.push(table("users"));
from.columns.push(column_not_null("users", "id", "integer"));
from.columns.push(column("users", "name", "text"));
from.pks.push(primary_key("users", vec!["id"]));
let mut to = PostgresDDL::new();
to.tables.push(table("users"));
to.columns.push(column_not_null("users", "id", "integer"));
to.pks.push(primary_key("users", vec!["id"]));
let sql = diff_to_sql(&from, &to);
assert_eq!(sql.len(), 1, "Expected 1 SQL statement, got: {:?}", sql);
assert_eq!(sql[0], "ALTER TABLE \"users\" DROP COLUMN \"name\";");
}
#[test]
fn test_alter_column_add_not_null() {
let mut from = PostgresDDL::new();
from.tables.push(table("users"));
from.columns.push(column_not_null("users", "id", "integer"));
from.columns.push(column("users", "email", "text")); from.pks.push(primary_key("users", vec!["id"]));
let mut to = PostgresDDL::new();
to.tables.push(table("users"));
to.columns.push(column_not_null("users", "id", "integer"));
to.columns.push(column_not_null("users", "email", "text")); to.pks.push(primary_key("users", vec!["id"]));
let sql = diff_to_sql(&from, &to);
assert_eq!(sql.len(), 1, "Expected 1 SQL statement, got: {:?}", sql);
assert_eq!(
sql[0],
"ALTER TABLE \"users\" ALTER COLUMN \"email\" SET NOT NULL;"
);
}
#[test]
fn test_alter_column_drop_not_null() {
let mut from = PostgresDDL::new();
from.tables.push(table("users"));
from.columns.push(column_not_null("users", "id", "integer"));
from.columns.push(column_not_null("users", "email", "text")); from.pks.push(primary_key("users", vec!["id"]));
let mut to = PostgresDDL::new();
to.tables.push(table("users"));
to.columns.push(column_not_null("users", "id", "integer"));
to.columns.push(column("users", "email", "text")); to.pks.push(primary_key("users", vec!["id"]));
let sql = diff_to_sql(&from, &to);
assert_eq!(sql.len(), 1, "Expected 1 SQL statement, got: {:?}", sql);
assert_eq!(
sql[0],
"ALTER TABLE \"users\" ALTER COLUMN \"email\" DROP NOT NULL;"
);
}
#[test]
fn test_alter_column_add_default() {
let mut from = PostgresDDL::new();
from.tables.push(table("users"));
from.columns.push(column_not_null("users", "id", "integer"));
from.columns.push(column("users", "status", "text")); from.pks.push(primary_key("users", vec!["id"]));
let mut to = PostgresDDL::new();
to.tables.push(table("users"));
to.columns.push(column_not_null("users", "id", "integer"));
to.columns
.push(column_default("users", "status", "text", "'active'")); to.pks.push(primary_key("users", vec!["id"]));
let sql = diff_to_sql(&from, &to);
assert_eq!(sql.len(), 1, "Expected 1 SQL statement, got: {:?}", sql);
assert_eq!(
sql[0],
"ALTER TABLE \"users\" ALTER COLUMN \"status\" SET DEFAULT 'active';"
);
}
#[test]
fn test_alter_column_drop_default() {
let mut from = PostgresDDL::new();
from.tables.push(table("users"));
from.columns.push(column_not_null("users", "id", "integer"));
from.columns
.push(column_default("users", "status", "text", "'active'")); from.pks.push(primary_key("users", vec!["id"]));
let mut to = PostgresDDL::new();
to.tables.push(table("users"));
to.columns.push(column_not_null("users", "id", "integer"));
to.columns.push(column("users", "status", "text")); to.pks.push(primary_key("users", vec!["id"]));
let sql = diff_to_sql(&from, &to);
assert_eq!(sql.len(), 1, "Expected 1 SQL statement, got: {:?}", sql);
assert_eq!(
sql[0],
"ALTER TABLE \"users\" ALTER COLUMN \"status\" DROP DEFAULT;"
);
}
#[test]
fn test_alter_column_type_change() {
let mut from = PostgresDDL::new();
from.tables.push(table("users"));
from.columns.push(column_not_null("users", "id", "integer"));
from.columns.push(column("users", "age", "text")); from.pks.push(primary_key("users", vec!["id"]));
let mut to = PostgresDDL::new();
to.tables.push(table("users"));
to.columns.push(column_not_null("users", "id", "integer"));
to.columns.push(column("users", "age", "integer")); to.pks.push(primary_key("users", vec!["id"]));
let sql = diff_to_sql(&from, &to);
assert_eq!(sql.len(), 1, "Expected 1 SQL statement, got: {:?}", sql);
assert_eq!(
sql[0],
"ALTER TABLE \"users\" ALTER COLUMN \"age\" SET DATA TYPE integer USING \"age\"::integer;"
);
}
#[test]
fn test_alter_column_array_dimension_change() {
let mut from = PostgresDDL::new();
from.tables.push(table("users"));
from.columns.push(column("users", "scores", "integer"));
let mut to = PostgresDDL::new();
to.tables.push(table("users"));
let mut scores = column("users", "scores", "integer");
scores.dimensions = Some(1);
to.columns.push(scores);
let sql = diff_to_sql(&from, &to);
assert_eq!(sql.len(), 1, "Expected 1 SQL statement, got: {:?}", sql);
assert_eq!(
sql[0],
"ALTER TABLE \"users\" ALTER COLUMN \"scores\" SET DATA TYPE integer[] USING \"scores\"::integer[];"
);
}
#[test]
fn test_column_comment_change_and_removal() {
let mut from = PostgresDDL::new();
from.tables.push(table("users"));
let mut from_email = column("users", "email", "text");
from_email.comment = Some(Cow::Borrowed("Old comment"));
from.columns.push(from_email);
let mut to = PostgresDDL::new();
to.tables.push(table("users"));
let mut to_email = column("users", "email", "text");
to_email.comment = Some(Cow::Borrowed("It's new"));
to.columns.push(to_email);
let sql = diff_to_sql(&from, &to);
assert_eq!(
sql,
vec!["COMMENT ON COLUMN \"users\".\"email\" IS 'It''s new';"]
);
let mut removed = PostgresDDL::new();
removed.tables.push(table("users"));
removed.columns.push(column("users", "email", "text"));
let sql = diff_to_sql(&to, &removed);
assert_eq!(sql, vec!["COMMENT ON COLUMN \"users\".\"email\" IS NULL;"]);
}
#[test]
fn test_alter_column_multiple_changes() {
let mut from = PostgresDDL::new();
from.tables.push(table("users"));
from.columns.push(column_not_null("users", "id", "integer"));
from.columns.push(column("users", "status", "text")); from.pks.push(primary_key("users", vec!["id"]));
let mut to = PostgresDDL::new();
to.tables.push(table("users"));
to.columns.push(column_not_null("users", "id", "integer"));
let mut status_col = column_default("users", "status", "text", "'pending'");
status_col.not_null = true;
to.columns.push(status_col);
to.pks.push(primary_key("users", vec!["id"]));
let sql = diff_to_sql(&from, &to);
assert_eq!(sql.len(), 2, "Expected 2 SQL statements, got: {:?}", sql);
assert_eq!(
sql[0],
"ALTER TABLE \"users\" ALTER COLUMN \"status\" SET NOT NULL;"
);
assert_eq!(
sql[1],
"ALTER TABLE \"users\" ALTER COLUMN \"status\" SET DEFAULT 'pending';"
);
}
#[test]
fn test_create_table_with_generated_column() {
let from = PostgresDDL::new();
let mut to = PostgresDDL::new();
to.tables.push(table("users"));
to.columns.push(column_not_null("users", "id", "integer"));
to.columns.push(column("users", "first_name", "text"));
to.columns.push(column("users", "last_name", "text"));
let mut full_name = column("users", "full_name", "text");
full_name.generated = Some(Generated {
expression: Cow::Borrowed("first_name || ' ' || last_name"),
gen_type: GeneratedType::Stored,
});
to.columns.push(full_name);
to.pks.push(primary_key("users", vec!["id"]));
let sql = diff_to_sql(&from, &to);
assert_eq!(sql.len(), 1, "Expected 1 SQL statement, got: {:?}", sql);
let expected = "CREATE TABLE \"users\" (\n\
\t\"id\" integer NOT NULL,\n\
\t\"first_name\" text,\n\
\t\"last_name\" text,\n\
\t\"full_name\" text GENERATED ALWAYS AS (first_name || ' ' || last_name) STORED,\n\
\tPRIMARY KEY(\"id\")\n\
);";
assert_eq!(
normalize_create_table(&sql[0]),
normalize_create_table(expected),
"Unexpected CREATE TABLE with generated column SQL: {}",
sql[0]
);
}
#[test]
fn test_create_table_with_virtual_generated_column() {
let from = PostgresDDL::new();
let mut to = PostgresDDL::new();
to.tables.push(table("users"));
to.columns.push(column_not_null("users", "id", "integer"));
to.columns.push(column("users", "name", "text"));
let mut name_len = column("users", "name_len", "integer");
name_len.generated = Some(Generated {
expression: Cow::Borrowed("length(name)"),
gen_type: GeneratedType::Virtual,
});
to.columns.push(name_len);
to.pks.push(primary_key("users", vec!["id"]));
let sql = diff_to_sql(&from, &to);
assert_eq!(sql.len(), 1, "Expected 1 SQL statement, got: {:?}", sql);
let expected = "CREATE TABLE \"users\" (\n\
\t\"id\" integer NOT NULL,\n\
\t\"name\" text,\n\
\t\"name_len\" integer GENERATED ALWAYS AS (length(name)) VIRTUAL,\n\
\tPRIMARY KEY(\"id\")\n\
);";
assert_eq!(
normalize_create_table(&sql[0]),
normalize_create_table(expected),
"Unexpected CREATE TABLE with virtual generated column SQL: {}",
sql[0]
);
}
#[test]
fn test_alter_column_add_generated_expression() {
let mut from = PostgresDDL::new();
from.tables.push(table("users"));
from.columns.push(column_not_null("users", "id", "integer"));
from.columns.push(column("users", "first_name", "text"));
from.columns.push(column("users", "last_name", "text"));
from.columns.push(column("users", "full_name", "text")); from.pks.push(primary_key("users", vec!["id"]));
let mut to = PostgresDDL::new();
to.tables.push(table("users"));
to.columns.push(column_not_null("users", "id", "integer"));
to.columns.push(column("users", "first_name", "text"));
to.columns.push(column("users", "last_name", "text"));
let mut full_name = column("users", "full_name", "text");
full_name.generated = Some(Generated {
expression: Cow::Borrowed("first_name || ' ' || last_name"),
gen_type: GeneratedType::Stored,
});
to.columns.push(full_name);
to.pks.push(primary_key("users", vec!["id"]));
let sql = diff_to_sql(&from, &to);
assert_eq!(sql.len(), 2, "Expected 2 SQL statements, got: {:?}", sql);
assert_eq!(sql[0], "ALTER TABLE \"users\" DROP COLUMN \"full_name\";");
assert_eq!(
sql[1],
"ALTER TABLE \"users\" ADD COLUMN \"full_name\" text GENERATED ALWAYS AS (first_name || ' ' || last_name) STORED;"
);
}
#[test]
fn test_alter_column_drop_generated_expression() {
let mut from = PostgresDDL::new();
from.tables.push(table("users"));
from.columns.push(column_not_null("users", "id", "integer"));
from.columns.push(column("users", "first_name", "text"));
from.columns.push(column("users", "last_name", "text"));
let mut full_name = column("users", "full_name", "text");
full_name.generated = Some(Generated {
expression: Cow::Borrowed("first_name || ' ' || last_name"),
gen_type: GeneratedType::Stored,
});
from.columns.push(full_name);
from.pks.push(primary_key("users", vec!["id"]));
let mut to = PostgresDDL::new();
to.tables.push(table("users"));
to.columns.push(column_not_null("users", "id", "integer"));
to.columns.push(column("users", "first_name", "text"));
to.columns.push(column("users", "last_name", "text"));
to.columns.push(column("users", "full_name", "text")); to.pks.push(primary_key("users", vec!["id"]));
let sql = diff_to_sql(&from, &to);
assert_eq!(sql.len(), 1, "Expected 1 SQL statement, got: {:?}", sql);
assert_eq!(
sql[0],
"ALTER TABLE \"users\" ALTER COLUMN \"full_name\" DROP EXPRESSION;"
);
}
#[test]
fn test_alter_column_add_identity() {
let mut from = PostgresDDL::new();
from.tables.push(table("users"));
from.columns.push(column_not_null("users", "id", "integer")); from.pks.push(primary_key("users", vec!["id"]));
let mut to = PostgresDDL::new();
to.tables.push(table("users"));
let mut id_col = column_not_null("users", "id", "integer");
id_col.identity = Some(Identity::always("users_id_seq").schema("public"));
to.columns.push(id_col);
to.pks.push(primary_key("users", vec!["id"]));
let sql = diff_to_sql(&from, &to);
assert_eq!(sql.len(), 1, "Expected 1 SQL statement, got: {:?}", sql);
assert_eq!(
sql[0],
"ALTER TABLE \"users\" ALTER COLUMN \"id\" ADD GENERATED ALWAYS AS IDENTITY;"
);
}
#[test]
fn test_alter_column_drop_identity() {
let mut from = PostgresDDL::new();
from.tables.push(table("users"));
let mut id_col = column_not_null("users", "id", "integer");
id_col.identity = Some(Identity::always("users_id_seq").schema("public"));
from.columns.push(id_col);
from.pks.push(primary_key("users", vec!["id"]));
let mut to = PostgresDDL::new();
to.tables.push(table("users"));
to.columns.push(column_not_null("users", "id", "integer")); to.pks.push(primary_key("users", vec!["id"]));
let sql = diff_to_sql(&from, &to);
assert_eq!(sql.len(), 1, "Expected 1 SQL statement, got: {:?}", sql);
assert_eq!(
sql[0],
"ALTER TABLE \"users\" ALTER COLUMN \"id\" DROP IDENTITY;"
);
}
#[test]
fn test_add_foreign_key() {
let mut from = PostgresDDL::new();
from.tables.push(table("users"));
from.columns.push(column_not_null("users", "id", "integer"));
from.pks.push(primary_key("users", vec!["id"]));
from.tables.push(table("posts"));
from.columns.push(column_not_null("posts", "id", "integer"));
from.columns.push(column("posts", "author_id", "integer"));
from.pks.push(primary_key("posts", vec!["id"]));
let mut to = from.clone();
to.fks.push(foreign_key(
"posts",
"posts_author_fk",
vec!["author_id"],
"users",
vec!["id"],
));
let sql = diff_to_sql(&from, &to);
assert_eq!(sql.len(), 1, "Expected 1 SQL statement, got: {:?}", sql);
assert_eq!(
sql[0],
"ALTER TABLE \"posts\" ADD CONSTRAINT \"posts_author_fk\" FOREIGN KEY (\"author_id\") REFERENCES \"users\"(\"id\");"
);
}
#[test]
fn test_drop_foreign_key() {
let mut from = PostgresDDL::new();
from.tables.push(table("users"));
from.columns.push(column_not_null("users", "id", "integer"));
from.pks.push(primary_key("users", vec!["id"]));
from.tables.push(table("posts"));
from.columns.push(column_not_null("posts", "id", "integer"));
from.columns.push(column("posts", "author_id", "integer"));
from.pks.push(primary_key("posts", vec!["id"]));
from.fks.push(foreign_key(
"posts",
"posts_author_fk",
vec!["author_id"],
"users",
vec!["id"],
));
let mut to = PostgresDDL::new();
to.tables.push(table("users"));
to.columns.push(column_not_null("users", "id", "integer"));
to.pks.push(primary_key("users", vec!["id"]));
to.tables.push(table("posts"));
to.columns.push(column_not_null("posts", "id", "integer"));
to.columns.push(column("posts", "author_id", "integer"));
to.pks.push(primary_key("posts", vec!["id"]));
let sql = diff_to_sql(&from, &to);
assert_eq!(sql.len(), 1, "Expected 1 SQL statement, got: {:?}", sql);
assert_eq!(
sql[0],
"ALTER TABLE \"posts\" DROP CONSTRAINT \"posts_author_fk\";"
);
}
#[test]
fn test_foreign_key_deferrability_change_recreates_constraint() {
let mut from = PostgresDDL::new();
from.tables.push(table("users"));
from.columns.push(column_not_null("users", "id", "integer"));
from.pks.push(primary_key("users", vec!["id"]));
from.tables.push(table("posts"));
from.columns.push(column_not_null("posts", "id", "integer"));
from.columns.push(column("posts", "author_id", "integer"));
from.pks.push(primary_key("posts", vec!["id"]));
from.fks.push(foreign_key(
"posts",
"posts_author_fk",
vec!["author_id"],
"users",
vec!["id"],
));
let mut to = from.clone();
to.fks.list_mut()[0].deferrable = true;
to.fks.list_mut()[0].initially_deferred = true;
let sql = diff_to_sql(&from, &to);
assert_eq!(sql.len(), 2, "Expected 2 SQL statements, got: {:?}", sql);
assert_eq!(
sql[0],
"ALTER TABLE \"posts\" DROP CONSTRAINT \"posts_author_fk\";"
);
assert_eq!(
sql[1],
"ALTER TABLE \"posts\" ADD CONSTRAINT \"posts_author_fk\" FOREIGN KEY (\"author_id\") REFERENCES \"users\"(\"id\") DEFERRABLE INITIALLY DEFERRED;"
);
}
#[test]
fn test_add_primary_key() {
let mut from = PostgresDDL::new();
from.tables.push(table("users"));
from.columns.push(column_not_null("users", "id", "integer"));
from.columns.push(column("users", "name", "text"));
let mut to = PostgresDDL::new();
to.tables.push(table("users"));
to.columns.push(column_not_null("users", "id", "integer"));
to.columns.push(column("users", "name", "text"));
to.pks.push(primary_key("users", vec!["id"]));
let sql = diff_to_sql(&from, &to);
assert_eq!(sql.len(), 1, "Expected 1 SQL statement, got: {:?}", sql);
assert_eq!(
sql[0],
"ALTER TABLE \"users\" ADD CONSTRAINT \"users_pkey\" PRIMARY KEY(\"id\");"
);
}
#[test]
fn test_drop_primary_key() {
let mut from = PostgresDDL::new();
from.tables.push(table("users"));
from.columns.push(column_not_null("users", "id", "integer"));
from.columns.push(column("users", "name", "text"));
from.pks.push(primary_key("users", vec!["id"]));
let mut to = PostgresDDL::new();
to.tables.push(table("users"));
to.columns.push(column_not_null("users", "id", "integer"));
to.columns.push(column("users", "name", "text"));
let sql = diff_to_sql(&from, &to);
assert_eq!(sql.len(), 1, "Expected 1 SQL statement, got: {:?}", sql);
assert_eq!(
sql[0],
"ALTER TABLE \"users\" DROP CONSTRAINT \"users_pkey\";"
);
}
#[test]
fn test_add_unique_constraint() {
let mut from = PostgresDDL::new();
from.tables.push(table("users"));
from.columns.push(column_not_null("users", "id", "integer"));
from.columns.push(column("users", "email", "text"));
from.pks.push(primary_key("users", vec!["id"]));
let mut to = from.clone();
to.uniques.push(unique_constraint(
"users",
"users_email_unique",
vec!["email"],
));
let sql = diff_to_sql(&from, &to);
assert_eq!(sql.len(), 1, "Expected 1 SQL statement, got: {:?}", sql);
assert_eq!(
sql[0],
"ALTER TABLE \"users\" ADD CONSTRAINT \"users_email_unique\" UNIQUE (\"email\");"
);
}
#[test]
fn test_drop_unique_constraint() {
let mut from = PostgresDDL::new();
from.tables.push(table("users"));
from.columns.push(column_not_null("users", "id", "integer"));
from.columns.push(column("users", "email", "text"));
from.pks.push(primary_key("users", vec!["id"]));
from.uniques.push(unique_constraint(
"users",
"users_email_unique",
vec!["email"],
));
let mut to = PostgresDDL::new();
to.tables.push(table("users"));
to.columns.push(column_not_null("users", "id", "integer"));
to.columns.push(column("users", "email", "text"));
to.pks.push(primary_key("users", vec!["id"]));
let sql = diff_to_sql(&from, &to);
assert_eq!(sql.len(), 1, "Expected 1 SQL statement, got: {:?}", sql);
assert_eq!(
sql[0],
"ALTER TABLE \"users\" DROP CONSTRAINT \"users_email_unique\";"
);
}
#[test]
fn test_unique_constraint_deferrability_change_recreates_constraint() {
let mut from = PostgresDDL::new();
from.tables.push(table("users"));
from.columns.push(column_not_null("users", "id", "integer"));
from.columns.push(column("users", "email", "text"));
from.pks.push(primary_key("users", vec!["id"]));
from.uniques.push(unique_constraint(
"users",
"users_email_unique",
vec!["email"],
));
let mut to = from.clone();
to.uniques.list_mut()[0].deferrable = true;
to.uniques.list_mut()[0].initially_deferred = true;
let sql = diff_to_sql(&from, &to);
assert_eq!(sql.len(), 2, "Expected 2 SQL statements, got: {:?}", sql);
assert_eq!(
sql[0],
"ALTER TABLE \"users\" DROP CONSTRAINT \"users_email_unique\";"
);
assert_eq!(
sql[1],
"ALTER TABLE \"users\" ADD CONSTRAINT \"users_email_unique\" UNIQUE (\"email\") DEFERRABLE INITIALLY DEFERRED;"
);
}
#[test]
fn test_add_index() {
let mut from = PostgresDDL::new();
from.tables.push(table("users"));
from.columns.push(column_not_null("users", "id", "integer"));
from.columns.push(column("users", "email", "text"));
from.pks.push(primary_key("users", vec!["id"]));
let mut to = from.clone();
to.indexes
.push(index("users", "users_email_idx", vec!["email"]));
let sql = diff_to_sql(&from, &to);
assert_eq!(sql.len(), 1, "Expected 1 SQL statement, got: {:?}", sql);
assert_eq!(
sql[0],
"CREATE INDEX \"users_email_idx\" ON \"users\"(\"email\");"
);
}
#[test]
fn test_drop_index() {
let mut from = PostgresDDL::new();
from.tables.push(table("users"));
from.columns.push(column_not_null("users", "id", "integer"));
from.columns.push(column("users", "email", "text"));
from.pks.push(primary_key("users", vec!["id"]));
from.indexes
.push(index("users", "users_email_idx", vec!["email"]));
let mut to = PostgresDDL::new();
to.tables.push(table("users"));
to.columns.push(column_not_null("users", "id", "integer"));
to.columns.push(column("users", "email", "text"));
to.pks.push(primary_key("users", vec!["id"]));
let sql = diff_to_sql(&from, &to);
assert_eq!(sql.len(), 1, "Expected 1 SQL statement, got: {:?}", sql);
assert_eq!(sql[0], "DROP INDEX \"users_email_idx\";");
}
#[test]
fn test_no_changes_no_sql() {
let mut schema = PostgresDDL::new();
schema.tables.push(table("users"));
schema
.columns
.push(column_not_null("users", "id", "integer"));
schema
.columns
.push(column_not_null("users", "name", "text"));
schema.pks.push(primary_key("users", vec!["id"]));
let sql = diff_to_sql(&schema, &schema.clone());
assert!(
sql.is_empty(),
"Expected no SQL for identical schemas, got: {:?}",
sql
);
}
#[test]
fn test_multiple_tables_different_changes() {
let mut from = PostgresDDL::new();
from.tables.push(table("users"));
from.columns.push(column_not_null("users", "id", "integer"));
from.columns.push(column("users", "email", "text")); from.pks.push(primary_key("users", vec!["id"]));
from.tables.push(table("posts"));
from.columns.push(column_not_null("posts", "id", "integer"));
from.columns.push(column_not_null("posts", "title", "text"));
from.pks.push(primary_key("posts", vec!["id"]));
let mut to = PostgresDDL::new();
to.tables.push(table("users"));
to.columns.push(column_not_null("users", "id", "integer"));
to.columns.push(column_not_null("users", "email", "text")); to.pks.push(primary_key("users", vec!["id"]));
to.tables.push(table("posts"));
to.columns.push(column_not_null("posts", "id", "integer"));
to.columns.push(column_not_null("posts", "title", "text"));
to.columns.push(column("posts", "content", "text")); to.pks.push(primary_key("posts", vec!["id"]));
let sql = diff_to_sql(&from, &to);
assert_eq!(sql.len(), 2, "Expected 2 SQL statements, got: {:?}", sql);
let mut sorted = sql.clone();
sorted.sort();
assert_eq!(
sorted,
vec![
"ALTER TABLE \"posts\" ADD COLUMN \"content\" text;",
"ALTER TABLE \"users\" ALTER COLUMN \"email\" SET NOT NULL;",
],
"Unexpected multi-table alteration statements: {:?}",
sql
);
}
#[test]
fn test_custom_schema_alterations() {
let mut from = PostgresDDL::new();
from.tables.push(Table {
schema: Cow::Borrowed("myschema"),
name: Cow::Borrowed("users"),
is_unlogged: None,
is_temporary: None,
inherits: None,
tablespace: None,
is_rls_enabled: None,
comment: None,
});
from.columns.push(Column {
schema: Cow::Borrowed("myschema"),
table: Cow::Borrowed("users"),
name: Cow::Borrowed("id"),
sql_type: Cow::Borrowed("integer"),
type_schema: None,
not_null: true,
default: None,
generated: None,
identity: None,
dimensions: None,
comment: None,
collate: None,
ordinal_position: None,
});
let mut to = from.clone();
to.columns.push(Column {
schema: Cow::Borrowed("myschema"),
table: Cow::Borrowed("users"),
name: Cow::Borrowed("name"),
sql_type: Cow::Borrowed("text"),
type_schema: None,
not_null: false,
default: None,
generated: None,
identity: None,
dimensions: None,
comment: None,
collate: None,
ordinal_position: None,
});
let sql = diff_to_sql(&from, &to);
assert_eq!(sql.len(), 1, "Expected 1 SQL statement, got: {:?}", sql);
assert_eq!(
sql[0],
"ALTER TABLE \"myschema\".\"users\" ADD COLUMN \"name\" text;"
);
}
#[test]
fn test_alter_identity_kind_uses_set_generated() {
let mut from = PostgresDDL::new();
from.tables.push(table("users"));
let mut old_col = column_not_null("users", "id", "integer");
old_col.identity = Some(Identity::always("users_id_seq").schema("public"));
from.columns.push(old_col);
let mut to = PostgresDDL::new();
to.tables.push(table("users"));
let mut new_col = column_not_null("users", "id", "integer");
new_col.identity = Some(Identity::by_default("users_id_seq").schema("public"));
to.columns.push(new_col);
let sql = diff_to_sql(&from, &to);
assert_eq!(
sql,
vec!["ALTER TABLE \"users\" ALTER COLUMN \"id\" SET GENERATED BY DEFAULT;".to_string()]
);
}
#[test]
fn test_alter_identity_options_uses_set_clauses() {
let mut from = PostgresDDL::new();
from.tables.push(table("users"));
let mut old_col = column_not_null("users", "id", "integer");
old_col.identity = Some(Identity::always("users_id_seq").schema("public"));
from.columns.push(old_col);
let mut to = PostgresDDL::new();
to.tables.push(table("users"));
let mut new_col = column_not_null("users", "id", "integer");
let mut identity = Identity::always("users_id_seq").schema("public");
identity.increment = Some(Cow::Borrowed("10"));
identity.max_value = Some(Cow::Borrowed("5000"));
identity.cycle = Some(true);
new_col.identity = Some(identity);
to.columns.push(new_col);
let sql = diff_to_sql(&from, &to);
assert_eq!(
sql,
vec![
"ALTER TABLE \"users\" ALTER COLUMN \"id\" SET INCREMENT BY 10 SET MAXVALUE 5000 SET CYCLE;"
.to_string()
]
);
}
#[test]
fn test_alter_identity_add_still_uses_add_generated() {
let mut from = PostgresDDL::new();
from.tables.push(table("users"));
from.columns.push(column_not_null("users", "id", "integer"));
let mut to = PostgresDDL::new();
to.tables.push(table("users"));
let mut new_col = column_not_null("users", "id", "integer");
let mut identity = Identity::always("users_id_seq").schema("public");
identity.start_with = Some(Cow::Borrowed("100"));
new_col.identity = Some(identity);
to.columns.push(new_col);
let sql = diff_to_sql(&from, &to);
assert_eq!(
sql,
vec![
"ALTER TABLE \"users\" ALTER COLUMN \"id\" ADD GENERATED ALWAYS AS IDENTITY (START WITH 100);"
.to_string()
]
);
}