File tree Expand file tree Collapse file tree
store/test-store/tests/graph
runner-tests/file-data-sources/src Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -463,6 +463,37 @@ async fn offchain_trigger_vid_no_collision_with_shared_generator() {
463463 ) ;
464464}
465465
466+ // Simulate the ipfs.map() pattern: multiple EntityCache instances each create
467+ // an entity using a shared SeqGenerator. VIDs must be unique and sequential.
468+ #[ graph:: test]
469+ async fn ipfs_map_pattern_vid_uniqueness ( ) {
470+ let block: i32 = 42 ;
471+ let vid_gen = SeqGenerator :: new ( block) ;
472+
473+ let mut all_vids = Vec :: new ( ) ;
474+ for i in 0 ..5u32 {
475+ let store = Arc :: new ( MockStore :: new ( BTreeMap :: new ( ) ) ) ;
476+ let mut cache = EntityCache :: new ( store, vid_gen. cheap_clone ( ) ) ;
477+ let data = entity ! { SCHEMA => id: format!( "band{i}" ) , name: format!( "Band {i}" ) } ;
478+ let key = make_band_key ( & format ! ( "band{i}" ) ) ;
479+ cache. set ( key, data, None ) . await . unwrap ( ) ;
480+ let result = cache. as_modifications ( block, & STOPWATCH ) . await . unwrap ( ) ;
481+ let vid = match & result. modifications [ 0 ] {
482+ EntityModification :: Insert { data, .. } => data. vid ( ) ,
483+ _ => panic ! ( "expected Insert" ) ,
484+ } ;
485+ all_vids. push ( vid) ;
486+ }
487+
488+ for i in 1 ..all_vids. len ( ) {
489+ assert_eq ! (
490+ all_vids[ i] ,
491+ all_vids[ i - 1 ] + 1 ,
492+ "VIDs should be sequential"
493+ ) ;
494+ }
495+ }
496+
466497const ACCOUNT_GQL : & str = "
467498 type Account @entity {
468499 id: ID!
Original file line number Diff line number Diff line change @@ -126,6 +126,13 @@ export function handleFile(data: Bytes): void {
126126 let contextCommand = context . getString ( 'command' ) ;
127127
128128 if ( contextCommand == SPAWN_FDS_FROM_OFFCHAIN_HANDLER ) {
129+ // Create an entity for THIS file data source too, so that two offchain
130+ // triggers in the same block each write an entity. This exercises the
131+ // shared VID generator: if VIDs collide the DB will reject the insert.
132+ let entity = new FileEntity ( dataSource . stringParam ( ) ) ;
133+ entity . content = data . toString ( ) ;
134+ entity . save ( ) ;
135+
129136 let hash = context . getString ( 'hash' ) ;
130137 log . info ( 'Creating file data source from handleFile: {}' , [ hash ] ) ;
131138 dataSource . createWithContext ( 'File' , [ hash ] , new DataSourceContext ( ) ) ;
Original file line number Diff line number Diff line change @@ -673,11 +673,25 @@ async fn file_data_sources() {
673673 assert ! ( datasources. len( ) == 1 ) ;
674674 }
675675
676- // Create a File data source from a same type of file data source handler
676+ // Create a File data source from a same type of file data source handler.
677+ // Both hash_2 and hash_3 handlers create entities in the same block,
678+ // exercising the shared VID generator: colliding VIDs would cause a DB
679+ // constraint violation.
677680 {
678681 ctx. start_and_sync_to ( test_ptr ( 4 ) ) . await ;
679682
680- let content = "EXAMPLE_3" ;
683+ let query_res = ctx
684+ . query ( & format ! (
685+ r#"{{ fileEntity(id: "{}") {{ id, content }} }}"# ,
686+ hash_2. clone( )
687+ ) )
688+ . await
689+ . unwrap ( ) ;
690+ assert_json_eq ! (
691+ query_res,
692+ Some ( object! { fileEntity: object!{ id: hash_2. clone( ) , content: "EXAMPLE_2" } } )
693+ ) ;
694+
681695 let query_res = ctx
682696 . query ( & format ! (
683697 r#"{{ fileEntity(id: "{}") {{ id, content }} }}"# ,
@@ -687,7 +701,7 @@ async fn file_data_sources() {
687701 . unwrap ( ) ;
688702 assert_json_eq ! (
689703 query_res,
690- Some ( object! { fileEntity: object!{ id: hash_3. clone( ) , content: content } } )
704+ Some ( object! { fileEntity: object!{ id: hash_3. clone( ) , content: "EXAMPLE_3" } } )
691705 ) ;
692706 }
693707
You can’t perform that action at this time.
0 commit comments