@@ -249,16 +249,14 @@ fn scalar_function_to_iceberg_predicate(func_name: &str, args: &[Expr]) -> Trans
249249/// wrapped column is NaN — so both `isnan(arg)` and `NOT isnan(arg)` are sound:
250250///
251251/// * negation: `-x` is NaN iff `x` is NaN
252+ /// * `abs(x)`: `abs(x)` is NaN iff `x` is NaN
252253/// * casts between numeric types preserve NaN
253254/// * `x + c`, `c + x`, `x - c`, `c - x` for a finite literal `c`
254255/// * `x * c`, `c * x`, `x / c` for a finite, non-zero literal `c`
255256///
256257/// Multiplication/division by zero and `c / x` are intentionally rejected: e.g.
257258/// `x * 0` is NaN when `x` is `±inf`, so it does not imply `x` is NaN.
258259///
259- /// Scalar-function arguments (e.g. `abs(x)`) are intentionally left for a
260- /// follow-up; only references, negation, casts and arithmetic are resolved here.
261- ///
262260/// [`IcebergTableProvider::supports_filters_pushdown`]: crate::table::IcebergTableProvider
263261fn resolve_nan_preserving_reference ( expr : & Expr ) -> Option < Reference > {
264262 match expr {
@@ -272,9 +270,12 @@ fn resolve_nan_preserving_reference(expr: &Expr) -> Option<Reference> {
272270 }
273271 resolve_nan_preserving_reference ( & cast. expr )
274272 }
273+ Expr :: ScalarFunction ( ScalarFunction { func, args } )
274+ if func. name ( ) == "abs" && args. len ( ) == 1 =>
275+ {
276+ resolve_nan_preserving_reference ( & args[ 0 ] )
277+ }
275278 Expr :: BinaryExpr ( binary) => resolve_nan_preserving_binary ( binary) ,
276- // TODO(#2154): resolve NaN-preserving scalar-function arguments such as
277- // `abs(x)` (and similar, e.g. `signum`).
278279 _ => None ,
279280 }
280281}
@@ -338,14 +339,9 @@ fn is_finite_nonzero_literal(expr: &Expr) -> bool {
338339/// Returns the value of `expr` as an `f64` if it is a numeric literal. Used only
339340/// to inspect the finiteness and sign of literals (precision loss is irrelevant).
340341fn literal_as_f64 ( expr : & Expr ) -> Option < f64 > {
341- match expr {
342- Expr :: Literal ( value, _) => scalar_value_as_f64 ( value) ,
343- _ => None ,
344- }
345- }
346-
347- /// Extracts a numeric [`ScalarValue`] as an `f64`.
348- fn scalar_value_as_f64 ( value : & ScalarValue ) -> Option < f64 > {
342+ let Expr :: Literal ( value, _) = expr else {
343+ return None ;
344+ } ;
349345 match value {
350346 ScalarValue :: Int8 ( Some ( v) ) => Some ( * v as f64 ) ,
351347 ScalarValue :: Int16 ( Some ( v) ) => Some ( * v as f64 ) ,
@@ -864,6 +860,13 @@ mod tests {
864860 assert_eq ! ( predicate, !Reference :: new( "qux" ) . is_nan( ) ) ;
865861 }
866862
863+ #[ test]
864+ fn test_predicate_conversion_with_isnan_abs ( ) {
865+ // abs(x) is NaN iff x is NaN
866+ let predicate = convert_to_iceberg_predicate ( "isnan(abs(qux))" ) . unwrap ( ) ;
867+ assert_eq ! ( predicate, Reference :: new( "qux" ) . is_nan( ) ) ;
868+ }
869+
867870 #[ test]
868871 fn test_predicate_conversion_with_isnan_additive ( ) {
869872 // x + c, c + x, x - c, c - x are NaN iff x is NaN (for finite c)
@@ -891,7 +894,7 @@ mod tests {
891894 #[ test]
892895 fn test_predicate_conversion_with_isnan_nested_expr ( ) {
893896 // Nested NaN-preserving transformations resolve to the inner column
894- let predicate = convert_to_iceberg_predicate ( "isnan(-(qux + 1) * 3)" ) . unwrap ( ) ;
897+ let predicate = convert_to_iceberg_predicate ( "isnan(-(abs( qux) + 1) * 3)" ) . unwrap ( ) ;
895898 assert_eq ! ( predicate, Reference :: new( "qux" ) . is_nan( ) ) ;
896899 }
897900
@@ -920,9 +923,6 @@ mod tests {
920923 // single column reference.
921924 assert_eq ! ( convert_to_iceberg_predicate( "isnan(qux + foo)" ) , None ) ;
922925
923- // Scalar-function arguments (e.g. abs) are left for a follow-up.
924- assert_eq ! ( convert_to_iceberg_predicate( "isnan(abs(qux))" ) , None ) ;
925-
926926 // Unknown scalar functions are not pushed down.
927927 assert_eq ! ( convert_to_iceberg_predicate( "isnan(sqrt(qux))" ) , None ) ;
928928 }
0 commit comments