@@ -90,6 +90,10 @@ impl<E: TaskExecutor> DefaultJsonHandler<E> {
9090
9191 /// Number of ordered file chunks to parse concurrently in [`Self::read_json_files`].
9292 /// `None` (the default) means no parallelism, no chunking.
93+ ///
94+ /// Chunk tasks are spawned on the current Tokio runtime. Real speedup needs a
95+ /// multi-thread executor ([`TokioMultiThreadExecutor`]). The default
96+ /// [`TokioBackgroundExecutor`] is single-threaded, so chunks share one thread.
9397 pub fn with_parallel_chunks ( mut self , parallel_chunks : Option < NonZero < usize > > ) -> Self {
9498 self . parallel_chunks = parallel_chunks;
9599 self
@@ -1253,6 +1257,89 @@ mod tests {
12531257 assert ! ( result. is_err( ) , "missing file must produce an error" ) ;
12541258 }
12551259
1260+ #[ tokio:: test( flavor = "multi_thread" ) ]
1261+ async fn test_read_json_files_parallel_later_chunk_errors_after_data ( ) {
1262+ // 4 files / 2 chunks -> [0,1] then [missing, 3]. Chunk 0 yields data;
1263+ // chunk 1 errors. try_collect would hide that ordering.
1264+ let store = Arc :: new ( InMemory :: new ( ) ) ;
1265+ for i in [ 0 , 1 , 3 ] {
1266+ store
1267+ . put (
1268+ & Path :: from ( format ! ( "test/{i}" ) ) ,
1269+ Bytes :: from ( format ! ( "{{\" val\" : {i}}}" ) ) . into ( ) ,
1270+ )
1271+ . await
1272+ . unwrap ( ) ;
1273+ }
1274+ let files: Vec < FileMeta > = ( 0 ..4 )
1275+ . map ( |i| FileMeta {
1276+ location : Url :: parse ( & format ! ( "memory:///test/{i}" ) ) . unwrap ( ) ,
1277+ last_modified : 0 ,
1278+ size : 12 ,
1279+ } )
1280+ . collect ( ) ;
1281+ let handler = DefaultJsonHandler :: new (
1282+ store,
1283+ Arc :: new ( TokioMultiThreadExecutor :: new (
1284+ tokio:: runtime:: Handle :: current ( ) ,
1285+ ) ) ,
1286+ )
1287+ . with_parallel_chunks ( NonZero :: new ( 2 ) ) ;
1288+ let physical_schema = schema_ref ! { nullable "val" : INTEGER } ;
1289+ let mut iter = handler
1290+ . read_json_files ( & files, physical_schema, None )
1291+ . unwrap ( ) ;
1292+ let first = iter. next ( ) . expect ( "chunk 0 must yield a batch" ) ;
1293+ assert ! ( first. is_ok( ) , "chunk 0 must succeed before chunk 1 errors" ) ;
1294+ let rest: DeltaResult < Vec < _ > > = iter. try_collect ( ) ;
1295+ assert ! ( rest. is_err( ) , "missing file in a later chunk must error" ) ;
1296+ }
1297+
1298+ #[ tokio:: test( flavor = "multi_thread" ) ]
1299+ async fn test_read_json_files_parallel_ragged_chunk_sizes ( ) {
1300+ // 7 files / 3 chunks -> sizes 3, 3, 1 (not an even split).
1301+ const N : i32 = 7 ;
1302+ let store = Arc :: new ( InMemory :: new ( ) ) ;
1303+ for i in 0 ..N {
1304+ store
1305+ . put (
1306+ & Path :: from ( format ! ( "test/{i}" ) ) ,
1307+ Bytes :: from ( format ! ( "{{\" val\" : {i}}}" ) ) . into ( ) ,
1308+ )
1309+ . await
1310+ . unwrap ( ) ;
1311+ }
1312+ let files: Vec < FileMeta > = ( 0 ..N )
1313+ . map ( |i| FileMeta {
1314+ location : Url :: parse ( & format ! ( "memory:///test/{i}" ) ) . unwrap ( ) ,
1315+ last_modified : 0 ,
1316+ size : 12 ,
1317+ } )
1318+ . collect ( ) ;
1319+ let handler = DefaultJsonHandler :: new (
1320+ store,
1321+ Arc :: new ( TokioMultiThreadExecutor :: new (
1322+ tokio:: runtime:: Handle :: current ( ) ,
1323+ ) ) ,
1324+ )
1325+ . with_parallel_chunks ( NonZero :: new ( 3 ) ) ;
1326+ let physical_schema = schema_ref ! { nullable "val" : INTEGER } ;
1327+ let data: Vec < RecordBatch > = handler
1328+ . read_json_files ( & files, physical_schema, None )
1329+ . unwrap ( )
1330+ . map_ok ( into_record_batch)
1331+ . try_collect ( )
1332+ . unwrap ( ) ;
1333+ let all_values: Vec < i32 > = data
1334+ . iter ( )
1335+ . flat_map ( |batch| {
1336+ let val_col: & Int32Array = batch. column ( 0 ) . as_primitive ( ) ;
1337+ ( 0 ..val_col. len ( ) ) . map ( |i| val_col. value ( i) ) . collect_vec ( )
1338+ } )
1339+ . collect ( ) ;
1340+ assert_eq ! ( all_values, ( 0 ..N ) . collect_vec( ) ) ;
1341+ }
1342+
12561343 #[ tokio:: test( flavor = "multi_thread" ) ]
12571344 async fn test_read_json_files_parallel_with_cancelled_token ( ) {
12581345 let store = Arc :: new ( InMemory :: new ( ) ) ;
0 commit comments