@@ -214,14 +214,23 @@ impl<'a> TableScanBuilder<'a> {
214
214
)
215
215
} ) ?
216
216
. clone ( ) ,
217
- None => self
218
- . table
219
- . metadata ( )
220
- . current_snapshot ( )
221
- . ok_or_else ( || {
222
- Error :: new ( ErrorKind :: Unexpected , "Can't scan table without snapshots" )
223
- } ) ?
224
- . clone ( ) ,
217
+ None => {
218
+ let Some ( current_snapshot_id) = self . table . metadata ( ) . current_snapshot ( ) else {
219
+ return Ok ( TableScan {
220
+ batch_size : self . batch_size ,
221
+ column_names : self . column_names ,
222
+ file_io : self . table . file_io ( ) . clone ( ) ,
223
+ plan_context : None ,
224
+ concurrency_limit_data_files : self . concurrency_limit_data_files ,
225
+ concurrency_limit_manifest_entries : self . concurrency_limit_manifest_entries ,
226
+ concurrency_limit_manifest_files : self . concurrency_limit_manifest_files ,
227
+ row_group_filtering_enabled : self . row_group_filtering_enabled ,
228
+ row_selection_enabled : self . row_selection_enabled ,
229
+ delete_file_processing_enabled : self . delete_file_processing_enabled ,
230
+ } ) ;
231
+ } ;
232
+ current_snapshot_id. clone ( )
233
+ }
225
234
} ;
226
235
227
236
let schema = snapshot. schema ( self . table . metadata ( ) ) ?;
@@ -302,7 +311,7 @@ impl<'a> TableScanBuilder<'a> {
302
311
batch_size : self . batch_size ,
303
312
column_names : self . column_names ,
304
313
file_io : self . table . file_io ( ) . clone ( ) ,
305
- plan_context,
314
+ plan_context : Some ( plan_context ) ,
306
315
concurrency_limit_data_files : self . concurrency_limit_data_files ,
307
316
concurrency_limit_manifest_entries : self . concurrency_limit_manifest_entries ,
308
317
concurrency_limit_manifest_files : self . concurrency_limit_manifest_files ,
@@ -316,7 +325,10 @@ impl<'a> TableScanBuilder<'a> {
316
325
/// Table scan.
317
326
#[ derive( Debug ) ]
318
327
pub struct TableScan {
319
- plan_context : PlanContext ,
328
+ /// A [PlanContext], if this table has at least one snapshot, otherwise None.
329
+ ///
330
+ /// If this is None, then the scan contains no rows.
331
+ plan_context : Option < PlanContext > ,
320
332
batch_size : Option < usize > ,
321
333
file_io : FileIO ,
322
334
column_names : Option < Vec < String > > ,
@@ -340,6 +352,10 @@ pub struct TableScan {
340
352
impl TableScan {
341
353
/// Returns a stream of [`FileScanTask`]s.
342
354
pub async fn plan_files ( & self ) -> Result < FileScanTaskStream > {
355
+ let Some ( plan_context) = self . plan_context . as_ref ( ) else {
356
+ return Ok ( Box :: pin ( futures:: stream:: empty ( ) ) ) ;
357
+ } ;
358
+
343
359
let concurrency_limit_manifest_files = self . concurrency_limit_manifest_files ;
344
360
let concurrency_limit_manifest_entries = self . concurrency_limit_manifest_entries ;
345
361
@@ -359,12 +375,12 @@ impl TableScan {
359
375
None
360
376
} ;
361
377
362
- let manifest_list = self . plan_context . get_manifest_list ( ) . await ?;
378
+ let manifest_list = plan_context. get_manifest_list ( ) . await ?;
363
379
364
380
// get the [`ManifestFile`]s from the [`ManifestList`], filtering out any
365
381
// whose partitions cannot match this
366
382
// scan's filter
367
- let manifest_file_contexts = self . plan_context . build_manifest_file_contexts (
383
+ let manifest_file_contexts = plan_context. build_manifest_file_contexts (
368
384
manifest_list,
369
385
manifest_entry_data_ctx_tx,
370
386
delete_file_idx_and_tx. as_ref ( ) . map ( |( delete_file_idx, _) | {
@@ -463,8 +479,8 @@ impl TableScan {
463
479
}
464
480
465
481
/// Returns a reference to the snapshot of the table scan.
466
- pub fn snapshot ( & self ) -> & SnapshotRef {
467
- & self . plan_context . snapshot
482
+ pub fn snapshot ( & self ) -> Option < & SnapshotRef > {
483
+ self . plan_context . as_ref ( ) . map ( |x| & x . snapshot )
468
484
}
469
485
470
486
async fn process_data_manifest_entry (
@@ -652,6 +668,45 @@ pub mod tests {
652
668
}
653
669
}
654
670
671
+ #[ allow( clippy:: new_without_default) ]
672
+ pub fn new_empty ( ) -> Self {
673
+ let tmp_dir = TempDir :: new ( ) . unwrap ( ) ;
674
+ let table_location = tmp_dir. path ( ) . join ( "table1" ) ;
675
+ let table_metadata1_location = table_location. join ( "metadata/v1.json" ) ;
676
+
677
+ let file_io = FileIO :: from_path ( table_location. as_os_str ( ) . to_str ( ) . unwrap ( ) )
678
+ . unwrap ( )
679
+ . build ( )
680
+ . unwrap ( ) ;
681
+
682
+ let table_metadata = {
683
+ let template_json_str = fs:: read_to_string ( format ! (
684
+ "{}/testdata/example_empty_table_metadata_v2.json" ,
685
+ env!( "CARGO_MANIFEST_DIR" )
686
+ ) )
687
+ . unwrap ( ) ;
688
+ let mut context = Context :: new ( ) ;
689
+ context. insert ( "table_location" , & table_location) ;
690
+ context. insert ( "table_metadata_1_location" , & table_metadata1_location) ;
691
+
692
+ let metadata_json = Tera :: one_off ( & template_json_str, & context, false ) . unwrap ( ) ;
693
+ serde_json:: from_str :: < TableMetadata > ( & metadata_json) . unwrap ( )
694
+ } ;
695
+
696
+ let table = Table :: builder ( )
697
+ . metadata ( table_metadata)
698
+ . identifier ( TableIdent :: from_strs ( [ "db" , "table1" ] ) . unwrap ( ) )
699
+ . file_io ( file_io. clone ( ) )
700
+ . metadata_location ( table_metadata1_location. as_os_str ( ) . to_str ( ) . unwrap ( ) )
701
+ . build ( )
702
+ . unwrap ( ) ;
703
+
704
+ Self {
705
+ table_location : table_location. to_str ( ) . unwrap ( ) . to_string ( ) ,
706
+ table,
707
+ }
708
+ }
709
+
655
710
pub fn new_unpartitioned ( ) -> Self {
656
711
let tmp_dir = TempDir :: new ( ) . unwrap ( ) ;
657
712
let table_location = tmp_dir. path ( ) . join ( "table1" ) ;
@@ -1178,7 +1233,7 @@ pub mod tests {
1178
1233
let table_scan = table. scan ( ) . build ( ) . unwrap ( ) ;
1179
1234
assert_eq ! (
1180
1235
table. metadata( ) . current_snapshot( ) . unwrap( ) . snapshot_id( ) ,
1181
- table_scan. snapshot( ) . snapshot_id( )
1236
+ table_scan. snapshot( ) . unwrap ( ) . snapshot_id( )
1182
1237
) ;
1183
1238
}
1184
1239
@@ -1200,7 +1255,18 @@ pub mod tests {
1200
1255
. with_row_selection_enabled ( true )
1201
1256
. build ( )
1202
1257
. unwrap ( ) ;
1203
- assert_eq ! ( table_scan. snapshot( ) . snapshot_id( ) , 3051729675574597004 ) ;
1258
+ assert_eq ! (
1259
+ table_scan. snapshot( ) . unwrap( ) . snapshot_id( ) ,
1260
+ 3051729675574597004
1261
+ ) ;
1262
+ }
1263
+
1264
+ #[ tokio:: test]
1265
+ async fn test_plan_files_on_table_without_any_snapshots ( ) {
1266
+ let table = TableTestFixture :: new_empty ( ) . table ;
1267
+ let batch_stream = table. scan ( ) . build ( ) . unwrap ( ) . to_arrow ( ) . await . unwrap ( ) ;
1268
+ let batches: Vec < _ > = batch_stream. try_collect ( ) . await . unwrap ( ) ;
1269
+ assert ! ( batches. is_empty( ) ) ;
1204
1270
}
1205
1271
1206
1272
#[ tokio:: test]
0 commit comments