on main help sign in
Add option to create unique index with add_index
2a3fda7fabianlindfors
README.md+4−1
@@ -379,7 +379,7 @@ down = "'N/A'"
379
379
380The `add_index` action will add a new index to an existing table.
380The `add_index` action will add a new index to an existing table.
381
381
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*
383
383
384```toml
384```toml
385[[actions]]
385[[actions]]
@@ -401,6 +401,9 @@ type = "add_index"
401table = "users"
401table = "users"
402name = "name_idx"
402name = "name_idx"
403columns = ["name"]
403columns = ["name"]
404+
405+# Defaults to false
406+unique = true
404```
407```
405
408
406#### Remove index
409#### Remove index
src/migrations/add_index.rs+5−1
@@ -11,6 +11,8 @@ pub struct AddIndex {
11 pub table: String,
11 pub table: String,
12 pub name: String,
12 pub name: String,
13 pub columns: Vec<String>,
13 pub columns: Vec<String>,
14+ #[serde(default)]
15+ pub unique: bool,
14}
16}
15
17
16#[typetag::serde(name = "add_index")]
18#[typetag::serde(name = "add_index")]
@@ -34,9 +36,11 @@ impl Action for AddIndex {
34 .map(|column| format!("\"{}\"", column.real_name))
36 .map(|column| format!("\"{}\"", column.real_name))
35 .collect();
37 .collect();
36
38
39+ let unique = if self.unique { "UNIQUE" } else { "" };
40+
37 db.run(&format!(
41 db.run(&format!(
38 r#"
42 r#"
39− CREATE INDEX CONCURRENTLY "{name}" ON "{table}" ({columns})
43+ CREATE {unique} INDEX CONCURRENTLY "{name}" ON "{table}" ({columns})
40 "#,
44 "#,
41 name = self.name,
45 name = self.name,
42 table = self.table,
46 table = self.table,
tests/add_index.rs+81−4
@@ -29,6 +29,7 @@ fn add_index() {
29 table: "users".to_string(),
29 table: "users".to_string(),
30 name: "name_idx".to_string(),
30 name: "name_idx".to_string(),
31 columns: vec!["name".to_string()],
31 columns: vec!["name".to_string()],
32+ unique: false,
32 });
33 });
33
34
34 let first_migrations = vec![create_table_migration.clone()];
35 let first_migrations = vec![create_table_migration.clone()];
@@ -47,7 +48,7 @@ fn add_index() {
47 new_db.simple_query(&new_schema_query).unwrap();
48 new_db.simple_query(&new_schema_query).unwrap();
48
49
49 // Ensure index is valid and ready
50 // Ensure index is valid and ready
50− let result: Vec<(bool, bool)> = new_db
51+ let (is_ready, is_valid): (bool, bool) = new_db
51 .query(
52 .query(
52 "
53 "
53 SELECT pg_index.indisready, pg_index.indisvalid
54 SELECT pg_index.indisready, pg_index.indisvalid
@@ -58,11 +59,87 @@ fn add_index() {
58 &[],
59 &[],
59 )
60 )
60 .unwrap()
61 .unwrap()
61− .iter()
62+ .first()
62 .map(|row| (row.get("indisready"), row.get("indisvalid")))
63 .map(|row| (row.get("indisready"), row.get("indisvalid")))
63− .collect();
64+ .unwrap();
64
65
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");
66
143
67 reshape.complete().unwrap();
144 reshape.complete().unwrap();
68 common::assert_cleaned_up(&mut new_db);
145 common::assert_cleaned_up(&mut new_db);
tests/alter_column.rs+1
@@ -539,6 +539,7 @@ fn alter_column_with_index() {
539 table: "users".to_string(),
539 table: "users".to_string(),
540 name: "users_name_idx".to_string(),
540 name: "users_name_idx".to_string(),
541 columns: vec!["first_name".to_string(), "last_name".to_string()],
541 columns: vec!["first_name".to_string(), "last_name".to_string()],
542+ unique: false,
542 });
543 });
543 let uppercase_name = Migration::new("uppercase_name", None).with_action(AlterColumn {
544 let uppercase_name = Migration::new("uppercase_name", None).with_action(AlterColumn {
544 table: "users".to_string(),
545 table: "users".to_string(),
tests/remove_index.rs+1
@@ -30,6 +30,7 @@ fn remove_index() {
30 table: "users".to_string(),
30 table: "users".to_string(),
31 name: "name_idx".to_string(),
31 name: "name_idx".to_string(),
32 columns: vec!["name".to_string()],
32 columns: vec!["name".to_string()],
33+ unique: false,
33 });
34 });
34
35
35 let remove_index_migration =
36 let remove_index_migration =