@@ -419,13 +419,13 @@ mod tests {
419419 CopyOptions , GetOptions , GetResult , ListResult , MultipartUpload , ObjectMeta , ObjectStore ,
420420 PutMultipartOptions , PutOptions , PutPayload , PutResult , Result ,
421421 } ;
422- use delta_kernel:: schema:: schema_ref;
422+ use delta_kernel:: schema:: { schema_ref, DataType as KernelDataType , StructField , StructType } ;
423423 use delta_kernel_default_engine_test_utils:: { into_record_batch, string_array_to_engine_data} ;
424424 use futures:: future;
425425 use itertools:: Itertools ;
426426 use serde_json:: json;
427427 use test_utils:: engine_contract:: test_json_handler_file_path_contract;
428- use test_utils:: TestCancellationToken ;
428+ use test_utils:: { assert_result_error_with_message , TestCancellationToken } ;
429429 use tracing:: info;
430430
431431 use super :: * ;
@@ -1178,10 +1178,7 @@ mod tests {
11781178 async fn test_read_json_files_parallel_with_cancelled_token ( ) {
11791179 let store = Arc :: new ( InMemory :: new ( ) ) ;
11801180 store
1181- . put (
1182- & Path :: from ( "test/0" ) ,
1183- Bytes :: from ( r#"{"val": 0}"# ) . into ( ) ,
1184- )
1181+ . put ( & Path :: from ( "test/0" ) , Bytes :: from ( r#"{"val": 0}"# ) . into ( ) )
11851182 . await
11861183 . unwrap ( ) ;
11871184 let url = Url :: parse ( "memory:///test/0" ) . unwrap ( ) ;
@@ -1193,19 +1190,102 @@ mod tests {
11931190 let executor = Arc :: new ( TokioMultiThreadExecutor :: new (
11941191 tokio:: runtime:: Handle :: current ( ) ,
11951192 ) ) ;
1196- let handler = DefaultJsonHandler :: new ( store , executor )
1197- . with_parallel_chunks ( NonZero :: new ( 4 ) ) ;
1193+ let handler =
1194+ DefaultJsonHandler :: new ( store , executor ) . with_parallel_chunks ( NonZero :: new ( 4 ) ) ;
11981195 let physical_schema = schema_ref ! { nullable "val" : INTEGER } ;
1199- let token: CancellationTokenRef =
1200- Arc :: new ( TestCancellationToken :: cancelled ( ) ) ;
1201- let result = handler
1202- . read_json_files_with_cancellation ( & files, physical_schema, None , Some ( token) ) ;
1196+ let token: CancellationTokenRef = Arc :: new ( TestCancellationToken :: cancelled ( ) ) ;
1197+ let result =
1198+ handler. read_json_files_with_cancellation ( & files, physical_schema, None , Some ( token) ) ;
12031199 assert ! (
12041200 matches!( result, Err ( Error :: Cancelled ) ) ,
12051201 "pre-cancelled token must yield Cancelled, not data"
12061202 ) ;
12071203 }
12081204
1205+ #[ tokio:: test( flavor = "multi_thread" ) ]
1206+ async fn test_read_json_files_parallel_propagates_schema_conversion_error ( ) {
1207+ let store = Arc :: new ( InMemory :: new ( ) ) ;
1208+ store
1209+ . put ( & Path :: from ( "test/0" ) , Bytes :: from ( r#"{"val": 0}"# ) . into ( ) )
1210+ . await
1211+ . unwrap ( ) ;
1212+ let files = vec ! [ FileMeta {
1213+ location: Url :: parse( "memory:///test/0" ) . unwrap( ) ,
1214+ last_modified: 0 ,
1215+ size: 12 ,
1216+ } ] ;
1217+
1218+ // A shredded Variant is a valid kernel type but Arrow conversion only
1219+ // accepts the unshredded (metadata + value) shape. That makes
1220+ // json_arrow_schema fail inside each spawned chunk task.
1221+ let shredded_variant = KernelDataType :: variant_type ( [
1222+ StructField :: not_null ( "metadata" , KernelDataType :: BINARY ) ,
1223+ StructField :: not_null ( "value" , KernelDataType :: BINARY ) ,
1224+ StructField :: nullable ( "typed_value" , KernelDataType :: INTEGER ) ,
1225+ ] )
1226+ . unwrap ( ) ;
1227+ let physical_schema =
1228+ Arc :: new ( StructType :: try_new ( [ StructField :: nullable ( "v" , shredded_variant) ] ) . unwrap ( ) ) ;
1229+
1230+ let handler = DefaultJsonHandler :: new (
1231+ store,
1232+ Arc :: new ( TokioMultiThreadExecutor :: new (
1233+ tokio:: runtime:: Handle :: current ( ) ,
1234+ ) ) ,
1235+ )
1236+ . with_parallel_chunks ( NonZero :: new ( 2 ) ) ;
1237+ let result: DeltaResult < Vec < _ > > = handler
1238+ . read_json_files ( & files, physical_schema, None )
1239+ . unwrap ( )
1240+ . try_collect ( ) ;
1241+ assert_result_error_with_message ( result, "Incorrect Variant Schema" ) ;
1242+ }
1243+
1244+ #[ tokio:: test( flavor = "multi_thread" ) ]
1245+ async fn test_read_json_files_parallel_stops_when_consumer_drops ( ) {
1246+ // More files per chunk than the per-chunk channel can hold, so producers
1247+ // block on send. Dropping after the first batch then hits is_err().
1248+ const N : usize = 64 ;
1249+ let memory_store = InMemory :: new ( ) ;
1250+ for i in 0 ..N {
1251+ memory_store
1252+ . put (
1253+ & Path :: from ( format ! ( "test/{i}" ) ) ,
1254+ Bytes :: from ( format ! ( "{{\" val\" : {i}}}" ) ) . into ( ) ,
1255+ )
1256+ . await
1257+ . unwrap ( ) ;
1258+ }
1259+ let store = Arc :: new ( LatencyStore :: new ( memory_store, Duration :: from_millis ( 5 ) ) ) ;
1260+ let files: Vec < FileMeta > = ( 0 ..N )
1261+ . map ( |i| FileMeta {
1262+ location : Url :: parse ( & format ! ( "memory:///test/{i}" ) ) . unwrap ( ) ,
1263+ last_modified : 0 ,
1264+ size : 12 ,
1265+ } )
1266+ . collect ( ) ;
1267+
1268+ let handler = DefaultJsonHandler :: new (
1269+ store,
1270+ Arc :: new ( TokioMultiThreadExecutor :: new (
1271+ tokio:: runtime:: Handle :: current ( ) ,
1272+ ) ) ,
1273+ )
1274+ . with_buffer_size ( NonZero :: new ( 1 ) . unwrap ( ) )
1275+ . with_batch_size ( NonZero :: new ( 1 ) . unwrap ( ) )
1276+ . with_parallel_chunks ( NonZero :: new ( 2 ) ) ;
1277+ let physical_schema = schema_ref ! { nullable "val" : INTEGER } ;
1278+ let mut iter = handler
1279+ . read_json_files ( & files, physical_schema, None )
1280+ . unwrap ( ) ;
1281+ let first = iter. next ( ) . expect ( "at least one batch" ) ;
1282+ assert ! ( first. is_ok( ) , "first batch must succeed before drop" ) ;
1283+ drop ( iter) ;
1284+ // Let blocked chunk tasks observe the closed channel and take the
1285+ // consumer-dropped return before the test runtime shuts down.
1286+ tokio:: time:: sleep ( Duration :: from_millis ( 50 ) ) . await ;
1287+ }
1288+
12091289 #[ tokio:: test( flavor = "multi_thread" ) ]
12101290 async fn test_read_json_files_parallel_via_builder ( ) {
12111291 let store = Arc :: new ( InMemory :: new ( ) ) ;
0 commit comments