@@ -6,7 +6,7 @@ use evaluate_expression::{evaluate_expression, evaluate_predicate};
66use itertools:: Itertools ;
77use tracing:: debug;
88
9- use super :: arrow_conversion:: { TryFromKernel as _, TryIntoArrow as _} ;
9+ use super :: arrow_conversion:: { TryFromArrow as _ , TryFromKernel as _, TryIntoArrow as _} ;
1010use crate :: arrow:: array:: { self , ArrayBuilder , ArrayRef , RecordBatch , StructArray } ;
1111use crate :: arrow:: datatypes:: {
1212 DataType as ArrowDataType , Field as ArrowField , Schema as ArrowSchema ,
@@ -15,7 +15,7 @@ use crate::engine::arrow_data::{extract_record_batch, ArrowEngineData};
1515use crate :: engine:: arrow_utils:: apply_schema:: { apply_schema, apply_schema_to} ;
1616use crate :: error:: { DeltaResult , Error } ;
1717use crate :: expressions:: { ArrayData , Expression , ExpressionRef , PredicateRef , Scalar } ;
18- use crate :: schema:: { DataType , PrimitiveType , SchemaRef } ;
18+ use crate :: schema:: { DataType , PrimitiveType , SchemaRef , StructType } ;
1919use crate :: utils:: require;
2020use crate :: { EngineData , EvaluationHandler , ExpressionEvaluator , PredicateEvaluator } ;
2121
@@ -254,7 +254,7 @@ impl EvaluationHandler for ArrowEvaluationHandler {
254254 output_type : DataType ,
255255 ) -> DeltaResult < Arc < dyn ExpressionEvaluator > > {
256256 Ok ( Arc :: new ( DefaultExpressionEvaluator {
257- _input_schema : schema,
257+ input_schema : schema,
258258 expression,
259259 output_type,
260260 } ) )
@@ -266,7 +266,7 @@ impl EvaluationHandler for ArrowEvaluationHandler {
266266 predicate : PredicateRef ,
267267 ) -> DeltaResult < Arc < dyn PredicateEvaluator > > {
268268 Ok ( Arc :: new ( DefaultPredicateEvaluator {
269- _input_schema : schema,
269+ input_schema : schema,
270270 predicate,
271271 } ) )
272272 }
@@ -341,7 +341,7 @@ impl EvaluationHandler for ArrowEvaluationHandler {
341341
342342#[ derive( Debug ) ]
343343pub struct DefaultExpressionEvaluator {
344- _input_schema : SchemaRef ,
344+ input_schema : SchemaRef ,
345345 expression : ExpressionRef ,
346346 output_type : DataType ,
347347}
@@ -350,14 +350,7 @@ impl ExpressionEvaluator for DefaultExpressionEvaluator {
350350 fn evaluate ( & self , batch : & dyn EngineData ) -> DeltaResult < Box < dyn EngineData > > {
351351 debug ! ( "Arrow evaluator evaluating: {:#?}" , self . expression) ;
352352 let batch = extract_record_batch ( batch) ?;
353- // TODO: make sure we have matching schemas for validation
354- // if batch.schema().as_ref() != &input_schema {
355- // return Err(Error::Generic(format!(
356- // "input schema does not match batch schema: {:?} != {:?}",
357- // input_schema,
358- // batch.schema()
359- // )));
360- // };
353+ validate_input_schema ( & self . input_schema , batch. schema ( ) . as_ref ( ) ) ?;
361354 let batch = match ( self . expression . as_ref ( ) , & self . output_type ) {
362355 ( Expression :: StructPatch ( patch) , DataType :: Struct ( _) ) if patch. is_empty ( ) => {
363356 // Empty patch optimization: Skip expression evaluation and directly apply the
@@ -388,22 +381,15 @@ impl ExpressionEvaluator for DefaultExpressionEvaluator {
388381
389382#[ derive( Debug ) ]
390383pub struct DefaultPredicateEvaluator {
391- _input_schema : SchemaRef ,
384+ input_schema : SchemaRef ,
392385 predicate : PredicateRef ,
393386}
394387
395388impl PredicateEvaluator for DefaultPredicateEvaluator {
396389 fn evaluate ( & self , batch : & dyn EngineData ) -> DeltaResult < Box < dyn EngineData > > {
397390 debug ! ( "Arrow evaluator evaluating: {:#?}" , self . predicate) ;
398391 let batch = extract_record_batch ( batch) ?;
399- // TODO: make sure we have matching schemas for validation
400- // if batch.schema().as_ref() != &input_schema {
401- // return Err(Error::Generic(format!(
402- // "input schema does not match batch schema: {:?} != {:?}",
403- // input_schema,
404- // batch.schema()
405- // )));
406- // };
392+ validate_input_schema ( & self . input_schema , batch. schema ( ) . as_ref ( ) ) ?;
407393 let array = evaluate_predicate ( & self . predicate , batch, false ) ?;
408394 let schema = ArrowSchema :: new ( vec ! [ ArrowField :: new(
409395 "output" ,
@@ -414,3 +400,88 @@ impl PredicateEvaluator for DefaultPredicateEvaluator {
414400 Ok ( Box :: new ( ArrowEngineData :: new ( batch) ) )
415401 }
416402}
403+
404+ fn validate_input_schema ( input_schema : & SchemaRef , batch_schema : & ArrowSchema ) -> DeltaResult < ( ) > {
405+ let batch_schema = StructType :: try_from_arrow ( batch_schema) ?;
406+ require ! (
407+ input_schema. num_fields( ) == batch_schema. num_fields( ) ,
408+ Error :: generic( format!(
409+ "Input schema fields {:?} do not match batch schema fields {:?}" ,
410+ input_schema
411+ . fields( )
412+ . map( |field| field. name( ) )
413+ . collect_vec( ) ,
414+ batch_schema
415+ . fields( )
416+ . map( |field| field. name( ) )
417+ . collect_vec( )
418+ ) )
419+ ) ;
420+
421+ for ( input_field, batch_field) in input_schema. fields ( ) . zip ( batch_schema. fields ( ) ) {
422+ require ! (
423+ input_field. name( ) == batch_field. name( ) ,
424+ Error :: generic( format!(
425+ "Input schema field '{}' does not match batch schema field '{}'" ,
426+ input_field. name( ) ,
427+ batch_field. name( )
428+ ) )
429+ ) ;
430+ require ! (
431+ input_types_compatible(
432+ input_field. data_type( ) ,
433+ batch_field. data_type( ) ,
434+ input_field. is_nullable( ) || batch_field. is_nullable( ) ,
435+ ) ,
436+ Error :: generic( format!(
437+ "Input schema type for '{}' does not match the batch schema type: {:?} != {:?}" ,
438+ input_field. name( ) ,
439+ input_field. data_type( ) ,
440+ batch_field. data_type( )
441+ ) )
442+ ) ;
443+ }
444+ Ok ( ( ) )
445+ }
446+
447+ fn input_types_compatible (
448+ input_type : & DataType ,
449+ batch_type : & DataType ,
450+ allow_omitted_struct_fields : bool ,
451+ ) -> bool {
452+ match ( input_type, batch_type) {
453+ ( DataType :: Primitive ( input) , DataType :: Primitive ( batch) ) => input == batch,
454+ ( DataType :: Struct ( input) , DataType :: Struct ( batch) ) => {
455+ if !allow_omitted_struct_fields && input. num_fields ( ) != batch. num_fields ( ) {
456+ return false ;
457+ }
458+
459+ input. fields ( ) . all ( |input_field| {
460+ batch. field ( input_field. name ( ) ) . is_none_or ( |batch_field| {
461+ input_types_compatible (
462+ input_field. data_type ( ) ,
463+ batch_field. data_type ( ) ,
464+ input_field. is_nullable ( ) || batch_field. is_nullable ( ) ,
465+ )
466+ } )
467+ } ) && batch. fields ( ) . all ( |batch_field| {
468+ input. field ( batch_field. name ( ) ) . is_some ( ) || allow_omitted_struct_fields
469+ } )
470+ }
471+ ( DataType :: Array ( input) , DataType :: Array ( batch) ) => input_types_compatible (
472+ input. element_type ( ) ,
473+ batch. element_type ( ) ,
474+ input. contains_null ( ) || batch. contains_null ( ) ,
475+ ) ,
476+ ( DataType :: Map ( input) , DataType :: Map ( batch) ) => {
477+ input_types_compatible ( input. key_type ( ) , batch. key_type ( ) , false )
478+ && input_types_compatible (
479+ input. value_type ( ) ,
480+ batch. value_type ( ) ,
481+ input. value_contains_null ( ) || batch. value_contains_null ( ) ,
482+ )
483+ }
484+ ( DataType :: Variant ( input) , DataType :: Variant ( batch) ) => input == batch,
485+ _ => false ,
486+ }
487+ }
0 commit comments