on main help sign in
Add option to create unique index with add_index
commit2a3fda7a901d29b8177d7bf388c62f516c958e14 authorfabianlindfors <fabian@flapplabs.se> dateFri 14 Jan 2022, 11:44 (5 years ago) parent88da0d2
5 files changed+92−6
README.md+4−1 src/migrations/add_index.rs+5−1 tests/add_index.rs+81−4 tests/alter_column.rs+1 tests/remove_index.rs+1
README.md+4−1
@@ -379,7 +379,7 @@ down = "'N/A'"
379379
380380The `add_index` action will add a new index to an existing table.
381381
382−*Example: create a `users` table with an index on the `name` column*
382+*Example: create a `users` table with a unique index on the `name` column*
383383
384384```toml
385385[[actions]]
@@ -401,6 +401,9 @@ type = "add_index"
401401table = "users"
402402name = "name_idx"
403403columns = ["name"]
404+
405+# Defaults to false
406+unique = true
404407```
405408
406409#### Remove index
src/migrations/add_index.rs+5−1
@@ -11,6 +11,8 @@ pub struct AddIndex {
1111 pub table: String,
1212 pub name: String,
1313 pub columns: Vec<String>,
14+ #[serde(default)]
15+ pub unique: bool,
1416}
1517
1618#[typetag::serde(name = "add_index")]
@@ -34,9 +36,11 @@ impl Action for AddIndex {
3436 .map(|column| format!("\"{}\"", column.real_name))
3537 .collect();
3638
39+ let unique = if self.unique { "UNIQUE" } else { "" };
40+
3741 db.run(&format!(
3842 r#"
39− CREATE INDEX CONCURRENTLY "{name}" ON "{table}" ({columns})
43+ CREATE {unique} INDEX CONCURRENTLY "{name}" ON "{table}" ({columns})
4044 "#,
4145 name = self.name,
4246 table = self.table,
tests/add_index.rs+81−4
@@ -29,6 +29,7 @@ fn add_index() {
2929 table: "users".to_string(),
3030 name: "name_idx".to_string(),
3131 columns: vec!["name".to_string()],
32+ unique: false,
3233 });
3334
3435 let first_migrations = vec![create_table_migration.clone()];
@@ -47,7 +48,7 @@ fn add_index() {
4748 new_db.simple_query(&new_schema_query).unwrap();
4849
4950 // Ensure index is valid and ready
50− let result: Vec<(bool, bool)> = new_db
51+ let (is_ready, is_valid): (bool, bool) = new_db
5152 .query(
5253 "
5354 SELECT pg_index.indisready, pg_index.indisvalid
@@ -58,11 +59,87 @@ fn add_index() {
5859 &[],
5960 )
6061 .unwrap()
61− .iter()
62+ .first()
6263 .map(|row| (row.get("indisready"), row.get("indisvalid")))
63− .collect();
64+ .unwrap();
6465
65− assert_eq!(vec![(true, true)], result);
66+ assert!(is_ready, "expected index to be ready");
67+ assert!(is_valid, "expected index to be valid");
68+
69+ reshape.complete().unwrap();
70+ common::assert_cleaned_up(&mut new_db);
71+}
72+
73+#[test]
74+fn add_index_unique() {
75+ let (mut reshape, mut old_db, mut new_db) = common::setup();
76+
77+ let create_table_migration = Migration::new("create_user_table", None).with_action(
78+ CreateTableBuilder::default()
79+ .name("users")
80+ .primary_key(vec!["id".to_string()])
81+ .columns(vec![
82+ ColumnBuilder::default()
83+ .name("id")
84+ .data_type("INTEGER")
85+ .build()
86+ .unwrap(),
87+ ColumnBuilder::default()
88+ .name("name")
89+ .data_type("TEXT")
90+ .build()
91+ .unwrap(),
92+ ])
93+ .build()
94+ .unwrap(),
95+ );
96+ let add_index_migration = Migration::new("add_name_index", None).with_action(AddIndex {
97+ table: "users".to_string(),
98+ name: "name_idx".to_string(),
99+ columns: vec!["name".to_string()],
100+ unique: true,
101+ });
102+
103+ let first_migrations = vec![create_table_migration.clone()];
104+ let second_migrations = vec![create_table_migration.clone(), add_index_migration.clone()];
105+
106+ // Run migrations
107+ reshape.migrate(first_migrations.clone()).unwrap();
108+ reshape.migrate(second_migrations.clone()).unwrap();
109+
110+ // Update schemas of Postgres connections
111+ let old_schema_query =
112+ reshape::schema_query_for_migration(&first_migrations.last().unwrap().name);
113+ let new_schema_query =
114+ reshape::schema_query_for_migration(&second_migrations.last().unwrap().name);
115+ old_db.simple_query(&old_schema_query).unwrap();
116+ new_db.simple_query(&new_schema_query).unwrap();
117+
118+ // Ensure index is valid, ready and unique
119+ let (is_ready, is_valid, is_unique): (bool, bool, bool) = new_db
120+ .query(
121+ "
122+ SELECT pg_index.indisready, pg_index.indisvalid, pg_index.indisunique
123+ FROM pg_catalog.pg_index
124+ JOIN pg_catalog.pg_class ON pg_index.indexrelid = pg_class.oid
125+ WHERE pg_class.relname = 'name_idx'
126+ ",
127+ &[],
128+ )
129+ .unwrap()
130+ .first()
131+ .map(|row| {
132+ (
133+ row.get("indisready"),
134+ row.get("indisvalid"),
135+ row.get("indisunique"),
136+ )
137+ })
138+ .unwrap();
139+
140+ assert!(is_ready, "expected index to be ready");
141+ assert!(is_valid, "expected index to be valid");
142+ assert!(is_unique, "expected index to be unique");
66143
67144 reshape.complete().unwrap();
68145 common::assert_cleaned_up(&mut new_db);
tests/alter_column.rs+1
@@ -539,6 +539,7 @@ fn alter_column_with_index() {
539539 table: "users".to_string(),
540540 name: "users_name_idx".to_string(),
541541 columns: vec!["first_name".to_string(), "last_name".to_string()],
542+ unique: false,
542543 });
543544 let uppercase_name = Migration::new("uppercase_name", None).with_action(AlterColumn {
544545 table: "users".to_string(),
tests/remove_index.rs+1
@@ -30,6 +30,7 @@ fn remove_index() {
3030 table: "users".to_string(),
3131 name: "name_idx".to_string(),
3232 columns: vec!["name".to_string()],
33+ unique: false,
3334 });
3435
3536 let remove_index_migration =