@@ -44,6 +44,7 @@ use ruma::{
44
44
RoomVersionId , UserId ,
45
45
} ;
46
46
use serde:: { de:: DeserializeOwned , Serialize } ;
47
+ use serde_json:: value:: { RawValue as RawJsonValue , Value as JsonValue } ;
47
48
use sled:: {
48
49
transaction:: { ConflictableTransactionError , TransactionError } ,
49
50
Config , Db , Transactional , Tree ,
@@ -115,7 +116,7 @@ impl From<SledStoreError> for StoreError {
115
116
}
116
117
}
117
118
}
118
- const DATABASE_VERSION : u8 = 2 ;
119
+ const DATABASE_VERSION : u8 = 3 ;
119
120
120
121
const VERSION_KEY : & str = "state-store-version" ;
121
122
@@ -438,17 +439,17 @@ impl SledStateStore {
438
439
439
440
debug ! ( old_version, new_version = DATABASE_VERSION , "Upgrading the Sled state store" ) ;
440
441
441
- if old_version == 1 {
442
- if self . store_cipher . is_some ( ) {
443
- // we stored some fields un-encrypted. Drop them to force re-creation
444
- return Err ( SledStoreError :: MigrationConflict {
445
- path : self . path . take ( ) . expect ( "Path must exist for a migration to fail" ) ,
446
- old_version : old_version . into ( ) ,
447
- new_version : DATABASE_VERSION . into ( ) ,
448
- } ) ;
449
- }
450
- // no migration to handle
451
- self . set_db_version ( 2u8 ) ?;
442
+ if old_version == 1 && self . store_cipher . is_some ( ) {
443
+ // we stored some fields un-encrypted. Drop them to force re-creation
444
+ return Err ( SledStoreError :: MigrationConflict {
445
+ path : self . path . take ( ) . expect ( "Path must exist for a migration to fail" ) ,
446
+ old_version : old_version . into ( ) ,
447
+ new_version : DATABASE_VERSION . into ( ) ,
448
+ } ) ;
449
+ }
450
+
451
+ if old_version < 3 {
452
+ self . migrate_to_v3 ( ) ?;
452
453
return Ok ( ( ) ) ;
453
454
}
454
455
@@ -462,6 +463,54 @@ impl SledStateStore {
462
463
} )
463
464
}
464
465
466
+ fn v3_fix_tree ( & self , tree : & Tree , batch : & mut sled:: Batch ) -> Result < ( ) > {
467
+ fn maybe_fix_json ( raw_json : & RawJsonValue ) -> Result < Option < JsonValue > > {
468
+ let json = raw_json. get ( ) ;
469
+
470
+ if json. contains ( r#""content":null"# ) {
471
+ let mut value: JsonValue = serde_json:: from_str ( json) ?;
472
+ if let Some ( content) = value. get_mut ( "content" ) {
473
+ if matches ! ( content, JsonValue :: Null ) {
474
+ * content = JsonValue :: Object ( Default :: default ( ) ) ;
475
+ return Ok ( Some ( value) ) ;
476
+ }
477
+ }
478
+ }
479
+
480
+ Ok ( None )
481
+ }
482
+
483
+ for entry in tree. iter ( ) {
484
+ let ( key, value) = entry?;
485
+ let raw_json: Box < RawJsonValue > = self . deserialize_value ( & value) ?;
486
+
487
+ if let Some ( fixed_json) = maybe_fix_json ( & raw_json) ? {
488
+ batch. insert ( key, self . serialize_value ( & fixed_json) ?) ;
489
+ }
490
+ }
491
+
492
+ Ok ( ( ) )
493
+ }
494
+
495
+ fn migrate_to_v3 ( & self ) -> Result < ( ) > {
496
+ let mut room_info_batch = sled:: Batch :: default ( ) ;
497
+ self . v3_fix_tree ( & self . room_info , & mut room_info_batch) ?;
498
+
499
+ let mut room_state_batch = sled:: Batch :: default ( ) ;
500
+ self . v3_fix_tree ( & self . room_state , & mut room_state_batch) ?;
501
+
502
+ let ret: Result < ( ) , TransactionError < SledStoreError > > = ( & self . room_info , & self . room_state )
503
+ . transaction ( |( room_info, room_state) | {
504
+ room_info. apply_batch ( & room_info_batch) ?;
505
+ room_state. apply_batch ( & room_state_batch) ?;
506
+
507
+ Ok ( ( ) )
508
+ } ) ;
509
+ ret?;
510
+
511
+ self . set_db_version ( 3u8 )
512
+ }
513
+
465
514
/// Open a `SledCryptoStore` that uses the same database as this store.
466
515
///
467
516
/// The given passphrase will be used to encrypt private data.
@@ -1551,9 +1600,15 @@ mod encrypted_tests {
1551
1600
#[ cfg( test) ]
1552
1601
mod migration {
1553
1602
use matrix_sdk_test:: async_test;
1603
+ use ruma:: {
1604
+ events:: { AnySyncStateEvent , StateEventType } ,
1605
+ room_id,
1606
+ } ;
1607
+ use serde_json:: json;
1554
1608
use tempfile:: TempDir ;
1555
1609
1556
1610
use super :: { MigrationConflictStrategy , Result , SledStateStore , SledStoreError } ;
1611
+ use crate :: state_store:: ROOM_STATE ;
1557
1612
1558
1613
#[ async_test]
1559
1614
pub async fn migrating_v1_to_2_plain ( ) -> Result < ( ) > {
@@ -1638,4 +1693,58 @@ mod migration {
1638
1693
assert_eq ! ( std:: fs:: read_dir( folder. path( ) ) ?. count( ) , 1 ) ;
1639
1694
Ok ( ( ) )
1640
1695
}
1696
+
1697
+ #[ async_test]
1698
+ pub async fn migrating_v2_to_v3 ( ) {
1699
+ // An event that fails to deserialize.
1700
+ let wrong_redacted_state_event = json ! ( {
1701
+ "content" : null,
1702
+ "event_id" : "$wrongevent" ,
1703
+ "origin_server_ts" : 1673887516047_u64 ,
1704
+ "sender" : "@example:localhost" ,
1705
+ "state_key" : "" ,
1706
+ "type" : "m.room.topic" ,
1707
+ "unsigned" : {
1708
+ "redacted_because" : {
1709
+ "type" : "m.room.redaction" ,
1710
+ "sender" : "@example:localhost" ,
1711
+ "content" : { } ,
1712
+ "redacts" : "$wrongevent" ,
1713
+ "origin_server_ts" : 1673893816047_u64 ,
1714
+ "unsigned" : { } ,
1715
+ "event_id" : "$redactionevent" ,
1716
+ } ,
1717
+ } ,
1718
+ } ) ;
1719
+ serde_json:: from_value :: < AnySyncStateEvent > ( wrong_redacted_state_event. clone ( ) )
1720
+ . unwrap_err ( ) ;
1721
+
1722
+ let room_id = room_id ! ( "!some_room:localhost" ) ;
1723
+ let folder = TempDir :: new ( ) . unwrap ( ) ;
1724
+
1725
+ let store = SledStateStore :: builder ( )
1726
+ . path ( folder. path ( ) . to_path_buf ( ) )
1727
+ . passphrase ( "secret" . to_owned ( ) )
1728
+ . build ( )
1729
+ . unwrap ( ) ;
1730
+
1731
+ store
1732
+ . room_state
1733
+ . insert (
1734
+ store. encode_key ( ROOM_STATE , ( room_id, StateEventType :: RoomTopic , "" ) ) ,
1735
+ store. serialize_value ( & wrong_redacted_state_event) . unwrap ( ) ,
1736
+ )
1737
+ . unwrap ( ) ;
1738
+ store. set_db_version ( 2u8 ) . unwrap ( ) ;
1739
+ drop ( store) ;
1740
+
1741
+ let store = SledStateStore :: builder ( )
1742
+ . path ( folder. path ( ) . to_path_buf ( ) )
1743
+ . passphrase ( "secret" . to_owned ( ) )
1744
+ . build ( )
1745
+ . unwrap ( ) ;
1746
+ let event =
1747
+ store. get_state_event ( room_id, StateEventType :: RoomTopic , "" ) . await . unwrap ( ) . unwrap ( ) ;
1748
+ event. deserialize ( ) . unwrap ( ) ;
1749
+ }
1641
1750
}
0 commit comments