@@ -246,6 +246,42 @@ impl InListExpr {
246246
247247 Ok ( Self :: new ( expr, list, negated, static_filter) )
248248 }
249+
250+ #[ cfg( feature = "proto" ) ]
251+ pub fn try_from_proto (
252+ node : & datafusion_proto_models:: protobuf:: PhysicalExprNode ,
253+ ctx : & datafusion_physical_expr_common:: physical_expr:: proto_decode:: PhysicalExprDecodeCtx < ' _ > ,
254+ ) -> Result < Arc < dyn PhysicalExpr > > {
255+ use datafusion_proto_models:: protobuf;
256+
257+ let node = match & node. expr_type {
258+ Some ( protobuf:: physical_expr_node:: ExprType :: InList ( n) ) => n,
259+ _ => {
260+ return datafusion_common:: internal_err!(
261+ "PhysicalExprNode is not an InList"
262+ ) ;
263+ }
264+ } ;
265+
266+ let expr = ctx. decode ( node. expr . as_deref ( ) . ok_or_else ( || {
267+ datafusion_common:: DataFusionError :: Internal (
268+ "InList is missing required field 'expr'" . to_string ( ) ,
269+ )
270+ } ) ?) ?;
271+
272+ let list = node
273+ . list
274+ . iter ( )
275+ . map ( |e| ctx. decode ( e) )
276+ . collect :: < Result < Vec < _ > > > ( ) ?;
277+
278+ Ok ( Arc :: new ( InListExpr :: try_new (
279+ expr,
280+ list,
281+ node. negated ,
282+ ctx. schema ( ) ,
283+ ) ?) )
284+ }
249285}
250286impl std:: fmt:: Display for InListExpr {
251287 fn fmt ( & self , f : & mut std:: fmt:: Formatter ) -> std:: fmt:: Result {
@@ -442,6 +478,29 @@ impl PhysicalExpr for InListExpr {
442478 }
443479 write ! ( f, ")" )
444480 }
481+
482+ #[ cfg( feature = "proto" ) ]
483+ fn try_to_proto (
484+ & self ,
485+ ctx : & datafusion_physical_expr_common:: physical_expr:: proto_encode:: PhysicalExprEncodeCtx < ' _ > ,
486+ ) -> Result < Option < datafusion_proto_models:: protobuf:: PhysicalExprNode > > {
487+ use datafusion_proto_models:: protobuf;
488+
489+ Ok ( Some ( protobuf:: PhysicalExprNode {
490+ expr_id : None ,
491+ expr_type : Some ( protobuf:: physical_expr_node:: ExprType :: InList ( Box :: new (
492+ protobuf:: PhysicalInListNode {
493+ expr : Some ( Box :: new ( ctx. encode_child ( & self . expr ) ?) ) ,
494+ list : self
495+ . list
496+ . iter ( )
497+ . map ( |e| ctx. encode_child ( e) )
498+ . collect :: < Result < Vec < _ > > > ( ) ?,
499+ negated : self . negated ,
500+ } ,
501+ ) ) ) ,
502+ } ) )
503+ }
445504}
446505
447506impl PartialEq for InListExpr {
@@ -3821,3 +3880,163 @@ mod tests {
38213880 Ok ( ( ) )
38223881 }
38233882}
3883+
3884+ #[ cfg( all( test, feature = "proto" ) ) ]
3885+ mod proto_tests {
3886+ use super :: * ;
3887+ use crate :: expressions:: { Column , col, lit} ;
3888+ use crate :: proto_test_util:: {
3889+ StubDecoder , StubEncoder , UnreachableDecoder , column_node,
3890+ } ;
3891+ use arrow:: datatypes:: Field ;
3892+ use datafusion_common:: DataFusionError ;
3893+ use datafusion_physical_expr_common:: physical_expr:: proto_decode:: PhysicalExprDecodeCtx ;
3894+ use datafusion_physical_expr_common:: physical_expr:: proto_encode:: PhysicalExprEncodeCtx ;
3895+ use datafusion_proto_models:: protobuf:: {
3896+ PhysicalExprNode , PhysicalInListNode , physical_expr_node,
3897+ } ;
3898+
3899+ /// Build an `InListExpr` proto node with the given children.
3900+ fn in_list_node (
3901+ expr : Option < Box < PhysicalExprNode > > ,
3902+ list : Vec < PhysicalExprNode > ,
3903+ negated : bool ,
3904+ ) -> PhysicalExprNode {
3905+ PhysicalExprNode {
3906+ expr_id : None ,
3907+ expr_type : Some ( physical_expr_node:: ExprType :: InList ( Box :: new (
3908+ PhysicalInListNode {
3909+ expr,
3910+ list,
3911+ negated,
3912+ } ,
3913+ ) ) ) ,
3914+ }
3915+ }
3916+
3917+ /// An `InListExpr` over a column with one literal value.
3918+ fn in_list_fixture ( ) -> InListExpr {
3919+ let schema = Schema :: new ( vec ! [ Field :: new( "a" , DataType :: Int32 , true ) ] ) ;
3920+ InListExpr :: try_new ( col ( "a" , & schema) . unwrap ( ) , vec ! [ lit( 1 ) ] , false , & schema)
3921+ . unwrap ( )
3922+ }
3923+
3924+ #[ test]
3925+ fn try_to_proto_encodes_in_list ( ) {
3926+ let in_list = in_list_fixture ( ) ;
3927+ let encoder = StubEncoder :: ok ( ) ;
3928+ let ctx = PhysicalExprEncodeCtx :: new ( & encoder) ;
3929+
3930+ let node = in_list
3931+ . try_to_proto ( & ctx)
3932+ . unwrap ( )
3933+ . expect ( "InListExpr should encode to Some(node)" ) ;
3934+
3935+ // Built-in exprs never set expr_id; only dynamic filters do.
3936+ assert ! ( node. expr_id. is_none( ) ) ;
3937+ let in_list_node = match node. expr_type {
3938+ Some ( physical_expr_node:: ExprType :: InList ( boxed) ) => * boxed,
3939+ other => panic ! ( "expected an InList node, got {other:?}" ) ,
3940+ } ;
3941+ assert ! ( !in_list_node. negated) ;
3942+ assert ! ( in_list_node. expr. is_some( ) ) ;
3943+ assert_eq ! ( in_list_node. list. len( ) , 1 ) ;
3944+ }
3945+
3946+ #[ test]
3947+ fn try_to_proto_propagates_expr_encode_error ( ) {
3948+ let in_list = in_list_fixture ( ) ;
3949+ let encoder = StubEncoder :: failing_on ( 1 ) ;
3950+ let ctx = PhysicalExprEncodeCtx :: new ( & encoder) ;
3951+ let err = in_list. try_to_proto ( & ctx) . unwrap_err ( ) ;
3952+ assert ! ( matches!( err, DataFusionError :: Internal ( msg) if msg. contains( "call 1" ) ) ) ;
3953+ }
3954+
3955+ #[ test]
3956+ fn try_to_proto_propagates_list_encode_error ( ) {
3957+ let in_list = in_list_fixture ( ) ;
3958+ // Call 1 is for `expr`, Call 2 is for the first element of `list`
3959+ let encoder = StubEncoder :: failing_on ( 2 ) ;
3960+ let ctx = PhysicalExprEncodeCtx :: new ( & encoder) ;
3961+ let err = in_list. try_to_proto ( & ctx) . unwrap_err ( ) ;
3962+ assert ! ( matches!( err, DataFusionError :: Internal ( msg) if msg. contains( "call 2" ) ) ) ;
3963+ }
3964+
3965+ #[ test]
3966+ fn try_from_proto_decodes_in_list ( ) {
3967+ let node = in_list_node (
3968+ Some ( Box :: new ( column_node ( "a" ) ) ) ,
3969+ vec ! [ column_node( "b" ) ] ,
3970+ true ,
3971+ ) ;
3972+ let schema = Schema :: new ( vec ! [ Field :: new( "decoded" , DataType :: Int32 , true ) ] ) ;
3973+ let decoder = StubDecoder :: ok ( ) ;
3974+ let ctx = PhysicalExprDecodeCtx :: new ( & schema, & decoder) ;
3975+
3976+ let decoded = InListExpr :: try_from_proto ( & node, & ctx) . unwrap ( ) ;
3977+ let in_list = decoded
3978+ . downcast_ref :: < InListExpr > ( )
3979+ . expect ( "decoded expr should be an InListExpr" ) ;
3980+
3981+ assert ! ( in_list. negated( ) ) ;
3982+ assert ! ( in_list. expr( ) . downcast_ref:: <Column >( ) . is_some( ) ) ;
3983+ assert_eq ! ( in_list. list( ) . len( ) , 1 ) ;
3984+ }
3985+
3986+ #[ test]
3987+ fn try_from_proto_rejects_non_in_list_node ( ) {
3988+ let node = column_node ( "a" ) ;
3989+ let schema = Schema :: empty ( ) ;
3990+ let decoder = UnreachableDecoder ;
3991+ let ctx = PhysicalExprDecodeCtx :: new ( & schema, & decoder) ;
3992+
3993+ let err = InListExpr :: try_from_proto ( & node, & ctx) . unwrap_err ( ) ;
3994+ assert ! ( matches!(
3995+ err,
3996+ DataFusionError :: Internal ( msg) if msg. contains( "PhysicalExprNode is not an InList" )
3997+ ) ) ;
3998+ }
3999+
4000+ #[ test]
4001+ fn try_from_proto_rejects_missing_expr ( ) {
4002+ let node = in_list_node ( None , vec ! [ column_node( "b" ) ] , false ) ;
4003+ let schema = Schema :: empty ( ) ;
4004+ let decoder = UnreachableDecoder ;
4005+ let ctx = PhysicalExprDecodeCtx :: new ( & schema, & decoder) ;
4006+
4007+ let err = InListExpr :: try_from_proto ( & node, & ctx) . unwrap_err ( ) ;
4008+ assert ! ( matches!(
4009+ err,
4010+ DataFusionError :: Internal ( msg) if msg. contains( "InList is missing required field 'expr'" )
4011+ ) ) ;
4012+ }
4013+
4014+ #[ test]
4015+ fn try_from_proto_propagates_expr_decode_error ( ) {
4016+ let node = in_list_node (
4017+ Some ( Box :: new ( column_node ( "a" ) ) ) ,
4018+ vec ! [ column_node( "b" ) ] ,
4019+ false ,
4020+ ) ;
4021+ let schema = Schema :: empty ( ) ;
4022+ let decoder = StubDecoder :: failing_on ( 1 ) ;
4023+ let ctx = PhysicalExprDecodeCtx :: new ( & schema, & decoder) ;
4024+ let err = InListExpr :: try_from_proto ( & node, & ctx) . unwrap_err ( ) ;
4025+ assert ! ( matches!( err, DataFusionError :: Internal ( msg) if msg. contains( "call 1" ) ) ) ;
4026+ }
4027+
4028+ #[ test]
4029+ fn try_from_proto_propagates_list_decode_error ( ) {
4030+ let node = in_list_node (
4031+ Some ( Box :: new ( column_node ( "a" ) ) ) ,
4032+ vec ! [ column_node( "b" ) ] ,
4033+ false ,
4034+ ) ;
4035+ let schema = Schema :: empty ( ) ;
4036+ // Call 1 is `expr`, Call 2 is the first element of `list`
4037+ let decoder = StubDecoder :: failing_on ( 2 ) ;
4038+ let ctx = PhysicalExprDecodeCtx :: new ( & schema, & decoder) ;
4039+ let err = InListExpr :: try_from_proto ( & node, & ctx) . unwrap_err ( ) ;
4040+ assert ! ( matches!( err, DataFusionError :: Internal ( msg) if msg. contains( "call 2" ) ) ) ;
4041+ }
4042+ }
0 commit comments