Skip to main content

datafusion_physical_expr/expressions/
in_list.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! Implementation of `InList` expressions: [`InListExpr`]
19
20use std::fmt::Debug;
21use std::hash::{Hash, Hasher};
22use std::sync::Arc;
23
24use crate::PhysicalExpr;
25use crate::physical_expr::physical_exprs_bag_equal;
26
27use arrow::array::*;
28use arrow::buffer::{BooleanBuffer, NullBuffer};
29use arrow::compute::SortOptions;
30use arrow::compute::kernels::boolean::{not, or_kleene};
31use arrow::compute::kernels::cmp::eq as arrow_eq;
32use arrow::datatypes::*;
33
34use datafusion_common::{
35    DFSchema, Result, ScalarValue, assert_or_internal_err, exec_err,
36};
37use datafusion_expr::{ColumnarValue, expr_vec_fmt};
38
39mod array_static_filter;
40mod branchless_filter;
41mod primitive_filter;
42mod result;
43mod static_filter;
44mod strategy;
45
46use static_filter::StaticFilter;
47use strategy::instantiate_static_filter;
48
49/// InList
50pub struct InListExpr {
51    expr: Arc<dyn PhysicalExpr>,
52    list: Vec<Arc<dyn PhysicalExpr>>,
53    negated: bool,
54    static_filter: Option<Arc<dyn StaticFilter + Send + Sync>>,
55}
56
57impl Debug for InListExpr {
58    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
59        f.debug_struct("InListExpr")
60            .field("expr", &self.expr)
61            .field("list", &self.list)
62            .field("negated", &self.negated)
63            .finish()
64    }
65}
66
67/// Returns true if Arrow's vectorized `eq` kernel supports this data type.
68///
69/// Supported: primitives, boolean, strings (Utf8/LargeUtf8/Utf8View),
70/// binary (Binary/LargeBinary/BinaryView/FixedSizeBinary), Null, and
71/// Dictionary-encoded variants of the above.
72/// Unsupported: nested types (Struct, List, Map, Union) and RunEndEncoded.
73fn supports_arrow_eq(dt: &DataType) -> bool {
74    use DataType::*;
75    match dt {
76        Boolean | Binary | LargeBinary | BinaryView | FixedSizeBinary(_) => true,
77        Dictionary(_, v) => supports_arrow_eq(v.as_ref()),
78        _ => dt.is_primitive() || dt.is_null() || dt.is_string(),
79    }
80}
81
82/// Evaluates the list of expressions into an array, flattening any dictionaries
83fn evaluate_list(
84    list: &[Arc<dyn PhysicalExpr>],
85    batch: &RecordBatch,
86) -> Result<ArrayRef> {
87    let scalars = list
88        .iter()
89        .map(|expr| {
90            expr.evaluate(batch).and_then(|r| match r {
91                ColumnarValue::Array(_) => {
92                    exec_err!("InList expression must evaluate to a scalar")
93                }
94                // Flatten dictionary values
95                ColumnarValue::Scalar(ScalarValue::Dictionary(_, v)) => Ok(*v),
96                ColumnarValue::Scalar(s) => Ok(s),
97            })
98        })
99        .collect::<Result<Vec<_>>>()?;
100
101    ScalarValue::iter_to_array(scalars)
102}
103
104/// Try to evaluate a list of expressions as constants.
105///
106/// Returns:
107/// - `Ok(Some(ArrayRef))` if all expressions are constants (can be evaluated on an empty RecordBatch)
108/// - `Ok(None)` if the list contains non-constant expressions
109/// - `Err(...)` only for actual errors (not for non-constant expressions)
110///
111/// This is used to detect when a list contains only literals, casts of literals,
112/// or other constant expressions.
113fn try_evaluate_constant_list(
114    list: &[Arc<dyn PhysicalExpr>],
115    schema: &Schema,
116) -> Result<Option<ArrayRef>> {
117    let batch = RecordBatch::new_empty(Arc::new(schema.clone()));
118    match evaluate_list(list, &batch) {
119        Ok(array) => Ok(Some(array)),
120        Err(_) => {
121            // Non-constant expressions can't be evaluated on an empty batch
122            // This is not an error, just means we can't use a static filter
123            Ok(None)
124        }
125    }
126}
127
128/// Asserts that the InList expression's data type matches a list element's
129/// data type. `DataType::Null` list elements are accepted unconditionally so
130/// that null literals and `NullArray` haystacks remain compatible with any
131/// expression type.
132fn assert_inlist_data_types_match(
133    expr_data_type: &DataType,
134    list_data_type: &DataType,
135) -> Result<()> {
136    if *list_data_type != DataType::Null {
137        assert_or_internal_err!(
138            DFSchema::datatype_is_logically_equal(expr_data_type, list_data_type),
139            "The data type inlist should be same, the value type is {expr_data_type}, one of list expr type is {list_data_type}"
140        );
141    }
142    Ok(())
143}
144
145impl InListExpr {
146    /// Create a new InList expression
147    fn new(
148        expr: Arc<dyn PhysicalExpr>,
149        list: Vec<Arc<dyn PhysicalExpr>>,
150        negated: bool,
151        static_filter: Option<Arc<dyn StaticFilter + Send + Sync>>,
152    ) -> Self {
153        Self {
154            expr,
155            list,
156            negated,
157            static_filter,
158        }
159    }
160
161    /// Input expression
162    pub fn expr(&self) -> &Arc<dyn PhysicalExpr> {
163        &self.expr
164    }
165
166    /// List to search in
167    pub fn list(&self) -> &[Arc<dyn PhysicalExpr>] {
168        &self.list
169    }
170
171    pub fn is_empty(&self) -> bool {
172        self.list.is_empty()
173    }
174
175    pub fn len(&self) -> usize {
176        self.list.len()
177    }
178
179    /// Is this negated e.g. NOT IN LIST
180    pub fn negated(&self) -> bool {
181        self.negated
182    }
183
184    /// Create a new InList expression directly from an array, bypassing expression evaluation.
185    ///
186    /// This is more efficient than [`InListExpr::try_new`] when you already have the list
187    /// as an array, as it builds the static filter directly from the array instead of
188    /// reconstructing an intermediate array from literal expressions.
189    ///
190    /// The `list` field is populated with literal expressions extracted from
191    /// the array, and the array is used to build a static filter for
192    /// efficient set membership evaluation.
193    ///
194    /// The `array` may be dictionary-encoded — it will be flattened to its
195    /// value type such that specialized filters are used.
196    ///
197    /// Returns an error if the expression's data type and the array's data type
198    /// are not logically equal. Null arrays are always accepted.
199    pub fn try_new_from_array(
200        expr: Arc<dyn PhysicalExpr>,
201        array: ArrayRef,
202        negated: bool,
203        schema: &Schema,
204    ) -> Result<Self> {
205        let expr_data_type = expr.data_type(schema)?;
206        assert_inlist_data_types_match(&expr_data_type, array.data_type())?;
207
208        let list = (0..array.len())
209            .map(|i| {
210                let scalar = ScalarValue::try_from_array(array.as_ref(), i)?;
211                Ok(crate::expressions::lit(scalar) as Arc<dyn PhysicalExpr>)
212            })
213            .collect::<Result<Vec<_>>>()?;
214        Ok(Self::new(
215            expr,
216            list,
217            negated,
218            Some(instantiate_static_filter(array)?),
219        ))
220    }
221
222    /// Create a new InList expression, using a static filter when possible.
223    ///
224    /// This validates data types and attempts to create a static filter for constant
225    /// list expressions. Uses specialized StaticFilter implementations for better
226    /// performance (e.g., Int32StaticFilter for Int32).
227    ///
228    /// Returns an error if data types don't match. If the list contains non-constant
229    /// expressions, falls back to dynamic evaluation at runtime.
230    pub fn try_new(
231        expr: Arc<dyn PhysicalExpr>,
232        list: Vec<Arc<dyn PhysicalExpr>>,
233        negated: bool,
234        schema: &Schema,
235    ) -> Result<Self> {
236        // Check the data types match
237        let expr_data_type = expr.data_type(schema)?;
238        for list_expr in list.iter() {
239            let list_expr_data_type = list_expr.data_type(schema)?;
240            assert_inlist_data_types_match(&expr_data_type, &list_expr_data_type)?;
241        }
242
243        // Try to create a static filter if all list expressions are constants
244        let static_filter = match try_evaluate_constant_list(&list, schema)? {
245            Some(in_array) => Some(instantiate_static_filter(in_array)?),
246            None => None, // Non-constant expressions, fall back to dynamic evaluation
247        };
248
249        Ok(Self::new(expr, list, negated, static_filter))
250    }
251
252    #[cfg(feature = "proto")]
253    pub fn try_from_proto(
254        node: &datafusion_proto_models::protobuf::PhysicalExprNode,
255        ctx: &datafusion_physical_expr_common::physical_expr::proto_decode::PhysicalExprDecodeCtx<'_>,
256    ) -> Result<Arc<dyn PhysicalExpr>> {
257        use datafusion_physical_expr_common::expect_expr_variant;
258        use datafusion_proto_models::protobuf;
259
260        let node = expect_expr_variant!(
261            node,
262            protobuf::physical_expr_node::ExprType::InList,
263            "InList",
264        );
265
266        let expr =
267            ctx.decode_required_expression(node.expr.as_deref(), "InListExpr", "expr")?;
268        let list = ctx.decode_children_expressions(&node.list)?;
269
270        Ok(Arc::new(InListExpr::try_new(
271            expr,
272            list,
273            node.negated,
274            ctx.schema(),
275        )?))
276    }
277}
278impl std::fmt::Display for InListExpr {
279    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
280        let list = expr_vec_fmt!(self.list);
281
282        if self.negated {
283            if self.static_filter.is_some() {
284                write!(f, "{} NOT IN (SET) ([{list}])", self.expr)
285            } else {
286                write!(f, "{} NOT IN ([{list}])", self.expr)
287            }
288        } else if self.static_filter.is_some() {
289            write!(f, "{} IN (SET) ([{list}])", self.expr)
290        } else {
291            write!(f, "{} IN ([{list}])", self.expr)
292        }
293    }
294}
295
296impl PhysicalExpr for InListExpr {
297    fn data_type(&self, _input_schema: &Schema) -> Result<DataType> {
298        Ok(DataType::Boolean)
299    }
300
301    fn nullable(&self, input_schema: &Schema) -> Result<bool> {
302        if self.expr.nullable(input_schema)? {
303            return Ok(true);
304        }
305
306        if let Some(static_filter) = &self.static_filter {
307            Ok(static_filter.null_count() > 0)
308        } else {
309            for expr in &self.list {
310                if expr.nullable(input_schema)? {
311                    return Ok(true);
312                }
313            }
314            Ok(false)
315        }
316    }
317
318    fn evaluate(&self, batch: &RecordBatch) -> Result<ColumnarValue> {
319        let num_rows = batch.num_rows();
320        let value = self.expr.evaluate(batch)?;
321        let r = match &self.static_filter {
322            Some(filter) => {
323                match value {
324                    ColumnarValue::Array(array) => {
325                        filter.contains(&array, self.negated)?
326                    }
327                    ColumnarValue::Scalar(scalar) => {
328                        if scalar.is_null() {
329                            // SQL three-valued logic: null IN (...) is always null
330                            // The code below would handle this correctly but this is a faster path
331                            let nulls = NullBuffer::new_null(num_rows);
332                            return Ok(ColumnarValue::Array(Arc::new(
333                                BooleanArray::new(
334                                    BooleanBuffer::new_unset(num_rows),
335                                    Some(nulls),
336                                ),
337                            )));
338                        }
339                        // Use a 1 row array to avoid code duplication/branching
340                        // Since all we do is compute hash and lookup this should be efficient enough
341                        let array = scalar.to_array()?;
342                        let result_array =
343                            filter.contains(array.as_ref(), self.negated)?;
344                        // Broadcast the single result to all rows
345                        // Must check is_null() to preserve NULL values (SQL three-valued logic)
346                        if result_array.is_null(0) {
347                            let nulls = NullBuffer::new_null(num_rows);
348                            BooleanArray::new(
349                                BooleanBuffer::new_unset(num_rows),
350                                Some(nulls),
351                            )
352                        } else if result_array.value(0) {
353                            BooleanArray::new(BooleanBuffer::new_set(num_rows), None)
354                        } else {
355                            BooleanArray::new(BooleanBuffer::new_unset(num_rows), None)
356                        }
357                    }
358                }
359            }
360            None => {
361                // No static filter: iterate through each expression, compare, and OR results.
362                // Use Arrow's vectorized eq kernel for types it supports (primitive,
363                // boolean, string, binary, dictionary), falling back to row-by-row
364                // comparator for unsupported types (nested, RunEndEncoded, etc.).
365                let value = value.into_array(num_rows)?;
366                let lhs_supports_arrow_eq = supports_arrow_eq(value.data_type());
367
368                // Helper: compare value against a single list expression
369                let compare_one = |expr: &Arc<dyn PhysicalExpr>| -> Result<BooleanArray> {
370                    match expr.evaluate(batch)? {
371                        ColumnarValue::Array(array) => {
372                            if lhs_supports_arrow_eq
373                                && supports_arrow_eq(array.data_type())
374                            {
375                                Ok(arrow_eq(&value, &array)?)
376                            } else {
377                                let cmp = make_comparator(
378                                    value.as_ref(),
379                                    array.as_ref(),
380                                    SortOptions::default(),
381                                )?;
382                                let buffer = BooleanBuffer::collect_bool(num_rows, |i| {
383                                    cmp(i, i).is_eq()
384                                });
385                                let nulls =
386                                    NullBuffer::union(value.nulls(), array.nulls());
387                                Ok(BooleanArray::new(buffer, nulls))
388                            }
389                        }
390                        ColumnarValue::Scalar(scalar) => {
391                            // Check if scalar is null once, before the loop
392                            if scalar.is_null() {
393                                // If scalar is null, all comparisons return null
394                                Ok(BooleanArray::from(vec![None; num_rows]))
395                            } else if lhs_supports_arrow_eq {
396                                let scalar_datum = scalar.to_scalar()?;
397                                Ok(arrow_eq(&value, &scalar_datum)?)
398                            } else {
399                                // Convert scalar to 1-element array
400                                let array = scalar.to_array()?;
401                                let cmp = make_comparator(
402                                    value.as_ref(),
403                                    array.as_ref(),
404                                    SortOptions::default(),
405                                )?;
406                                // Compare each row of value with the single scalar element
407                                let buffer = BooleanBuffer::collect_bool(num_rows, |i| {
408                                    cmp(i, 0).is_eq()
409                                });
410                                Ok(BooleanArray::new(buffer, value.nulls().cloned()))
411                            }
412                        }
413                    }
414                };
415
416                // Evaluate first expression directly to avoid a redundant
417                // or_kleene with an all-false accumulator.
418                let mut found = if let Some(first) = self.list.first() {
419                    compare_one(first)?
420                } else {
421                    BooleanArray::new(BooleanBuffer::new_unset(num_rows), None)
422                };
423
424                for expr in self.list.iter().skip(1) {
425                    // Short-circuit: if every non-null row is already true,
426                    // no further list items can change the result.
427                    if found.null_count() == 0 && !found.has_false() {
428                        break;
429                    }
430                    found = or_kleene(&found, &compare_one(expr)?)?;
431                }
432
433                if self.negated { not(&found)? } else { found }
434            }
435        };
436        Ok(ColumnarValue::Array(Arc::new(r)))
437    }
438
439    fn children(&self) -> Vec<&Arc<dyn PhysicalExpr>> {
440        let mut children = vec![&self.expr];
441        children.extend(&self.list);
442        children
443    }
444
445    fn with_new_children(
446        self: Arc<Self>,
447        children: Vec<Arc<dyn PhysicalExpr>>,
448    ) -> Result<Arc<dyn PhysicalExpr>> {
449        // assume the static_filter will not change during the rewrite process
450        Ok(Arc::new(InListExpr::new(
451            Arc::clone(&children[0]),
452            children[1..].to_vec(),
453            self.negated,
454            self.static_filter.as_ref().map(Arc::clone),
455        )))
456    }
457
458    fn fmt_sql(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
459        self.expr.fmt_sql(f)?;
460        if self.negated {
461            write!(f, " NOT")?;
462        }
463
464        write!(f, " IN (")?;
465        for (i, expr) in self.list.iter().enumerate() {
466            if i > 0 {
467                write!(f, ", ")?;
468            }
469            expr.fmt_sql(f)?;
470        }
471        write!(f, ")")
472    }
473
474    #[cfg(feature = "proto")]
475    fn try_to_proto(
476        &self,
477        ctx: &datafusion_physical_expr_common::physical_expr::proto_encode::PhysicalExprEncodeCtx<'_>,
478    ) -> Result<Option<datafusion_proto_models::protobuf::PhysicalExprNode>> {
479        use datafusion_proto_models::protobuf;
480
481        Ok(Some(protobuf::PhysicalExprNode {
482            expr_id: None,
483            expr_type: Some(protobuf::physical_expr_node::ExprType::InList(Box::new(
484                protobuf::PhysicalInListNode {
485                    expr: Some(Box::new(ctx.encode_child(&self.expr)?)),
486                    list: ctx.encode_children_expressions(&self.list)?,
487                    negated: self.negated,
488                },
489            ))),
490        }))
491    }
492}
493
494impl PartialEq for InListExpr {
495    fn eq(&self, other: &Self) -> bool {
496        self.expr.eq(&other.expr)
497            && physical_exprs_bag_equal(&self.list, &other.list)
498            && self.negated == other.negated
499    }
500}
501
502impl Eq for InListExpr {}
503
504impl Hash for InListExpr {
505    fn hash<H: Hasher>(&self, state: &mut H) {
506        self.expr.hash(state);
507        self.negated.hash(state);
508        // Add `self.static_filter` when hash is available
509        self.list.hash(state);
510    }
511}
512
513/// Creates a unary expression InList
514pub fn in_list(
515    expr: Arc<dyn PhysicalExpr>,
516    list: Vec<Arc<dyn PhysicalExpr>>,
517    negated: &bool,
518    schema: &Schema,
519) -> Result<Arc<dyn PhysicalExpr>> {
520    Ok(Arc::new(InListExpr::try_new(expr, list, *negated, schema)?))
521}
522
523#[cfg(test)]
524mod tests {
525    use super::*;
526    use crate::expressions::{col, lit, try_cast};
527    use arrow::datatypes::{IntervalDayTime, IntervalMonthDayNano, i256};
528    use datafusion_common::plan_err;
529    use datafusion_expr::type_coercion::binary::comparison_coercion;
530    use datafusion_physical_expr_common::physical_expr::fmt_sql;
531    use insta::assert_snapshot;
532    use itertools::Itertools;
533
534    type InListCastResult = (Arc<dyn PhysicalExpr>, Vec<Arc<dyn PhysicalExpr>>);
535
536    // Try to do the type coercion for list physical expr.
537    // It's just used in the test
538    fn in_list_cast(
539        expr: Arc<dyn PhysicalExpr>,
540        list: Vec<Arc<dyn PhysicalExpr>>,
541        input_schema: &Schema,
542    ) -> Result<InListCastResult> {
543        let expr_type = &expr.data_type(input_schema)?;
544        let list_types: Vec<DataType> = list
545            .iter()
546            .map(|list_expr| list_expr.data_type(input_schema).unwrap())
547            .collect();
548        let result_type = get_coerce_type(expr_type, &list_types);
549        match result_type {
550            None => plan_err!(
551                "Can not find compatible types to compare {expr_type} with [{}]",
552                list_types.iter().join(", ")
553            ),
554            Some(data_type) => {
555                // find the coerced type
556                let cast_expr = try_cast(expr, input_schema, data_type.clone())?;
557                let cast_list_expr = list
558                    .into_iter()
559                    .map(|list_expr| {
560                        try_cast(list_expr, input_schema, data_type.clone()).unwrap()
561                    })
562                    .collect();
563                Ok((cast_expr, cast_list_expr))
564            }
565        }
566    }
567
568    // Attempts to coerce the types of `list_type` to be comparable with the
569    // `expr_type`
570    fn get_coerce_type(expr_type: &DataType, list_type: &[DataType]) -> Option<DataType> {
571        list_type
572            .iter()
573            .try_fold(expr_type.clone(), |left_type, right_type| {
574                comparison_coercion(&left_type, right_type)
575            })
576    }
577
578    /// Test helper macro that evaluates an IN LIST expression with automatic type casting.
579    ///
580    /// # Parameters
581    /// - `$BATCH`: The `RecordBatch` containing the input data to evaluate against
582    /// - `$LIST`: A `Vec<Arc<dyn PhysicalExpr>>` of literal expressions representing the IN list values
583    /// - `$NEGATED`: A `&bool` indicating whether this is a NOT IN operation (true) or IN operation (false)
584    /// - `$EXPECTED`: A `Vec<Option<bool>>` representing the expected boolean results for each row
585    /// - `$COL`: An `Arc<dyn PhysicalExpr>` representing the column expression to evaluate
586    /// - `$SCHEMA`: A `&Schema` reference for the input batch
587    ///
588    /// This macro first applies type casting to the column and list expressions to ensure
589    /// type compatibility, then delegates to `in_list_raw!` to perform the evaluation and assertion.
590    macro_rules! in_list {
591        ($BATCH:expr, $LIST:expr, $NEGATED:expr, $EXPECTED:expr, $COL:expr, $SCHEMA:expr) => {{
592            let (cast_expr, cast_list_exprs) = in_list_cast($COL, $LIST, $SCHEMA)?;
593            in_list_raw!(
594                $BATCH,
595                cast_list_exprs,
596                $NEGATED,
597                $EXPECTED,
598                cast_expr,
599                $SCHEMA
600            );
601        }};
602    }
603
604    /// Test helper macro that evaluates an IN LIST expression without automatic type casting.
605    ///
606    /// # Parameters
607    /// - `$BATCH`: The `RecordBatch` containing the input data to evaluate against
608    /// - `$LIST`: A `Vec<Arc<dyn PhysicalExpr>>` of literal expressions representing the IN list values
609    /// - `$NEGATED`: A `&bool` indicating whether this is a NOT IN operation (true) or IN operation (false)
610    /// - `$EXPECTED`: A `Vec<Option<bool>>` representing the expected boolean results for each row
611    /// - `$COL`: An `Arc<dyn PhysicalExpr>` representing the column expression to evaluate
612    /// - `$SCHEMA`: A `&Schema` reference for the input batch
613    ///
614    /// This macro creates an IN LIST expression, evaluates it against the batch, converts the result
615    /// to a `BooleanArray`, and asserts that it matches the expected output. Use this when the column
616    /// and list expressions are already the correct types and don't require casting.
617    macro_rules! in_list_raw {
618        ($BATCH:expr, $LIST:expr, $NEGATED:expr, $EXPECTED:expr, $COL:expr, $SCHEMA:expr) => {{
619            let col_expr = $COL;
620            let expr = in_list(Arc::clone(&col_expr), $LIST, $NEGATED, $SCHEMA).unwrap();
621            let result = expr
622                .evaluate(&$BATCH)?
623                .into_array($BATCH.num_rows())
624                .expect("Failed to convert to array");
625            let result = as_boolean_array(&result);
626            let expected = &BooleanArray::from($EXPECTED);
627            assert_eq!(
628                expected,
629                result,
630                "Failed for: {}\n{}: {:?}",
631                fmt_sql(expr.as_ref()),
632                fmt_sql(col_expr.as_ref()),
633                col_expr
634                    .evaluate(&$BATCH)?
635                    .into_array($BATCH.num_rows())
636                    .unwrap()
637            );
638        }};
639    }
640
641    /// Test case for primitive types following the standard IN LIST pattern.
642    ///
643    /// Each test case represents a data type with:
644    /// - `value_in`: A value that appears in both the test array and the IN list (matches → true)
645    /// - `value_not_in`: A value that appears in the test array but NOT in the IN list (doesn't match → false)
646    /// - `other_list_values`: Additional values in the IN list besides `value_in`
647    /// - `null_value`: Optional null scalar value for NULL handling tests. When None, tests
648    ///   without nulls are run, exercising the `(false, false)` and `(false, true)` branches.
649    struct InListPrimitiveTestCase {
650        name: &'static str,
651        value_in: ScalarValue,
652        value_not_in: ScalarValue,
653        other_list_values: Vec<ScalarValue>,
654        null_value: Option<ScalarValue>,
655    }
656
657    /// Generic test data struct for primitive types.
658    ///
659    /// Holds test values needed for IN LIST tests, allowing the data
660    /// to be declared explicitly and reused across multiple types.
661    #[derive(Clone)]
662    struct PrimitiveTestCaseData<T> {
663        value_in: T,
664        value_not_in: T,
665        other_list_values: Vec<T>,
666    }
667
668    /// Helper to create test cases for any primitive type using generic data.
669    ///
670    /// Uses TryInto for flexible type conversion, allowing test data to be
671    /// declared in any convertible type (e.g., i32 for all integer types).
672    /// Creates a test case WITH null support (for null handling tests).
673    fn primitive_test_case<T, D, F>(
674        name: &'static str,
675        constructor: F,
676        data: PrimitiveTestCaseData<D>,
677    ) -> InListPrimitiveTestCase
678    where
679        D: TryInto<T> + Clone,
680        <D as TryInto<T>>::Error: Debug,
681        F: Fn(Option<T>) -> ScalarValue,
682        T: Clone,
683    {
684        InListPrimitiveTestCase {
685            name,
686            value_in: constructor(Some(data.value_in.try_into().unwrap())),
687            value_not_in: constructor(Some(data.value_not_in.try_into().unwrap())),
688            other_list_values: data
689                .other_list_values
690                .into_iter()
691                .map(|v| constructor(Some(v.try_into().unwrap())))
692                .collect(),
693            null_value: Some(constructor(None)),
694        }
695    }
696
697    /// Helper to create test cases WITHOUT null support.
698    /// These test cases exercise the `(false, true)` branch (no nulls, negated).
699    fn primitive_test_case_no_nulls<T, D, F>(
700        name: &'static str,
701        constructor: F,
702        data: PrimitiveTestCaseData<D>,
703    ) -> InListPrimitiveTestCase
704    where
705        D: TryInto<T> + Clone,
706        <D as TryInto<T>>::Error: Debug,
707        F: Fn(Option<T>) -> ScalarValue,
708        T: Clone,
709    {
710        InListPrimitiveTestCase {
711            name,
712            value_in: constructor(Some(data.value_in.try_into().unwrap())),
713            value_not_in: constructor(Some(data.value_not_in.try_into().unwrap())),
714            other_list_values: data
715                .other_list_values
716                .into_iter()
717                .map(|v| constructor(Some(v.try_into().unwrap())))
718                .collect(),
719            null_value: None,
720        }
721    }
722
723    /// Runs test cases for multiple types, providing detailed SQL error messages on failure.
724    ///
725    /// For each test case, runs IN LIST scenarios based on whether null_value is Some or None:
726    /// - With null_value (Some): 4 tests including null handling
727    /// - Without null_value (None): 2 tests exercising the no-nulls paths
728    fn run_test_cases(test_cases: Vec<InListPrimitiveTestCase>) -> Result<()> {
729        for test_case in test_cases {
730            let test_name = test_case.name;
731
732            // Get the data type from the scalar value
733            let data_type = test_case.value_in.data_type();
734
735            // Build the base list: [value_in, ...other_list_values]
736            let build_base_list = || -> Vec<Arc<dyn PhysicalExpr>> {
737                let mut list = vec![lit(test_case.value_in.clone())];
738                list.extend(test_case.other_list_values.iter().map(|v| lit(v.clone())));
739                list
740            };
741
742            match &test_case.null_value {
743                Some(null_val) => {
744                    // Tests WITH nulls in the needle array
745                    let schema =
746                        Schema::new(vec![Field::new("a", data_type.clone(), true)]);
747
748                    // Create array from scalar values: [value_in, value_not_in, None]
749                    let array = ScalarValue::iter_to_array(vec![
750                        test_case.value_in.clone(),
751                        test_case.value_not_in.clone(),
752                        null_val.clone(),
753                    ])?;
754
755                    let col_a = col("a", &schema)?;
756                    let batch = RecordBatch::try_new(
757                        Arc::new(schema.clone()),
758                        vec![Arc::clone(&array)],
759                    )?;
760
761                    // Test 1: a IN (list) → [true, false, null]
762                    let list = build_base_list();
763                    in_list!(
764                        batch,
765                        list,
766                        &false,
767                        vec![Some(true), Some(false), None],
768                        Arc::clone(&col_a),
769                        &schema
770                    );
771
772                    // Test 2: a NOT IN (list) → [false, true, null]
773                    let list = build_base_list();
774                    in_list!(
775                        batch,
776                        list,
777                        &true,
778                        vec![Some(false), Some(true), None],
779                        Arc::clone(&col_a),
780                        &schema
781                    );
782
783                    // Test 3: a IN (list, NULL) → [true, null, null]
784                    let mut list = build_base_list();
785                    list.push(lit(null_val.clone()));
786                    in_list!(
787                        batch,
788                        list,
789                        &false,
790                        vec![Some(true), None, None],
791                        Arc::clone(&col_a),
792                        &schema
793                    );
794
795                    // Test 4: a NOT IN (list, NULL) → [false, null, null]
796                    let mut list = build_base_list();
797                    list.push(lit(null_val.clone()));
798                    in_list!(
799                        batch,
800                        list,
801                        &true,
802                        vec![Some(false), None, None],
803                        Arc::clone(&col_a),
804                        &schema
805                    );
806                }
807                None => {
808                    // Tests WITHOUT nulls - exercises the (false, false) and (false, true) branches
809                    let schema =
810                        Schema::new(vec![Field::new("a", data_type.clone(), false)]);
811
812                    // Create array from scalar values: [value_in, value_not_in] (no NULL)
813                    let array = ScalarValue::iter_to_array(vec![
814                        test_case.value_in.clone(),
815                        test_case.value_not_in.clone(),
816                    ])?;
817
818                    let col_a = col("a", &schema)?;
819                    let batch = RecordBatch::try_new(
820                        Arc::new(schema.clone()),
821                        vec![Arc::clone(&array)],
822                    )?;
823
824                    // Test 1: a IN (list) → [true, false] - exercises (false, false) branch
825                    let list = build_base_list();
826                    in_list!(
827                        batch,
828                        list,
829                        &false,
830                        vec![Some(true), Some(false)],
831                        Arc::clone(&col_a),
832                        &schema
833                    );
834
835                    // Test 2: a NOT IN (list) → [false, true] - exercises (false, true) branch
836                    let list = build_base_list();
837                    in_list!(
838                        batch,
839                        list,
840                        &true,
841                        vec![Some(false), Some(true)],
842                        Arc::clone(&col_a),
843                        &schema
844                    );
845
846                    eprintln!(
847                        "Test '{test_name}': exercised (false, true) branch (no nulls, negated)",
848                    );
849                }
850            }
851        }
852
853        Ok(())
854    }
855
856    /// Test IN LIST for all integer types (Int8/16/32/64, UInt8/16/32/64).
857    ///
858    /// Test data: 0 (in list), 2 (not in list), [1, 3, 5] (other list values)
859    #[test]
860    fn in_list_int_types() -> Result<()> {
861        let int_data = PrimitiveTestCaseData {
862            value_in: 0,
863            value_not_in: 2,
864            other_list_values: vec![1, 3, 5],
865        };
866
867        run_test_cases(vec![
868            // Tests WITH nulls
869            primitive_test_case("int8", ScalarValue::Int8, int_data.clone()),
870            primitive_test_case("int16", ScalarValue::Int16, int_data.clone()),
871            primitive_test_case("int32", ScalarValue::Int32, int_data.clone()),
872            primitive_test_case("int64", ScalarValue::Int64, int_data.clone()),
873            primitive_test_case("uint8", ScalarValue::UInt8, int_data.clone()),
874            primitive_test_case("uint16", ScalarValue::UInt16, int_data.clone()),
875            primitive_test_case("uint32", ScalarValue::UInt32, int_data.clone()),
876            primitive_test_case("uint64", ScalarValue::UInt64, int_data.clone()),
877            // Tests WITHOUT nulls - exercises (false, true) branch
878            primitive_test_case_no_nulls("int32_no_nulls", ScalarValue::Int32, int_data),
879        ])
880    }
881
882    /// Test IN LIST for all string types (Utf8, LargeUtf8, Utf8View).
883    ///
884    /// Test data: "a" (in list), "d" (not in list), ["b", "c"] (other list values)
885    #[test]
886    fn in_list_string_types() -> Result<()> {
887        let string_data = PrimitiveTestCaseData {
888            value_in: "a",
889            value_not_in: "d",
890            other_list_values: vec!["b", "c"],
891        };
892
893        run_test_cases(vec![
894            primitive_test_case("utf8", ScalarValue::Utf8, string_data.clone()),
895            primitive_test_case(
896                "large_utf8",
897                ScalarValue::LargeUtf8,
898                string_data.clone(),
899            ),
900            primitive_test_case("utf8_view", ScalarValue::Utf8View, string_data),
901        ])
902    }
903
904    /// Test IN LIST for all binary types (Binary, LargeBinary, BinaryView).
905    ///
906    /// Test data: [1,2,3] (in list), [1,2,2] (not in list), [[4,5,6], [7,8,9]] (other list values)
907    #[test]
908    fn in_list_binary_types() -> Result<()> {
909        let binary_data = PrimitiveTestCaseData {
910            value_in: vec![1_u8, 2, 3],
911            value_not_in: vec![1_u8, 2, 2],
912            other_list_values: vec![vec![4_u8, 5, 6], vec![7_u8, 8, 9]],
913        };
914
915        run_test_cases(vec![
916            primitive_test_case("binary", ScalarValue::Binary, binary_data.clone()),
917            primitive_test_case(
918                "large_binary",
919                ScalarValue::LargeBinary,
920                binary_data.clone(),
921            ),
922            primitive_test_case("binary_view", ScalarValue::BinaryView, binary_data),
923        ])
924    }
925
926    /// Test IN LIST for date types (Date32, Date64).
927    ///
928    /// Test data: 0 (in list), 2 (not in list), [1, 3] (other list values)
929    #[test]
930    fn in_list_date_types() -> Result<()> {
931        let date_data = PrimitiveTestCaseData {
932            value_in: 0,
933            value_not_in: 2,
934            other_list_values: vec![1, 3],
935        };
936
937        run_test_cases(vec![
938            primitive_test_case("date32", ScalarValue::Date32, date_data.clone()),
939            primitive_test_case("date64", ScalarValue::Date64, date_data),
940        ])
941    }
942
943    /// Test IN LIST for Decimal128 type.
944    ///
945    /// Test data: 0 (in list), 200 (not in list), [100, 300] (other list values) with precision=10, scale=2
946    #[test]
947    fn in_list_decimal() -> Result<()> {
948        run_test_cases(vec![InListPrimitiveTestCase {
949            name: "decimal128",
950            value_in: ScalarValue::Decimal128(Some(0), 10, 2),
951            value_not_in: ScalarValue::Decimal128(Some(200), 10, 2),
952            other_list_values: vec![
953                ScalarValue::Decimal128(Some(100), 10, 2),
954                ScalarValue::Decimal128(Some(300), 10, 2),
955            ],
956            null_value: Some(ScalarValue::Decimal128(None, 10, 2)),
957        }])
958    }
959
960    /// Test IN LIST for timestamp types.
961    ///
962    /// Test data: 0 (in list), 2000 (not in list), [1000, 3000] (other list values)
963    #[test]
964    fn in_list_timestamp_types() -> Result<()> {
965        run_test_cases(vec![
966            InListPrimitiveTestCase {
967                name: "timestamp_nanosecond",
968                value_in: ScalarValue::TimestampNanosecond(Some(0), None),
969                value_not_in: ScalarValue::TimestampNanosecond(Some(2000), None),
970                other_list_values: vec![
971                    ScalarValue::TimestampNanosecond(Some(1000), None),
972                    ScalarValue::TimestampNanosecond(Some(3000), None),
973                ],
974                null_value: Some(ScalarValue::TimestampNanosecond(None, None)),
975            },
976            InListPrimitiveTestCase {
977                name: "timestamp_millisecond_with_tz",
978                value_in: ScalarValue::TimestampMillisecond(
979                    Some(1500000),
980                    Some("+05:00".into()),
981                ),
982                value_not_in: ScalarValue::TimestampMillisecond(
983                    Some(2500000),
984                    Some("+05:00".into()),
985                ),
986                other_list_values: vec![ScalarValue::TimestampMillisecond(
987                    Some(3500000),
988                    Some("+05:00".into()),
989                )],
990                null_value: Some(ScalarValue::TimestampMillisecond(
991                    None,
992                    Some("+05:00".into()),
993                )),
994            },
995            InListPrimitiveTestCase {
996                name: "timestamp_millisecond_mixed_tz",
997                value_in: ScalarValue::TimestampMillisecond(
998                    Some(1500000),
999                    Some("+05:00".into()),
1000                ),
1001                value_not_in: ScalarValue::TimestampMillisecond(
1002                    Some(2500000),
1003                    Some("+05:00".into()),
1004                ),
1005                other_list_values: vec![
1006                    ScalarValue::TimestampMillisecond(
1007                        Some(3500000),
1008                        Some("+01:00".into()),
1009                    ),
1010                    ScalarValue::TimestampMillisecond(Some(4500000), Some("UTC".into())),
1011                ],
1012                null_value: Some(ScalarValue::TimestampMillisecond(
1013                    None,
1014                    Some("+05:00".into()),
1015                )),
1016            },
1017        ])
1018    }
1019
1020    #[test]
1021    fn in_list_float64() -> Result<()> {
1022        let schema = Schema::new(vec![Field::new("a", DataType::Float64, true)]);
1023        let a = Float64Array::from(vec![
1024            Some(0.0),
1025            Some(0.2),
1026            None,
1027            Some(f64::NAN),
1028            Some(-f64::NAN),
1029        ]);
1030        let col_a = col("a", &schema)?;
1031        let batch = RecordBatch::try_new(Arc::new(schema.clone()), vec![Arc::new(a)])?;
1032
1033        // expression: "a in (0.0, 0.1)"
1034        let list = vec![lit(0.0f64), lit(0.1f64)];
1035        in_list!(
1036            batch,
1037            list,
1038            &false,
1039            vec![Some(true), Some(false), None, Some(false), Some(false)],
1040            Arc::clone(&col_a),
1041            &schema
1042        );
1043
1044        // expression: "a not in (0.0, 0.1)"
1045        let list = vec![lit(0.0f64), lit(0.1f64)];
1046        in_list!(
1047            batch,
1048            list,
1049            &true,
1050            vec![Some(false), Some(true), None, Some(true), Some(true)],
1051            Arc::clone(&col_a),
1052            &schema
1053        );
1054
1055        // expression: "a in (0.0, 0.1, NULL)"
1056        let list = vec![lit(0.0f64), lit(0.1f64), lit(ScalarValue::Null)];
1057        in_list!(
1058            batch,
1059            list,
1060            &false,
1061            vec![Some(true), None, None, None, None],
1062            Arc::clone(&col_a),
1063            &schema
1064        );
1065
1066        // expression: "a not in (0.0, 0.1, NULL)"
1067        let list = vec![lit(0.0f64), lit(0.1f64), lit(ScalarValue::Null)];
1068        in_list!(
1069            batch,
1070            list,
1071            &true,
1072            vec![Some(false), None, None, None, None],
1073            Arc::clone(&col_a),
1074            &schema
1075        );
1076
1077        // expression: "a in (0.0, 0.1, NaN)"
1078        let list = vec![lit(0.0f64), lit(0.1f64), lit(f64::NAN)];
1079        in_list!(
1080            batch,
1081            list,
1082            &false,
1083            vec![Some(true), Some(false), None, Some(true), Some(false)],
1084            Arc::clone(&col_a),
1085            &schema
1086        );
1087
1088        // expression: "a not in (0.0, 0.1, NaN)"
1089        let list = vec![lit(0.0f64), lit(0.1f64), lit(f64::NAN)];
1090        in_list!(
1091            batch,
1092            list,
1093            &true,
1094            vec![Some(false), Some(true), None, Some(false), Some(true)],
1095            Arc::clone(&col_a),
1096            &schema
1097        );
1098
1099        // expression: "a in (0.0, 0.1, -NaN)"
1100        let list = vec![lit(0.0f64), lit(0.1f64), lit(-f64::NAN)];
1101        in_list!(
1102            batch,
1103            list,
1104            &false,
1105            vec![Some(true), Some(false), None, Some(false), Some(true)],
1106            Arc::clone(&col_a),
1107            &schema
1108        );
1109
1110        // expression: "a not in (0.0, 0.1, -NaN)"
1111        let list = vec![lit(0.0f64), lit(0.1f64), lit(-f64::NAN)];
1112        in_list!(
1113            batch,
1114            list,
1115            &true,
1116            vec![Some(false), Some(true), None, Some(true), Some(false)],
1117            Arc::clone(&col_a),
1118            &schema
1119        );
1120
1121        Ok(())
1122    }
1123
1124    #[test]
1125    fn in_list_bool() -> Result<()> {
1126        let schema = Schema::new(vec![Field::new("a", DataType::Boolean, true)]);
1127        let a = BooleanArray::from(vec![Some(true), None]);
1128        let col_a = col("a", &schema)?;
1129        let batch = RecordBatch::try_new(Arc::new(schema.clone()), vec![Arc::new(a)])?;
1130
1131        // expression: "a in (true)"
1132        let list = vec![lit(true)];
1133        in_list!(
1134            batch,
1135            list,
1136            &false,
1137            vec![Some(true), None],
1138            Arc::clone(&col_a),
1139            &schema
1140        );
1141
1142        // expression: "a not in (true)"
1143        let list = vec![lit(true)];
1144        in_list!(
1145            batch,
1146            list,
1147            &true,
1148            vec![Some(false), None],
1149            Arc::clone(&col_a),
1150            &schema
1151        );
1152
1153        // expression: "a in (true, NULL)"
1154        let list = vec![lit(true), lit(ScalarValue::Null)];
1155        in_list!(
1156            batch,
1157            list,
1158            &false,
1159            vec![Some(true), None],
1160            Arc::clone(&col_a),
1161            &schema
1162        );
1163
1164        // expression: "a not in (true, NULL)"
1165        let list = vec![lit(true), lit(ScalarValue::Null)];
1166        in_list!(
1167            batch,
1168            list,
1169            &true,
1170            vec![Some(false), None],
1171            Arc::clone(&col_a),
1172            &schema
1173        );
1174
1175        Ok(())
1176    }
1177
1178    macro_rules! test_nullable {
1179        ($COL:expr, $LIST:expr, $SCHEMA:expr, $EXPECTED:expr) => {{
1180            let (cast_expr, cast_list_exprs) = in_list_cast($COL, $LIST, $SCHEMA)?;
1181            let expr = in_list(cast_expr, cast_list_exprs, &false, $SCHEMA).unwrap();
1182            let result = expr.nullable($SCHEMA)?;
1183            assert_eq!($EXPECTED, result);
1184        }};
1185    }
1186
1187    #[test]
1188    fn in_list_nullable() -> Result<()> {
1189        let schema = Schema::new(vec![
1190            Field::new("c1_nullable", DataType::Int64, true),
1191            Field::new("c2_non_nullable", DataType::Int64, false),
1192        ]);
1193
1194        let c1_nullable = col("c1_nullable", &schema)?;
1195        let c2_non_nullable = col("c2_non_nullable", &schema)?;
1196
1197        // static_filter has no nulls
1198        let list = vec![lit(1_i64), lit(2_i64)];
1199        test_nullable!(Arc::clone(&c1_nullable), list.clone(), &schema, true);
1200        test_nullable!(Arc::clone(&c2_non_nullable), list.clone(), &schema, false);
1201
1202        // static_filter has nulls
1203        let list = vec![lit(1_i64), lit(2_i64), lit(ScalarValue::Null)];
1204        test_nullable!(Arc::clone(&c1_nullable), list.clone(), &schema, true);
1205        test_nullable!(Arc::clone(&c2_non_nullable), list.clone(), &schema, true);
1206
1207        let list = vec![Arc::clone(&c1_nullable)];
1208        test_nullable!(Arc::clone(&c2_non_nullable), list.clone(), &schema, true);
1209
1210        let list = vec![Arc::clone(&c2_non_nullable)];
1211        test_nullable!(Arc::clone(&c1_nullable), list.clone(), &schema, true);
1212
1213        let list = vec![Arc::clone(&c2_non_nullable), Arc::clone(&c2_non_nullable)];
1214        test_nullable!(Arc::clone(&c2_non_nullable), list.clone(), &schema, false);
1215
1216        Ok(())
1217    }
1218
1219    #[test]
1220    fn in_list_no_cols() -> Result<()> {
1221        // test logic when the in_list expression doesn't have any columns
1222        let schema = Schema::new(vec![Field::new("a", DataType::Int32, true)]);
1223        let a = Int32Array::from(vec![Some(1), Some(2), None]);
1224        let batch = RecordBatch::try_new(Arc::new(schema.clone()), vec![Arc::new(a)])?;
1225
1226        let list = vec![lit(ScalarValue::from(1i32)), lit(ScalarValue::from(6i32))];
1227
1228        // 1 IN (1, 6)
1229        let expr = lit(ScalarValue::Int32(Some(1)));
1230        in_list!(
1231            batch,
1232            list.clone(),
1233            &false,
1234            // should have three outputs, as the input batch has three rows
1235            vec![Some(true), Some(true), Some(true)],
1236            expr,
1237            &schema
1238        );
1239
1240        // 2 IN (1, 6)
1241        let expr = lit(ScalarValue::Int32(Some(2)));
1242        in_list!(
1243            batch,
1244            list.clone(),
1245            &false,
1246            // should have three outputs, as the input batch has three rows
1247            vec![Some(false), Some(false), Some(false)],
1248            expr,
1249            &schema
1250        );
1251
1252        // NULL IN (1, 6)
1253        let expr = lit(ScalarValue::Int32(None));
1254        in_list!(
1255            batch,
1256            list.clone(),
1257            &false,
1258            // should have three outputs, as the input batch has three rows
1259            vec![None, None, None],
1260            expr,
1261            &schema
1262        );
1263
1264        Ok(())
1265    }
1266
1267    #[test]
1268    fn in_list_utf8_with_dict_types() -> Result<()> {
1269        fn dict_lit(key_type: DataType, value: &str) -> Arc<dyn PhysicalExpr> {
1270            lit(ScalarValue::Dictionary(
1271                Box::new(key_type),
1272                Box::new(ScalarValue::new_utf8(value.to_string())),
1273            ))
1274        }
1275
1276        fn null_dict_lit(key_type: DataType) -> Arc<dyn PhysicalExpr> {
1277            lit(ScalarValue::Dictionary(
1278                Box::new(key_type),
1279                Box::new(ScalarValue::Utf8(None)),
1280            ))
1281        }
1282
1283        let schema = Schema::new(vec![Field::new(
1284            "a",
1285            DataType::Dictionary(Box::new(DataType::UInt16), Box::new(DataType::Utf8)),
1286            true,
1287        )]);
1288        let a: UInt16DictionaryArray =
1289            vec![Some("a"), Some("d"), None].into_iter().collect();
1290        let col_a = col("a", &schema)?;
1291        let batch = RecordBatch::try_new(Arc::new(schema.clone()), vec![Arc::new(a)])?;
1292
1293        // expression: "a in ("a", "b")"
1294        let lists = [
1295            vec![lit("a"), lit("b")],
1296            vec![
1297                dict_lit(DataType::Int8, "a"),
1298                dict_lit(DataType::UInt16, "b"),
1299            ],
1300        ];
1301        for list in lists.iter() {
1302            in_list_raw!(
1303                batch,
1304                list.clone(),
1305                &false,
1306                vec![Some(true), Some(false), None],
1307                Arc::clone(&col_a),
1308                &schema
1309            );
1310        }
1311
1312        // expression: "a not in ("a", "b")"
1313        for list in lists.iter() {
1314            in_list_raw!(
1315                batch,
1316                list.clone(),
1317                &true,
1318                vec![Some(false), Some(true), None],
1319                Arc::clone(&col_a),
1320                &schema
1321            );
1322        }
1323
1324        // expression: "a in ("a", "b", null)"
1325        let lists = [
1326            vec![lit("a"), lit("b"), lit(ScalarValue::Utf8(None))],
1327            vec![
1328                dict_lit(DataType::Int8, "a"),
1329                dict_lit(DataType::UInt16, "b"),
1330                null_dict_lit(DataType::UInt16),
1331            ],
1332        ];
1333        for list in lists.iter() {
1334            in_list_raw!(
1335                batch,
1336                list.clone(),
1337                &false,
1338                vec![Some(true), None, None],
1339                Arc::clone(&col_a),
1340                &schema
1341            );
1342        }
1343
1344        // expression: "a not in ("a", "b", null)"
1345        for list in lists.iter() {
1346            in_list_raw!(
1347                batch,
1348                list.clone(),
1349                &true,
1350                vec![Some(false), None, None],
1351                Arc::clone(&col_a),
1352                &schema
1353            );
1354        }
1355
1356        Ok(())
1357    }
1358
1359    #[test]
1360    fn test_fmt_sql_1() -> Result<()> {
1361        let schema = Schema::new(vec![Field::new("a", DataType::Utf8, true)]);
1362        let col_a = col("a", &schema)?;
1363
1364        // Test: a IN ('a', 'b')
1365        let list = vec![lit("a"), lit("b")];
1366        let expr = in_list(Arc::clone(&col_a), list, &false, &schema)?;
1367        let sql_string = fmt_sql(expr.as_ref()).to_string();
1368        let display_string = expr.to_string();
1369        assert_snapshot!(sql_string, @"a IN (a, b)");
1370        assert_snapshot!(display_string, @"a@0 IN (SET) ([a, b])");
1371        Ok(())
1372    }
1373
1374    #[test]
1375    fn test_fmt_sql_2() -> Result<()> {
1376        let schema = Schema::new(vec![Field::new("a", DataType::Utf8, true)]);
1377        let col_a = col("a", &schema)?;
1378
1379        // Test: a NOT IN ('a', 'b')
1380        let list = vec![lit("a"), lit("b")];
1381        let expr = in_list(Arc::clone(&col_a), list, &true, &schema)?;
1382        let sql_string = fmt_sql(expr.as_ref()).to_string();
1383        let display_string = expr.to_string();
1384
1385        assert_snapshot!(sql_string, @"a NOT IN (a, b)");
1386        assert_snapshot!(display_string, @"a@0 NOT IN (SET) ([a, b])");
1387        Ok(())
1388    }
1389
1390    #[test]
1391    fn test_fmt_sql_3() -> Result<()> {
1392        let schema = Schema::new(vec![Field::new("a", DataType::Utf8, true)]);
1393        let col_a = col("a", &schema)?;
1394        // Test: a IN ('a', 'b', NULL)
1395        let list = vec![lit("a"), lit("b"), lit(ScalarValue::Utf8(None))];
1396        let expr = in_list(Arc::clone(&col_a), list, &false, &schema)?;
1397        let sql_string = fmt_sql(expr.as_ref()).to_string();
1398        let display_string = expr.to_string();
1399
1400        assert_snapshot!(sql_string, @"a IN (a, b, NULL)");
1401        assert_snapshot!(display_string, @"a@0 IN (SET) ([a, b, NULL])");
1402        Ok(())
1403    }
1404
1405    #[test]
1406    fn test_fmt_sql_4() -> Result<()> {
1407        let schema = Schema::new(vec![Field::new("a", DataType::Utf8, true)]);
1408        let col_a = col("a", &schema)?;
1409        // Test: a NOT IN ('a', 'b', NULL)
1410        let list = vec![lit("a"), lit("b"), lit(ScalarValue::Utf8(None))];
1411        let expr = in_list(Arc::clone(&col_a), list, &true, &schema)?;
1412        let sql_string = fmt_sql(expr.as_ref()).to_string();
1413        let display_string = expr.to_string();
1414        assert_snapshot!(sql_string, @"a NOT IN (a, b, NULL)");
1415        assert_snapshot!(display_string, @"a@0 NOT IN (SET) ([a, b, NULL])");
1416        Ok(())
1417    }
1418
1419    #[test]
1420    fn in_list_struct() -> Result<()> {
1421        // Create schema with a struct column
1422        let struct_fields = Fields::from(vec![
1423            Field::new("x", DataType::Int32, false),
1424            Field::new("y", DataType::Utf8, false),
1425        ]);
1426        let schema = Schema::new(vec![Field::new(
1427            "a",
1428            DataType::Struct(struct_fields.clone()),
1429            true,
1430        )]);
1431
1432        // Create test data: array of structs
1433        let x_array = Arc::new(Int32Array::from(vec![1, 2, 3]));
1434        let y_array = Arc::new(StringArray::from(vec!["a", "b", "c"]));
1435        let struct_array =
1436            StructArray::new(struct_fields.clone(), vec![x_array, y_array], None);
1437
1438        let col_a = col("a", &schema)?;
1439        let batch =
1440            RecordBatch::try_new(Arc::new(schema.clone()), vec![Arc::new(struct_array)])?;
1441
1442        // Create literal structs for the IN list
1443        // Struct {x: 1, y: "a"}
1444        let struct1 = ScalarValue::Struct(Arc::new(StructArray::new(
1445            struct_fields.clone(),
1446            vec![
1447                Arc::new(Int32Array::from(vec![1])),
1448                Arc::new(StringArray::from(vec!["a"])),
1449            ],
1450            None,
1451        )));
1452
1453        // Struct {x: 3, y: "c"}
1454        let struct3 = ScalarValue::Struct(Arc::new(StructArray::new(
1455            struct_fields.clone(),
1456            vec![
1457                Arc::new(Int32Array::from(vec![3])),
1458                Arc::new(StringArray::from(vec!["c"])),
1459            ],
1460            None,
1461        )));
1462
1463        // Test: a IN ({1, "a"}, {3, "c"})
1464        let list = vec![lit(struct1.clone()), lit(struct3.clone())];
1465        in_list_raw!(
1466            batch,
1467            list.clone(),
1468            &false,
1469            vec![Some(true), Some(false), Some(true)],
1470            Arc::clone(&col_a),
1471            &schema
1472        );
1473
1474        // Test: a NOT IN ({1, "a"}, {3, "c"})
1475        in_list_raw!(
1476            batch,
1477            list,
1478            &true,
1479            vec![Some(false), Some(true), Some(false)],
1480            Arc::clone(&col_a),
1481            &schema
1482        );
1483
1484        Ok(())
1485    }
1486
1487    #[test]
1488    fn in_list_struct_with_nulls() -> Result<()> {
1489        // Create schema with a struct column
1490        let struct_fields = Fields::from(vec![
1491            Field::new("x", DataType::Int32, false),
1492            Field::new("y", DataType::Utf8, false),
1493        ]);
1494        let schema = Schema::new(vec![Field::new(
1495            "a",
1496            DataType::Struct(struct_fields.clone()),
1497            true,
1498        )]);
1499
1500        // Create test data with a null struct
1501        let x_array = Arc::new(Int32Array::from(vec![1, 2]));
1502        let y_array = Arc::new(StringArray::from(vec!["a", "b"]));
1503        let struct_array = StructArray::new(
1504            struct_fields.clone(),
1505            vec![x_array, y_array],
1506            Some(NullBuffer::from(vec![true, false])),
1507        );
1508
1509        let col_a = col("a", &schema)?;
1510        let batch =
1511            RecordBatch::try_new(Arc::new(schema.clone()), vec![Arc::new(struct_array)])?;
1512
1513        // Create literal struct for the IN list
1514        let struct1 = ScalarValue::Struct(Arc::new(StructArray::new(
1515            struct_fields.clone(),
1516            vec![
1517                Arc::new(Int32Array::from(vec![1])),
1518                Arc::new(StringArray::from(vec!["a"])),
1519            ],
1520            None,
1521        )));
1522
1523        // Test: a IN ({1, "a"})
1524        let list = vec![lit(struct1.clone())];
1525        in_list_raw!(
1526            batch,
1527            list.clone(),
1528            &false,
1529            vec![Some(true), None],
1530            Arc::clone(&col_a),
1531            &schema
1532        );
1533
1534        // Test: a NOT IN ({1, "a"})
1535        in_list_raw!(
1536            batch,
1537            list,
1538            &true,
1539            vec![Some(false), None],
1540            Arc::clone(&col_a),
1541            &schema
1542        );
1543
1544        Ok(())
1545    }
1546
1547    #[test]
1548    fn in_list_struct_with_null_in_list() -> Result<()> {
1549        // Create schema with a struct column
1550        let struct_fields = Fields::from(vec![
1551            Field::new("x", DataType::Int32, false),
1552            Field::new("y", DataType::Utf8, false),
1553        ]);
1554        let schema = Schema::new(vec![Field::new(
1555            "a",
1556            DataType::Struct(struct_fields.clone()),
1557            true,
1558        )]);
1559
1560        // Create test data
1561        let x_array = Arc::new(Int32Array::from(vec![1, 2, 3]));
1562        let y_array = Arc::new(StringArray::from(vec!["a", "b", "c"]));
1563        let struct_array =
1564            StructArray::new(struct_fields.clone(), vec![x_array, y_array], None);
1565
1566        let col_a = col("a", &schema)?;
1567        let batch =
1568            RecordBatch::try_new(Arc::new(schema.clone()), vec![Arc::new(struct_array)])?;
1569
1570        // Create literal structs including a NULL
1571        let struct1 = ScalarValue::Struct(Arc::new(StructArray::new(
1572            struct_fields.clone(),
1573            vec![
1574                Arc::new(Int32Array::from(vec![1])),
1575                Arc::new(StringArray::from(vec!["a"])),
1576            ],
1577            None,
1578        )));
1579
1580        let null_struct = ScalarValue::Struct(Arc::new(StructArray::new_null(
1581            struct_fields.clone(),
1582            1,
1583        )));
1584
1585        // Test: a IN ({1, "a"}, NULL)
1586        let list = vec![lit(struct1), lit(null_struct.clone())];
1587        in_list_raw!(
1588            batch,
1589            list.clone(),
1590            &false,
1591            vec![Some(true), None, None],
1592            Arc::clone(&col_a),
1593            &schema
1594        );
1595
1596        // Test: a NOT IN ({1, "a"}, NULL)
1597        in_list_raw!(
1598            batch,
1599            list,
1600            &true,
1601            vec![Some(false), None, None],
1602            Arc::clone(&col_a),
1603            &schema
1604        );
1605
1606        Ok(())
1607    }
1608
1609    #[test]
1610    fn in_list_nested_struct() -> Result<()> {
1611        // Create nested struct schema
1612        let inner_struct_fields = Fields::from(vec![
1613            Field::new("a", DataType::Int32, false),
1614            Field::new("b", DataType::Utf8, false),
1615        ]);
1616        let outer_struct_fields = Fields::from(vec![
1617            Field::new(
1618                "inner",
1619                DataType::Struct(inner_struct_fields.clone()),
1620                false,
1621            ),
1622            Field::new("c", DataType::Int32, false),
1623        ]);
1624        let schema = Schema::new(vec![Field::new(
1625            "x",
1626            DataType::Struct(outer_struct_fields.clone()),
1627            true,
1628        )]);
1629
1630        // Create test data with nested structs
1631        let inner1 = Arc::new(StructArray::new(
1632            inner_struct_fields.clone(),
1633            vec![
1634                Arc::new(Int32Array::from(vec![1, 2])),
1635                Arc::new(StringArray::from(vec!["x", "y"])),
1636            ],
1637            None,
1638        ));
1639        let c_array = Arc::new(Int32Array::from(vec![10, 20]));
1640        let outer_array =
1641            StructArray::new(outer_struct_fields.clone(), vec![inner1, c_array], None);
1642
1643        let col_x = col("x", &schema)?;
1644        let batch =
1645            RecordBatch::try_new(Arc::new(schema.clone()), vec![Arc::new(outer_array)])?;
1646
1647        // Create a nested struct literal matching the first row
1648        let inner_match = Arc::new(StructArray::new(
1649            inner_struct_fields.clone(),
1650            vec![
1651                Arc::new(Int32Array::from(vec![1])),
1652                Arc::new(StringArray::from(vec!["x"])),
1653            ],
1654            None,
1655        ));
1656        let outer_match = ScalarValue::Struct(Arc::new(StructArray::new(
1657            outer_struct_fields.clone(),
1658            vec![inner_match, Arc::new(Int32Array::from(vec![10]))],
1659            None,
1660        )));
1661
1662        // Test: x IN ({{1, "x"}, 10})
1663        let list = vec![lit(outer_match)];
1664        in_list_raw!(
1665            batch,
1666            list.clone(),
1667            &false,
1668            vec![Some(true), Some(false)],
1669            Arc::clone(&col_x),
1670            &schema
1671        );
1672
1673        // Test: x NOT IN ({{1, "x"}, 10})
1674        in_list_raw!(
1675            batch,
1676            list,
1677            &true,
1678            vec![Some(false), Some(true)],
1679            Arc::clone(&col_x),
1680            &schema
1681        );
1682
1683        Ok(())
1684    }
1685
1686    #[test]
1687    fn in_list_struct_with_exprs_not_array() -> Result<()> {
1688        // Test InList using expressions (not the array constructor) with structs
1689        // By using InListExpr::new directly, we bypass the array optimization
1690        // and use the Exprs variant, testing the expression evaluation path
1691
1692        // Create schema with a struct column {x: Int32, y: Utf8}
1693        let struct_fields = Fields::from(vec![
1694            Field::new("x", DataType::Int32, false),
1695            Field::new("y", DataType::Utf8, false),
1696        ]);
1697        let schema = Schema::new(vec![Field::new(
1698            "a",
1699            DataType::Struct(struct_fields.clone()),
1700            true,
1701        )]);
1702
1703        // Create test data: array of structs [{1, "a"}, {2, "b"}, {3, "c"}]
1704        let x_array = Arc::new(Int32Array::from(vec![1, 2, 3]));
1705        let y_array = Arc::new(StringArray::from(vec!["a", "b", "c"]));
1706        let struct_array =
1707            StructArray::new(struct_fields.clone(), vec![x_array, y_array], None);
1708
1709        let col_a = col("a", &schema)?;
1710        let batch =
1711            RecordBatch::try_new(Arc::new(schema.clone()), vec![Arc::new(struct_array)])?;
1712
1713        // Create struct literals with the SAME shape (so types are compatible)
1714        // Struct {x: 1, y: "a"}
1715        let struct1 = ScalarValue::Struct(Arc::new(StructArray::new(
1716            struct_fields.clone(),
1717            vec![
1718                Arc::new(Int32Array::from(vec![1])),
1719                Arc::new(StringArray::from(vec!["a"])),
1720            ],
1721            None,
1722        )));
1723
1724        // Struct {x: 3, y: "c"}
1725        let struct3 = ScalarValue::Struct(Arc::new(StructArray::new(
1726            struct_fields.clone(),
1727            vec![
1728                Arc::new(Int32Array::from(vec![3])),
1729                Arc::new(StringArray::from(vec!["c"])),
1730            ],
1731            None,
1732        )));
1733
1734        // Create list of struct expressions
1735        let list = vec![lit(struct1), lit(struct3)];
1736
1737        // Use InListExpr::new directly (not in_list()) to bypass array optimization
1738        // This creates an InList without a static filter
1739        let expr = Arc::new(InListExpr::new(Arc::clone(&col_a), list, false, None));
1740
1741        // Verify that the expression doesn't have a static filter
1742        // by checking the display string does NOT contain "(SET)"
1743        let display_string = expr.to_string();
1744        assert!(
1745            !display_string.contains("(SET)"),
1746            "Expected display string to NOT contain '(SET)' (should use Exprs variant), but got: {display_string}",
1747        );
1748
1749        // Evaluate the expression
1750        let result = expr.evaluate(&batch)?.into_array(batch.num_rows())?;
1751        let result = as_boolean_array(&result);
1752
1753        // Expected: first row {1, "a"} matches struct1,
1754        //           second row {2, "b"} doesn't match,
1755        //           third row {3, "c"} matches struct3
1756        let expected = BooleanArray::from(vec![Some(true), Some(false), Some(true)]);
1757        assert_eq!(result, &expected);
1758
1759        // Test NOT IN as well
1760        let expr_not = Arc::new(InListExpr::new(
1761            Arc::clone(&col_a),
1762            vec![
1763                lit(ScalarValue::Struct(Arc::new(StructArray::new(
1764                    struct_fields.clone(),
1765                    vec![
1766                        Arc::new(Int32Array::from(vec![1])),
1767                        Arc::new(StringArray::from(vec!["a"])),
1768                    ],
1769                    None,
1770                )))),
1771                lit(ScalarValue::Struct(Arc::new(StructArray::new(
1772                    struct_fields.clone(),
1773                    vec![
1774                        Arc::new(Int32Array::from(vec![3])),
1775                        Arc::new(StringArray::from(vec!["c"])),
1776                    ],
1777                    None,
1778                )))),
1779            ],
1780            true,
1781            None,
1782        ));
1783
1784        let result_not = expr_not.evaluate(&batch)?.into_array(batch.num_rows())?;
1785        let result_not = as_boolean_array(&result_not);
1786
1787        let expected_not = BooleanArray::from(vec![Some(false), Some(true), Some(false)]);
1788        assert_eq!(result_not, &expected_not);
1789
1790        Ok(())
1791    }
1792
1793    #[test]
1794    fn test_in_list_null_handling_comprehensive() -> Result<()> {
1795        // Comprehensive test demonstrating SQL three-valued logic for IN expressions
1796        // This test explicitly shows all possible outcomes: true, false, and null
1797        let schema = Schema::new(vec![Field::new("a", DataType::Int64, true)]);
1798
1799        // Test data: [1, 2, 3, null]
1800        // - 1 will match in both lists
1801        // - 2 will not match in either list
1802        // - 3 will not match in either list
1803        // - null is always null
1804        let a = Int64Array::from(vec![Some(1), Some(2), Some(3), None]);
1805        let col_a = col("a", &schema)?;
1806        let batch = RecordBatch::try_new(Arc::new(schema.clone()), vec![Arc::new(a)])?;
1807
1808        // Case 1: List WITHOUT null - demonstrates true/false/null outcomes
1809        // "a IN (1, 4)" - 1 matches, 2 and 3 don't match, null is null
1810        let list = vec![lit(1i64), lit(4i64)];
1811        in_list!(
1812            batch,
1813            list,
1814            &false,
1815            vec![
1816                Some(true),  // 1 is in the list → true
1817                Some(false), // 2 is not in the list → false
1818                Some(false), // 3 is not in the list → false
1819                None,        // null IN (...) → null (SQL three-valued logic)
1820            ],
1821            Arc::clone(&col_a),
1822            &schema
1823        );
1824
1825        // Case 2: List WITH null - demonstrates null propagation for non-matches
1826        // "a IN (1, NULL)" - 1 matches (true), 2/3 don't match but list has null (null), null is null
1827        let list = vec![lit(1i64), lit(ScalarValue::Int64(None))];
1828        in_list!(
1829            batch,
1830            list,
1831            &false,
1832            vec![
1833                Some(true), // 1 is in the list → true (found match)
1834                None, // 2 is not in list, but list has NULL → null (might match NULL)
1835                None, // 3 is not in list, but list has NULL → null (might match NULL)
1836                None, // null IN (...) → null (SQL three-valued logic)
1837            ],
1838            Arc::clone(&col_a),
1839            &schema
1840        );
1841
1842        Ok(())
1843    }
1844
1845    #[test]
1846    fn test_in_list_with_only_nulls() -> Result<()> {
1847        // Edge case: IN list contains ONLY null values
1848        let schema = Schema::new(vec![Field::new("a", DataType::Int64, true)]);
1849        let a = Int64Array::from(vec![Some(1), Some(2), None]);
1850        let col_a = col("a", &schema)?;
1851        let batch = RecordBatch::try_new(Arc::new(schema.clone()), vec![Arc::new(a)])?;
1852
1853        // "a IN (NULL, NULL)" - list has only nulls
1854        let list = vec![lit(ScalarValue::Int64(None)), lit(ScalarValue::Int64(None))];
1855
1856        // All results should be NULL because:
1857        // - Non-null values (1, 2) can't match anything concrete, but list might contain matching value
1858        // - NULL value is always NULL in IN expressions
1859        in_list!(
1860            batch,
1861            list.clone(),
1862            &false,
1863            vec![None, None, None],
1864            Arc::clone(&col_a),
1865            &schema
1866        );
1867
1868        // "a NOT IN (NULL, NULL)" - list has only nulls
1869        // All results should still be NULL due to three-valued logic
1870        in_list!(
1871            batch,
1872            list,
1873            &true,
1874            vec![None, None, None],
1875            Arc::clone(&col_a),
1876            &schema
1877        );
1878
1879        Ok(())
1880    }
1881
1882    #[test]
1883    fn test_in_list_multiple_nulls_deduplication() -> Result<()> {
1884        // Test that multiple NULLs in the list are handled correctly
1885        // This verifies deduplication doesn't break null handling
1886        let schema = Schema::new(vec![Field::new("a", DataType::Int64, true)]);
1887        let col_a = col("a", &schema)?;
1888
1889        // Create array with multiple nulls: [1, 2, NULL, NULL, 3, NULL]
1890        let array = Arc::new(Int64Array::from(vec![
1891            Some(1),
1892            Some(2),
1893            None,
1894            None,
1895            Some(3),
1896            None,
1897        ])) as ArrayRef;
1898
1899        // Create InListExpr from array
1900        let expr = Arc::new(InListExpr::try_new_from_array(
1901            Arc::clone(&col_a),
1902            array,
1903            false,
1904            &schema,
1905        )?) as Arc<dyn PhysicalExpr>;
1906
1907        // Create test data: [1, 2, 3, 4, null]
1908        let a = Int64Array::from(vec![Some(1), Some(2), Some(3), Some(4), None]);
1909        let batch = RecordBatch::try_new(Arc::new(schema.clone()), vec![Arc::new(a)])?;
1910
1911        // Evaluate the expression
1912        let result = expr.evaluate(&batch)?.into_array(batch.num_rows())?;
1913        let result = as_boolean_array(&result);
1914
1915        // Expected behavior with multiple NULLs in list:
1916        // - Values in the list (1,2,3) → true
1917        // - Values not in the list (4) → NULL (because list contains NULL)
1918        // - NULL input → NULL
1919        let expected = BooleanArray::from(vec![
1920            Some(true), // 1 is in list
1921            Some(true), // 2 is in list
1922            Some(true), // 3 is in list
1923            None,       // 4 not in list, but list has NULLs
1924            None,       // NULL input
1925        ]);
1926        assert_eq!(result, &expected);
1927
1928        Ok(())
1929    }
1930
1931    #[test]
1932    fn test_not_in_null_handling_comprehensive() -> Result<()> {
1933        // Comprehensive test demonstrating SQL three-valued logic for NOT IN expressions
1934        // This test explicitly shows all possible outcomes for NOT IN: true, false, and null
1935        let schema = Schema::new(vec![Field::new("a", DataType::Int64, true)]);
1936
1937        // Test data: [1, 2, 3, null]
1938        let a = Int64Array::from(vec![Some(1), Some(2), Some(3), None]);
1939        let col_a = col("a", &schema)?;
1940        let batch = RecordBatch::try_new(Arc::new(schema.clone()), vec![Arc::new(a)])?;
1941
1942        // Case 1: List WITHOUT null - demonstrates true/false/null outcomes for NOT IN
1943        // "a NOT IN (1, 4)" - 1 matches (false), 2 and 3 don't match (true), null is null
1944        let list = vec![lit(1i64), lit(4i64)];
1945        in_list!(
1946            batch,
1947            list,
1948            &true,
1949            vec![
1950                Some(false), // 1 is in the list → NOT IN returns false
1951                Some(true),  // 2 is not in the list → NOT IN returns true
1952                Some(true),  // 3 is not in the list → NOT IN returns true
1953                None,        // null NOT IN (...) → null (SQL three-valued logic)
1954            ],
1955            Arc::clone(&col_a),
1956            &schema
1957        );
1958
1959        // Case 2: List WITH null - demonstrates null propagation for NOT IN
1960        // "a NOT IN (1, NULL)" - 1 matches (false), 2/3 don't match but list has null (null), null is null
1961        let list = vec![lit(1i64), lit(ScalarValue::Int64(None))];
1962        in_list!(
1963            batch,
1964            list,
1965            &true,
1966            vec![
1967                Some(false), // 1 is in the list → NOT IN returns false
1968                None, // 2 is not in known values, but list has NULL → null (can't prove it's not in list)
1969                None, // 3 is not in known values, but list has NULL → null (can't prove it's not in list)
1970                None, // null NOT IN (...) → null (SQL three-valued logic)
1971            ],
1972            Arc::clone(&col_a),
1973            &schema
1974        );
1975
1976        Ok(())
1977    }
1978
1979    #[test]
1980    fn test_in_list_null_type_column() -> Result<()> {
1981        // Test with a column that has DataType::Null (not just nullable values)
1982        // All values in a NullArray are null by definition
1983        let schema = Schema::new(vec![Field::new("a", DataType::Null, true)]);
1984        let a = NullArray::new(3);
1985        let col_a = col("a", &schema)?;
1986        let batch = RecordBatch::try_new(Arc::new(schema.clone()), vec![Arc::new(a)])?;
1987
1988        // "null_column IN (1, 2)" - comparing Null type against Int64 list
1989        // Note: This tests type coercion behavior between Null and Int64
1990        let list = vec![lit(1i64), lit(2i64)];
1991
1992        // All results should be NULL because:
1993        // - Every value in the column is null (DataType::Null)
1994        // - null IN (anything) always returns null per SQL three-valued logic
1995        in_list!(
1996            batch,
1997            list.clone(),
1998            &false,
1999            vec![None, None, None],
2000            Arc::clone(&col_a),
2001            &schema
2002        );
2003
2004        // "null_column NOT IN (1, 2)"
2005        // Same behavior for NOT IN - null NOT IN (anything) is still null
2006        in_list!(
2007            batch,
2008            list,
2009            &true,
2010            vec![None, None, None],
2011            Arc::clone(&col_a),
2012            &schema
2013        );
2014
2015        Ok(())
2016    }
2017
2018    #[test]
2019    fn test_in_list_null_type_list() -> Result<()> {
2020        // Test with a list that has DataType::Null
2021        let schema = Schema::new(vec![Field::new("a", DataType::Int64, true)]);
2022        let a = Int64Array::from(vec![Some(1), Some(2), None]);
2023        let col_a = col("a", &schema)?;
2024
2025        // Create a NullArray as the list
2026        let null_array = Arc::new(NullArray::new(2)) as ArrayRef;
2027
2028        // Try to create InListExpr with a NullArray list
2029        // This tests whether try_new_from_array can handle Null type arrays
2030        let expr = Arc::new(InListExpr::try_new_from_array(
2031            Arc::clone(&col_a),
2032            null_array,
2033            false,
2034            &schema,
2035        )?) as Arc<dyn PhysicalExpr>;
2036        let batch = RecordBatch::try_new(Arc::new(schema.clone()), vec![Arc::new(a)])?;
2037        let result = expr.evaluate(&batch)?.into_array(batch.num_rows())?;
2038        let result = as_boolean_array(&result);
2039
2040        // If it succeeds, all results should be NULL
2041        // because the list contains only null type values
2042        let expected = BooleanArray::from(vec![None, None, None]);
2043        assert_eq!(result, &expected);
2044
2045        Ok(())
2046    }
2047
2048    #[test]
2049    fn test_in_list_null_type_both() -> Result<()> {
2050        // Test when both column and list are DataType::Null
2051        let schema = Schema::new(vec![Field::new("a", DataType::Null, true)]);
2052        let a = NullArray::new(3);
2053        let col_a = col("a", &schema)?;
2054
2055        // Create a NullArray as the list
2056        let null_array = Arc::new(NullArray::new(2)) as ArrayRef;
2057
2058        // Try to create InListExpr with both Null types
2059        let expr = Arc::new(InListExpr::try_new_from_array(
2060            Arc::clone(&col_a),
2061            null_array,
2062            false,
2063            &schema,
2064        )?) as Arc<dyn PhysicalExpr>;
2065
2066        let batch = RecordBatch::try_new(Arc::new(schema.clone()), vec![Arc::new(a)])?;
2067        let result = expr.evaluate(&batch)?.into_array(batch.num_rows())?;
2068        let result = as_boolean_array(&result);
2069
2070        // If successful, all results should be NULL
2071        // null IN [null, null] -> null
2072        let expected = BooleanArray::from(vec![None, None, None]);
2073        assert_eq!(result, &expected);
2074
2075        Ok(())
2076    }
2077
2078    #[test]
2079    fn test_in_list_comprehensive_null_handling() -> Result<()> {
2080        // Comprehensive test for IN LIST operations with various NULL handling scenarios.
2081        // This test covers the key cases validated against DuckDB as the source of truth.
2082        //
2083        // Note: Some scalar literal tests (like NULL IN (1, 2)) are omitted as they
2084        // appear to expose an issue with static filter optimization. These are covered
2085        // by existing tests like in_list_no_cols().
2086
2087        let schema = Arc::new(Schema::new(vec![Field::new("b", DataType::Int32, true)]));
2088        let col_b = col("b", &schema)?;
2089        let null_i32 = ScalarValue::Int32(None);
2090
2091        // Helper to create a batch
2092        let make_batch = |values: Vec<Option<i32>>| -> Result<RecordBatch> {
2093            let array = Arc::new(Int32Array::from(values));
2094            Ok(RecordBatch::try_new(Arc::clone(&schema), vec![array])?)
2095        };
2096
2097        // Helper to run a test
2098        let run_test = |batch: &RecordBatch,
2099                        expr: Arc<dyn PhysicalExpr>,
2100                        list: Vec<Arc<dyn PhysicalExpr>>,
2101                        expected: Vec<Option<bool>>|
2102         -> Result<()> {
2103            let in_expr = in_list(expr, list, &false, schema.as_ref())?;
2104            let result = in_expr.evaluate(batch)?.into_array(batch.num_rows())?;
2105            let result = as_boolean_array(&result);
2106            assert_eq!(result, &BooleanArray::from(expected));
2107            Ok(())
2108        };
2109
2110        // ========================================================================
2111        // COLUMN TESTS - col(b) IN [1, 2]
2112        // ========================================================================
2113
2114        // [1] IN (1, 2) => [TRUE]
2115        let batch = make_batch(vec![Some(1)])?;
2116        run_test(
2117            &batch,
2118            Arc::clone(&col_b),
2119            vec![lit(1i32), lit(2i32)],
2120            vec![Some(true)],
2121        )?;
2122
2123        // [1, 2] IN (1, 2) => [TRUE, TRUE]
2124        let batch = make_batch(vec![Some(1), Some(2)])?;
2125        run_test(
2126            &batch,
2127            Arc::clone(&col_b),
2128            vec![lit(1i32), lit(2i32)],
2129            vec![Some(true), Some(true)],
2130        )?;
2131
2132        // [3, 4] IN (1, 2) => [FALSE, FALSE]
2133        let batch = make_batch(vec![Some(3), Some(4)])?;
2134        run_test(
2135            &batch,
2136            Arc::clone(&col_b),
2137            vec![lit(1i32), lit(2i32)],
2138            vec![Some(false), Some(false)],
2139        )?;
2140
2141        // [1, NULL] IN (1, 2) => [TRUE, NULL]
2142        let batch = make_batch(vec![Some(1), None])?;
2143        run_test(
2144            &batch,
2145            Arc::clone(&col_b),
2146            vec![lit(1i32), lit(2i32)],
2147            vec![Some(true), None],
2148        )?;
2149
2150        // [3, NULL] IN (1, 2) => [FALSE, NULL] (no match, NULL is NULL)
2151        let batch = make_batch(vec![Some(3), None])?;
2152        run_test(
2153            &batch,
2154            Arc::clone(&col_b),
2155            vec![lit(1i32), lit(2i32)],
2156            vec![Some(false), None],
2157        )?;
2158
2159        // ========================================================================
2160        // COLUMN WITH NULL IN LIST - col(b) IN [NULL, 1]
2161        // ========================================================================
2162
2163        // [1] IN (NULL, 1) => [TRUE] (found match)
2164        let batch = make_batch(vec![Some(1)])?;
2165        run_test(
2166            &batch,
2167            Arc::clone(&col_b),
2168            vec![lit(null_i32.clone()), lit(1i32)],
2169            vec![Some(true)],
2170        )?;
2171
2172        // [2] IN (NULL, 1) => [NULL] (no match, but list has NULL)
2173        let batch = make_batch(vec![Some(2)])?;
2174        run_test(
2175            &batch,
2176            Arc::clone(&col_b),
2177            vec![lit(null_i32.clone()), lit(1i32)],
2178            vec![None],
2179        )?;
2180
2181        // [NULL] IN (NULL, 1) => [NULL]
2182        let batch = make_batch(vec![None])?;
2183        run_test(
2184            &batch,
2185            Arc::clone(&col_b),
2186            vec![lit(null_i32.clone()), lit(1i32)],
2187            vec![None],
2188        )?;
2189
2190        // ========================================================================
2191        // COLUMN WITH ALL NULLS IN LIST - col(b) IN [NULL, NULL]
2192        // ========================================================================
2193
2194        // [1] IN (NULL, NULL) => [NULL]
2195        let batch = make_batch(vec![Some(1)])?;
2196        run_test(
2197            &batch,
2198            Arc::clone(&col_b),
2199            vec![lit(null_i32.clone()), lit(null_i32.clone())],
2200            vec![None],
2201        )?;
2202
2203        // [NULL] IN (NULL, NULL) => [NULL]
2204        let batch = make_batch(vec![None])?;
2205        run_test(
2206            &batch,
2207            Arc::clone(&col_b),
2208            vec![lit(null_i32.clone()), lit(null_i32.clone())],
2209            vec![None],
2210        )?;
2211
2212        // ========================================================================
2213        // LITERAL IN LIST WITH COLUMN - lit(1) IN [2, col(b)]
2214        // ========================================================================
2215
2216        // 1 IN (2, [1]) => [TRUE] (matches column value)
2217        let batch = make_batch(vec![Some(1)])?;
2218        run_test(
2219            &batch,
2220            lit(1i32),
2221            vec![lit(2i32), Arc::clone(&col_b)],
2222            vec![Some(true)],
2223        )?;
2224
2225        // 1 IN (2, [3]) => [FALSE] (no match)
2226        let batch = make_batch(vec![Some(3)])?;
2227        run_test(
2228            &batch,
2229            lit(1i32),
2230            vec![lit(2i32), Arc::clone(&col_b)],
2231            vec![Some(false)],
2232        )?;
2233
2234        // 1 IN (2, [NULL]) => [NULL] (no match, column is NULL)
2235        let batch = make_batch(vec![None])?;
2236        run_test(
2237            &batch,
2238            lit(1i32),
2239            vec![lit(2i32), Arc::clone(&col_b)],
2240            vec![None],
2241        )?;
2242
2243        // ========================================================================
2244        // COLUMN IN LIST CONTAINING ITSELF - col(b) IN [1, col(b)]
2245        // ========================================================================
2246
2247        // [1] IN (1, [1]) => [TRUE] (always matches - either list literal or itself)
2248        let batch = make_batch(vec![Some(1)])?;
2249        run_test(
2250            &batch,
2251            Arc::clone(&col_b),
2252            vec![lit(1i32), Arc::clone(&col_b)],
2253            vec![Some(true)],
2254        )?;
2255
2256        // [2] IN (1, [2]) => [TRUE] (matches itself)
2257        let batch = make_batch(vec![Some(2)])?;
2258        run_test(
2259            &batch,
2260            Arc::clone(&col_b),
2261            vec![lit(1i32), Arc::clone(&col_b)],
2262            vec![Some(true)],
2263        )?;
2264
2265        // [NULL] IN (1, [NULL]) => [NULL] (NULL is never equal to anything)
2266        let batch = make_batch(vec![None])?;
2267        run_test(
2268            &batch,
2269            Arc::clone(&col_b),
2270            vec![lit(1i32), Arc::clone(&col_b)],
2271            vec![None],
2272        )?;
2273
2274        Ok(())
2275    }
2276
2277    #[test]
2278    fn test_in_list_scalar_literal_cases() -> Result<()> {
2279        // Test scalar literal cases (both NULL and non-NULL) to ensure SQL three-valued
2280        // logic is correctly implemented. This covers the important case where a scalar
2281        // value is tested against a list containing NULL.
2282
2283        let schema = Arc::new(Schema::new(vec![Field::new("b", DataType::Int32, true)]));
2284        let null_i32 = ScalarValue::Int32(None);
2285
2286        // Helper to create a batch
2287        let make_batch = |values: Vec<Option<i32>>| -> Result<RecordBatch> {
2288            let array = Arc::new(Int32Array::from(values));
2289            Ok(RecordBatch::try_new(Arc::clone(&schema), vec![array])?)
2290        };
2291
2292        // Helper to run a test
2293        let run_test = |batch: &RecordBatch,
2294                        expr: Arc<dyn PhysicalExpr>,
2295                        list: Vec<Arc<dyn PhysicalExpr>>,
2296                        negated: bool,
2297                        expected: Vec<Option<bool>>|
2298         -> Result<()> {
2299            let in_expr = in_list(expr, list, &negated, schema.as_ref())?;
2300            let result = in_expr.evaluate(batch)?.into_array(batch.num_rows())?;
2301            let result = as_boolean_array(&result);
2302            let expected_array = BooleanArray::from(expected);
2303            assert_eq!(
2304                result,
2305                &expected_array,
2306                "Expected {:?}, got {:?}",
2307                expected_array,
2308                result.iter().collect::<Vec<_>>()
2309            );
2310            Ok(())
2311        };
2312
2313        let batch = make_batch(vec![Some(1)])?;
2314
2315        // ========================================================================
2316        // NULL LITERAL TESTS
2317        // According to SQL semantics, NULL IN (any_list) should always return NULL
2318        // ========================================================================
2319
2320        // NULL IN (1, 1) => NULL
2321        run_test(
2322            &batch,
2323            lit(null_i32.clone()),
2324            vec![lit(1i32), lit(1i32)],
2325            false,
2326            vec![None],
2327        )?;
2328
2329        // NULL IN (NULL, 1) => NULL
2330        run_test(
2331            &batch,
2332            lit(null_i32.clone()),
2333            vec![lit(null_i32.clone()), lit(1i32)],
2334            false,
2335            vec![None],
2336        )?;
2337
2338        // NULL IN (NULL, NULL) => NULL
2339        run_test(
2340            &batch,
2341            lit(null_i32.clone()),
2342            vec![lit(null_i32.clone()), lit(null_i32.clone())],
2343            false,
2344            vec![None],
2345        )?;
2346
2347        // ========================================================================
2348        // NON-NULL SCALAR LITERALS WITH NULL IN LIST - Int32
2349        // When a scalar value is NOT in a list containing NULL, the result is NULL
2350        // When a scalar value IS in the list, the result is TRUE (NULL doesn't matter)
2351        // ========================================================================
2352
2353        // 3 IN (0, 1, 2, NULL) => NULL (not in list, but list has NULL)
2354        run_test(
2355            &batch,
2356            lit(3i32),
2357            vec![lit(0i32), lit(1i32), lit(2i32), lit(null_i32.clone())],
2358            false,
2359            vec![None],
2360        )?;
2361
2362        // 3 NOT IN (0, 1, 2, NULL) => NULL (not in list, but list has NULL)
2363        run_test(
2364            &batch,
2365            lit(3i32),
2366            vec![lit(0i32), lit(1i32), lit(2i32), lit(null_i32.clone())],
2367            true,
2368            vec![None],
2369        )?;
2370
2371        // 1 IN (0, 1, 2, NULL) => TRUE (found match, NULL doesn't matter)
2372        run_test(
2373            &batch,
2374            lit(1i32),
2375            vec![lit(0i32), lit(1i32), lit(2i32), lit(null_i32.clone())],
2376            false,
2377            vec![Some(true)],
2378        )?;
2379
2380        // 1 NOT IN (0, 1, 2, NULL) => FALSE (found match, NULL doesn't matter)
2381        run_test(
2382            &batch,
2383            lit(1i32),
2384            vec![lit(0i32), lit(1i32), lit(2i32), lit(null_i32.clone())],
2385            true,
2386            vec![Some(false)],
2387        )?;
2388
2389        // ========================================================================
2390        // NON-NULL SCALAR LITERALS WITH NULL IN LIST - String
2391        // Same semantics as Int32 but with string type
2392        // ========================================================================
2393
2394        let schema_str =
2395            Arc::new(Schema::new(vec![Field::new("s", DataType::Utf8, true)]));
2396        let batch_str = RecordBatch::try_new(
2397            Arc::clone(&schema_str),
2398            vec![Arc::new(StringArray::from(vec![Some("dummy")]))],
2399        )?;
2400        let null_str = ScalarValue::Utf8(None);
2401
2402        let run_test_str = |expr: Arc<dyn PhysicalExpr>,
2403                            list: Vec<Arc<dyn PhysicalExpr>>,
2404                            negated: bool,
2405                            expected: Vec<Option<bool>>|
2406         -> Result<()> {
2407            let in_expr = in_list(expr, list, &negated, schema_str.as_ref())?;
2408            let result = in_expr
2409                .evaluate(&batch_str)?
2410                .into_array(batch_str.num_rows())?;
2411            let result = as_boolean_array(&result);
2412            let expected_array = BooleanArray::from(expected);
2413            assert_eq!(
2414                result,
2415                &expected_array,
2416                "Expected {:?}, got {:?}",
2417                expected_array,
2418                result.iter().collect::<Vec<_>>()
2419            );
2420            Ok(())
2421        };
2422
2423        // 'c' IN ('a', 'b', NULL) => NULL (not in list, but list has NULL)
2424        run_test_str(
2425            lit("c"),
2426            vec![lit("a"), lit("b"), lit(null_str.clone())],
2427            false,
2428            vec![None],
2429        )?;
2430
2431        // 'c' NOT IN ('a', 'b', NULL) => NULL (not in list, but list has NULL)
2432        run_test_str(
2433            lit("c"),
2434            vec![lit("a"), lit("b"), lit(null_str.clone())],
2435            true,
2436            vec![None],
2437        )?;
2438
2439        // 'a' IN ('a', 'b', NULL) => TRUE (found match, NULL doesn't matter)
2440        run_test_str(
2441            lit("a"),
2442            vec![lit("a"), lit("b"), lit(null_str.clone())],
2443            false,
2444            vec![Some(true)],
2445        )?;
2446
2447        // 'a' NOT IN ('a', 'b', NULL) => FALSE (found match, NULL doesn't matter)
2448        run_test_str(
2449            lit("a"),
2450            vec![lit("a"), lit("b"), lit(null_str.clone())],
2451            true,
2452            vec![Some(false)],
2453        )?;
2454
2455        Ok(())
2456    }
2457
2458    #[test]
2459    fn test_in_list_tuple_cases() -> Result<()> {
2460        // Test tuple/struct cases from the original request: (lit, lit) IN (lit, lit)
2461        // These test row-wise comparisons like (1, 2) IN ((1, 2), (3, 4))
2462
2463        let schema = Arc::new(Schema::new(vec![Field::new("b", DataType::Int32, true)]));
2464
2465        // Helper to create struct scalars for tuple comparisons
2466        let make_struct = |v1: Option<i32>, v2: Option<i32>| -> ScalarValue {
2467            let fields = Fields::from(vec![
2468                Field::new("field_0", DataType::Int32, true),
2469                Field::new("field_1", DataType::Int32, true),
2470            ]);
2471            ScalarValue::Struct(Arc::new(StructArray::new(
2472                fields,
2473                vec![
2474                    Arc::new(Int32Array::from(vec![v1])),
2475                    Arc::new(Int32Array::from(vec![v2])),
2476                ],
2477                None,
2478            )))
2479        };
2480
2481        // Need a single row batch for scalar tests
2482        let batch = RecordBatch::try_new(
2483            Arc::clone(&schema),
2484            vec![Arc::new(Int32Array::from(vec![Some(1)]))],
2485        )?;
2486
2487        // Helper to run tuple tests
2488        let run_tuple_test = |lhs: ScalarValue,
2489                              list: Vec<ScalarValue>,
2490                              expected: Vec<Option<bool>>|
2491         -> Result<()> {
2492            let expr = in_list(
2493                lit(lhs),
2494                list.into_iter().map(lit).collect(),
2495                &false,
2496                schema.as_ref(),
2497            )?;
2498            let result = expr.evaluate(&batch)?.into_array(batch.num_rows())?;
2499            let result = as_boolean_array(&result);
2500            assert_eq!(result, &BooleanArray::from(expected));
2501            Ok(())
2502        };
2503
2504        // (NULL, NULL) IN ((1, 2)) => FALSE (tuples don't match)
2505        run_tuple_test(
2506            make_struct(None, None),
2507            vec![make_struct(Some(1), Some(2))],
2508            vec![Some(false)],
2509        )?;
2510
2511        // (NULL, NULL) IN ((NULL, 1)) => FALSE
2512        run_tuple_test(
2513            make_struct(None, None),
2514            vec![make_struct(None, Some(1))],
2515            vec![Some(false)],
2516        )?;
2517
2518        // (NULL, NULL) IN ((NULL, NULL)) => TRUE (exact match including nulls)
2519        run_tuple_test(
2520            make_struct(None, None),
2521            vec![make_struct(None, None)],
2522            vec![Some(true)],
2523        )?;
2524
2525        // (NULL, 1) IN ((1, 2)) => FALSE
2526        run_tuple_test(
2527            make_struct(None, Some(1)),
2528            vec![make_struct(Some(1), Some(2))],
2529            vec![Some(false)],
2530        )?;
2531
2532        // (NULL, 1) IN ((NULL, 1)) => TRUE (exact match)
2533        run_tuple_test(
2534            make_struct(None, Some(1)),
2535            vec![make_struct(None, Some(1))],
2536            vec![Some(true)],
2537        )?;
2538
2539        // (NULL, 1) IN ((NULL, NULL)) => FALSE
2540        run_tuple_test(
2541            make_struct(None, Some(1)),
2542            vec![make_struct(None, None)],
2543            vec![Some(false)],
2544        )?;
2545
2546        // (1, 2) IN ((1, 2)) => TRUE
2547        run_tuple_test(
2548            make_struct(Some(1), Some(2)),
2549            vec![make_struct(Some(1), Some(2))],
2550            vec![Some(true)],
2551        )?;
2552
2553        // (1, 3) IN ((1, 2)) => FALSE
2554        run_tuple_test(
2555            make_struct(Some(1), Some(3)),
2556            vec![make_struct(Some(1), Some(2))],
2557            vec![Some(false)],
2558        )?;
2559
2560        // (4, 4) IN ((1, 2)) => FALSE
2561        run_tuple_test(
2562            make_struct(Some(4), Some(4)),
2563            vec![make_struct(Some(1), Some(2))],
2564            vec![Some(false)],
2565        )?;
2566
2567        // (1, 1) IN ((NULL, 1)) => FALSE
2568        run_tuple_test(
2569            make_struct(Some(1), Some(1)),
2570            vec![make_struct(None, Some(1))],
2571            vec![Some(false)],
2572        )?;
2573
2574        // (1, 1) IN ((NULL, NULL)) => FALSE
2575        run_tuple_test(
2576            make_struct(Some(1), Some(1)),
2577            vec![make_struct(None, None)],
2578            vec![Some(false)],
2579        )?;
2580
2581        Ok(())
2582    }
2583
2584    #[test]
2585    fn test_in_list_dictionary_int32() -> Result<()> {
2586        // Create schema with dictionary-encoded Int32 column
2587        let dict_type =
2588            DataType::Dictionary(Box::new(DataType::Int8), Box::new(DataType::Int32));
2589        let schema = Schema::new(vec![Field::new("a", dict_type.clone(), false)]);
2590        let col_a = col("a", &schema)?;
2591
2592        // Create IN list with Int32 literals: (100, 200, 300)
2593        let list = vec![lit(100i32), lit(200i32), lit(300i32)];
2594
2595        // Create InListExpr via in_list() - this uses Int32StaticFilter for Int32 lists
2596        let expr = in_list(col_a, list, &false, &schema)?;
2597
2598        // Create dictionary-encoded batch with values [100, 200, 500]
2599        // Dictionary: keys [0, 1, 2] -> values [100, 200, 500]
2600        // Using values clearly distinct from keys to avoid confusion
2601        let keys = Int8Array::from(vec![0, 1, 2]);
2602        let values = Int32Array::from(vec![100, 200, 500]);
2603        let dict_array: ArrayRef =
2604            Arc::new(DictionaryArray::try_new(keys, Arc::new(values))?);
2605        let batch = RecordBatch::try_new(Arc::new(schema), vec![dict_array])?;
2606
2607        // Expected: [100 IN (100,200,300), 200 IN (100,200,300), 500 IN (100,200,300)] = [true, true, false]
2608        let result = expr.evaluate(&batch)?.into_array(3)?;
2609        let result = as_boolean_array(&result);
2610        assert_eq!(result, &BooleanArray::from(vec![true, true, false]));
2611        Ok(())
2612    }
2613
2614    #[test]
2615    fn test_in_list_dictionary_types() -> Result<()> {
2616        // Helper functions for creating dictionary literals
2617        fn dict_lit_int64(key_type: DataType, value: i64) -> Arc<dyn PhysicalExpr> {
2618            lit(ScalarValue::Dictionary(
2619                Box::new(key_type),
2620                Box::new(ScalarValue::Int64(Some(value))),
2621            ))
2622        }
2623
2624        fn dict_lit_float64(key_type: DataType, value: f64) -> Arc<dyn PhysicalExpr> {
2625            lit(ScalarValue::Dictionary(
2626                Box::new(key_type),
2627                Box::new(ScalarValue::Float64(Some(value))),
2628            ))
2629        }
2630
2631        // Test case structures
2632        struct DictNeedleTest {
2633            list_values: Vec<Arc<dyn PhysicalExpr>>,
2634            expected: Vec<Option<bool>>,
2635        }
2636
2637        struct DictionaryInListTestCase {
2638            name: &'static str,
2639            dict_type: DataType,
2640            dict_keys: Vec<Option<i8>>,
2641            dict_values: ArrayRef,
2642            list_values_no_null: Vec<Arc<dyn PhysicalExpr>>,
2643            list_values_with_null: Vec<Arc<dyn PhysicalExpr>>,
2644            expected_1: Vec<Option<bool>>,
2645            expected_2: Vec<Option<bool>>,
2646            expected_3: Vec<Option<bool>>,
2647            expected_4: Vec<Option<bool>>,
2648            dict_needle_test: Option<DictNeedleTest>,
2649        }
2650
2651        // Test harness function
2652        fn run_dictionary_in_list_test(
2653            test_case: DictionaryInListTestCase,
2654        ) -> Result<()> {
2655            // Create schema with dictionary type
2656            let schema =
2657                Schema::new(vec![Field::new("a", test_case.dict_type.clone(), true)]);
2658            let col_a = col("a", &schema)?;
2659
2660            // Create dictionary array from keys and values
2661            let keys = Int8Array::from(test_case.dict_keys.clone());
2662            let dict_array: ArrayRef =
2663                Arc::new(DictionaryArray::try_new(keys, test_case.dict_values)?);
2664            let batch = RecordBatch::try_new(Arc::new(schema.clone()), vec![dict_array])?;
2665
2666            let exp1 = test_case.expected_1.clone();
2667            let exp2 = test_case.expected_2.clone();
2668            let exp3 = test_case.expected_3.clone();
2669            let exp4 = test_case.expected_4;
2670
2671            // Test 1: a IN (values_no_null)
2672            in_list!(
2673                batch,
2674                test_case.list_values_no_null.clone(),
2675                &false,
2676                exp1,
2677                Arc::clone(&col_a),
2678                &schema
2679            );
2680
2681            // Test 2: a NOT IN (values_no_null)
2682            in_list!(
2683                batch,
2684                test_case.list_values_no_null.clone(),
2685                &true,
2686                exp2,
2687                Arc::clone(&col_a),
2688                &schema
2689            );
2690
2691            // Test 3: a IN (values_with_null)
2692            in_list!(
2693                batch,
2694                test_case.list_values_with_null.clone(),
2695                &false,
2696                exp3,
2697                Arc::clone(&col_a),
2698                &schema
2699            );
2700
2701            // Test 4: a NOT IN (values_with_null)
2702            in_list!(
2703                batch,
2704                test_case.list_values_with_null,
2705                &true,
2706                exp4,
2707                Arc::clone(&col_a),
2708                &schema
2709            );
2710
2711            // Optional: Dictionary needle test (if provided)
2712            if let Some(needle_test) = test_case.dict_needle_test {
2713                in_list_raw!(
2714                    batch,
2715                    needle_test.list_values,
2716                    &false,
2717                    needle_test.expected,
2718                    Arc::clone(&col_a),
2719                    &schema
2720                );
2721            }
2722
2723            Ok(())
2724        }
2725
2726        // Test case 1: UTF8
2727        // Dictionary: keys [0, 1, null] → values ["a", "d", -]
2728        // Rows: ["a", "d", null]
2729        let utf8_case = DictionaryInListTestCase {
2730            name: "dictionary_utf8",
2731            dict_type: DataType::Dictionary(
2732                Box::new(DataType::Int8),
2733                Box::new(DataType::Utf8),
2734            ),
2735            dict_keys: vec![Some(0), Some(1), None],
2736            dict_values: Arc::new(StringArray::from(vec![Some("a"), Some("d")])),
2737            list_values_no_null: vec![lit("a"), lit("b")],
2738            list_values_with_null: vec![lit("a"), lit("b"), lit(ScalarValue::Utf8(None))],
2739            expected_1: vec![Some(true), Some(false), None],
2740            expected_2: vec![Some(false), Some(true), None],
2741            expected_3: vec![Some(true), None, None],
2742            expected_4: vec![Some(false), None, None],
2743            dict_needle_test: None,
2744        };
2745
2746        // Test case 2: Int64 with dictionary needles
2747        // Dictionary: keys [0, 1, null] → values [10, 20, -]
2748        // Rows: [10, 20, null]
2749        let int64_case = DictionaryInListTestCase {
2750            name: "dictionary_int64",
2751            dict_type: DataType::Dictionary(
2752                Box::new(DataType::Int8),
2753                Box::new(DataType::Int64),
2754            ),
2755            dict_keys: vec![Some(0), Some(1), None],
2756            dict_values: Arc::new(Int64Array::from(vec![Some(10), Some(20)])),
2757            list_values_no_null: vec![lit(10i64), lit(15i64)],
2758            list_values_with_null: vec![
2759                lit(10i64),
2760                lit(15i64),
2761                lit(ScalarValue::Int64(None)),
2762            ],
2763            expected_1: vec![Some(true), Some(false), None],
2764            expected_2: vec![Some(false), Some(true), None],
2765            expected_3: vec![Some(true), None, None],
2766            expected_4: vec![Some(false), None, None],
2767            dict_needle_test: Some(DictNeedleTest {
2768                list_values: vec![
2769                    dict_lit_int64(DataType::Int16, 10),
2770                    dict_lit_int64(DataType::Int16, 15),
2771                ],
2772                expected: vec![Some(true), Some(false), None],
2773            }),
2774        };
2775
2776        // Test case 3: Float64 with NaN and dictionary needles
2777        // Dictionary: keys [0, 1, null, 2] → values [1.5, 3.7, NaN, -]
2778        // Rows: [1.5, 3.7, null, NaN]
2779        // Note: NaN is a value (not null), so it goes in the values array
2780        let float64_case = DictionaryInListTestCase {
2781            name: "dictionary_float64",
2782            dict_type: DataType::Dictionary(
2783                Box::new(DataType::Int8),
2784                Box::new(DataType::Float64),
2785            ),
2786            dict_keys: vec![Some(0), Some(1), None, Some(2)],
2787            dict_values: Arc::new(Float64Array::from(vec![
2788                Some(1.5),      // index 0
2789                Some(3.7),      // index 1
2790                Some(f64::NAN), // index 2
2791            ])),
2792            list_values_no_null: vec![lit(1.5f64), lit(2.0f64)],
2793            list_values_with_null: vec![
2794                lit(1.5f64),
2795                lit(2.0f64),
2796                lit(ScalarValue::Float64(None)),
2797            ],
2798            // Test 1: a IN (1.5, 2.0) → [true, false, null, false]
2799            // NaN is false because NaN not in list and no NULL in list
2800            expected_1: vec![Some(true), Some(false), None, Some(false)],
2801            // Test 2: a NOT IN (1.5, 2.0) → [false, true, null, true]
2802            // NaN is true because NaN not in list
2803            expected_2: vec![Some(false), Some(true), None, Some(true)],
2804            // Test 3: a IN (1.5, 2.0, NULL) → [true, null, null, null]
2805            // 3.7 and NaN become null due to NULL in list (three-valued logic)
2806            expected_3: vec![Some(true), None, None, None],
2807            // Test 4: a NOT IN (1.5, 2.0, NULL) → [false, null, null, null]
2808            // 3.7 and NaN become null due to NULL in list
2809            expected_4: vec![Some(false), None, None, None],
2810            dict_needle_test: Some(DictNeedleTest {
2811                list_values: vec![
2812                    dict_lit_float64(DataType::UInt16, 1.5),
2813                    dict_lit_float64(DataType::UInt16, 2.0),
2814                ],
2815                expected: vec![Some(true), Some(false), None, Some(false)],
2816            }),
2817        };
2818
2819        // Execute all test cases
2820        let test_name = utf8_case.name;
2821        run_dictionary_in_list_test(utf8_case).map_err(|e| {
2822            datafusion_common::DataFusionError::Execution(format!(
2823                "Dictionary test '{test_name}' failed: {e}"
2824            ))
2825        })?;
2826
2827        let test_name = int64_case.name;
2828        run_dictionary_in_list_test(int64_case).map_err(|e| {
2829            datafusion_common::DataFusionError::Execution(format!(
2830                "Dictionary test '{test_name}' failed: {e}"
2831            ))
2832        })?;
2833
2834        let test_name = float64_case.name;
2835        run_dictionary_in_list_test(float64_case).map_err(|e| {
2836            datafusion_common::DataFusionError::Execution(format!(
2837                "Dictionary test '{test_name}' failed: {e}"
2838            ))
2839        })?;
2840
2841        // Additional test: Dictionary deduplication with repeated keys
2842        // This tests that multiple rows with the same key (pointing to the same value)
2843        // are evaluated correctly
2844        let dedup_case = DictionaryInListTestCase {
2845            name: "dictionary_deduplication",
2846            dict_type: DataType::Dictionary(
2847                Box::new(DataType::Int8),
2848                Box::new(DataType::Utf8),
2849            ),
2850            // Keys: [0, 1, 0, 1, null] - keys 0 and 1 are repeated
2851            // This creates data: ["a", "d", "a", "d", null]
2852            dict_keys: vec![Some(0), Some(1), Some(0), Some(1), None],
2853            dict_values: Arc::new(StringArray::from(vec![Some("a"), Some("d")])),
2854            list_values_no_null: vec![lit("a"), lit("b")],
2855            list_values_with_null: vec![lit("a"), lit("b"), lit(ScalarValue::Utf8(None))],
2856            // Test 1: a IN ("a", "b") → [true, false, true, false, null]
2857            // Rows 0 and 2 both have key 0 → "a", so both are true
2858            expected_1: vec![Some(true), Some(false), Some(true), Some(false), None],
2859            // Test 2: a NOT IN ("a", "b") → [false, true, false, true, null]
2860            expected_2: vec![Some(false), Some(true), Some(false), Some(true), None],
2861            // Test 3: a IN ("a", "b", NULL) → [true, null, true, null, null]
2862            // "d" becomes null due to NULL in list
2863            expected_3: vec![Some(true), None, Some(true), None, None],
2864            // Test 4: a NOT IN ("a", "b", NULL) → [false, null, false, null, null]
2865            expected_4: vec![Some(false), None, Some(false), None, None],
2866            dict_needle_test: None,
2867        };
2868
2869        let test_name = dedup_case.name;
2870        run_dictionary_in_list_test(dedup_case).map_err(|e| {
2871            datafusion_common::DataFusionError::Execution(format!(
2872                "Dictionary test '{test_name}' failed: {e}"
2873            ))
2874        })?;
2875
2876        // Additional test for Float64 NaN in IN list
2877        let dict_type =
2878            DataType::Dictionary(Box::new(DataType::Int8), Box::new(DataType::Float64));
2879        let schema = Schema::new(vec![Field::new("a", dict_type.clone(), true)]);
2880        let col_a = col("a", &schema)?;
2881
2882        let keys = Int8Array::from(vec![Some(0), Some(1), None, Some(2)]);
2883        let values = Float64Array::from(vec![Some(1.5), Some(3.7), Some(f64::NAN)]);
2884        let dict_array: ArrayRef =
2885            Arc::new(DictionaryArray::try_new(keys, Arc::new(values))?);
2886        let batch = RecordBatch::try_new(Arc::new(schema.clone()), vec![dict_array])?;
2887
2888        // Test: a IN (1.5, 2.0, NaN)
2889        let list_with_nan = vec![lit(1.5f64), lit(2.0f64), lit(f64::NAN)];
2890        in_list!(
2891            batch,
2892            list_with_nan,
2893            &false,
2894            vec![Some(true), Some(false), None, Some(true)],
2895            col_a,
2896            &schema
2897        );
2898
2899        Ok(())
2900    }
2901
2902    #[test]
2903    fn test_in_list_esoteric_types() -> Result<()> {
2904        // Test less common types covered by IN-list evaluation. Some of these
2905        // use specialized filters, and others fall back to the generic path;
2906        // this keeps the end-to-end behavior covered either way.
2907
2908        // Helper: simple IN test that expects [Some(true), Some(false)]
2909        let test_type = |data_type: DataType,
2910                         in_array: ArrayRef,
2911                         list_values: Vec<ScalarValue>|
2912         -> Result<()> {
2913            let schema = Schema::new(vec![Field::new("a", data_type.clone(), false)]);
2914            let col_a = col("a", &schema)?;
2915            let batch = RecordBatch::try_new(Arc::new(schema.clone()), vec![in_array])?;
2916
2917            let list = list_values.into_iter().map(lit).collect();
2918            in_list!(
2919                batch,
2920                list,
2921                &false,
2922                vec![Some(true), Some(false)],
2923                col_a,
2924                &schema
2925            );
2926            Ok(())
2927        };
2928
2929        // Timestamp types
2930        test_type(
2931            DataType::Timestamp(TimeUnit::Second, None),
2932            Arc::new(TimestampSecondArray::from(vec![Some(1000), Some(2000)])),
2933            vec![
2934                ScalarValue::TimestampSecond(Some(1000), None),
2935                ScalarValue::TimestampSecond(Some(1500), None),
2936            ],
2937        )?;
2938
2939        test_type(
2940            DataType::Timestamp(TimeUnit::Millisecond, None),
2941            Arc::new(TimestampMillisecondArray::from(vec![
2942                Some(1000000),
2943                Some(2000000),
2944            ])),
2945            vec![
2946                ScalarValue::TimestampMillisecond(Some(1000000), None),
2947                ScalarValue::TimestampMillisecond(Some(1500000), None),
2948            ],
2949        )?;
2950
2951        test_type(
2952            DataType::Timestamp(TimeUnit::Microsecond, None),
2953            Arc::new(TimestampMicrosecondArray::from(vec![
2954                Some(1000000000),
2955                Some(2000000000),
2956            ])),
2957            vec![
2958                ScalarValue::TimestampMicrosecond(Some(1000000000), None),
2959                ScalarValue::TimestampMicrosecond(Some(1500000000), None),
2960            ],
2961        )?;
2962
2963        // Time32 and Time64
2964        test_type(
2965            DataType::Time32(TimeUnit::Second),
2966            Arc::new(Time32SecondArray::from(vec![Some(3600), Some(7200)])),
2967            vec![
2968                ScalarValue::Time32Second(Some(3600)),
2969                ScalarValue::Time32Second(Some(5400)),
2970            ],
2971        )?;
2972
2973        test_type(
2974            DataType::Time32(TimeUnit::Millisecond),
2975            Arc::new(Time32MillisecondArray::from(vec![
2976                Some(3600000),
2977                Some(7200000),
2978            ])),
2979            vec![
2980                ScalarValue::Time32Millisecond(Some(3600000)),
2981                ScalarValue::Time32Millisecond(Some(5400000)),
2982            ],
2983        )?;
2984
2985        test_type(
2986            DataType::Time64(TimeUnit::Microsecond),
2987            Arc::new(Time64MicrosecondArray::from(vec![
2988                Some(3600000000),
2989                Some(7200000000),
2990            ])),
2991            vec![
2992                ScalarValue::Time64Microsecond(Some(3600000000)),
2993                ScalarValue::Time64Microsecond(Some(5400000000)),
2994            ],
2995        )?;
2996
2997        test_type(
2998            DataType::Time64(TimeUnit::Nanosecond),
2999            Arc::new(Time64NanosecondArray::from(vec![
3000                Some(3600000000000),
3001                Some(7200000000000),
3002            ])),
3003            vec![
3004                ScalarValue::Time64Nanosecond(Some(3600000000000)),
3005                ScalarValue::Time64Nanosecond(Some(5400000000000)),
3006            ],
3007        )?;
3008
3009        // Duration types
3010        test_type(
3011            DataType::Duration(TimeUnit::Second),
3012            Arc::new(DurationSecondArray::from(vec![Some(86400), Some(172800)])),
3013            vec![
3014                ScalarValue::DurationSecond(Some(86400)),
3015                ScalarValue::DurationSecond(Some(129600)),
3016            ],
3017        )?;
3018
3019        test_type(
3020            DataType::Duration(TimeUnit::Millisecond),
3021            Arc::new(DurationMillisecondArray::from(vec![
3022                Some(86400000),
3023                Some(172800000),
3024            ])),
3025            vec![
3026                ScalarValue::DurationMillisecond(Some(86400000)),
3027                ScalarValue::DurationMillisecond(Some(129600000)),
3028            ],
3029        )?;
3030
3031        test_type(
3032            DataType::Duration(TimeUnit::Microsecond),
3033            Arc::new(DurationMicrosecondArray::from(vec![
3034                Some(86400000000),
3035                Some(172800000000),
3036            ])),
3037            vec![
3038                ScalarValue::DurationMicrosecond(Some(86400000000)),
3039                ScalarValue::DurationMicrosecond(Some(129600000000)),
3040            ],
3041        )?;
3042
3043        test_type(
3044            DataType::Duration(TimeUnit::Nanosecond),
3045            Arc::new(DurationNanosecondArray::from(vec![
3046                Some(86400000000000),
3047                Some(172800000000000),
3048            ])),
3049            vec![
3050                ScalarValue::DurationNanosecond(Some(86400000000000)),
3051                ScalarValue::DurationNanosecond(Some(129600000000000)),
3052            ],
3053        )?;
3054
3055        // Interval types
3056        test_type(
3057            DataType::Interval(IntervalUnit::YearMonth),
3058            Arc::new(IntervalYearMonthArray::from(vec![Some(12), Some(24)])),
3059            vec![
3060                ScalarValue::IntervalYearMonth(Some(12)),
3061                ScalarValue::IntervalYearMonth(Some(18)),
3062            ],
3063        )?;
3064
3065        test_type(
3066            DataType::Interval(IntervalUnit::DayTime),
3067            Arc::new(IntervalDayTimeArray::from(vec![
3068                Some(IntervalDayTime {
3069                    days: 1,
3070                    milliseconds: 0,
3071                }),
3072                Some(IntervalDayTime {
3073                    days: 2,
3074                    milliseconds: 0,
3075                }),
3076            ])),
3077            vec![
3078                ScalarValue::IntervalDayTime(Some(IntervalDayTime {
3079                    days: 1,
3080                    milliseconds: 0,
3081                })),
3082                ScalarValue::IntervalDayTime(Some(IntervalDayTime {
3083                    days: 1,
3084                    milliseconds: 500,
3085                })),
3086            ],
3087        )?;
3088
3089        test_type(
3090            DataType::Interval(IntervalUnit::MonthDayNano),
3091            Arc::new(IntervalMonthDayNanoArray::from(vec![
3092                Some(IntervalMonthDayNano {
3093                    months: 1,
3094                    days: 0,
3095                    nanoseconds: 0,
3096                }),
3097                Some(IntervalMonthDayNano {
3098                    months: 2,
3099                    days: 0,
3100                    nanoseconds: 0,
3101                }),
3102            ])),
3103            vec![
3104                ScalarValue::IntervalMonthDayNano(Some(IntervalMonthDayNano {
3105                    months: 1,
3106                    days: 0,
3107                    nanoseconds: 0,
3108                })),
3109                ScalarValue::IntervalMonthDayNano(Some(IntervalMonthDayNano {
3110                    months: 1,
3111                    days: 15,
3112                    nanoseconds: 0,
3113                })),
3114            ],
3115        )?;
3116
3117        // Decimal256. Need to use with_precision_and_scale() to set the metadata.
3118        let precision = 38;
3119        let scale = 10;
3120        test_type(
3121            DataType::Decimal256(precision, scale),
3122            Arc::new(
3123                Decimal256Array::from(vec![
3124                    Some(i256::from(12345)),
3125                    Some(i256::from(67890)),
3126                ])
3127                .with_precision_and_scale(precision, scale)?,
3128            ),
3129            vec![
3130                ScalarValue::Decimal256(Some(i256::from(12345)), precision, scale),
3131                ScalarValue::Decimal256(Some(i256::from(54321)), precision, scale),
3132            ],
3133        )?;
3134
3135        Ok(())
3136    }
3137
3138    /// Helper: creates an InListExpr with `static_filter = None`
3139    /// to force the column-reference evaluation path.
3140    fn make_in_list_with_columns(
3141        expr: Arc<dyn PhysicalExpr>,
3142        list: Vec<Arc<dyn PhysicalExpr>>,
3143        negated: bool,
3144    ) -> Arc<InListExpr> {
3145        Arc::new(InListExpr::new(expr, list, negated, None))
3146    }
3147
3148    #[test]
3149    fn test_in_list_with_columns_int32_scalars() -> Result<()> {
3150        // Column-reference path with scalar literals (bypassing static filter)
3151        let schema = Schema::new(vec![Field::new("a", DataType::Int32, true)]);
3152        let col_a = col("a", &schema)?;
3153        let batch = RecordBatch::try_new(
3154            Arc::new(schema),
3155            vec![Arc::new(Int32Array::from(vec![
3156                Some(1),
3157                Some(2),
3158                Some(3),
3159                None,
3160            ]))],
3161        )?;
3162
3163        let list = vec![
3164            lit(ScalarValue::Int32(Some(1))),
3165            lit(ScalarValue::Int32(Some(3))),
3166        ];
3167        let expr = make_in_list_with_columns(col_a, list, false);
3168
3169        let result = expr.evaluate(&batch)?.into_array(batch.num_rows())?;
3170        let result = as_boolean_array(&result);
3171        assert_eq!(
3172            result,
3173            &BooleanArray::from(vec![Some(true), Some(false), Some(true), None,])
3174        );
3175        Ok(())
3176    }
3177
3178    #[test]
3179    fn test_in_list_with_columns_int32_column_refs() -> Result<()> {
3180        // IN list with column references
3181        let schema = Schema::new(vec![
3182            Field::new("a", DataType::Int32, true),
3183            Field::new("b", DataType::Int32, true),
3184            Field::new("c", DataType::Int32, true),
3185        ]);
3186        let batch = RecordBatch::try_new(
3187            Arc::new(schema.clone()),
3188            vec![
3189                Arc::new(Int32Array::from(vec![Some(1), Some(2), Some(3), None])),
3190                Arc::new(Int32Array::from(vec![
3191                    Some(1),
3192                    Some(99),
3193                    Some(99),
3194                    Some(99),
3195                ])),
3196                Arc::new(Int32Array::from(vec![Some(99), Some(99), Some(3), None])),
3197            ],
3198        )?;
3199
3200        let col_a = col("a", &schema)?;
3201        let list = vec![col("b", &schema)?, col("c", &schema)?];
3202        let expr = make_in_list_with_columns(col_a, list, false);
3203
3204        let result = expr.evaluate(&batch)?.into_array(batch.num_rows())?;
3205        let result = as_boolean_array(&result);
3206        // row 0: 1 IN (1, 99) → true
3207        // row 1: 2 IN (99, 99) → false
3208        // row 2: 3 IN (99, 3) → true
3209        // row 3: NULL IN (99, NULL) → NULL
3210        assert_eq!(
3211            result,
3212            &BooleanArray::from(vec![Some(true), Some(false), Some(true), None,])
3213        );
3214        Ok(())
3215    }
3216
3217    #[test]
3218    fn test_in_list_with_columns_utf8_column_refs() -> Result<()> {
3219        // IN list with Utf8 column references
3220        let schema = Schema::new(vec![
3221            Field::new("a", DataType::Utf8, false),
3222            Field::new("b", DataType::Utf8, false),
3223        ]);
3224        let batch = RecordBatch::try_new(
3225            Arc::new(schema.clone()),
3226            vec![
3227                Arc::new(StringArray::from(vec!["x", "y", "z"])),
3228                Arc::new(StringArray::from(vec!["x", "x", "z"])),
3229            ],
3230        )?;
3231
3232        let col_a = col("a", &schema)?;
3233        let list = vec![col("b", &schema)?];
3234        let expr = make_in_list_with_columns(col_a, list, false);
3235
3236        let result = expr.evaluate(&batch)?.into_array(batch.num_rows())?;
3237        let result = as_boolean_array(&result);
3238        // row 0: "x" IN ("x") → true
3239        // row 1: "y" IN ("x") → false
3240        // row 2: "z" IN ("z") → true
3241        assert_eq!(result, &BooleanArray::from(vec![true, false, true]));
3242        Ok(())
3243    }
3244
3245    #[test]
3246    fn test_in_list_with_columns_negated() -> Result<()> {
3247        // NOT IN with column references
3248        let schema = Schema::new(vec![
3249            Field::new("a", DataType::Int32, false),
3250            Field::new("b", DataType::Int32, false),
3251        ]);
3252        let batch = RecordBatch::try_new(
3253            Arc::new(schema.clone()),
3254            vec![
3255                Arc::new(Int32Array::from(vec![1, 2, 3])),
3256                Arc::new(Int32Array::from(vec![1, 99, 3])),
3257            ],
3258        )?;
3259
3260        let col_a = col("a", &schema)?;
3261        let list = vec![col("b", &schema)?];
3262        let expr = make_in_list_with_columns(col_a, list, true);
3263
3264        let result = expr.evaluate(&batch)?.into_array(batch.num_rows())?;
3265        let result = as_boolean_array(&result);
3266        // row 0: 1 NOT IN (1) → false
3267        // row 1: 2 NOT IN (99) → true
3268        // row 2: 3 NOT IN (3) → false
3269        assert_eq!(result, &BooleanArray::from(vec![false, true, false]));
3270        Ok(())
3271    }
3272
3273    #[test]
3274    fn test_in_list_with_columns_null_in_list() -> Result<()> {
3275        // IN list with NULL scalar (column-reference path)
3276        let schema = Schema::new(vec![Field::new("a", DataType::Int32, false)]);
3277        let col_a = col("a", &schema)?;
3278        let batch = RecordBatch::try_new(
3279            Arc::new(schema),
3280            vec![Arc::new(Int32Array::from(vec![1, 2]))],
3281        )?;
3282
3283        let list = vec![
3284            lit(ScalarValue::Int32(None)),
3285            lit(ScalarValue::Int32(Some(1))),
3286        ];
3287        let expr = make_in_list_with_columns(col_a, list, false);
3288
3289        let result = expr.evaluate(&batch)?.into_array(batch.num_rows())?;
3290        let result = as_boolean_array(&result);
3291        // row 0: 1 IN (NULL, 1) → true (true OR null = true)
3292        // row 1: 2 IN (NULL, 1) → NULL (false OR null = null)
3293        assert_eq!(result, &BooleanArray::from(vec![Some(true), None]));
3294        Ok(())
3295    }
3296
3297    #[test]
3298    fn test_in_list_with_columns_float_nan() -> Result<()> {
3299        // Verify NaN == NaN is true in the column-reference path
3300        // (consistent with Arrow's totalOrder semantics)
3301        let schema = Schema::new(vec![
3302            Field::new("a", DataType::Float64, false),
3303            Field::new("b", DataType::Float64, false),
3304        ]);
3305        let batch = RecordBatch::try_new(
3306            Arc::new(schema.clone()),
3307            vec![
3308                Arc::new(Float64Array::from(vec![f64::NAN, 1.0, f64::NAN])),
3309                Arc::new(Float64Array::from(vec![f64::NAN, 2.0, 0.0])),
3310            ],
3311        )?;
3312
3313        let col_a = col("a", &schema)?;
3314        let list = vec![col("b", &schema)?];
3315        let expr = make_in_list_with_columns(col_a, list, false);
3316
3317        let result = expr.evaluate(&batch)?.into_array(batch.num_rows())?;
3318        let result = as_boolean_array(&result);
3319        // row 0: NaN IN (NaN) → true
3320        // row 1: 1.0 IN (2.0) → false
3321        // row 2: NaN IN (0.0) → false
3322        assert_eq!(result, &BooleanArray::from(vec![true, false, false]));
3323        Ok(())
3324    }
3325
3326    /// Tests that short-circuit evaluation produces correct results.
3327    /// When all rows match after the first list item, remaining items
3328    /// should be skipped without affecting correctness.
3329    #[test]
3330    fn test_in_list_with_columns_short_circuit() -> Result<()> {
3331        // a IN (b, c) where b already matches every row of a
3332        // The short-circuit should skip evaluating c
3333        let schema = Schema::new(vec![
3334            Field::new("a", DataType::Int32, false),
3335            Field::new("b", DataType::Int32, false),
3336            Field::new("c", DataType::Int32, false),
3337        ]);
3338        let batch = RecordBatch::try_new(
3339            Arc::new(schema.clone()),
3340            vec![
3341                Arc::new(Int32Array::from(vec![1, 2, 3])),
3342                Arc::new(Int32Array::from(vec![1, 2, 3])), // b == a for all rows
3343                Arc::new(Int32Array::from(vec![99, 99, 99])),
3344            ],
3345        )?;
3346
3347        let col_a = col("a", &schema)?;
3348        let list = vec![col("b", &schema)?, col("c", &schema)?];
3349        let expr = make_in_list_with_columns(col_a, list, false);
3350
3351        let result = expr.evaluate(&batch)?.into_array(batch.num_rows())?;
3352        let result = as_boolean_array(&result);
3353        assert_eq!(result, &BooleanArray::from(vec![true, true, true]));
3354        Ok(())
3355    }
3356
3357    /// Short-circuit must NOT skip when nulls are present (three-valued logic).
3358    /// Even if all non-null values are true, null rows keep the result as null.
3359    #[test]
3360    fn test_in_list_with_columns_short_circuit_with_nulls() -> Result<()> {
3361        // a IN (b, c) where a has nulls
3362        // Even if b matches all non-null rows, result should preserve nulls
3363        let schema = Schema::new(vec![
3364            Field::new("a", DataType::Int32, true),
3365            Field::new("b", DataType::Int32, false),
3366            Field::new("c", DataType::Int32, false),
3367        ]);
3368        let batch = RecordBatch::try_new(
3369            Arc::new(schema.clone()),
3370            vec![
3371                Arc::new(Int32Array::from(vec![Some(1), None, Some(3)])),
3372                Arc::new(Int32Array::from(vec![1, 2, 3])), // matches non-null rows
3373                Arc::new(Int32Array::from(vec![99, 99, 99])),
3374            ],
3375        )?;
3376
3377        let col_a = col("a", &schema)?;
3378        let list = vec![col("b", &schema)?, col("c", &schema)?];
3379        let expr = make_in_list_with_columns(col_a, list, false);
3380
3381        let result = expr.evaluate(&batch)?.into_array(batch.num_rows())?;
3382        let result = as_boolean_array(&result);
3383        // row 0: 1 IN (1, 99) → true
3384        // row 1: NULL IN (2, 99) → NULL
3385        // row 2: 3 IN (3, 99) → true
3386        assert_eq!(
3387            result,
3388            &BooleanArray::from(vec![Some(true), None, Some(true)])
3389        );
3390        Ok(())
3391    }
3392
3393    /// Tests the make_comparator + collect_bool fallback path using
3394    /// struct column references (nested types don't support arrow_eq).
3395    #[test]
3396    fn test_in_list_with_columns_struct() -> Result<()> {
3397        let struct_fields = Fields::from(vec![
3398            Field::new("x", DataType::Int32, false),
3399            Field::new("y", DataType::Utf8, false),
3400        ]);
3401        let struct_dt = DataType::Struct(struct_fields.clone());
3402
3403        let schema = Schema::new(vec![
3404            Field::new("a", struct_dt.clone(), true),
3405            Field::new("b", struct_dt.clone(), false),
3406            Field::new("c", struct_dt.clone(), false),
3407        ]);
3408
3409        // a: [{1,"a"}, {2,"b"}, NULL,    {4,"d"}]
3410        // b: [{1,"a"}, {9,"z"}, {3,"c"}, {4,"d"}]
3411        // c: [{9,"z"}, {2,"b"}, {9,"z"}, {9,"z"}]
3412        let a = Arc::new(StructArray::new(
3413            struct_fields.clone(),
3414            vec![
3415                Arc::new(Int32Array::from(vec![1, 2, 3, 4])),
3416                Arc::new(StringArray::from(vec!["a", "b", "c", "d"])),
3417            ],
3418            Some(vec![true, true, false, true].into()),
3419        ));
3420        let b = Arc::new(StructArray::new(
3421            struct_fields.clone(),
3422            vec![
3423                Arc::new(Int32Array::from(vec![1, 9, 3, 4])),
3424                Arc::new(StringArray::from(vec!["a", "z", "c", "d"])),
3425            ],
3426            None,
3427        ));
3428        let c = Arc::new(StructArray::new(
3429            struct_fields.clone(),
3430            vec![
3431                Arc::new(Int32Array::from(vec![9, 2, 9, 9])),
3432                Arc::new(StringArray::from(vec!["z", "b", "z", "z"])),
3433            ],
3434            None,
3435        ));
3436
3437        let batch = RecordBatch::try_new(Arc::new(schema.clone()), vec![a, b, c])?;
3438
3439        let col_a = col("a", &schema)?;
3440        let list = vec![col("b", &schema)?, col("c", &schema)?];
3441        let expr = make_in_list_with_columns(col_a, list, false);
3442
3443        let result = expr.evaluate(&batch)?.into_array(batch.num_rows())?;
3444        let result = as_boolean_array(&result);
3445        // row 0: {1,"a"} IN ({1,"a"}, {9,"z"}) → true  (matches b)
3446        // row 1: {2,"b"} IN ({9,"z"}, {2,"b"}) → true  (matches c)
3447        // row 2: NULL    IN ({3,"c"}, {9,"z"}) → NULL
3448        // row 3: {4,"d"} IN ({4,"d"}, {9,"z"}) → true  (matches b)
3449        assert_eq!(
3450            result,
3451            &BooleanArray::from(vec![Some(true), Some(true), None, Some(true)])
3452        );
3453
3454        // Also test NOT IN
3455        let col_a = col("a", &schema)?;
3456        let list = vec![col("b", &schema)?, col("c", &schema)?];
3457        let expr = make_in_list_with_columns(col_a, list, true);
3458
3459        let result = expr.evaluate(&batch)?.into_array(batch.num_rows())?;
3460        let result = as_boolean_array(&result);
3461        // row 0: {1,"a"} NOT IN ({1,"a"}, {9,"z"}) → false
3462        // row 1: {2,"b"} NOT IN ({9,"z"}, {2,"b"}) → false
3463        // row 2: NULL    NOT IN ({3,"c"}, {9,"z"}) → NULL
3464        // row 3: {4,"d"} NOT IN ({4,"d"}, {9,"z"}) → false
3465        assert_eq!(
3466            result,
3467            &BooleanArray::from(vec![Some(false), Some(false), None, Some(false)])
3468        );
3469        Ok(())
3470    }
3471
3472    // -----------------------------------------------------------------------
3473    // Tests for try_new_from_array: evaluates `needle IN in_array`.
3474    //
3475    // This exercises the code path used by HashJoin dynamic filter pushdown,
3476    // where in_array is built directly from the join's build-side arrays.
3477    // Unlike try_new (used by SQL IN expressions), which always produces a
3478    // non-Dictionary in_array because evaluate_list() flattens Dictionary
3479    // scalars, try_new_from_array passes the array directly and can produce
3480    // a Dictionary in_array.
3481    // -----------------------------------------------------------------------
3482
3483    fn wrap_in_dict(array: ArrayRef) -> ArrayRef {
3484        let keys = Int32Array::from((0..array.len() as i32).collect::<Vec<_>>());
3485        Arc::new(DictionaryArray::new(keys, array))
3486    }
3487
3488    /// Evaluates `needle IN in_array` via try_new_from_array, the same
3489    /// path used by HashJoin dynamic filter pushdown (not the SQL literal
3490    /// IN path which goes through try_new).
3491    fn eval_in_list_from_array(
3492        needle: ArrayRef,
3493        in_array: ArrayRef,
3494    ) -> Result<BooleanArray> {
3495        let schema =
3496            Schema::new(vec![Field::new("a", needle.data_type().clone(), false)]);
3497        let col_a = col("a", &schema)?;
3498        let expr = Arc::new(InListExpr::try_new_from_array(
3499            col_a, in_array, false, &schema,
3500        )?) as Arc<dyn PhysicalExpr>;
3501        let batch = RecordBatch::try_new(Arc::new(schema), vec![needle])?;
3502        let result = expr.evaluate(&batch)?.into_array(batch.num_rows())?;
3503        Ok(as_boolean_array(&result).clone())
3504    }
3505
3506    #[test]
3507    fn test_in_list_from_array_type_combinations() -> Result<()> {
3508        use arrow::compute::cast;
3509
3510        // All cases: needle[0] and needle[2] match, needle[1] does not.
3511        let expected = BooleanArray::from(vec![Some(true), Some(false), Some(true)]);
3512
3513        // Base arrays cast to each target type
3514        let base_in = Arc::new(Int64Array::from(vec![1i64, 2, 3])) as ArrayRef;
3515        let base_needle = Arc::new(Int64Array::from(vec![1i64, 4, 2])) as ArrayRef;
3516
3517        // Test all specializations in instantiate_static_filter
3518        let primitive_types = vec![
3519            DataType::Int8,
3520            DataType::Int16,
3521            DataType::Int32,
3522            DataType::Int64,
3523            DataType::UInt8,
3524            DataType::UInt16,
3525            DataType::UInt32,
3526            DataType::UInt64,
3527            DataType::Float16,
3528            DataType::Float32,
3529            DataType::Float64,
3530        ];
3531
3532        for dt in &primitive_types {
3533            let in_array = cast(&base_in, dt)?;
3534            let needle = cast(&base_needle, dt)?;
3535
3536            // T in_array, T needle
3537            assert_eq!(
3538                expected,
3539                eval_in_list_from_array(Arc::clone(&needle), Arc::clone(&in_array))?,
3540                "same-type failed for {dt:?}"
3541            );
3542
3543            // T in_array, Dict(Int32, T) needle
3544            assert_eq!(
3545                expected,
3546                eval_in_list_from_array(wrap_in_dict(needle), in_array)?,
3547                "dict-needle failed for {dt:?}"
3548            );
3549        }
3550
3551        // Utf8 (falls through to ArrayStaticFilter)
3552        let utf8_in = Arc::new(StringArray::from(vec!["a", "b", "c"])) as ArrayRef;
3553        let utf8_needle = Arc::new(StringArray::from(vec!["a", "d", "b"])) as ArrayRef;
3554
3555        // Utf8 in_array, Utf8 needle
3556        assert_eq!(
3557            expected,
3558            eval_in_list_from_array(Arc::clone(&utf8_needle), Arc::clone(&utf8_in),)?
3559        );
3560
3561        // Utf8 in_array, Dict(Utf8) needle
3562        assert_eq!(
3563            expected,
3564            eval_in_list_from_array(
3565                wrap_in_dict(Arc::clone(&utf8_needle)),
3566                Arc::clone(&utf8_in),
3567            )?
3568        );
3569
3570        // Dict(Utf8) in_array, Dict(Utf8) needle: the #20937 bug
3571        assert_eq!(
3572            expected,
3573            eval_in_list_from_array(
3574                wrap_in_dict(Arc::clone(&utf8_needle)),
3575                wrap_in_dict(Arc::clone(&utf8_in)),
3576            )?
3577        );
3578
3579        // Struct in_array, Struct needle: multi-column join
3580        let struct_fields = Fields::from(vec![
3581            Field::new("c0", DataType::Utf8, true),
3582            Field::new("c1", DataType::Int64, true),
3583        ]);
3584        let make_struct = |c0: ArrayRef, c1: ArrayRef| -> ArrayRef {
3585            let pairs: Vec<(FieldRef, ArrayRef)> =
3586                struct_fields.iter().cloned().zip([c0, c1]).collect();
3587            Arc::new(StructArray::from(pairs))
3588        };
3589        assert_eq!(
3590            expected,
3591            eval_in_list_from_array(
3592                make_struct(
3593                    Arc::clone(&utf8_needle),
3594                    Arc::new(Int64Array::from(vec![1, 4, 2])),
3595                ),
3596                make_struct(
3597                    Arc::clone(&utf8_in),
3598                    Arc::new(Int64Array::from(vec![1, 2, 3])),
3599                ),
3600            )?
3601        );
3602
3603        // Struct with Dict fields: multi-column Dict join
3604        let dict_struct_fields = Fields::from(vec![
3605            Field::new(
3606                "c0",
3607                DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8)),
3608                true,
3609            ),
3610            Field::new("c1", DataType::Int64, true),
3611        ]);
3612        let make_dict_struct = |c0: ArrayRef, c1: ArrayRef| -> ArrayRef {
3613            let pairs: Vec<(FieldRef, ArrayRef)> =
3614                dict_struct_fields.iter().cloned().zip([c0, c1]).collect();
3615            Arc::new(StructArray::from(pairs))
3616        };
3617        assert_eq!(
3618            expected,
3619            eval_in_list_from_array(
3620                make_dict_struct(
3621                    wrap_in_dict(Arc::clone(&utf8_needle)),
3622                    Arc::new(Int64Array::from(vec![1, 4, 2])),
3623                ),
3624                make_dict_struct(
3625                    wrap_in_dict(Arc::clone(&utf8_in)),
3626                    Arc::new(Int64Array::from(vec![1, 2, 3])),
3627                ),
3628            )?
3629        );
3630
3631        Ok(())
3632    }
3633
3634    fn make_int32_dict_array(values: Vec<Option<i32>>) -> ArrayRef {
3635        let mut builder = PrimitiveDictionaryBuilder::<Int8Type, Int32Type>::new();
3636        for v in values {
3637            match v {
3638                Some(val) => builder.append_value(val),
3639                None => builder.append_null(),
3640            }
3641        }
3642        Arc::new(builder.finish())
3643    }
3644
3645    fn make_f64_dict_array(values: Vec<Option<f64>>) -> ArrayRef {
3646        let mut builder = PrimitiveDictionaryBuilder::<Int8Type, Float64Type>::new();
3647        for v in values {
3648            match v {
3649                Some(val) => builder.append_value(val),
3650                None => builder.append_null(),
3651            }
3652        }
3653        Arc::new(builder.finish())
3654    }
3655
3656    #[test]
3657    fn test_try_new_from_array_dict_haystack_int32() -> Result<()> {
3658        let schema = Schema::new(vec![Field::new("a", DataType::Int32, false)]);
3659        let needle = Int32Array::from(vec![1, 2, 3, 4]);
3660        let batch =
3661            RecordBatch::try_new(Arc::new(schema.clone()), vec![Arc::new(needle)])?;
3662
3663        let haystack = make_int32_dict_array(vec![Some(1), None, Some(3)]);
3664
3665        let col_a = col("a", &schema)?;
3666        let expr = InListExpr::try_new_from_array(col_a, haystack, false, &schema)?;
3667        let result = expr.evaluate(&batch)?.into_array(batch.num_rows())?;
3668        let result = as_boolean_array(&result);
3669        assert_eq!(
3670            result,
3671            &BooleanArray::from(vec![Some(true), None, Some(true), None])
3672        );
3673
3674        Ok(())
3675    }
3676
3677    #[test]
3678    fn test_in_list_from_array_type_mismatch_errors() -> Result<()> {
3679        // Utf8 needle, Dict(Utf8) in_array: now works with dict haystack support
3680        assert_eq!(
3681            BooleanArray::from(vec![Some(true), Some(false), Some(true)]),
3682            eval_in_list_from_array(
3683                Arc::new(StringArray::from(vec!["a", "d", "b"])),
3684                wrap_in_dict(Arc::new(StringArray::from(vec!["a", "b", "c"]))),
3685            )?
3686        );
3687
3688        // Dict(Utf8) needle, Int64 in_array: type validation rejects at construction
3689        let err = eval_in_list_from_array(
3690            wrap_in_dict(Arc::new(StringArray::from(vec!["a", "d", "b"]))),
3691            Arc::new(Int64Array::from(vec![1, 2, 3])),
3692        )
3693        .unwrap_err()
3694        .to_string();
3695        assert!(err.contains("The data type inlist should be same"), "{err}");
3696
3697        // Dict(Int64) needle, Dict(Utf8) in_array: both Dict but different
3698        // value types, type validation rejects at construction
3699        let err = eval_in_list_from_array(
3700            wrap_in_dict(Arc::new(Int64Array::from(vec![1, 4, 2]))),
3701            wrap_in_dict(Arc::new(StringArray::from(vec!["a", "b", "c"]))),
3702        )
3703        .unwrap_err()
3704        .to_string();
3705        assert!(err.contains("The data type inlist should be same"), "{err}");
3706
3707        Ok(())
3708    }
3709
3710    #[test]
3711    fn test_try_new_from_array_dict_haystack_negated() -> Result<()> {
3712        let schema = Schema::new(vec![Field::new("a", DataType::Int32, false)]);
3713        let needle = Int32Array::from(vec![1, 2, 3, 4]);
3714        let batch =
3715            RecordBatch::try_new(Arc::new(schema.clone()), vec![Arc::new(needle)])?;
3716
3717        let haystack = make_int32_dict_array(vec![Some(1), None, Some(3)]);
3718
3719        let col_a = col("a", &schema)?;
3720        let expr = InListExpr::try_new_from_array(col_a, haystack, true, &schema)?;
3721        let result = expr.evaluate(&batch)?.into_array(batch.num_rows())?;
3722        let result = as_boolean_array(&result);
3723        assert_eq!(
3724            result,
3725            &BooleanArray::from(vec![Some(false), None, Some(false), None])
3726        );
3727
3728        Ok(())
3729    }
3730
3731    #[test]
3732    fn test_try_new_from_array_dict_haystack_utf8() -> Result<()> {
3733        let schema = Schema::new(vec![Field::new("a", DataType::Utf8, false)]);
3734        let needle = StringArray::from(vec!["a", "b", "c"]);
3735        let batch =
3736            RecordBatch::try_new(Arc::new(schema.clone()), vec![Arc::new(needle)])?;
3737
3738        let dict_builder = StringDictionaryBuilder::<Int8Type>::new();
3739        let mut builder = dict_builder;
3740        builder.append_value("a");
3741        builder.append_value("c");
3742        let haystack: ArrayRef = Arc::new(builder.finish());
3743
3744        let col_a = col("a", &schema)?;
3745        let expr = InListExpr::try_new_from_array(col_a, haystack, false, &schema)?;
3746        let result = expr.evaluate(&batch)?.into_array(batch.num_rows())?;
3747        let result = as_boolean_array(&result);
3748        assert_eq!(
3749            result,
3750            &BooleanArray::from(vec![Some(true), Some(false), Some(true)])
3751        );
3752
3753        Ok(())
3754    }
3755
3756    #[test]
3757    fn test_try_new_from_array_dict_needle_and_plain_haystack() -> Result<()> {
3758        let schema = Schema::new(vec![Field::new(
3759            "a",
3760            DataType::Dictionary(Box::new(DataType::Int8), Box::new(DataType::Int32)),
3761            false,
3762        )]);
3763
3764        let needle = make_int32_dict_array(vec![Some(1), Some(2), Some(3), Some(4)]);
3765        let batch =
3766            RecordBatch::try_new(Arc::new(schema.clone()), vec![Arc::clone(&needle)])?;
3767
3768        let haystack: ArrayRef = Arc::new(Int32Array::from(vec![1, 3]));
3769        let col_a = col("a", &schema)?;
3770        let expr = InListExpr::try_new_from_array(col_a, haystack, false, &schema)?;
3771        let result = expr.evaluate(&batch)?.into_array(batch.num_rows())?;
3772        let result = as_boolean_array(&result);
3773        assert_eq!(
3774            result,
3775            &BooleanArray::from(vec![Some(true), Some(false), Some(true), Some(false)])
3776        );
3777
3778        Ok(())
3779    }
3780
3781    #[test]
3782    fn test_try_new_from_array_dict_haystack_float64() -> Result<()> {
3783        let schema = Schema::new(vec![Field::new("a", DataType::Float64, false)]);
3784        let needle = Float64Array::from(vec![1.0, 2.0, 3.0]);
3785        let batch =
3786            RecordBatch::try_new(Arc::new(schema.clone()), vec![Arc::new(needle)])?;
3787
3788        let haystack = make_f64_dict_array(vec![Some(1.0), Some(3.0)]);
3789
3790        let col_a = col("a", &schema)?;
3791        let expr = InListExpr::try_new_from_array(col_a, haystack, false, &schema)?;
3792        let result = expr.evaluate(&batch)?.into_array(batch.num_rows())?;
3793        let result = as_boolean_array(&result);
3794        assert_eq!(
3795            result,
3796            &BooleanArray::from(vec![Some(true), Some(false), Some(true)])
3797        );
3798
3799        Ok(())
3800    }
3801
3802    #[test]
3803    fn test_try_new_from_array_type_mismatch_rejects() -> Result<()> {
3804        let schema = Schema::new(vec![Field::new("a", DataType::Int32, false)]);
3805        let col_a = col("a", &schema)?;
3806        let haystack: ArrayRef = Arc::new(Float64Array::from(vec![1.0, 2.0]));
3807
3808        let result = InListExpr::try_new_from_array(col_a, haystack, false, &schema);
3809        assert!(result.is_err());
3810        Ok(())
3811    }
3812
3813    #[test]
3814    fn test_try_new_from_array_struct_haystack() -> Result<()> {
3815        let struct_fields = Fields::from(vec![
3816            Field::new("x", DataType::Int32, false),
3817            Field::new("y", DataType::Utf8, false),
3818        ]);
3819        let struct_dt = DataType::Struct(struct_fields.clone());
3820        let schema = Schema::new(vec![Field::new("a", struct_dt, true)]);
3821
3822        // Needle: [{1,"a"}, {2,"b"}, NULL, {4,"d"}]
3823        let needle = Arc::new(StructArray::new(
3824            struct_fields.clone(),
3825            vec![
3826                Arc::new(Int32Array::from(vec![1, 2, 3, 4])),
3827                Arc::new(StringArray::from(vec!["a", "b", "c", "d"])),
3828            ],
3829            Some(vec![true, true, false, true].into()),
3830        ));
3831        let batch = RecordBatch::try_new(Arc::new(schema.clone()), vec![needle])?;
3832
3833        // Haystack: [{1,"a"}, {4,"d"}]
3834        let haystack: ArrayRef = Arc::new(StructArray::new(
3835            struct_fields,
3836            vec![
3837                Arc::new(Int32Array::from(vec![1, 4])),
3838                Arc::new(StringArray::from(vec!["a", "d"])),
3839            ],
3840            None,
3841        ));
3842
3843        let col_a = col("a", &schema)?;
3844        let expr = InListExpr::try_new_from_array(
3845            Arc::clone(&col_a),
3846            Arc::clone(&haystack),
3847            false,
3848            &schema,
3849        )?;
3850        let result = expr.evaluate(&batch)?.into_array(batch.num_rows())?;
3851        let result = as_boolean_array(&result);
3852        // {1,"a"} -> true, {2,"b"} -> false, NULL -> NULL, {4,"d"} -> true
3853        assert_eq!(
3854            result,
3855            &BooleanArray::from(vec![Some(true), Some(false), None, Some(true)])
3856        );
3857
3858        // Negated path
3859        let expr = InListExpr::try_new_from_array(col_a, haystack, true, &schema)?;
3860        let result = expr.evaluate(&batch)?.into_array(batch.num_rows())?;
3861        let result = as_boolean_array(&result);
3862        assert_eq!(
3863            result,
3864            &BooleanArray::from(vec![Some(false), Some(true), None, Some(false)])
3865        );
3866
3867        Ok(())
3868    }
3869}
3870
3871#[cfg(all(test, feature = "proto"))]
3872mod proto_tests {
3873    use super::*;
3874    use crate::expressions::{Column, col, lit};
3875    use crate::proto_test_util::{
3876        StubDecoder, StubEncoder, UnreachableDecoder, column_node,
3877    };
3878    use arrow::datatypes::Field;
3879    use datafusion_common::DataFusionError;
3880    use datafusion_physical_expr_common::physical_expr::proto_decode::PhysicalExprDecodeCtx;
3881    use datafusion_physical_expr_common::physical_expr::proto_encode::PhysicalExprEncodeCtx;
3882    use datafusion_proto_models::protobuf::{
3883        PhysicalExprNode, PhysicalInListNode, physical_expr_node,
3884    };
3885
3886    /// Build an `InListExpr` proto node with the given children.
3887    fn in_list_node(
3888        expr: Option<Box<PhysicalExprNode>>,
3889        list: Vec<PhysicalExprNode>,
3890        negated: bool,
3891    ) -> PhysicalExprNode {
3892        PhysicalExprNode {
3893            expr_id: None,
3894            expr_type: Some(physical_expr_node::ExprType::InList(Box::new(
3895                PhysicalInListNode {
3896                    expr,
3897                    list,
3898                    negated,
3899                },
3900            ))),
3901        }
3902    }
3903
3904    /// An `InListExpr` over a column with one literal value.
3905    fn in_list_fixture() -> InListExpr {
3906        let schema = Schema::new(vec![Field::new("a", DataType::Int32, true)]);
3907        InListExpr::try_new(col("a", &schema).unwrap(), vec![lit(1)], false, &schema)
3908            .unwrap()
3909    }
3910
3911    #[test]
3912    fn try_to_proto_encodes_in_list() {
3913        let in_list = in_list_fixture();
3914        let encoder = StubEncoder::ok();
3915        let ctx = PhysicalExprEncodeCtx::new(&encoder);
3916
3917        let node = in_list
3918            .try_to_proto(&ctx)
3919            .unwrap()
3920            .expect("InListExpr should encode to Some(node)");
3921
3922        // Built-in exprs never set expr_id; only dynamic filters do.
3923        assert!(node.expr_id.is_none());
3924        let in_list_node = match node.expr_type {
3925            Some(physical_expr_node::ExprType::InList(boxed)) => *boxed,
3926            other => panic!("expected an InList node, got {other:?}"),
3927        };
3928        assert!(!in_list_node.negated);
3929        assert!(in_list_node.expr.is_some());
3930        assert_eq!(in_list_node.list.len(), 1);
3931    }
3932
3933    #[test]
3934    fn try_to_proto_propagates_expr_encode_error() {
3935        let in_list = in_list_fixture();
3936        let encoder = StubEncoder::failing_on(1);
3937        let ctx = PhysicalExprEncodeCtx::new(&encoder);
3938        let err = in_list.try_to_proto(&ctx).unwrap_err();
3939        assert!(matches!(err, DataFusionError::Internal(msg) if msg.contains("call 1")));
3940    }
3941
3942    #[test]
3943    fn try_to_proto_propagates_list_encode_error() {
3944        let in_list = in_list_fixture();
3945        // Call 1 is for `expr`, Call 2 is for the first element of `list`
3946        let encoder = StubEncoder::failing_on(2);
3947        let ctx = PhysicalExprEncodeCtx::new(&encoder);
3948        let err = in_list.try_to_proto(&ctx).unwrap_err();
3949        assert!(matches!(err, DataFusionError::Internal(msg) if msg.contains("call 2")));
3950    }
3951
3952    #[test]
3953    fn try_from_proto_decodes_in_list() {
3954        let node = in_list_node(
3955            Some(Box::new(column_node("a"))),
3956            vec![column_node("b")],
3957            true,
3958        );
3959        let schema = Schema::new(vec![Field::new("decoded", DataType::Int32, true)]);
3960        let decoder = StubDecoder::ok();
3961        let ctx = PhysicalExprDecodeCtx::new(&schema, &decoder);
3962
3963        let decoded = InListExpr::try_from_proto(&node, &ctx).unwrap();
3964        let in_list = decoded
3965            .downcast_ref::<InListExpr>()
3966            .expect("decoded expr should be an InListExpr");
3967
3968        assert!(in_list.negated());
3969        assert!(in_list.expr().downcast_ref::<Column>().is_some());
3970        assert_eq!(in_list.list().len(), 1);
3971    }
3972
3973    #[test]
3974    fn try_from_proto_rejects_non_in_list_node() {
3975        let node = column_node("a");
3976        let schema = Schema::empty();
3977        let decoder = UnreachableDecoder;
3978        let ctx = PhysicalExprDecodeCtx::new(&schema, &decoder);
3979
3980        let err = InListExpr::try_from_proto(&node, &ctx).unwrap_err();
3981        assert!(matches!(
3982            err,
3983            DataFusionError::Internal(msg) if msg.contains("PhysicalExprNode is not a InList")
3984        ));
3985    }
3986
3987    #[test]
3988    fn try_from_proto_rejects_missing_expr() {
3989        let node = in_list_node(None, vec![column_node("b")], false);
3990        let schema = Schema::empty();
3991        let decoder = UnreachableDecoder;
3992        let ctx = PhysicalExprDecodeCtx::new(&schema, &decoder);
3993
3994        let err = InListExpr::try_from_proto(&node, &ctx).unwrap_err();
3995        assert!(matches!(
3996            err,
3997            DataFusionError::Internal(msg) if msg.contains("InListExpr is missing required field 'expr'")
3998        ));
3999    }
4000
4001    #[test]
4002    fn try_from_proto_propagates_expr_decode_error() {
4003        let node = in_list_node(
4004            Some(Box::new(column_node("a"))),
4005            vec![column_node("b")],
4006            false,
4007        );
4008        let schema = Schema::empty();
4009        let decoder = StubDecoder::failing_on(1);
4010        let ctx = PhysicalExprDecodeCtx::new(&schema, &decoder);
4011        let err = InListExpr::try_from_proto(&node, &ctx).unwrap_err();
4012        assert!(matches!(err, DataFusionError::Internal(msg) if msg.contains("call 1")));
4013    }
4014
4015    #[test]
4016    fn try_from_proto_propagates_list_decode_error() {
4017        let node = in_list_node(
4018            Some(Box::new(column_node("a"))),
4019            vec![column_node("b")],
4020            false,
4021        );
4022        let schema = Schema::empty();
4023        // Call 1 is `expr`, Call 2 is the first element of `list`
4024        let decoder = StubDecoder::failing_on(2);
4025        let ctx = PhysicalExprDecodeCtx::new(&schema, &decoder);
4026        let err = InListExpr::try_from_proto(&node, &ctx).unwrap_err();
4027        assert!(matches!(err, DataFusionError::Internal(msg) if msg.contains("call 2")));
4028    }
4029}