From 2d970e8385ec053ec740e53c8a60ef971fcd17ed Mon Sep 17 00:00:00 2001 From: daniel Date: Tue, 29 Jul 2025 22:57:32 +0000 Subject: [PATCH 1/2] feat(index): add support for `CONCURRENTLY` modifying indexes --- src/backend/postgres/index.rs | 8 +++++++ src/index/create.rs | 9 ++++++++ src/index/drop.rs | 7 ++++++ tests/postgres/index.rs | 41 ++++++++++++++++++++++++++++------- 4 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/backend/postgres/index.rs b/src/backend/postgres/index.rs index 020e2a070..09229d650 100644 --- a/src/backend/postgres/index.rs +++ b/src/backend/postgres/index.rs @@ -42,6 +42,10 @@ impl IndexBuilder for PostgresQueryBuilder { self.prepare_index_prefix(create, sql); write!(sql, "INDEX ").unwrap(); + if create.concurrently { + write!(sql, "CONCURRENTLY ").unwrap(); + } + if create.if_not_exists { write!(sql, "IF NOT EXISTS ").unwrap(); } @@ -89,6 +93,10 @@ impl IndexBuilder for PostgresQueryBuilder { fn prepare_index_drop_statement(&self, drop: &IndexDropStatement, sql: &mut dyn SqlWriter) { write!(sql, "DROP INDEX ").unwrap(); + if drop.concurrently { + write!(sql, "CONCURRENTLY ").unwrap(); + } + if drop.if_exists { write!(sql, "IF EXISTS ").unwrap(); } diff --git a/src/index/create.rs b/src/index/create.rs index c839f6fb6..cb872dd56 100644 --- a/src/index/create.rs +++ b/src/index/create.rs @@ -213,6 +213,7 @@ pub struct IndexCreateStatement { pub(crate) index: TableIndex, pub(crate) primary: bool, pub(crate) unique: bool, + pub(crate) concurrently: bool, pub(crate) nulls_not_distinct: bool, pub(crate) index_type: Option, pub(crate) if_not_exists: bool, @@ -238,6 +239,7 @@ impl IndexCreateStatement { index: Default::default(), primary: false, unique: false, + concurrently: false, nulls_not_distinct: false, index_type: None, if_not_exists: false, @@ -291,6 +293,12 @@ impl IndexCreateStatement { self } + /// Set index to be created concurrently. Only available on Postgres. + pub fn concurrently(&mut self) -> &mut Self { + self.concurrently = true; + self + } + /// Set nulls to not be treated as distinct values. Only available on Postgres. pub fn nulls_not_distinct(&mut self) -> &mut Self { self.nulls_not_distinct = true; @@ -340,6 +348,7 @@ impl IndexCreateStatement { index: self.index.take(), primary: self.primary, unique: self.unique, + concurrently: self.concurrently, nulls_not_distinct: self.nulls_not_distinct, index_type: self.index_type.take(), if_not_exists: self.if_not_exists, diff --git a/src/index/drop.rs b/src/index/drop.rs index c48eb952e..e0be31b22 100644 --- a/src/index/drop.rs +++ b/src/index/drop.rs @@ -32,6 +32,7 @@ pub struct IndexDropStatement { pub(crate) table: Option, pub(crate) index: TableIndex, pub(crate) if_exists: bool, + pub(crate) concurrently: bool, } impl IndexDropStatement { @@ -62,6 +63,12 @@ impl IndexDropStatement { self.if_exists = true; self } + + /// Set index to be dropped concurrently. Only available on Postgres. + pub fn concurrently(&mut self) -> &mut Self { + self.concurrently = true; + self + } } #[inherent] diff --git a/tests/postgres/index.rs b/tests/postgres/index.rs index bb75a26ee..dfb7725b1 100644 --- a/tests/postgres/index.rs +++ b/tests/postgres/index.rs @@ -42,6 +42,20 @@ fn create_3() { #[test] fn create_4() { + assert_eq!( + Index::create() + .full_text() + .name("idx-glyph-image") + .concurrently() + .table(Glyph::Table) + .col(Glyph::Image) + .to_string(PostgresQueryBuilder), + r#"CREATE INDEX CONCURRENTLY "idx-glyph-image" ON "glyph" USING GIN ("image")"# + ); +} + +#[test] +fn create_5() { assert_eq!( Index::create() .if_not_exists() @@ -55,7 +69,7 @@ fn create_4() { } #[test] -fn create_5() { +fn create_6() { assert_eq!( Index::create() .unique() @@ -69,7 +83,7 @@ fn create_5() { } #[test] -fn create_6() { +fn create_7() { assert_eq!( Index::create() .unique() @@ -84,7 +98,7 @@ fn create_6() { } #[test] -fn create_7() { +fn create_8() { assert_eq!( Index::create() .unique() @@ -99,7 +113,7 @@ fn create_7() { } #[test] -fn create_8() { +fn create_9() { assert_eq!( Index::create() .name("idx-font-name-include-id-language") @@ -115,7 +129,7 @@ fn create_8() { } #[test] -fn create_9() { +fn create_10() { assert_eq!( Index::create() .name("idx-character-area") @@ -127,7 +141,7 @@ fn create_9() { } #[test] -fn create_10() { +fn create_11() { assert_eq!( Index::create() .name("idx-character-character-area-desc-created_at") @@ -155,6 +169,17 @@ fn drop_1() { #[test] fn drop_2() { + assert_eq!( + Index::drop() + .name("idx-glyph-aspect") + .concurrently() + .to_string(PostgresQueryBuilder), + r#"DROP INDEX CONCURRENTLY "idx-glyph-aspect""# + ); +} + +#[test] +fn drop_3() { assert_eq!( Index::drop() .name("idx-glyph-aspect") @@ -165,7 +190,7 @@ fn drop_2() { } #[test] -fn drop_3() { +fn drop_4() { assert_eq!( Index::drop() .name("idx-glyph-aspect") @@ -177,7 +202,7 @@ fn drop_3() { #[test] #[should_panic(expected = "Not supported")] -fn drop_4() { +fn drop_5() { Index::drop() .name("idx-glyph-aspect") .table(("database", "schema", Glyph::Table)) From 8511dec9cb63eb4705a450d86e5433abcde1c69a Mon Sep 17 00:00:00 2001 From: daniel Date: Wed, 30 Jul 2025 10:43:07 -0400 Subject: [PATCH 2/2] feat(index columns): operator classes (#2) --- src/backend/postgres/index.rs | 3 +++ src/index/common.rs | 30 ++++++++++++++++++++++++++++++ tests/postgres/index.rs | 23 ++++++++++++++++++----- 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/src/backend/postgres/index.rs b/src/backend/postgres/index.rs index 09229d650..d4d91bf9a 100644 --- a/src/backend/postgres/index.rs +++ b/src/backend/postgres/index.rs @@ -170,6 +170,9 @@ impl IndexBuilder for PostgresQueryBuilder { } } } + if let Some(operator_class) = col.operator_class() { + write!(sql, " {}", operator_class.to_string()).unwrap(); + } false }); write!(sql, ")").unwrap(); diff --git a/src/index/common.rs b/src/index/common.rs index 4a76e3018..31e11c887 100644 --- a/src/index/common.rs +++ b/src/index/common.rs @@ -20,12 +20,14 @@ pub struct IndexColumnTableColumn { pub(crate) name: DynIden, pub(crate) prefix: Option, pub(crate) order: Option, + pub(crate) operator_class: Option, } #[derive(Debug, Clone)] pub struct IndexColumnExpr { pub(crate) expr: Expr, pub(crate) order: Option, + pub(crate) operator_class: Option, } impl IndexColumn { @@ -35,6 +37,26 @@ impl IndexColumn { IndexColumn::Expr(_) => None, } } + + pub(crate) fn operator_class(&self) -> &Option { + match self { + IndexColumn::TableColumn(IndexColumnTableColumn { operator_class, .. }) => operator_class, + IndexColumn::Expr(IndexColumnExpr { operator_class, .. }) => operator_class, + } + } + + /// Set index operator class. Only available on Postgres. + pub fn with_operator_class(mut self, operator_class: I) -> Self { + match self { + IndexColumn::TableColumn(ref mut index_column_table_column) => { + index_column_table_column.operator_class = Some(operator_class.into_iden()); + }, + IndexColumn::Expr(ref mut index_column_expr) => { + index_column_expr.operator_class = Some(operator_class.into_iden()) + }, + }; + self + } } #[derive(Debug, Clone)] @@ -63,6 +85,7 @@ where name: self.into_iden(), prefix: None, order: None, + operator_class: None, }) } } @@ -76,6 +99,7 @@ where name: self.0.into_iden(), prefix: Some(self.1), order: None, + operator_class: None, }) } } @@ -89,6 +113,7 @@ where name: self.0.into_iden(), prefix: None, order: Some(self.1), + operator_class: None, }) } } @@ -102,6 +127,7 @@ where name: self.0.into_iden(), prefix: Some(self.1), order: Some(self.2), + operator_class: None, }) } } @@ -111,6 +137,7 @@ impl IntoIndexColumn for FunctionCall { IndexColumn::Expr(IndexColumnExpr { expr: self.into(), order: None, + operator_class: None, }) } } @@ -120,6 +147,7 @@ impl IntoIndexColumn for (FunctionCall, IndexOrder) { IndexColumn::Expr(IndexColumnExpr { expr: self.0.into(), order: Some(self.1), + operator_class: None, }) } } @@ -129,6 +157,7 @@ impl IntoIndexColumn for Expr { IndexColumn::Expr(IndexColumnExpr { expr: self, order: None, + operator_class: None, }) } } @@ -138,6 +167,7 @@ impl IntoIndexColumn for (Expr, IndexOrder) { IndexColumn::Expr(IndexColumnExpr { expr: self.0, order: Some(self.1), + operator_class: None, }) } } diff --git a/tests/postgres/index.rs b/tests/postgres/index.rs index dfb7725b1..3747faac3 100644 --- a/tests/postgres/index.rs +++ b/tests/postgres/index.rs @@ -70,6 +70,19 @@ fn create_5() { #[test] fn create_6() { + assert_eq!( + Index::create() + .if_not_exists() + .name("idx-glyph-image") + .table(Glyph::Table) + .col(Glyph::Image.into_index_column().with_operator_class("text_pattern_ops")) + .to_string(PostgresQueryBuilder), + r#"CREATE INDEX IF NOT EXISTS "idx-glyph-image" ON "glyph" ("image" text_pattern_ops)"# + ); +} + +#[test] +fn create_7() { assert_eq!( Index::create() .unique() @@ -83,7 +96,7 @@ fn create_6() { } #[test] -fn create_7() { +fn create_8() { assert_eq!( Index::create() .unique() @@ -98,7 +111,7 @@ fn create_7() { } #[test] -fn create_8() { +fn create_9() { assert_eq!( Index::create() .unique() @@ -113,7 +126,7 @@ fn create_8() { } #[test] -fn create_9() { +fn create_10() { assert_eq!( Index::create() .name("idx-font-name-include-id-language") @@ -129,7 +142,7 @@ fn create_9() { } #[test] -fn create_10() { +fn create_11() { assert_eq!( Index::create() .name("idx-character-area") @@ -141,7 +154,7 @@ fn create_10() { } #[test] -fn create_11() { +fn create_12() { assert_eq!( Index::create() .name("idx-character-character-area-desc-created_at")