@@ -1658,7 +1658,16 @@ impl MergeInsertJob {
16581658 // indexed vs non-indexed cases. That should be handled by optimizer rules.
16591659 let session_config = SessionConfig :: default ( ) ;
16601660 let session_ctx = SessionContext :: new_with_config ( session_config) ;
1661- let scan = session_ctx. read_lance_unordered ( self . dataset . clone ( ) , true , true ) ?;
1661+ let provider = Arc :: new (
1662+ crate :: datafusion:: dataframe:: LanceTableProvider :: new_with_ordering (
1663+ self . dataset . clone ( ) ,
1664+ true ,
1665+ true ,
1666+ false ,
1667+ )
1668+ . with_blob_handling ( lance_core:: datatypes:: BlobHandling :: AllBinary ) ,
1669+ ) ;
1670+ let scan = session_ctx. read_table ( provider) ?;
16621671 // Wrap column names in double quotes to preserve case (DataFusion lowercases unquoted identifiers)
16631672 let on_cols = self
16641673 . params
@@ -11448,4 +11457,125 @@ MergeInsert: on=[id], when_matched=DoNothing, when_not_matched=InsertAll, when_n
1144811457 }
1144911458 }
1145011459 }
11460+ #[ tokio:: test]
11461+ async fn test_merge_insert_with_blob ( ) {
11462+ use arrow_array:: LargeBinaryArray ;
11463+ use arrow_schema:: Field ;
11464+ use arrow_schema:: Schema as ArrowSchema ;
11465+ use lance_arrow:: BLOB_META_KEY ;
11466+
11467+ let test_dir = TempStrDir :: default ( ) ;
11468+ let blob_meta = HashMap :: from ( [ ( BLOB_META_KEY . to_string ( ) , "true" . to_string ( ) ) ] ) ;
11469+ let schema = Arc :: new ( ArrowSchema :: new ( vec ! [
11470+ Field :: new( "blobs" , DataType :: LargeBinary , true ) . with_metadata( blob_meta. clone( ) ) ,
11471+ Field :: new( "id" , DataType :: Int64 , true ) ,
11472+ Field :: new( "other" , DataType :: Int64 , true ) ,
11473+ ] ) ) ;
11474+
11475+ let batch = RecordBatch :: try_new (
11476+ schema. clone ( ) ,
11477+ vec ! [
11478+ Arc :: new( LargeBinaryArray :: from( vec![
11479+ Some ( b"foo" . as_slice( ) ) ,
11480+ Some ( b"bar" . as_slice( ) ) ,
11481+ ] ) ) ,
11482+ Arc :: new( Int64Array :: from( vec![ 0 , 1 ] ) ) ,
11483+ Arc :: new( Int64Array :: from( vec![ 10 , 20 ] ) ) ,
11484+ ] ,
11485+ )
11486+ . unwrap ( ) ;
11487+
11488+ let reader = RecordBatchIterator :: new ( vec ! [ Ok ( batch) ] , schema. clone ( ) ) ;
11489+ let dataset = Dataset :: write (
11490+ reader,
11491+ & test_dir,
11492+ Some ( WriteParams {
11493+ data_storage_version : Some ( LanceFileVersion :: V2_1 ) ,
11494+ ..Default :: default ( )
11495+ } ) ,
11496+ )
11497+ . await
11498+ . unwrap ( ) ;
11499+
11500+ // Now update without providing 'blobs'
11501+ let source_schema = Arc :: new ( ArrowSchema :: new ( vec ! [
11502+ Field :: new( "id" , DataType :: Int64 , true ) ,
11503+ Field :: new( "other" , DataType :: Int64 , true ) ,
11504+ ] ) ) ;
11505+ let new_data = RecordBatch :: try_new (
11506+ source_schema. clone ( ) ,
11507+ vec ! [
11508+ Arc :: new( Int64Array :: from( vec![ 1 , 2 ] ) ) ,
11509+ Arc :: new( Int64Array :: from( vec![ 200 , 300 ] ) ) ,
11510+ ] ,
11511+ )
11512+ . unwrap ( ) ;
11513+ let new_reader = Box :: new ( RecordBatchIterator :: new (
11514+ vec ! [ Ok ( new_data) ] ,
11515+ source_schema. clone ( ) ,
11516+ ) ) ;
11517+
11518+ let job = MergeInsertBuilder :: try_new ( Arc :: new ( dataset) , vec ! [ "id" . to_string( ) ] )
11519+ . unwrap ( )
11520+ . when_matched ( WhenMatched :: UpdateAll )
11521+ . when_not_matched ( WhenNotMatched :: InsertAll )
11522+ . try_build ( )
11523+ . unwrap ( ) ;
11524+
11525+ // This will crash due to LargeBinary vs Struct schema mismatch!
11526+ let result = job. execute_reader ( new_reader) . await ;
11527+ // With AllBinary set, the partial update should succeed!
11528+ let ( new_dataset, _stats) = result. unwrap ( ) ;
11529+ assert_eq ! ( new_dataset. count_rows( None ) . await . unwrap( ) , 3 ) ;
11530+
11531+ let batches = new_dataset
11532+ . scan ( )
11533+ . try_into_stream ( )
11534+ . await
11535+ . unwrap ( )
11536+ . try_collect :: < Vec < _ > > ( )
11537+ . await
11538+ . unwrap ( ) ;
11539+ let batch = arrow_select:: concat:: concat_batches ( & batches[ 0 ] . schema ( ) , & batches) . unwrap ( ) ;
11540+
11541+ let id_col = batch
11542+ . column_by_name ( "id" )
11543+ . unwrap ( )
11544+ . as_any ( )
11545+ . downcast_ref :: < Int64Array > ( )
11546+ . unwrap ( ) ;
11547+ let other_col = batch
11548+ . column_by_name ( "other" )
11549+ . unwrap ( )
11550+ . as_any ( )
11551+ . downcast_ref :: < Int64Array > ( )
11552+ . unwrap ( ) ;
11553+ let blobs_col = batch
11554+ . column_by_name ( "blobs" )
11555+ . unwrap ( )
11556+ . as_any ( )
11557+ . downcast_ref :: < arrow_array:: StructArray > ( )
11558+ . unwrap ( ) ;
11559+
11560+ for i in 0 ..batch. num_rows ( ) {
11561+ let id = id_col. value ( i) ;
11562+ let other = other_col. value ( i) ;
11563+ let blob_is_null = blobs_col. is_null ( i) ;
11564+ match id {
11565+ 0 => {
11566+ assert_eq ! ( other, 10 ) ;
11567+ assert ! ( !blob_is_null, "id=0 should have kept its blob" ) ;
11568+ }
11569+ 1 => {
11570+ assert_eq ! ( other, 200 ) ;
11571+ assert ! ( !blob_is_null, "id=1 should have kept its blob on update" ) ;
11572+ }
11573+ 2 => {
11574+ assert_eq ! ( other, 300 ) ;
11575+ assert ! ( blob_is_null, "id=2 should have a null blob on insert" ) ;
11576+ }
11577+ _ => panic ! ( "Unexpected id: {}" , id) ,
11578+ }
11579+ }
11580+ }
1145111581}
0 commit comments