@@ -329,50 +329,35 @@ impl TableProvider for MemTable {
329329 continue ;
330330 }
331331
332- let filter_mask = if filters. is_empty ( ) {
333- BooleanArray :: from ( vec ! [ true ; batch. num_rows( ) ] )
334- } else {
335- let mut combined_mask: Option < BooleanArray > = None ;
336-
337- for filter_expr in & filters {
338- let physical_expr = create_physical_expr (
339- filter_expr,
340- & df_schema,
341- state. execution_props ( ) ,
342- ) ?;
343-
344- let result = physical_expr. evaluate ( batch) ?;
345- let array = result. into_array ( batch. num_rows ( ) ) ?;
346- let bool_array = array
347- . as_any ( )
348- . downcast_ref :: < BooleanArray > ( )
349- . ok_or_else ( || {
350- datafusion_common:: DataFusionError :: Internal (
351- "Filter did not evaluate to boolean" . to_string ( ) ,
352- )
353- } ) ?
354- . clone ( ) ;
355-
356- combined_mask = Some ( match combined_mask {
357- Some ( existing) => and ( & existing, & bool_array) ?,
358- None => bool_array,
359- } ) ;
332+ // Evaluate filters - None means "match all rows"
333+ let filter_mask = evaluate_filters_to_mask (
334+ & filters,
335+ batch,
336+ & df_schema,
337+ state. execution_props ( ) ,
338+ ) ?;
339+
340+ let ( delete_count, keep_mask) = match filter_mask {
341+ Some ( mask) => {
342+ // Count rows where mask is true (will be deleted)
343+ let count = mask. iter ( ) . filter ( |v| v == & Some ( true ) ) . count ( ) ;
344+ // Keep rows where predicate is false or NULL (SQL three-valued logic)
345+ let keep: BooleanArray =
346+ mask. iter ( ) . map ( |v| Some ( v != Some ( true ) ) ) . collect ( ) ;
347+ ( count, keep)
348+ }
349+ None => {
350+ // No filters = delete all rows
351+ (
352+ batch. num_rows ( ) ,
353+ BooleanArray :: from ( vec ! [ false ; batch. num_rows( ) ] ) ,
354+ )
360355 }
361-
362- combined_mask. unwrap_or_else ( || {
363- BooleanArray :: from ( vec ! [ true ; batch. num_rows( ) ] )
364- } )
365356 } ;
366357
367- let delete_count =
368- filter_mask. iter ( ) . filter ( |v| v == & Some ( true ) ) . count ( ) ;
369358 total_deleted += delete_count as u64 ;
370359
371- // Keep rows where predicate is false or NULL (SQL three-valued logic)
372- let keep_mask: BooleanArray =
373- filter_mask. iter ( ) . map ( |v| Some ( v != Some ( true ) ) ) . collect ( ) ;
374360 let filtered_batch = filter_record_batch ( batch, & keep_mask) ?;
375-
376361 if filtered_batch. num_rows ( ) > 0 {
377362 new_batches. push ( filtered_batch) ;
378363 }
@@ -412,15 +397,24 @@ impl TableProvider for MemTable {
412397 }
413398 }
414399
415- let assignment_map: HashMap < & str , & Expr > = assignments
400+ let df_schema = DFSchema :: try_from ( Arc :: clone ( & self . schema ) ) ?;
401+
402+ // Create physical expressions for assignments upfront (outside batch loop)
403+ let physical_assignments: HashMap <
404+ String ,
405+ Arc < dyn datafusion_physical_plan:: PhysicalExpr > ,
406+ > = assignments
416407 . iter ( )
417- . map ( |( name, expr) | ( name. as_str ( ) , expr) )
418- . collect ( ) ;
408+ . map ( |( name, expr) | {
409+ let physical_expr =
410+ create_physical_expr ( expr, & df_schema, state. execution_props ( ) ) ?;
411+ Ok ( ( name. clone ( ) , physical_expr) )
412+ } )
413+ . collect :: < Result < _ > > ( ) ?;
419414
420415 * self . sort_order . lock ( ) = vec ! [ ] ;
421416
422417 let mut total_updated: u64 = 0 ;
423- let df_schema = DFSchema :: try_from ( Arc :: clone ( & self . schema ) ) ?;
424418
425419 for partition_data in & self . batches {
426420 let mut partition = partition_data. write ( ) . await ;
@@ -431,54 +425,39 @@ impl TableProvider for MemTable {
431425 continue ;
432426 }
433427
434- let filter_mask = if filters. is_empty ( ) {
435- BooleanArray :: from ( vec ! [ true ; batch. num_rows( ) ] )
436- } else {
437- let mut combined_mask: Option < BooleanArray > = None ;
438-
439- for filter_expr in & filters {
440- let physical_expr = create_physical_expr (
441- filter_expr,
442- & df_schema,
443- state. execution_props ( ) ,
444- ) ?;
445-
446- let result = physical_expr. evaluate ( batch) ?;
447- let array = result. into_array ( batch. num_rows ( ) ) ?;
448- let bool_array = array
449- . as_any ( )
450- . downcast_ref :: < BooleanArray > ( )
451- . ok_or_else ( || {
452- datafusion_common:: DataFusionError :: Internal (
453- "Filter did not evaluate to boolean" . to_string ( ) ,
454- )
455- } ) ?
456- . clone ( ) ;
457-
458- combined_mask = Some ( match combined_mask {
459- Some ( existing) => and ( & existing, & bool_array) ?,
460- None => bool_array,
461- } ) ;
428+ // Evaluate filters - None means "match all rows"
429+ let filter_mask = evaluate_filters_to_mask (
430+ & filters,
431+ batch,
432+ & df_schema,
433+ state. execution_props ( ) ,
434+ ) ?;
435+
436+ let ( update_count, update_mask) = match filter_mask {
437+ Some ( mask) => {
438+ // Count rows where mask is true (will be updated)
439+ let count = mask. iter ( ) . filter ( |v| v == & Some ( true ) ) . count ( ) ;
440+ // Normalize mask: only true (not NULL) triggers update
441+ let normalized: BooleanArray =
442+ mask. iter ( ) . map ( |v| Some ( v == Some ( true ) ) ) . collect ( ) ;
443+ ( count, normalized)
444+ }
445+ None => {
446+ // No filters = update all rows
447+ (
448+ batch. num_rows ( ) ,
449+ BooleanArray :: from ( vec ! [ true ; batch. num_rows( ) ] ) ,
450+ )
462451 }
463-
464- combined_mask. unwrap_or_else ( || {
465- BooleanArray :: from ( vec ! [ true ; batch. num_rows( ) ] )
466- } )
467452 } ;
468453
469- let update_count =
470- filter_mask. iter ( ) . filter ( |v| v == & Some ( true ) ) . count ( ) ;
471454 total_updated += update_count as u64 ;
472455
473456 if update_count == 0 {
474457 new_batches. push ( batch. clone ( ) ) ;
475458 continue ;
476459 }
477460
478- // Normalize mask: only true (not NULL) triggers update
479- let update_mask: BooleanArray =
480- filter_mask. iter ( ) . map ( |v| Some ( v == Some ( true ) ) ) . collect ( ) ;
481-
482461 let mut new_columns: Vec < ArrayRef > =
483462 Vec :: with_capacity ( batch. num_columns ( ) ) ;
484463
@@ -491,16 +470,15 @@ impl TableProvider for MemTable {
491470 ) )
492471 } ) ?;
493472
494- let new_column = if let Some ( value_expr ) =
495- assignment_map . get ( column_name. as_str ( ) )
473+ let new_column = if let Some ( physical_expr ) =
474+ physical_assignments . get ( column_name. as_str ( ) )
496475 {
497- let physical_expr = create_physical_expr (
498- value_expr,
499- & df_schema,
500- state. execution_props ( ) ,
501- ) ?;
502-
503- let new_values = physical_expr. evaluate ( batch) ?;
476+ // Use evaluate_selection to only evaluate on matching rows.
477+ // This avoids errors (e.g., divide-by-zero) on rows that won't
478+ // be updated. The result is scattered back with nulls for
479+ // non-matching rows, which zip() will replace with originals.
480+ let new_values =
481+ physical_expr. evaluate_selection ( batch, & update_mask) ?;
504482 let new_array = new_values. into_array ( batch. num_rows ( ) ) ?;
505483
506484 // Convert to &dyn Array which implements Datum
@@ -526,6 +504,46 @@ impl TableProvider for MemTable {
526504 }
527505}
528506
507+ /// Evaluate filter expressions against a batch and return a combined boolean mask.
508+ /// Returns None if filters is empty (meaning "match all rows").
509+ /// The returned mask has true for rows that match the filter predicates.
510+ fn evaluate_filters_to_mask (
511+ filters : & [ Expr ] ,
512+ batch : & RecordBatch ,
513+ df_schema : & DFSchema ,
514+ execution_props : & datafusion_expr:: execution_props:: ExecutionProps ,
515+ ) -> Result < Option < BooleanArray > > {
516+ if filters. is_empty ( ) {
517+ return Ok ( None ) ;
518+ }
519+
520+ let mut combined_mask: Option < BooleanArray > = None ;
521+
522+ for filter_expr in filters {
523+ let physical_expr =
524+ create_physical_expr ( filter_expr, df_schema, execution_props) ?;
525+
526+ let result = physical_expr. evaluate ( batch) ?;
527+ let array = result. into_array ( batch. num_rows ( ) ) ?;
528+ let bool_array = array
529+ . as_any ( )
530+ . downcast_ref :: < BooleanArray > ( )
531+ . ok_or_else ( || {
532+ datafusion_common:: DataFusionError :: Internal (
533+ "Filter did not evaluate to boolean" . to_string ( ) ,
534+ )
535+ } ) ?
536+ . clone ( ) ;
537+
538+ combined_mask = Some ( match combined_mask {
539+ Some ( existing) => and ( & existing, & bool_array) ?,
540+ None => bool_array,
541+ } ) ;
542+ }
543+
544+ Ok ( combined_mask)
545+ }
546+
529547/// Returns a single row with the count of affected rows.
530548#[ derive( Debug ) ]
531549struct DmlResultExec {
0 commit comments