@@ -422,6 +422,90 @@ fn test_db_resources_all() {
422422 ) ;
423423}
424424
425+ #[ test]
426+ fn persistable_multi_store_dual_write_and_fallback ( ) {
427+ // New isolated store
428+ let store = Db :: init_temp ( "persistable_dual_write" ) . unwrap ( ) ;
429+
430+ // Create and save a resource
431+ let mut r = crate :: Resource :: new_generate_subject ( & store) ;
432+ r. set_propval_string ( crate :: urls:: SHORTNAME . into ( ) , "dual" , & store)
433+ . unwrap ( ) ;
434+ r. save ( & store) . unwrap ( ) ;
435+ let subject = r. get_subject ( ) . to_string ( ) ;
436+
437+ // Both OpenDAL operators should have the blob
438+ let dash = store. dal_ops . get ( "dashmap" ) . expect ( "dashmap op" ) ;
439+ let sled = store. dal_ops . get ( "sled" ) . expect ( "sled op" ) ;
440+ let dash_bytes = store
441+ . runtime
442+ . block_on ( async { dash. read ( & subject) . await . expect ( "dash read" ) } ) ;
443+ let sled_bytes = store
444+ . runtime
445+ . block_on ( async { sled. read ( & subject) . await . expect ( "sled read" ) } ) ;
446+ assert ! ( dash_bytes. len( ) > 0 ) ;
447+ assert_eq ! ( dash_bytes, sled_bytes, "stores should contain same bytes" ) ;
448+
449+ // Delete from the fastest store, ensure fallback (sled tree) still serves resource
450+ store
451+ . runtime
452+ . block_on ( async { store. dal_fastest . delete ( & subject) . await } )
453+ . ok ( ) ;
454+ // Should still be able to fetch via sled resources fallback
455+ let fetched = store. get_resource ( & subject) . expect ( "fallback should work" ) ;
456+ assert_eq ! ( fetched. get_subject( ) , subject) ;
457+
458+ // Remove from sled resources tree and ensure not found now
459+ store
460+ . resources
461+ . remove ( subject. as_bytes ( ) )
462+ . expect ( "sled remove" ) ;
463+ assert ! ( store. get_resource( & subject) . is_err( ) , "should be gone now" ) ;
464+ }
465+
466+ #[ test]
467+ fn persistable_collections_still_work ( ) {
468+ let store = Db :: init_temp ( "persistable_collections" ) . unwrap ( ) ;
469+ let filter_prop = crate :: urls:: DESTINATION ;
470+ let sort_by = crate :: urls:: DESCRIPTION ;
471+
472+ // Create a few resources that match collection filters
473+ for _ in 0 ..8 {
474+ let mut res = crate :: Resource :: new_generate_subject ( & store) ;
475+ res. set_propval (
476+ filter_prop. into ( ) ,
477+ crate :: Value :: AtomicUrl ( crate :: urls:: PARAGRAPH . into ( ) ) ,
478+ & store,
479+ )
480+ . unwrap ( ) ;
481+ res. set_propval (
482+ sort_by. into ( ) ,
483+ crate :: Value :: Markdown ( crate :: utils:: random_string ( 10 ) ) ,
484+ & store,
485+ )
486+ . unwrap ( ) ;
487+ res. save ( & store) . unwrap ( ) ;
488+ }
489+
490+ // Build a collection via Query and check pagination + sorting paths remain valid
491+ let q = crate :: storelike:: Query {
492+ property : Some ( filter_prop. into ( ) ) ,
493+ value : Some ( crate :: Value :: AtomicUrl ( crate :: urls:: PARAGRAPH . into ( ) ) ) ,
494+ limit : Some ( 5 ) ,
495+ start_val : None ,
496+ end_val : None ,
497+ offset : 0 ,
498+ sort_by : Some ( sort_by. into ( ) ) ,
499+ sort_desc : false ,
500+ include_external : true ,
501+ include_nested : true ,
502+ for_agent : ForAgent :: Sudo ,
503+ } ;
504+ let res = store. query ( & q) . unwrap ( ) ;
505+ assert_eq ! ( res. resources. len( ) , 5 , "limit respected" ) ;
506+ assert ! ( res. count >= 8 , "count should include all members" ) ;
507+ }
508+
425509#[ test]
426510/// Changing these values actually correctly updates the index.
427511fn index_invalidate_cache ( ) {
0 commit comments