on main help sign in
Add option to set index type for add_index
353a5c2fabianlindfors
README.md+15
@@ -408,6 +408,21 @@ table = "users"
408 unique = true
408 unique = true
409```
409```
410
410
411+*Example: add GIN index to `data` column on `products` table*
412+
413+```toml
414+[[actions]]
415+type = "add_index"
416+table = "products"
417+
418+ [actions.index]
419+ name = "data_idx"
420+ columns = ["data"]
421+
422+ # One of: btree (default), hash, gist, spgist, gin, brin
423+ type = "gin"
424+```
425+
411#### Remove index
426#### Remove index
412
427
413The `remove_index` action will remove an existing index. The index won't actually be removed until the migration is completed.
428The `remove_index` action will remove an existing index. The index won't actually be removed until the migration is completed.
src/migrations/add_index.rs+17−2
@@ -1,4 +1,4 @@
1−use super::{common::Index, Action, MigrationContext};
1+use super::{Action, MigrationContext};
2use crate::{
2use crate::{
3 db::{Conn, Transaction},
3 db::{Conn, Transaction},
4 schema::Schema,
4 schema::Schema,
@@ -12,6 +12,16 @@ pub struct AddIndex {
12 pub index: Index,
12 pub index: Index,
13}
13}
14
14
15+#[derive(Serialize, Deserialize, Clone, Debug)]
16+pub struct Index {
17+ pub name: String,
18+ pub columns: Vec<String>,
19+ #[serde(default)]
20+ pub unique: bool,
21+ #[serde(rename = "type")]
22+ pub index_type: Option<String>,
23+}
24+
15#[typetag::serde(name = "add_index")]
25#[typetag::serde(name = "add_index")]
16impl Action for AddIndex {
26impl Action for AddIndex {
17 fn describe(&self) -> String {
27 fn describe(&self) -> String {
@@ -37,10 +47,15 @@ impl Action for AddIndex {
37 .collect();
47 .collect();
38
48
39 let unique = if self.index.unique { "UNIQUE" } else { "" };
49 let unique = if self.index.unique { "UNIQUE" } else { "" };
50+ let index_type_def = if let Some(index_type) = &self.index.index_type {
51+ format!("USING {index_type}")
52+ } else {
53+ "".to_string()
54+ };
40
55
41 db.run(&format!(
56 db.run(&format!(
42 r#"
57 r#"
43− CREATE {unique} INDEX CONCURRENTLY "{name}" ON "{table}" ({columns})
58+ CREATE {unique} INDEX CONCURRENTLY "{name}" ON "{table}" {index_type_def} ({columns})
44 "#,
59 "#,
45 name = self.index.name,
60 name = self.index.name,
46 table = self.table,
61 table = self.table,
src/migrations/common.rs−8
@@ -28,14 +28,6 @@ fn nullable_default() -> bool {
28 true
28 true
29}
29}
30
30
31−#[derive(Serialize, Deserialize, Clone, Debug)]
32−pub struct Index {
33− pub name: String,
34− pub columns: Vec<String>,
35− #[serde(default)]
36− pub unique: bool,
37−}
38−
39#[derive(Debug)]
31#[derive(Debug)]
40struct PostgresRawValue {
32struct PostgresRawValue {
41 bytes: Vec<u8>,
33 bytes: Vec<u8>,
src/migrations/mod.rs+2−2
@@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
7
7
8// Re-export migration types
8// Re-export migration types
9mod common;
9mod common;
10−pub use common::{Column, ColumnBuilder, Index};
10+pub use common::{Column, ColumnBuilder};
11
11
12mod create_table;
12mod create_table;
13pub use create_table::{CreateTable, CreateTableBuilder, ForeignKey};
13pub use create_table::{CreateTable, CreateTableBuilder, ForeignKey};
@@ -22,7 +22,7 @@ mod remove_column;
22pub use remove_column::RemoveColumn;
22pub use remove_column::RemoveColumn;
23
23
24mod add_index;
24mod add_index;
25−pub use add_index::AddIndex;
25+pub use add_index::{AddIndex, Index};
26
26
27mod remove_index;
27mod remove_index;
28pub use remove_index::RemoveIndex;
28pub use remove_index::RemoveIndex;
tests/add_index.rs+81
@@ -31,6 +31,7 @@ fn add_index() {
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: true,
33 unique: true,
34+ index_type: None,
34 },
35 },
35 });
36 });
36
37
@@ -101,6 +102,7 @@ fn add_index_unique() {
101 name: "name_idx".to_string(),
102 name: "name_idx".to_string(),
102 columns: vec!["name".to_string()],
103 columns: vec!["name".to_string()],
103 unique: true,
104 unique: true,
105+ index_type: None,
104 },
106 },
105 });
107 });
106
108
@@ -148,3 +150,82 @@ fn add_index_unique() {
148 reshape.complete().unwrap();
150 reshape.complete().unwrap();
149 common::assert_cleaned_up(&mut new_db);
151 common::assert_cleaned_up(&mut new_db);
150}
152}
153+
154+#[test]
155+fn add_index_with_type() {
156+ let (mut reshape, mut old_db, mut new_db) = common::setup();
157+
158+ let create_table_migration = Migration::new("create_user_table", None).with_action(
159+ CreateTableBuilder::default()
160+ .name("users")
161+ .primary_key(vec!["id".to_string()])
162+ .columns(vec![
163+ ColumnBuilder::default()
164+ .name("id")
165+ .data_type("INTEGER")
166+ .build()
167+ .unwrap(),
168+ ColumnBuilder::default()
169+ .name("data")
170+ .data_type("JSONB")
171+ .build()
172+ .unwrap(),
173+ ])
174+ .build()
175+ .unwrap(),
176+ );
177+ let add_index_migration = Migration::new("add_name_index", None).with_action(AddIndex {
178+ table: "users".to_string(),
179+ index: Index {
180+ name: "name_idx".to_string(),
181+ columns: vec!["data".to_string()],
182+ unique: false,
183+ index_type: Some("gin".to_string()),
184+ },
185+ });
186+
187+ let first_migrations = vec![create_table_migration.clone()];
188+ let second_migrations = vec![create_table_migration.clone(), add_index_migration.clone()];
189+
190+ // Run migrations
191+ reshape.migrate(first_migrations.clone()).unwrap();
192+ reshape.migrate(second_migrations.clone()).unwrap();
193+
194+ // Update schemas of Postgres connections
195+ let old_schema_query =
196+ reshape::schema_query_for_migration(&first_migrations.last().unwrap().name);
197+ let new_schema_query =
198+ reshape::schema_query_for_migration(&second_migrations.last().unwrap().name);
199+ old_db.simple_query(&old_schema_query).unwrap();
200+ new_db.simple_query(&new_schema_query).unwrap();
201+
202+ // Ensure index is valid, ready and unique
203+ let (is_ready, is_valid, index_type): (bool, bool, String) = new_db
204+ .query(
205+ "
206+ SELECT pg_index.indisready, pg_index.indisvalid, pg_am.amname
207+ FROM pg_catalog.pg_index
208+ JOIN pg_catalog.pg_class ON pg_index.indexrelid = pg_class.oid
209+ JOIN pg_catalog.pg_am ON pg_class.relam = pg_am.oid
210+ WHERE pg_class.relname = 'name_idx'
211+ ",
212+ &[],
213+ )
214+ .unwrap()
215+ .first()
216+ .map(|row| {
217+ (
218+ row.get("indisready"),
219+ row.get("indisvalid"),
220+ row.get("amname"),
221+ )
222+ })
223+ .unwrap();
224+
225+ assert!(is_ready, "expected index to be ready");
226+ assert!(is_valid, "expected index to be valid");
227+ assert_eq!("gin", index_type, "expected index type to be GIN");
228+
229+ reshape.complete().unwrap();
230+ common::assert_cleaned_up(&mut new_db);
231+}
tests/alter_column.rs+1
@@ -541,6 +541,7 @@ fn alter_column_with_index() {
541 name: "users_name_idx".to_string(),
541 name: "users_name_idx".to_string(),
542 columns: vec!["first_name".to_string(), "last_name".to_string()],
542 columns: vec!["first_name".to_string(), "last_name".to_string()],
543 unique: false,
543 unique: false,
544+ index_type: None,
544 },
545 },
545 });
546 });
546 let uppercase_name = Migration::new("uppercase_name", None).with_action(AlterColumn {
547 let uppercase_name = Migration::new("uppercase_name", None).with_action(AlterColumn {
tests/remove_index.rs+1
@@ -34,6 +34,7 @@ fn remove_index() {
34 name: "name_idx".to_string(),
34 name: "name_idx".to_string(),
35 columns: vec!["name".to_string()],
35 columns: vec!["name".to_string()],
36 unique: false,
36 unique: false,
37+ index_type: None,
37 },
38 },
38 });
39 });
39
40