@@ -44,6 +44,7 @@ use ruma::{
4444 RoomVersionId , UserId ,
4545} ;
4646use serde:: { de:: DeserializeOwned , Serialize } ;
47+ use serde_json:: value:: { RawValue as RawJsonValue , Value as JsonValue } ;
4748use sled:: {
4849 transaction:: { ConflictableTransactionError , TransactionError } ,
4950 Config , Db , Transactional , Tree ,
@@ -115,7 +116,7 @@ impl From<SledStoreError> for StoreError {
115116 }
116117 }
117118}
118- const DATABASE_VERSION : u8 = 2 ;
119+ const DATABASE_VERSION : u8 = 3 ;
119120
120121const VERSION_KEY : & str = "state-store-version" ;
121122
@@ -438,17 +439,17 @@ impl SledStateStore {
438439
439440 debug ! ( old_version, new_version = DATABASE_VERSION , "Upgrading the Sled state store" ) ;
440441
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 ( ) ?;
452453 return Ok ( ( ) ) ;
453454 }
454455
@@ -462,6 +463,54 @@ impl SledStateStore {
462463 } )
463464 }
464465
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+
465514 /// Open a `SledCryptoStore` that uses the same database as this store.
466515 ///
467516 /// The given passphrase will be used to encrypt private data.
@@ -1551,9 +1600,15 @@ mod encrypted_tests {
15511600#[ cfg( test) ]
15521601mod migration {
15531602 use matrix_sdk_test:: async_test;
1603+ use ruma:: {
1604+ events:: { AnySyncStateEvent , StateEventType } ,
1605+ room_id,
1606+ } ;
1607+ use serde_json:: json;
15541608 use tempfile:: TempDir ;
15551609
15561610 use super :: { MigrationConflictStrategy , Result , SledStateStore , SledStoreError } ;
1611+ use crate :: state_store:: ROOM_STATE ;
15571612
15581613 #[ async_test]
15591614 pub async fn migrating_v1_to_2_plain ( ) -> Result < ( ) > {
@@ -1638,4 +1693,58 @@ mod migration {
16381693 assert_eq ! ( std:: fs:: read_dir( folder. path( ) ) ?. count( ) , 1 ) ;
16391694 Ok ( ( ) )
16401695 }
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+ }
16411750}
0 commit comments