@@ -1912,31 +1912,40 @@ def test_strict_metrics_eval_bounds_after_promotion(
19121912 assert evaluator .eval (data_file ) == expected
19131913
19141914
1915+ def test_bind_preserves_out_of_range_literals () -> None :
1916+ """Binding keeps the literals the caller wrote, so the bound predicate still round-trips."""
1917+ schema = Schema (NestedField (1 , "id" , IntegerType (), required = False ))
1918+ literals = [1 , IntegerType .max + 1 ]
1919+
1920+ for predicate in [In ("id" , literals ), NotIn ("id" , literals )]:
1921+ bound = predicate .bind (schema )
1922+ assert {lit .value for lit in bound .literals } == set (literals )
1923+ assert bound .as_unbound (bound .term .ref ().field .name , bound .literals ) == predicate
1924+ # Only the values the field can hold reach an evaluator
1925+ assert bound .value_set == {1 }
1926+
1927+
19151928def test_above_int_bounds_in () -> None :
19161929 schema = Schema (NestedField (1 , "id" , IntegerType (), required = False ))
19171930 above_max = IntegerType .max + 1
19181931
1919- assert In ("id" , [1 , above_max ]).bind (schema ) == EqualTo ("id" , 1 ).bind (schema )
1920- assert NotIn ("id" , [1 , above_max ]).bind (schema ) == NotEqualTo ("id" , 1 ).bind (schema )
1921- assert In ("id" , [above_max ]).bind (schema ) == AlwaysFalse ()
1922- assert NotIn ("id" , [above_max ]).bind (schema ) == AlwaysTrue ()
1923-
1924- # The clamped literal used to match the field's maximum
1932+ # The out-of-range literal used to be clamped to the maximum and match rows there
19251933 assert expression_evaluator (schema , In ("id" , [1 , above_max ]), True )(Record (IntegerType .max )) is False
19261934 assert expression_evaluator (schema , NotIn ("id" , [1 , above_max ]), True )(Record (IntegerType .max )) is True
1935+ assert expression_evaluator (schema , In ("id" , [1 , above_max ]), True )(Record (1 )) is True
1936+ assert In ("id" , [above_max ]).bind (schema ) == AlwaysFalse ()
1937+ assert NotIn ("id" , [above_max ]).bind (schema ) == AlwaysTrue ()
19271938
19281939
19291940def test_below_int_bounds_in () -> None :
19301941 schema = Schema (NestedField (1 , "id" , IntegerType (), required = False ))
19311942 below_min = IntegerType .min - 1
19321943
1933- assert In ("id" , [1 , below_min ]).bind (schema ) == EqualTo ("id" , 1 ).bind (schema )
1934- assert NotIn ("id" , [1 , below_min ]).bind (schema ) == NotEqualTo ("id" , 1 ).bind (schema )
1935- assert In ("id" , [below_min ]).bind (schema ) == AlwaysFalse ()
1936- assert NotIn ("id" , [below_min ]).bind (schema ) == AlwaysTrue ()
1937-
19381944 assert expression_evaluator (schema , In ("id" , [1 , below_min ]), True )(Record (IntegerType .min )) is False
19391945 assert expression_evaluator (schema , NotIn ("id" , [1 , below_min ]), True )(Record (IntegerType .min )) is True
1946+ assert expression_evaluator (schema , In ("id" , [1 , below_min ]), True )(Record (1 )) is True
1947+ assert In ("id" , [below_min ]).bind (schema ) == AlwaysFalse ()
1948+ assert NotIn ("id" , [below_min ]).bind (schema ) == AlwaysTrue ()
19401949
19411950
19421951@pytest .mark .parametrize (
@@ -1952,8 +1961,6 @@ def test_int_bounds_in_all_literals_out_of_range(literals: list[int]) -> None:
19521961 in_expr = In ("id" , literals )
19531962 not_in_expr = NotIn ("id" , literals )
19541963
1955- assert in_expr .bind (schema ) == AlwaysFalse ()
1956- assert not_in_expr .bind (schema ) == AlwaysTrue ()
19571964 for value in [None , IntegerType .min , 0 , IntegerType .max ]:
19581965 assert expression_evaluator (schema , in_expr , True )(Record (value )) is False
19591966 assert expression_evaluator (schema , not_in_expr , True )(Record (value )) is True
@@ -1965,8 +1972,6 @@ def test_int_bounds_in_keeps_multiple_literals() -> None:
19651972 in_expr = In ("id" , literals )
19661973 not_in_expr = NotIn ("id" , literals )
19671974
1968- assert in_expr .bind (schema ) == In ("id" , [1 , 2 ]).bind (schema )
1969- assert not_in_expr .bind (schema ) == NotIn ("id" , [1 , 2 ]).bind (schema )
19701975 values = [None , 1 , 2 , 3 , IntegerType .min , IntegerType .max ]
19711976 eval_in = expression_evaluator (schema , in_expr , True )
19721977 eval_not_in = expression_evaluator (schema , not_in_expr , True )
@@ -1988,13 +1993,13 @@ def test_int_bounds_in_metrics(schema_data_file: Schema, boundary: int, out_of_r
19881993
19891994
19901995def test_int_bounds_in_keeps_the_boundary_value () -> None :
1991- """A sentinel is equal to the boundary literal it clamps to , so it must not absorb it."""
1996+ """A converted out-of-range literal clamps to the boundary, so it must not shadow it."""
19921997 schema = Schema (NestedField (1 , "id" , IntegerType (), required = False ))
19931998
1994- assert In ( "id" , [ IntegerType .max , IntegerType .max + 1 ]). bind ( schema ) == EqualTo ( "id" , IntegerType .max ). bind ( schema )
1995- assert NotIn ( "id" , [ IntegerType . max , IntegerType . max + 1 ]). bind (schema ) == NotEqualTo ("id" , IntegerType . max ). bind ( schema )
1996- assert In ( "id" , [ IntegerType . min , IntegerType . min - 1 ]). bind (schema ) == EqualTo ("id" , IntegerType . min ). bind ( schema )
1997- assert NotIn ( "id" , [ IntegerType . min , IntegerType . min - 1 ]). bind ( schema ) == NotEqualTo ( "id" , IntegerType . min ). bind ( schema )
1998-
1999- assert expression_evaluator ( schema , In ( "id" , [ IntegerType . max , IntegerType . max + 1 ]), True ) (Record (IntegerType . max )) is True
2000- assert expression_evaluator ( schema , In ( "id" , [ IntegerType . min , IntegerType . min - 1 ]), True ) (Record (IntegerType . min )) is True
1999+ for boundary , out_of_range in [( IntegerType .max , IntegerType .max + 1 ), ( IntegerType . min , IntegerType .min - 1 )]:
2000+ eval_in = expression_evaluator (schema , In ("id" , [ boundary , out_of_range ]), True )
2001+ eval_not_in = expression_evaluator (schema , NotIn ("id" , [ boundary , out_of_range ]), True )
2002+ assert eval_in ( Record ( boundary )) is True
2003+ assert eval_not_in ( Record ( boundary )) is False
2004+ assert eval_in (Record (0 )) is False
2005+ assert eval_not_in (Record (0 )) is True
0 commit comments