on main help sign in
Add option to set index type for add_index
commit353a5c22b110a04f6a2a679303342c0a2c4e700c authorfabianlindfors <fabian@flapplabs.se> dateFri 14 Jan 2022, 12:34 (5 years ago) parent5691ad2
7 files changed+117−12
README.md+15 src/migrations/add_index.rs+17−2 src/migrations/common.rs−8 src/migrations/mod.rs+2−2 tests/add_index.rs+81 tests/alter_column.rs+1 tests/remove_index.rs+1
README.md+15
@@ -408,6 +408,21 @@ table = "users"
408408 unique = true
409409```
410410
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+
411426#### Remove index
412427
413428The `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};
22use crate::{
33 db::{Conn, Transaction},
44 schema::Schema,
@@ -12,6 +12,16 @@ pub struct AddIndex {
1212 pub index: Index,
1313}
1414
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+
1525#[typetag::serde(name = "add_index")]
1626impl Action for AddIndex {
1727 fn describe(&self) -> String {
@@ -37,10 +47,15 @@ impl Action for AddIndex {
3747 .collect();
3848
3949 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+ };
4055
4156 db.run(&format!(
4257 r#"
43− CREATE {unique} INDEX CONCURRENTLY "{name}" ON "{table}" ({columns})
58+ CREATE {unique} INDEX CONCURRENTLY "{name}" ON "{table}" {index_type_def} ({columns})
4459 "#,
4560 name = self.index.name,
4661 table = self.table,
src/migrations/common.rs−8
@@ -28,14 +28,6 @@ fn nullable_default() -> bool {
2828 true
2929}
3030
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−
3931#[derive(Debug)]
4032struct PostgresRawValue {
4133 bytes: Vec<u8>,
src/migrations/mod.rs+2−2
@@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
77
88// Re-export migration types
99mod common;
10−pub use common::{Column, ColumnBuilder, Index};
10+pub use common::{Column, ColumnBuilder};
1111
1212mod create_table;
1313pub use create_table::{CreateTable, CreateTableBuilder, ForeignKey};
@@ -22,7 +22,7 @@ mod remove_column;
2222pub use remove_column::RemoveColumn;
2323
2424mod add_index;
25−pub use add_index::AddIndex;
25+pub use add_index::{AddIndex, Index};
2626
2727mod remove_index;
2828pub use remove_index::RemoveIndex;
tests/add_index.rs+81
@@ -31,6 +31,7 @@ fn add_index() {
3131 name: "name_idx".to_string(),
3232 columns: vec!["name".to_string()],
3333 unique: true,
34+ index_type: None,
3435 },
3536 });
3637
@@ -101,6 +102,7 @@ fn add_index_unique() {
101102 name: "name_idx".to_string(),
102103 columns: vec!["name".to_string()],
103104 unique: true,
105+ index_type: None,
104106 },
105107 });
106108
@@ -148,3 +150,82 @@ fn add_index_unique() {
148150 reshape.complete().unwrap();
149151 common::assert_cleaned_up(&mut new_db);
150152}
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() {
541541 name: "users_name_idx".to_string(),
542542 columns: vec!["first_name".to_string(), "last_name".to_string()],
543543 unique: false,
544+ index_type: None,
544545 },
545546 });
546547 let uppercase_name = Migration::new("uppercase_name", None).with_action(AlterColumn {
tests/remove_index.rs+1
@@ -34,6 +34,7 @@ fn remove_index() {
3434 name: "name_idx".to_string(),
3535 columns: vec!["name".to_string()],
3636 unique: false,
37+ index_type: None,
3738 },
3839 });
3940