reshape/tests/alter_column.rs
fabianlindfors 7cc4f23db9 Fix changing nullability not working with alter_column
The existing test to set a column as NOT NULL didn't actually check that
the constraint was added, which has also been fixed. I also added a new
test for the opposite change, making a NOT NULL column nullable. The
README has also been updated with a new example for setting a column as
NOT NULL.
2022-02-04 17:47:39 +01:00

735 lines
18 KiB
Rust

mod common;
use common::Test;
#[test]
fn alter_column_data() {
let mut test = Test::new("Alter column");
test.first_migration(
r#"
name = "create_user_table"
[[actions]]
type = "create_table"
name = "users"
primary_key = ["id"]
[[actions.columns]]
name = "id"
type = "INTEGER"
[[actions.columns]]
name = "name"
type = "TEXT"
"#,
);
test.second_migration(
r#"
name = "uppercase_name"
[[actions]]
type = "alter_column"
table = "users"
column = "name"
up = "UPPER(name)"
down = "LOWER(name)"
"#,
);
test.after_first(|db| {
// Insert some test users
db.simple_query(
"
INSERT INTO users (id, name) VALUES
(1, 'john Doe'),
(2, 'jane Doe');
",
)
.unwrap();
});
test.intermediate(|old_db, new_db| {
// Check that the existing users has the altered data
let expected = vec!["JOHN DOE", "JANE DOE"];
assert!(new_db
.query("SELECT name FROM users ORDER BY id", &[],)
.unwrap()
.iter()
.map(|row| row.get::<_, String>("name"))
.eq(expected));
// Insert data using old schema and make sure the new schema gets correct values
old_db
.simple_query("INSERT INTO users (id, name) VALUES (3, 'test testsson')")
.unwrap();
let result = new_db
.query_one("SELECT name from users WHERE id = 3", &[])
.unwrap();
assert_eq!("TEST TESTSSON", result.get::<_, &str>("name"));
// Insert data using new schema and make sure the old schema gets correct values
new_db
.simple_query("INSERT INTO users (id, name) VALUES (4, 'TEST TESTSSON')")
.unwrap();
let result = old_db
.query_one("SELECT name from users WHERE id = 4", &[])
.unwrap();
assert_eq!("test testsson", result.get::<_, &str>("name"));
});
test.run();
}
#[test]
fn alter_column_set_not_null() {
let mut test = Test::new("Set column not null");
test.first_migration(
r#"
name = "create_user_table"
[[actions]]
type = "create_table"
name = "users"
primary_key = ["id"]
[[actions.columns]]
name = "id"
type = "INTEGER"
[[actions.columns]]
name = "name"
type = "TEXT"
"#,
);
test.second_migration(
r#"
name = "set_name_not_null"
[[actions]]
type = "alter_column"
table = "users"
column = "name"
up = "COALESCE(name, 'TEST_DEFAULT_VALUE')"
[actions.changes]
nullable = false
"#,
);
test.after_first(|db| {
// Insert some test users
db.simple_query(
"
INSERT INTO users (id, name) VALUES
(1, 'John Doe'),
(2, NULL);
",
)
.unwrap();
});
test.intermediate(|old_db, new_db| {
// Check that existing users got the correct values
let expected = vec!["John Doe", "TEST_DEFAULT_VALUE"];
assert!(new_db
.query("SELECT name FROM users ORDER BY id", &[],)
.unwrap()
.iter()
.map(|row| row.get::<_, String>("name"))
.eq(expected));
// Insert data using old schema and make sure the new schema gets correct values
old_db
.simple_query("INSERT INTO users (id, name) VALUES (3, NULL)")
.unwrap();
let result = new_db
.query_one("SELECT name from users WHERE id = 3", &[])
.unwrap();
assert_eq!("TEST_DEFAULT_VALUE", result.get::<_, &str>("name"));
// Insert data using new schema and make sure the old schema gets correct values
new_db
.simple_query("INSERT INTO users (id, name) VALUES (4, 'Jane Doe')")
.unwrap();
let result = old_db
.query_one("SELECT name from users WHERE id = 4", &[])
.unwrap();
assert_eq!("Jane Doe", result.get::<_, &str>("name"));
// Ensure NULL can't be inserted using the new schema
let result = new_db.simple_query("INSERT INTO users (id, name) VALUES (5, NULL)");
assert!(result.is_err(), "expected insert to fail");
});
test.after_completion(|db| {
// Ensure NULL can't be inserted
let result = db.simple_query("INSERT INTO users (id, name) VALUES (5, NULL)");
assert!(result.is_err(), "expected insert to fail");
});
test.after_abort(|db| {
// Ensure NULL can be inserted
let result = db.simple_query("INSERT INTO users (id, name) VALUES (5, NULL)");
assert!(result.is_ok(), "expected insert to succeed");
});
test.run();
}
#[test]
fn alter_column_set_nullable() {
let mut test = Test::new("Set column nullable");
test.first_migration(
r#"
name = "create_user_table"
[[actions]]
type = "create_table"
name = "users"
primary_key = ["id"]
[[actions.columns]]
name = "id"
type = "INTEGER"
[[actions.columns]]
name = "name"
type = "TEXT"
nullable = false
"#,
);
test.second_migration(
r#"
name = "set_name_nullable"
[[actions]]
type = "alter_column"
table = "users"
column = "name"
down = "COALESCE(name, 'TEST_DEFAULT_VALUE')"
[actions.changes]
nullable = true
"#,
);
test.after_first(|db| {
// Insert a test user
db.simple_query(
"
INSERT INTO users (id, name) VALUES
(1, 'John Doe')
",
)
.unwrap();
});
test.intermediate(|old_db, new_db| {
// Insert data using new schema and make sure the old schema gets correct values
new_db
.simple_query("INSERT INTO users (id, name) VALUES (2, NULL)")
.unwrap();
let result = old_db
.query_one("SELECT name from users WHERE id = 2", &[])
.unwrap();
assert_eq!("TEST_DEFAULT_VALUE", result.get::<_, &str>("name"));
// Ensure NULL can't be inserted using the old schema
let result = old_db.simple_query("INSERT INTO users (id, name) VALUES (3, NULL)");
assert!(result.is_err(), "expected insert to fail");
// Ensure NULL can be inserted using the new schema
let result = new_db.simple_query("INSERT INTO users (id, name) VALUES (4, NULL)");
assert!(result.is_ok(), "expected insert to succeed");
});
test.after_completion(|db| {
// Ensure NULL can be inserted
let result = db.simple_query("INSERT INTO users (id, name) VALUES (5, NULL)");
assert!(result.is_ok(), "expected insert to succeed");
});
test.after_abort(|db| {
// Ensure NULL can't be inserted
let result = db.simple_query("INSERT INTO users (id, name) VALUES (5, NULL)");
assert!(result.is_err(), "expected insert to fail");
});
test.run();
}
#[test]
fn alter_column_rename() {
let mut test = Test::new("Rename column");
test.first_migration(
r#"
name = "create_user_table"
[[actions]]
type = "create_table"
name = "users"
primary_key = ["id"]
[[actions.columns]]
name = "id"
type = "INTEGER"
[[actions.columns]]
name = "name"
type = "TEXT"
"#,
);
test.second_migration(
r#"
name = "set_name_not_null"
[[actions]]
type = "alter_column"
table = "users"
column = "name"
[actions.changes]
name = "full_name"
"#,
);
test.after_first(|db| {
// Insert some test data
db.simple_query(
"
INSERT INTO users (id, name) VALUES
(1, 'John Doe'),
(2, 'Jane Doe');
",
)
.unwrap();
});
test.intermediate(|_, new_db| {
// Check that existing values can be queried using new column name
let expected = vec!["John Doe", "Jane Doe"];
assert!(new_db
.query("SELECT full_name FROM users ORDER BY id", &[],)
.unwrap()
.iter()
.map(|row| row.get::<_, String>("full_name"))
.eq(expected));
});
test.run();
}
#[test]
fn alter_column_multiple() {
let mut test = Test::new("Alter column value multiple times");
test.first_migration(
r#"
name = "create_user_table"
[[actions]]
type = "create_table"
name = "users"
primary_key = ["id"]
[[actions.columns]]
name = "id"
type = "INTEGER"
[[actions.columns]]
name = "counter"
type = "INTEGER"
nullable = false
"#,
);
test.second_migration(
r#"
name = "increment_counter_twice"
[[actions]]
type = "alter_column"
table = "users"
column = "counter"
up = "counter + 1"
down = "counter - 1"
[[actions]]
type = "alter_column"
table = "users"
column = "counter"
up = "counter + 1"
down = "counter - 1"
"#,
);
test.after_first(|db| {
// Insert some test data
db.simple_query(
"
INSERT INTO users (id, counter) VALUES
(1, 0),
(2, 100);
",
)
.unwrap();
});
test.intermediate(|old_db, new_db| {
// Check that the existing data has been updated
let expected = vec![2, 102];
let results: Vec<i32> = new_db
.query("SELECT counter FROM users ORDER BY id", &[])
.unwrap()
.iter()
.map(|row| row.get::<_, i32>("counter"))
.collect();
assert_eq!(expected, results);
// Update data using old schema and make sure it was updated for the new schema
old_db
.query("UPDATE users SET counter = 50 WHERE id = 1", &[])
.unwrap();
let result: i32 = new_db
.query("SELECT counter FROM users WHERE id = 1", &[])
.unwrap()
.iter()
.map(|row| row.get("counter"))
.nth(0)
.unwrap();
assert_eq!(52, result);
// Update data using new schema and make sure it was updated for the old schema
new_db
.query("UPDATE users SET counter = 50 WHERE id = 1", &[])
.unwrap();
let result: i32 = old_db
.query("SELECT counter FROM users WHERE id = 1", &[])
.unwrap()
.iter()
.map(|row| row.get("counter"))
.nth(0)
.unwrap();
assert_eq!(48, result);
});
test.run();
}
#[test]
fn alter_column_default() {
let mut test = Test::new("Change default value for column");
test.first_migration(
r#"
name = "create_user_table"
[[actions]]
type = "create_table"
name = "users"
primary_key = ["id"]
[[actions.columns]]
name = "id"
type = "INTEGER"
[[actions.columns]]
name = "name"
type = "TEXT"
nullable = false
default = "'DEFAULT'"
"#,
);
test.second_migration(
r#"
name = "change_name_default"
[[actions]]
type = "alter_column"
table = "users"
column = "name"
[actions.changes]
default = "'NEW DEFAULT'"
"#,
);
test.after_first(|db| {
// Insert a test user
db.simple_query(
"
INSERT INTO users (id) VALUES (1)
",
)
.unwrap();
});
test.intermediate(|old_db, new_db| {
// Check that the existing users has the old default value
let expected = vec!["DEFAULT"];
assert!(new_db
.query("SELECT name FROM users", &[],)
.unwrap()
.iter()
.map(|row| row.get::<_, String>("name"))
.eq(expected));
// Insert data using old schema and make those get the old default value
old_db
.simple_query("INSERT INTO users (id) VALUES (2)")
.unwrap();
let result = new_db
.query_one("SELECT name from users WHERE id = 2", &[])
.unwrap();
assert_eq!("DEFAULT", result.get::<_, &str>("name"));
// Insert data using new schema and make sure it gets the new default value
new_db
.simple_query("INSERT INTO users (id) VALUES (3)")
.unwrap();
let result = old_db
.query_one("SELECT name from users WHERE id = 3", &[])
.unwrap();
assert_eq!("NEW DEFAULT", result.get::<_, &str>("name"));
});
test.run();
}
#[test]
fn alter_column_with_index() {
let mut test = Test::new("Alter column with index");
test.first_migration(
r#"
name = "create_user_table"
[[actions]]
type = "create_table"
name = "users"
primary_key = ["id"]
[[actions.columns]]
name = "id"
type = "INTEGER"
[[actions.columns]]
name = "first_name"
type = "TEXT"
[[actions.columns]]
name = "last_name"
type = "TEXT"
[[actions]]
type = "add_index"
table = "users"
[actions.index]
name = "users_name_idx"
columns = ["first_name", "last_name"]
"#,
);
test.second_migration(
r#"
name = "uppercase_last_name"
[[actions]]
type = "alter_column"
table = "users"
column = "last_name"
up = "UPPER(last_name)"
down = "LOWER(last_name)"
"#,
);
test.after_completion(|db| {
// Make sure index still exists
let result: i64 = db
.query(
"
SELECT COUNT(*)
FROM pg_catalog.pg_index
JOIN pg_catalog.pg_class ON pg_index.indexrelid = pg_class.oid
WHERE pg_class.relname = 'users_name_idx'
",
&[],
)
.unwrap()
.first()
.map(|row| row.get(0))
.unwrap();
assert_eq!(1, result, "expected index to still exist");
});
test.run();
}
#[test]
fn alter_column_with_unique_index() {
let mut test = Test::new("Alter column with unique index");
test.first_migration(
r#"
name = "create_user_table"
[[actions]]
type = "create_table"
name = "users"
primary_key = ["id"]
[[actions.columns]]
name = "id"
type = "INTEGER"
[[actions.columns]]
name = "name"
type = "TEXT"
[[actions]]
type = "add_index"
table = "users"
[actions.index]
name = "name_idx"
columns = ["name"]
unique = true
"#,
);
test.second_migration(
r#"
name = "uppercase_name"
[[actions]]
type = "alter_column"
table = "users"
column = "name"
up = "UPPER(name)"
down = "LOWER(name)"
"#,
);
test.after_first(|db| {
db.simple_query("INSERT INTO users (id, name) VALUES (1, 'Test')")
.unwrap();
});
test.intermediate(|old_db, new_db| {
// Try inserting a value which duplicates the uppercase value of an existing row
let result = new_db.simple_query("INSERT INTO users (id, name) VALUES (2, 'TEST')");
assert!(
result.is_err(),
"expected duplicate insert to new schema to fail"
);
// Try inserting a value which duplicates the lowercase value of an existing row
new_db
.simple_query("INSERT INTO users (id, name) VALUES (2, 'JOHN')")
.unwrap();
let result = old_db.simple_query("INSERT INTO users (id, name) VALUES (3, 'john')");
assert!(
result.is_err(),
"expected duplicate insert to old schema to fail"
);
});
test.after_completion(|db| {
// Make sure index still exists
let is_unique: bool = db
.query(
"
SELECT pg_index.indisunique
FROM pg_catalog.pg_index
JOIN pg_catalog.pg_class ON pg_index.indexrelid = pg_class.oid
WHERE pg_class.relname = 'name_idx'
",
&[],
)
.unwrap()
.first()
.map(|row| row.get("indisunique"))
.unwrap();
assert!(is_unique, "expected index to still be unique");
});
test.run();
}
#[test]
fn alter_column_with_hash_index() {
let mut test = Test::new("Alter column with custom index type");
test.first_migration(
r#"
name = "create_user_table"
[[actions]]
type = "create_table"
name = "users"
primary_key = ["id"]
[[actions.columns]]
name = "id"
type = "INTEGER"
[[actions.columns]]
name = "name"
type = "TEXT"
[[actions]]
type = "add_index"
table = "users"
[actions.index]
name = "name_idx"
columns = ["name"]
type = "hash"
"#,
);
test.second_migration(
r#"
name = "uppercase_name"
[[actions]]
type = "alter_column"
table = "users"
column = "name"
up = "UPPER(name)"
down = "LOWER(name)"
"#,
);
test.after_completion(|db| {
// Make sure index still has type GIN
let index_type: String = db
.query(
"
SELECT pg_am.amname
FROM pg_catalog.pg_index
JOIN pg_catalog.pg_class ON pg_index.indexrelid = pg_class.oid
JOIN pg_catalog.pg_am ON pg_class.relam = pg_am.oid
WHERE pg_class.relname = 'name_idx'
",
&[],
)
.unwrap()
.first()
.map(|row| row.get("amname"))
.unwrap();
assert_eq!("hash", index_type);
});
test.run();
}