@@ -10,25 +10,6 @@ use sea_orm::{
1010 sea_query:: { Condition , Expr , Query } ,
1111} ;
1212
13- #[ cfg( feature = "sqlx-postgres" ) ]
14- async fn pg_index_exists ( db : & DatabaseConnection , table : & str , index : & str ) -> Result < bool , DbErr > {
15- db. query_one (
16- Query :: select ( )
17- . expr ( Expr :: cust ( "COUNT(*) > 0" ) )
18- . from ( "pg_indexes" )
19- . cond_where (
20- Condition :: all ( )
21- . add ( Expr :: cust ( "schemaname = CURRENT_SCHEMA()" ) )
22- . add ( Expr :: col ( "tablename" ) . eq ( table) )
23- . add ( Expr :: col ( "indexname" ) . eq ( index) ) ,
24- ) ,
25- )
26- . await ?
27- . unwrap ( )
28- . try_get_by_index ( 0 )
29- . map_err ( DbErr :: from)
30- }
31-
3213// Scenario 1: table is first synced with a `#[sea_orm(unique)]` column already
3314// present. Repeated syncs must not drop the column-level unique constraint.
3415mod item_v1 {
@@ -47,110 +28,71 @@ mod item_v1 {
4728 impl ActiveModelBehavior for ActiveModel { }
4829}
4930
50- // Scenario 3a : initial version — column exists without UNIQUE .
51- mod user_v1 {
31+ // Scenario 2a : initial version of the table — no unique column yet .
32+ mod product_v1 {
5233 use sea_orm:: entity:: prelude:: * ;
5334
5435 #[ sea_orm:: model]
5536 #[ derive( Clone , Debug , PartialEq , Eq , DeriveEntityModel ) ]
56- #[ sea_orm( table_name = "sync_user " ) ]
37+ #[ sea_orm( table_name = "sync_product " ) ]
5738 pub struct Model {
5839 #[ sea_orm( primary_key) ]
5940 pub id : i32 ,
60- pub email : String ,
6141 }
6242
6343 impl ActiveModelBehavior for ActiveModel { }
6444}
6545
66- // Scenario 3b : updated version — the existing column is made unique .
67- mod user_v2 {
46+ // Scenario 2b : updated version — a unique column is added .
47+ mod product_v2 {
6848 use sea_orm:: entity:: prelude:: * ;
6949
7050 #[ sea_orm:: model]
7151 #[ derive( Clone , Debug , PartialEq , Eq , DeriveEntityModel ) ]
72- #[ sea_orm( table_name = "sync_user " ) ]
52+ #[ sea_orm( table_name = "sync_product " ) ]
7353 pub struct Model {
7454 #[ sea_orm( primary_key) ]
7555 pub id : i32 ,
7656 #[ sea_orm( unique) ]
77- pub email : String ,
57+ pub sku : String ,
7858 }
7959
8060 impl ActiveModelBehavior for ActiveModel { }
8161}
8262
83- // Scenario 2a : initial version of the table — no unique column yet .
84- mod product_v1 {
63+ // Scenario 3a : initial version — column exists without UNIQUE .
64+ mod user_v1 {
8565 use sea_orm:: entity:: prelude:: * ;
8666
8767 #[ sea_orm:: model]
8868 #[ derive( Clone , Debug , PartialEq , Eq , DeriveEntityModel ) ]
89- #[ sea_orm( table_name = "sync_product " ) ]
69+ #[ sea_orm( table_name = "sync_user " ) ]
9070 pub struct Model {
9171 #[ sea_orm( primary_key) ]
9272 pub id : i32 ,
73+ pub email : String ,
9374 }
9475
9576 impl ActiveModelBehavior for ActiveModel { }
9677}
9778
98- // Scenario 2b : updated version — a unique column is added .
99- mod product_v2 {
79+ // Scenario 3b : updated version — the existing column is made unique .
80+ mod user_v2 {
10081 use sea_orm:: entity:: prelude:: * ;
10182
10283 #[ sea_orm:: model]
10384 #[ derive( Clone , Debug , PartialEq , Eq , DeriveEntityModel ) ]
104- #[ sea_orm( table_name = "sync_product " ) ]
85+ #[ sea_orm( table_name = "sync_user " ) ]
10586 pub struct Model {
10687 #[ sea_orm( primary_key) ]
10788 pub id : i32 ,
10889 #[ sea_orm( unique) ]
109- pub sku : String ,
90+ pub email : String ,
11091 }
11192
11293 impl ActiveModelBehavior for ActiveModel { }
11394}
11495
115- /// Scenario 3: an existing column is made unique in a later sync.
116- ///
117- /// When a column that already exists in the DB is annotated with
118- /// `#[sea_orm(unique)]`, the sync must create a unique index for it.
119- #[ sea_orm_macros:: test]
120- async fn test_sync_make_existing_column_unique ( ) -> Result < ( ) , DbErr > {
121- let ctx = TestContext :: new ( "test_sync_make_existing_column_unique" ) . await ;
122- let db = & ctx. db ;
123-
124- #[ cfg( feature = "schema-sync" ) ]
125- {
126- // First sync: creates the table with a plain (non-unique) email column
127- db. get_schema_builder ( )
128- . register ( user_v1:: Entity )
129- . sync ( db)
130- . await ?;
131-
132- // Second sync: email is now marked unique — should create the unique index
133- db. get_schema_builder ( )
134- . register ( user_v2:: Entity )
135- . sync ( db)
136- . await ?;
137-
138- // Third sync: must not try to drop or re-create the index
139- db. get_schema_builder ( )
140- . register ( user_v2:: Entity )
141- . sync ( db)
142- . await ?;
143-
144- #[ cfg( feature = "sqlx-postgres" ) ]
145- assert ! (
146- pg_index_exists( db, "sync_user" , "idx-sync_user-email" ) . await ?,
147- "unique index on `sync_user.email` should be created when column is made unique"
148- ) ;
149- }
150-
151- Ok ( ( ) )
152- }
153-
15496/// Regression test for <https://github.com/SeaQL/sea-orm/issues/2970>.
15597///
15698/// A table with a `#[sea_orm(unique)]` column is created on the first sync.
@@ -223,3 +165,61 @@ async fn test_sync_add_unique_column_no_drop() -> Result<(), DbErr> {
223165
224166 Ok ( ( ) )
225167}
168+
169+ /// Scenario 3: an existing column is made unique in a later sync.
170+ ///
171+ /// When a column that already exists in the DB is annotated with
172+ /// `#[sea_orm(unique)]`, the sync must create a unique index for it.
173+ #[ sea_orm_macros:: test]
174+ async fn test_sync_make_existing_column_unique ( ) -> Result < ( ) , DbErr > {
175+ let ctx = TestContext :: new ( "test_sync_make_existing_column_unique" ) . await ;
176+ let db = & ctx. db ;
177+
178+ #[ cfg( feature = "schema-sync" ) ]
179+ {
180+ // First sync: creates the table with a plain (non-unique) email column
181+ db. get_schema_builder ( )
182+ . register ( user_v1:: Entity )
183+ . sync ( db)
184+ . await ?;
185+
186+ // Second sync: email is now marked unique — should create the unique index
187+ db. get_schema_builder ( )
188+ . register ( user_v2:: Entity )
189+ . sync ( db)
190+ . await ?;
191+
192+ // Third sync: must not try to drop or re-create the index
193+ db. get_schema_builder ( )
194+ . register ( user_v2:: Entity )
195+ . sync ( db)
196+ . await ?;
197+
198+ #[ cfg( feature = "sqlx-postgres" ) ]
199+ assert ! (
200+ pg_index_exists( db, "sync_user" , "idx-sync_user-email" ) . await ?,
201+ "unique index on `sync_user.email` should be created when column is made unique"
202+ ) ;
203+ }
204+
205+ Ok ( ( ) )
206+ }
207+
208+ #[ cfg( feature = "sqlx-postgres" ) ]
209+ async fn pg_index_exists ( db : & DatabaseConnection , table : & str , index : & str ) -> Result < bool , DbErr > {
210+ db. query_one (
211+ Query :: select ( )
212+ . expr ( Expr :: cust ( "COUNT(*) > 0" ) )
213+ . from ( "pg_indexes" )
214+ . cond_where (
215+ Condition :: all ( )
216+ . add ( Expr :: cust ( "schemaname = CURRENT_SCHEMA()" ) )
217+ . add ( Expr :: col ( "tablename" ) . eq ( table) )
218+ . add ( Expr :: col ( "indexname" ) . eq ( index) ) ,
219+ ) ,
220+ )
221+ . await ?
222+ . unwrap ( )
223+ . try_get_by_index ( 0 )
224+ . map_err ( DbErr :: from)
225+ }
0 commit comments