From 5ff1cd20ba117870706bf924620e3a731c2198f0 Mon Sep 17 00:00:00 2001 From: Devanshu Date: Sun, 1 Feb 2026 16:21:30 +0700 Subject: [PATCH 1/8] Add decimal support --- datafusion/functions/src/math/floor.rs | 143 ++++++++++++++++++++++++- 1 file changed, 139 insertions(+), 4 deletions(-) diff --git a/datafusion/functions/src/math/floor.rs b/datafusion/functions/src/math/floor.rs index 7c7604b7fd88..1631fa2e0abe 100644 --- a/datafusion/functions/src/math/floor.rs +++ b/datafusion/functions/src/math/floor.rs @@ -19,9 +19,10 @@ use std::any::Any; use std::sync::Arc; use arrow::array::{ArrayRef, AsArray}; +use arrow::compute::{DecimalCast, rescale_decimal}; use arrow::datatypes::{ - DataType, Decimal32Type, Decimal64Type, Decimal128Type, Decimal256Type, Float32Type, - Float64Type, + ArrowNativeTypeOp, DataType, DecimalType, Decimal32Type, Decimal64Type, Decimal128Type, + Decimal256Type, Float32Type, Float64Type, }; use datafusion_common::{Result, ScalarValue, exec_err}; use datafusion_expr::interval_arithmetic::Interval; @@ -230,8 +231,6 @@ impl ScalarUDFImpl for FloorFunc { // Compute lower bound (N) and upper bound (N + 1) using helper functions let Some((lower, upper)) = (match lit_value { - // Decimal types should be supported and tracked in - // https://github.com/apache/datafusion/issues/20080 // Floating-point types ScalarValue::Float64(Some(n)) => float_preimage_bounds(*n).map(|(lo, hi)| { ( @@ -260,6 +259,18 @@ impl ScalarUDFImpl for FloorFunc { (ScalarValue::Int64(Some(lo)), ScalarValue::Int64(Some(hi))) }), + // Decimal types + ScalarValue::Decimal32(Some(n), precision, scale) => { + decimal_preimage_bounds::(*n, *precision, *scale).map( + |(lo, hi)| { + ( + ScalarValue::Decimal32(Some(lo), *precision, *scale), + ScalarValue::Decimal32(Some(hi), *precision, *scale), + ) + }, + ) + } + // Unsupported types _ => None, }) else { @@ -310,6 +321,41 @@ fn int_preimage_bounds(n: I) -> Option<(I, I)> { Some((n, upper)) } +/// Compute preimage bounds for floor function on decimal types. +/// For floor(x) = n, the preimage is [n, n+1). +/// Returns None if: +/// - The value has a fractional part (floor always returns integers) +/// - Adding 1 would overflow +fn decimal_preimage_bounds( + value: D::Native, + precision: u8, + scale: i8, +) -> Option<(D::Native, D::Native)> +where + D::Native: DecimalCast + ArrowNativeTypeOp + std::ops::Rem, +{ + // Use rescale_decimal to compute "1" at target scale (avoids manual pow) + // Convert integer 1 (scale=0) to the target scale + let one_scaled: D::Native = rescale_decimal::( + D::Native::ONE, // value = 1 + 1, // input_precision = 1 + 0, // input_scale = 0 (integer) + precision, // output_precision + scale, // output_scale + )?; + + // floor always returns an integer, so if value has a fractional part, there's no solution + // Check: value % one_scaled != 0 means fractional part exists + if scale > 0 && value % one_scaled != D::Native::ZERO { + return None; + } + + // Compute upper bound using checked addition + let upper = value.add_checked(one_scaled).ok()?; + + Some((value, upper)) +} + #[cfg(test)] mod tests { use super::*; @@ -463,4 +509,93 @@ mod tests { "Expected None for zero args" ); } + + // ============ Decimal32 Tests (mirrors float/int tests) ============ + + #[test] + fn test_floor_preimage_decimal_valid_cases() { + // Positive integer decimal: 100.00 (scale=2, so raw=10000) + // floor(x) = 100.00 -> x in [100.00, 101.00) + assert_preimage_range( + ScalarValue::Decimal32(Some(10000), 9, 2), + ScalarValue::Decimal32(Some(10000), 9, 2), // 100.00 + ScalarValue::Decimal32(Some(10100), 9, 2), // 101.00 + ); + + // Smaller positive: 50.00 + assert_preimage_range( + ScalarValue::Decimal32(Some(5000), 9, 2), + ScalarValue::Decimal32(Some(5000), 9, 2), // 50.00 + ScalarValue::Decimal32(Some(5100), 9, 2), // 51.00 + ); + + // Negative integer decimal: -5.00 + assert_preimage_range( + ScalarValue::Decimal32(Some(-500), 9, 2), + ScalarValue::Decimal32(Some(-500), 9, 2), // -5.00 + ScalarValue::Decimal32(Some(-400), 9, 2), // -4.00 + ); + + // Zero: 0.00 + assert_preimage_range( + ScalarValue::Decimal32(Some(0), 9, 2), + ScalarValue::Decimal32(Some(0), 9, 2), // 0.00 + ScalarValue::Decimal32(Some(100), 9, 2), // 1.00 + ); + + // Scale 0 (pure integer): 42 + assert_preimage_range( + ScalarValue::Decimal32(Some(42), 9, 0), + ScalarValue::Decimal32(Some(42), 9, 0), + ScalarValue::Decimal32(Some(43), 9, 0), + ); + } + + #[test] + fn test_floor_preimage_decimal_non_integer() { + // floor(x) = 1.30 has NO SOLUTION because floor always returns an integer + // Therefore preimage should return None for non-integer decimals + assert_preimage_none(ScalarValue::Decimal32(Some(130), 9, 2)); // 1.30 + assert_preimage_none(ScalarValue::Decimal32(Some(-250), 9, 2)); // -2.50 + assert_preimage_none(ScalarValue::Decimal32(Some(370), 9, 2)); // 3.70 + assert_preimage_none(ScalarValue::Decimal32(Some(1), 9, 2)); // 0.01 + } + + #[test] + fn test_floor_preimage_decimal_overflow() { + // Test near i32::MAX where adding scale_factor would overflow + // For scale=2, we add 100, so i32::MAX - 50 would overflow + assert_preimage_none(ScalarValue::Decimal32(Some(i32::MAX - 50), 9, 2)); + + // For scale=0, we add 1, so i32::MAX would overflow + assert_preimage_none(ScalarValue::Decimal32(Some(i32::MAX), 9, 0)); + } + + #[test] + fn test_floor_preimage_decimal_edge_cases() { + // Large value that doesn't overflow + // i32::MAX = 2147483647, with scale=2, max safe is around i32::MAX - 100 + let safe_max = i32::MAX - 100; + // Make it divisible by 100 for scale=2 + let safe_max_aligned = (safe_max / 100) * 100; + assert_preimage_range( + ScalarValue::Decimal32(Some(safe_max_aligned), 9, 2), + ScalarValue::Decimal32(Some(safe_max_aligned), 9, 2), + ScalarValue::Decimal32(Some(safe_max_aligned + 100), 9, 2), + ); + + // Negative edge: i32::MIN should work since we're adding (not subtracting) + // i32::MIN = -2147483648, aligned to scale=2 + let min_aligned = (i32::MIN / 100) * 100; + assert_preimage_range( + ScalarValue::Decimal32(Some(min_aligned), 9, 2), + ScalarValue::Decimal32(Some(min_aligned), 9, 2), + ScalarValue::Decimal32(Some(min_aligned + 100), 9, 2), + ); + } + + #[test] + fn test_floor_preimage_decimal_null() { + assert_preimage_none(ScalarValue::Decimal32(None, 9, 2)); + } } From 77e3e4c660e41c3c962e5d7e31bdf3ec09353a86 Mon Sep 17 00:00:00 2001 From: Devanshu Date: Sun, 1 Feb 2026 16:39:13 +0700 Subject: [PATCH 2/8] Update for other decimal types --- datafusion/functions/src/math/floor.rs | 208 +++++++++++++++++++++++-- 1 file changed, 193 insertions(+), 15 deletions(-) diff --git a/datafusion/functions/src/math/floor.rs b/datafusion/functions/src/math/floor.rs index 1631fa2e0abe..f4c85032ca51 100644 --- a/datafusion/functions/src/math/floor.rs +++ b/datafusion/functions/src/math/floor.rs @@ -21,8 +21,8 @@ use std::sync::Arc; use arrow::array::{ArrayRef, AsArray}; use arrow::compute::{DecimalCast, rescale_decimal}; use arrow::datatypes::{ - ArrowNativeTypeOp, DataType, DecimalType, Decimal32Type, Decimal64Type, Decimal128Type, - Decimal256Type, Float32Type, Float64Type, + ArrowNativeTypeOp, DataType, Decimal32Type, Decimal64Type, Decimal128Type, + Decimal256Type, DecimalType, Float32Type, Float64Type, }; use datafusion_common::{Result, ScalarValue, exec_err}; use datafusion_expr::interval_arithmetic::Interval; @@ -270,6 +270,36 @@ impl ScalarUDFImpl for FloorFunc { }, ) } + ScalarValue::Decimal64(Some(n), precision, scale) => { + decimal_preimage_bounds::(*n, *precision, *scale).map( + |(lo, hi)| { + ( + ScalarValue::Decimal64(Some(lo), *precision, *scale), + ScalarValue::Decimal64(Some(hi), *precision, *scale), + ) + }, + ) + } + ScalarValue::Decimal128(Some(n), precision, scale) => { + decimal_preimage_bounds::(*n, *precision, *scale).map( + |(lo, hi)| { + ( + ScalarValue::Decimal128(Some(lo), *precision, *scale), + ScalarValue::Decimal128(Some(hi), *precision, *scale), + ) + }, + ) + } + ScalarValue::Decimal256(Some(n), precision, scale) => { + decimal_preimage_bounds::(*n, *precision, *scale).map( + |(lo, hi)| { + ( + ScalarValue::Decimal256(Some(lo), *precision, *scale), + ScalarValue::Decimal256(Some(hi), *precision, *scale), + ) + }, + ) + } // Unsupported types _ => None, @@ -359,6 +389,7 @@ where #[cfg(test)] mod tests { use super::*; + use arrow_buffer::i256; use datafusion_expr::col; /// Helper to test valid preimage cases that should return a Range @@ -514,6 +545,7 @@ mod tests { #[test] fn test_floor_preimage_decimal_valid_cases() { + // ===== Decimal32 ===== // Positive integer decimal: 100.00 (scale=2, so raw=10000) // floor(x) = 100.00 -> x in [100.00, 101.00) assert_preimage_range( @@ -539,7 +571,7 @@ mod tests { // Zero: 0.00 assert_preimage_range( ScalarValue::Decimal32(Some(0), 9, 2), - ScalarValue::Decimal32(Some(0), 9, 2), // 0.00 + ScalarValue::Decimal32(Some(0), 9, 2), // 0.00 ScalarValue::Decimal32(Some(100), 9, 2), // 1.00 ); @@ -549,53 +581,199 @@ mod tests { ScalarValue::Decimal32(Some(42), 9, 0), ScalarValue::Decimal32(Some(43), 9, 0), ); + + // ===== Decimal64 ===== + assert_preimage_range( + ScalarValue::Decimal64(Some(10000), 18, 2), + ScalarValue::Decimal64(Some(10000), 18, 2), // 100.00 + ScalarValue::Decimal64(Some(10100), 18, 2), // 101.00 + ); + + // Negative + assert_preimage_range( + ScalarValue::Decimal64(Some(-500), 18, 2), + ScalarValue::Decimal64(Some(-500), 18, 2), // -5.00 + ScalarValue::Decimal64(Some(-400), 18, 2), // -4.00 + ); + + // Zero + assert_preimage_range( + ScalarValue::Decimal64(Some(0), 18, 2), + ScalarValue::Decimal64(Some(0), 18, 2), + ScalarValue::Decimal64(Some(100), 18, 2), + ); + + // ===== Decimal128 ===== + assert_preimage_range( + ScalarValue::Decimal128(Some(10000), 38, 2), + ScalarValue::Decimal128(Some(10000), 38, 2), // 100.00 + ScalarValue::Decimal128(Some(10100), 38, 2), // 101.00 + ); + + // Negative + assert_preimage_range( + ScalarValue::Decimal128(Some(-500), 38, 2), + ScalarValue::Decimal128(Some(-500), 38, 2), // -5.00 + ScalarValue::Decimal128(Some(-400), 38, 2), // -4.00 + ); + + // Zero + assert_preimage_range( + ScalarValue::Decimal128(Some(0), 38, 2), + ScalarValue::Decimal128(Some(0), 38, 2), + ScalarValue::Decimal128(Some(100), 38, 2), + ); + + // ===== Decimal256 ===== + assert_preimage_range( + ScalarValue::Decimal256(Some(i256::from(10000)), 76, 2), + ScalarValue::Decimal256(Some(i256::from(10000)), 76, 2), // 100.00 + ScalarValue::Decimal256(Some(i256::from(10100)), 76, 2), // 101.00 + ); + + // Negative + assert_preimage_range( + ScalarValue::Decimal256(Some(i256::from(-500)), 76, 2), + ScalarValue::Decimal256(Some(i256::from(-500)), 76, 2), // -5.00 + ScalarValue::Decimal256(Some(i256::from(-400)), 76, 2), // -4.00 + ); + + // Zero + assert_preimage_range( + ScalarValue::Decimal256(Some(i256::ZERO), 76, 2), + ScalarValue::Decimal256(Some(i256::ZERO), 76, 2), + ScalarValue::Decimal256(Some(i256::from(100)), 76, 2), + ); } #[test] fn test_floor_preimage_decimal_non_integer() { // floor(x) = 1.30 has NO SOLUTION because floor always returns an integer // Therefore preimage should return None for non-integer decimals + + // Decimal32 assert_preimage_none(ScalarValue::Decimal32(Some(130), 9, 2)); // 1.30 assert_preimage_none(ScalarValue::Decimal32(Some(-250), 9, 2)); // -2.50 assert_preimage_none(ScalarValue::Decimal32(Some(370), 9, 2)); // 3.70 assert_preimage_none(ScalarValue::Decimal32(Some(1), 9, 2)); // 0.01 + + // Decimal64 + assert_preimage_none(ScalarValue::Decimal64(Some(130), 18, 2)); // 1.30 + assert_preimage_none(ScalarValue::Decimal64(Some(-250), 18, 2)); // -2.50 + + // Decimal128 + assert_preimage_none(ScalarValue::Decimal128(Some(130), 38, 2)); // 1.30 + assert_preimage_none(ScalarValue::Decimal128(Some(-250), 38, 2)); // -2.50 + + // Decimal256 + assert_preimage_none(ScalarValue::Decimal256(Some(i256::from(130)), 76, 2)); // 1.30 + assert_preimage_none(ScalarValue::Decimal256(Some(i256::from(-250)), 76, 2)); // -2.50 } #[test] fn test_floor_preimage_decimal_overflow() { - // Test near i32::MAX where adding scale_factor would overflow + // Test near MAX where adding scale_factor would overflow + + // Decimal32: i32::MAX // For scale=2, we add 100, so i32::MAX - 50 would overflow assert_preimage_none(ScalarValue::Decimal32(Some(i32::MAX - 50), 9, 2)); - // For scale=0, we add 1, so i32::MAX would overflow assert_preimage_none(ScalarValue::Decimal32(Some(i32::MAX), 9, 0)); + + // Decimal64: i64::MAX + assert_preimage_none(ScalarValue::Decimal64(Some(i64::MAX - 50), 18, 2)); + assert_preimage_none(ScalarValue::Decimal64(Some(i64::MAX), 18, 0)); + + // Decimal128: i128::MAX + assert_preimage_none(ScalarValue::Decimal128(Some(i128::MAX - 50), 38, 2)); + assert_preimage_none(ScalarValue::Decimal128(Some(i128::MAX), 38, 0)); + + // Decimal256: i256::MAX + assert_preimage_none(ScalarValue::Decimal256( + Some(i256::MAX.wrapping_sub(i256::from(50))), + 76, + 2, + )); + assert_preimage_none(ScalarValue::Decimal256(Some(i256::MAX), 76, 0)); } #[test] fn test_floor_preimage_decimal_edge_cases() { + // ===== Decimal32 ===== // Large value that doesn't overflow // i32::MAX = 2147483647, with scale=2, max safe is around i32::MAX - 100 - let safe_max = i32::MAX - 100; + let safe_max_32 = i32::MAX - 100; // Make it divisible by 100 for scale=2 - let safe_max_aligned = (safe_max / 100) * 100; + let safe_max_aligned_32 = (safe_max_32 / 100) * 100; assert_preimage_range( - ScalarValue::Decimal32(Some(safe_max_aligned), 9, 2), - ScalarValue::Decimal32(Some(safe_max_aligned), 9, 2), - ScalarValue::Decimal32(Some(safe_max_aligned + 100), 9, 2), + ScalarValue::Decimal32(Some(safe_max_aligned_32), 9, 2), + ScalarValue::Decimal32(Some(safe_max_aligned_32), 9, 2), + ScalarValue::Decimal32(Some(safe_max_aligned_32 + 100), 9, 2), ); // Negative edge: i32::MIN should work since we're adding (not subtracting) - // i32::MIN = -2147483648, aligned to scale=2 - let min_aligned = (i32::MIN / 100) * 100; + let min_aligned_32 = (i32::MIN / 100) * 100; + assert_preimage_range( + ScalarValue::Decimal32(Some(min_aligned_32), 9, 2), + ScalarValue::Decimal32(Some(min_aligned_32), 9, 2), + ScalarValue::Decimal32(Some(min_aligned_32 + 100), 9, 2), + ); + + // ===== Decimal64 ===== + let safe_max_64 = i64::MAX - 100; + let safe_max_aligned_64 = (safe_max_64 / 100) * 100; + assert_preimage_range( + ScalarValue::Decimal64(Some(safe_max_aligned_64), 18, 2), + ScalarValue::Decimal64(Some(safe_max_aligned_64), 18, 2), + ScalarValue::Decimal64(Some(safe_max_aligned_64 + 100), 18, 2), + ); + + let min_aligned_64 = (i64::MIN / 100) * 100; + assert_preimage_range( + ScalarValue::Decimal64(Some(min_aligned_64), 18, 2), + ScalarValue::Decimal64(Some(min_aligned_64), 18, 2), + ScalarValue::Decimal64(Some(min_aligned_64 + 100), 18, 2), + ); + + // ===== Decimal128 ===== + let safe_max_128 = i128::MAX - 100; + let safe_max_aligned_128 = (safe_max_128 / 100) * 100; + assert_preimage_range( + ScalarValue::Decimal128(Some(safe_max_aligned_128), 38, 2), + ScalarValue::Decimal128(Some(safe_max_aligned_128), 38, 2), + ScalarValue::Decimal128(Some(safe_max_aligned_128 + 100), 38, 2), + ); + + let min_aligned_128 = (i128::MIN / 100) * 100; + assert_preimage_range( + ScalarValue::Decimal128(Some(min_aligned_128), 38, 2), + ScalarValue::Decimal128(Some(min_aligned_128), 38, 2), + ScalarValue::Decimal128(Some(min_aligned_128 + 100), 38, 2), + ); + + // ===== Decimal256 ===== + // For i256, we use smaller values since MAX is huge + let large_256 = i256::from(1_000_000_000_000i64); + assert_preimage_range( + ScalarValue::Decimal256(Some(large_256), 76, 2), + ScalarValue::Decimal256(Some(large_256), 76, 2), + ScalarValue::Decimal256(Some(large_256.wrapping_add(i256::from(100))), 76, 2), + ); + + // Negative i256 + let neg_256 = i256::from(-1_000_000_000_000i64); assert_preimage_range( - ScalarValue::Decimal32(Some(min_aligned), 9, 2), - ScalarValue::Decimal32(Some(min_aligned), 9, 2), - ScalarValue::Decimal32(Some(min_aligned + 100), 9, 2), + ScalarValue::Decimal256(Some(neg_256), 76, 2), + ScalarValue::Decimal256(Some(neg_256), 76, 2), + ScalarValue::Decimal256(Some(neg_256.wrapping_add(i256::from(100))), 76, 2), ); } #[test] fn test_floor_preimage_decimal_null() { assert_preimage_none(ScalarValue::Decimal32(None, 9, 2)); + assert_preimage_none(ScalarValue::Decimal64(None, 18, 2)); + assert_preimage_none(ScalarValue::Decimal128(None, 38, 2)); + assert_preimage_none(ScalarValue::Decimal256(None, 76, 2)); } } From d32651d92f8d99a92db2162de9d5a4fabb3de5a4 Mon Sep 17 00:00:00 2001 From: Devanshu Date: Sun, 1 Feb 2026 17:10:05 +0700 Subject: [PATCH 3/8] SLT tests --- .../test_files/floor_preimage.slt | 177 ++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 datafusion/sqllogictest/test_files/floor_preimage.slt diff --git a/datafusion/sqllogictest/test_files/floor_preimage.slt b/datafusion/sqllogictest/test_files/floor_preimage.slt new file mode 100644 index 000000000000..62d7c93d769f --- /dev/null +++ b/datafusion/sqllogictest/test_files/floor_preimage.slt @@ -0,0 +1,177 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +########## +## Floor Preimage Tests +## +## Tests for floor function preimage optimization: +## floor(col) = N transforms to col >= N AND col < N + 1 +## +## Uses representative types only (Float64, Int32, Decimal128). +## Unit tests cover all type variants. +########## + +# Setup: Single table with representative types +statement ok +CREATE TABLE test_data ( + id INT, + float_val DOUBLE, + int_val INT, + decimal_val DECIMAL(10,2) +) AS VALUES + (1, 5.3, 100, 100.00), + (2, 5.7, 101, 100.50), + (3, 6.0, 102, 101.00), + (4, 6.5, -5, 101.99), + (5, 7.0, 0, 102.00), + (6, NULL, NULL, NULL); + +########## +## Data Correctness Tests +########## + +# Float64: floor(x) = 5 matches values in [5.0, 6.0) +query I rowsort +SELECT id FROM test_data WHERE floor(float_val) = arrow_cast(5, 'Float64'); +---- +1 +2 + +# Int32: floor(x) = 100 matches values in [100, 101) +query I rowsort +SELECT id FROM test_data WHERE floor(int_val) = 100; +---- +1 + +# Decimal128: floor(x) = 100 matches values in [100.00, 101.00) +query I rowsort +SELECT id FROM test_data WHERE floor(decimal_val) = arrow_cast(100, 'Decimal128(10,2)'); +---- +1 +2 + +# Negative value: floor(x) = -5 matches values in [-5, -4) +query I rowsort +SELECT id FROM test_data WHERE floor(int_val) = -5; +---- +4 + +# Zero value: floor(x) = 0 matches values in [0, 1) +query I rowsort +SELECT id FROM test_data WHERE floor(int_val) = 0; +---- +5 + +# Column on RHS (same result as LHS) +query I rowsort +SELECT id FROM test_data WHERE arrow_cast(5, 'Float64') = floor(float_val); +---- +1 +2 + +# IS NOT DISTINCT FROM (excludes NULLs) +query I rowsort +SELECT id FROM test_data WHERE floor(float_val) IS NOT DISTINCT FROM arrow_cast(5, 'Float64'); +---- +1 +2 + +# IS DISTINCT FROM (includes NULLs) +query I rowsort +SELECT id FROM test_data WHERE floor(float_val) IS DISTINCT FROM arrow_cast(5, 'Float64'); +---- +3 +4 +5 +6 + +# Non-integer literal (empty result - floor returns integers) +query I rowsort +SELECT id FROM test_data WHERE floor(float_val) = arrow_cast(5.5, 'Float64'); +---- + +########## +## EXPLAIN Tests - Plan Optimization +########## + +statement ok +set datafusion.explain.logical_plan_only = true; + +# 1. Basic: Float64 - floor(col) = N transforms to col >= N AND col < N+1 +query TT +EXPLAIN SELECT * FROM test_data WHERE floor(float_val) = arrow_cast(5, 'Float64'); +---- +logical_plan +01)Filter: test_data.float_val >= Float64(5) AND test_data.float_val < Float64(6) +02)--TableScan: test_data projection=[id, float_val, int_val, decimal_val] + +# 2. Basic: Int32 - transformed (coerced to Float64) +query TT +EXPLAIN SELECT * FROM test_data WHERE floor(int_val) = 100; +---- +logical_plan +01)Projection: test_data.id, test_data.float_val, test_data.int_val, test_data.decimal_val +02)--Filter: __common_expr_3 >= Float64(100) AND __common_expr_3 < Float64(101) +03)----Projection: CAST(test_data.int_val AS Float64) AS __common_expr_3, test_data.id, test_data.float_val, test_data.int_val, test_data.decimal_val +04)------TableScan: test_data projection=[id, float_val, int_val, decimal_val] + +# 3. Basic: Decimal128 - same transformation +query TT +EXPLAIN SELECT * FROM test_data WHERE floor(decimal_val) = arrow_cast(100, 'Decimal128(10,2)'); +---- +logical_plan +01)Filter: test_data.decimal_val >= Decimal128(Some(10000),10,2) AND test_data.decimal_val < Decimal128(Some(10100),10,2) +02)--TableScan: test_data projection=[id, float_val, int_val, decimal_val] + +# 4. Column on RHS - same transformation +query TT +EXPLAIN SELECT * FROM test_data WHERE arrow_cast(5, 'Float64') = floor(float_val); +---- +logical_plan +01)Filter: test_data.float_val >= Float64(5) AND test_data.float_val < Float64(6) +02)--TableScan: test_data projection=[id, float_val, int_val, decimal_val] + +# 5. IS NOT DISTINCT FROM - adds IS NOT NULL +query TT +EXPLAIN SELECT * FROM test_data WHERE floor(float_val) IS NOT DISTINCT FROM arrow_cast(5, 'Float64'); +---- +logical_plan +01)Filter: test_data.float_val IS NOT NULL AND test_data.float_val >= Float64(5) AND test_data.float_val < Float64(6) +02)--TableScan: test_data projection=[id, float_val, int_val, decimal_val] + +# 6. IS DISTINCT FROM - includes NULL check +query TT +EXPLAIN SELECT * FROM test_data WHERE floor(float_val) IS DISTINCT FROM arrow_cast(5, 'Float64'); +---- +logical_plan +01)Filter: test_data.float_val < Float64(5) OR test_data.float_val >= Float64(6) OR test_data.float_val IS NULL +02)--TableScan: test_data projection=[id, float_val, int_val, decimal_val] + +# 7. Non-optimizable: non-integer literal (original predicate preserved) +query TT +EXPLAIN SELECT * FROM test_data WHERE floor(float_val) = arrow_cast(5.5, 'Float64'); +---- +logical_plan +01)Filter: floor(test_data.float_val) = Float64(5.5) +02)--TableScan: test_data projection=[id, float_val, int_val, decimal_val] + +########## +## Cleanup +########## + +statement ok +DROP TABLE test_data; From 1830ace59770a937e518879e65c0d00cef07aec6 Mon Sep 17 00:00:00 2001 From: Devanshu Date: Sun, 1 Feb 2026 17:24:23 +0700 Subject: [PATCH 4/8] Add SLT tests for other operator types --- .../test_files/floor_preimage.slt | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/datafusion/sqllogictest/test_files/floor_preimage.slt b/datafusion/sqllogictest/test_files/floor_preimage.slt index 62d7c93d769f..67840d362335 100644 --- a/datafusion/sqllogictest/test_files/floor_preimage.slt +++ b/datafusion/sqllogictest/test_files/floor_preimage.slt @@ -169,6 +169,101 @@ logical_plan 01)Filter: floor(test_data.float_val) = Float64(5.5) 02)--TableScan: test_data projection=[id, float_val, int_val, decimal_val] +########## +## Other Comparison Operators +## +## The preimage framework automatically handles all comparison operators: +## floor(x) <> N -> x < N OR x >= N+1 +## floor(x) > N -> x >= N+1 +## floor(x) < N -> x < N +## floor(x) >= N -> x >= N +## floor(x) <= N -> x < N+1 +########## + +# Data correctness tests for other operators + +# Not equals: floor(x) <> 5 matches values outside [5.0, 6.0) +query I rowsort +SELECT id FROM test_data WHERE floor(float_val) <> arrow_cast(5, 'Float64'); +---- +3 +4 +5 + +# Greater than: floor(x) > 5 matches values in [6.0, inf) +query I rowsort +SELECT id FROM test_data WHERE floor(float_val) > arrow_cast(5, 'Float64'); +---- +3 +4 +5 + +# Less than: floor(x) < 6 matches values in (-inf, 6.0) +query I rowsort +SELECT id FROM test_data WHERE floor(float_val) < arrow_cast(6, 'Float64'); +---- +1 +2 + +# Greater than or equal: floor(x) >= 5 matches values in [5.0, inf) +query I rowsort +SELECT id FROM test_data WHERE floor(float_val) >= arrow_cast(5, 'Float64'); +---- +1 +2 +3 +4 +5 + +# Less than or equal: floor(x) <= 5 matches values in (-inf, 6.0) +query I rowsort +SELECT id FROM test_data WHERE floor(float_val) <= arrow_cast(5, 'Float64'); +---- +1 +2 + +# EXPLAIN tests showing optimized transformations + +# Not equals: floor(x) <> 5 -> x < 5 OR x >= 6 +query TT +EXPLAIN SELECT * FROM test_data WHERE floor(float_val) <> arrow_cast(5, 'Float64'); +---- +logical_plan +01)Filter: test_data.float_val < Float64(5) OR test_data.float_val >= Float64(6) +02)--TableScan: test_data projection=[id, float_val, int_val, decimal_val] + +# Greater than: floor(x) > 5 -> x >= 6 +query TT +EXPLAIN SELECT * FROM test_data WHERE floor(float_val) > arrow_cast(5, 'Float64'); +---- +logical_plan +01)Filter: test_data.float_val >= Float64(6) +02)--TableScan: test_data projection=[id, float_val, int_val, decimal_val] + +# Less than: floor(x) < 6 -> x < 6 +query TT +EXPLAIN SELECT * FROM test_data WHERE floor(float_val) < arrow_cast(6, 'Float64'); +---- +logical_plan +01)Filter: test_data.float_val < Float64(6) +02)--TableScan: test_data projection=[id, float_val, int_val, decimal_val] + +# Greater than or equal: floor(x) >= 5 -> x >= 5 +query TT +EXPLAIN SELECT * FROM test_data WHERE floor(float_val) >= arrow_cast(5, 'Float64'); +---- +logical_plan +01)Filter: test_data.float_val >= Float64(5) +02)--TableScan: test_data projection=[id, float_val, int_val, decimal_val] + +# Less than or equal: floor(x) <= 5 -> x < 6 +query TT +EXPLAIN SELECT * FROM test_data WHERE floor(float_val) <= arrow_cast(5, 'Float64'); +---- +logical_plan +01)Filter: test_data.float_val < Float64(6) +02)--TableScan: test_data projection=[id, float_val, int_val, decimal_val] + ########## ## Cleanup ########## From fccac54e39bb0fb93c240a32cfe092cca5b0ef0f Mon Sep 17 00:00:00 2001 From: Devanshu Date: Mon, 2 Feb 2026 21:53:22 +0700 Subject: [PATCH 5/8] Fix unit test and remove redundant tests --- datafusion/functions/src/math/floor.rs | 89 +++++--------------------- 1 file changed, 16 insertions(+), 73 deletions(-) diff --git a/datafusion/functions/src/math/floor.rs b/datafusion/functions/src/math/floor.rs index f4c85032ca51..cf1a4a018f2f 100644 --- a/datafusion/functions/src/math/floor.rs +++ b/datafusion/functions/src/math/floor.rs @@ -668,6 +668,14 @@ mod tests { // Decimal256 assert_preimage_none(ScalarValue::Decimal256(Some(i256::from(130)), 76, 2)); // 1.30 assert_preimage_none(ScalarValue::Decimal256(Some(i256::from(-250)), 76, 2)); // -2.50 + + // Decimal32: i32::MAX - 50 + // This return None because the value is not an integer, not because it is out of range. + assert_preimage_none(ScalarValue::Decimal32(Some(i32::MAX - 50), 10, 2)); + + // Decimal64: i64::MAX - 50 + // This return None because the value is not an integer, not because it is out of range. + assert_preimage_none(ScalarValue::Decimal64(Some(i64::MAX - 50), 19, 2)); } #[test] @@ -675,26 +683,10 @@ mod tests { // Test near MAX where adding scale_factor would overflow // Decimal32: i32::MAX - // For scale=2, we add 100, so i32::MAX - 50 would overflow - assert_preimage_none(ScalarValue::Decimal32(Some(i32::MAX - 50), 9, 2)); - // For scale=0, we add 1, so i32::MAX would overflow - assert_preimage_none(ScalarValue::Decimal32(Some(i32::MAX), 9, 0)); + assert_preimage_none(ScalarValue::Decimal32(Some(i32::MAX), 10, 0)); // Decimal64: i64::MAX - assert_preimage_none(ScalarValue::Decimal64(Some(i64::MAX - 50), 18, 2)); - assert_preimage_none(ScalarValue::Decimal64(Some(i64::MAX), 18, 0)); - - // Decimal128: i128::MAX - assert_preimage_none(ScalarValue::Decimal128(Some(i128::MAX - 50), 38, 2)); - assert_preimage_none(ScalarValue::Decimal128(Some(i128::MAX), 38, 0)); - - // Decimal256: i256::MAX - assert_preimage_none(ScalarValue::Decimal256( - Some(i256::MAX.wrapping_sub(i256::from(50))), - 76, - 2, - )); - assert_preimage_none(ScalarValue::Decimal256(Some(i256::MAX), 76, 0)); + assert_preimage_none(ScalarValue::Decimal64(Some(i64::MAX), 19, 0)); } #[test] @@ -706,66 +698,17 @@ mod tests { // Make it divisible by 100 for scale=2 let safe_max_aligned_32 = (safe_max_32 / 100) * 100; assert_preimage_range( - ScalarValue::Decimal32(Some(safe_max_aligned_32), 9, 2), - ScalarValue::Decimal32(Some(safe_max_aligned_32), 9, 2), - ScalarValue::Decimal32(Some(safe_max_aligned_32 + 100), 9, 2), + ScalarValue::Decimal32(Some(safe_max_aligned_32), 10, 2), + ScalarValue::Decimal32(Some(safe_max_aligned_32), 10, 2), + ScalarValue::Decimal32(Some(safe_max_aligned_32 + 100), 10, 2), ); // Negative edge: i32::MIN should work since we're adding (not subtracting) let min_aligned_32 = (i32::MIN / 100) * 100; assert_preimage_range( - ScalarValue::Decimal32(Some(min_aligned_32), 9, 2), - ScalarValue::Decimal32(Some(min_aligned_32), 9, 2), - ScalarValue::Decimal32(Some(min_aligned_32 + 100), 9, 2), - ); - - // ===== Decimal64 ===== - let safe_max_64 = i64::MAX - 100; - let safe_max_aligned_64 = (safe_max_64 / 100) * 100; - assert_preimage_range( - ScalarValue::Decimal64(Some(safe_max_aligned_64), 18, 2), - ScalarValue::Decimal64(Some(safe_max_aligned_64), 18, 2), - ScalarValue::Decimal64(Some(safe_max_aligned_64 + 100), 18, 2), - ); - - let min_aligned_64 = (i64::MIN / 100) * 100; - assert_preimage_range( - ScalarValue::Decimal64(Some(min_aligned_64), 18, 2), - ScalarValue::Decimal64(Some(min_aligned_64), 18, 2), - ScalarValue::Decimal64(Some(min_aligned_64 + 100), 18, 2), - ); - - // ===== Decimal128 ===== - let safe_max_128 = i128::MAX - 100; - let safe_max_aligned_128 = (safe_max_128 / 100) * 100; - assert_preimage_range( - ScalarValue::Decimal128(Some(safe_max_aligned_128), 38, 2), - ScalarValue::Decimal128(Some(safe_max_aligned_128), 38, 2), - ScalarValue::Decimal128(Some(safe_max_aligned_128 + 100), 38, 2), - ); - - let min_aligned_128 = (i128::MIN / 100) * 100; - assert_preimage_range( - ScalarValue::Decimal128(Some(min_aligned_128), 38, 2), - ScalarValue::Decimal128(Some(min_aligned_128), 38, 2), - ScalarValue::Decimal128(Some(min_aligned_128 + 100), 38, 2), - ); - - // ===== Decimal256 ===== - // For i256, we use smaller values since MAX is huge - let large_256 = i256::from(1_000_000_000_000i64); - assert_preimage_range( - ScalarValue::Decimal256(Some(large_256), 76, 2), - ScalarValue::Decimal256(Some(large_256), 76, 2), - ScalarValue::Decimal256(Some(large_256.wrapping_add(i256::from(100))), 76, 2), - ); - - // Negative i256 - let neg_256 = i256::from(-1_000_000_000_000i64); - assert_preimage_range( - ScalarValue::Decimal256(Some(neg_256), 76, 2), - ScalarValue::Decimal256(Some(neg_256), 76, 2), - ScalarValue::Decimal256(Some(neg_256.wrapping_add(i256::from(100))), 76, 2), + ScalarValue::Decimal32(Some(min_aligned_32), 10, 2), + ScalarValue::Decimal32(Some(min_aligned_32), 10, 2), + ScalarValue::Decimal32(Some(min_aligned_32 + 100), 10, 2), ); } From ccd28cfdddd8827ecf9aab4c557e858c9f204be7 Mon Sep 17 00:00:00 2001 From: Devanshu Date: Mon, 2 Feb 2026 22:22:59 +0700 Subject: [PATCH 6/8] Fix failing test --- datafusion/functions/src/math/floor.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/datafusion/functions/src/math/floor.rs b/datafusion/functions/src/math/floor.rs index cf1a4a018f2f..3bbe119159fe 100644 --- a/datafusion/functions/src/math/floor.rs +++ b/datafusion/functions/src/math/floor.rs @@ -693,22 +693,22 @@ mod tests { fn test_floor_preimage_decimal_edge_cases() { // ===== Decimal32 ===== // Large value that doesn't overflow - // i32::MAX = 2147483647, with scale=2, max safe is around i32::MAX - 100 - let safe_max_32 = i32::MAX - 100; - // Make it divisible by 100 for scale=2 - let safe_max_aligned_32 = (safe_max_32 / 100) * 100; + // Decimal(9,2) max value is 9,999,999.99 (stored as 999,999,999) + // Use a large value that fits Decimal(9,2) and is divisible by 100 + let safe_max_aligned_32 = 999_999_900; // 9,999,999.00 assert_preimage_range( - ScalarValue::Decimal32(Some(safe_max_aligned_32), 10, 2), - ScalarValue::Decimal32(Some(safe_max_aligned_32), 10, 2), - ScalarValue::Decimal32(Some(safe_max_aligned_32 + 100), 10, 2), + ScalarValue::Decimal32(Some(safe_max_aligned_32), 9, 2), + ScalarValue::Decimal32(Some(safe_max_aligned_32), 9, 2), + ScalarValue::Decimal32(Some(safe_max_aligned_32 + 100), 9, 2), ); - // Negative edge: i32::MIN should work since we're adding (not subtracting) - let min_aligned_32 = (i32::MIN / 100) * 100; + // Negative edge: use a large negative value that fits Decimal(9,2) + // Decimal(9,2) min value is -9,999,999.99 (stored as -999,999,999) + let min_aligned_32 = -999_999_900; // -9,999,999.00 assert_preimage_range( - ScalarValue::Decimal32(Some(min_aligned_32), 10, 2), - ScalarValue::Decimal32(Some(min_aligned_32), 10, 2), - ScalarValue::Decimal32(Some(min_aligned_32 + 100), 10, 2), + ScalarValue::Decimal32(Some(min_aligned_32), 9, 2), + ScalarValue::Decimal32(Some(min_aligned_32), 9, 2), + ScalarValue::Decimal32(Some(min_aligned_32 + 100), 9, 2), ); } From 74196253d08830d034437aff50efc9508fe4399a Mon Sep 17 00:00:00 2001 From: Devanshu Date: Tue, 3 Feb 2026 20:50:59 +0700 Subject: [PATCH 7/8] Overflow SLT case + cleanup --- datafusion/functions/src/math/floor.rs | 47 +++++-------------- .../test_files/floor_preimage.slt | 13 +++++ 2 files changed, 25 insertions(+), 35 deletions(-) diff --git a/datafusion/functions/src/math/floor.rs b/datafusion/functions/src/math/floor.rs index 3bbe119159fe..22db2ba62b3d 100644 --- a/datafusion/functions/src/math/floor.rs +++ b/datafusion/functions/src/math/floor.rs @@ -217,10 +217,8 @@ impl ScalarUDFImpl for FloorFunc { lit_expr: &Expr, _info: &SimplifyContext, ) -> Result { - // floor takes exactly one argument - if args.len() != 1 { - return Ok(PreimageResult::None); - } + // floor takes exactly one argument and we do not expect to reach here with multiple arguments. + debug_assert!(args.len() == 1, "floor() takes exactly one argument"); let arg = args[0].clone(); @@ -245,7 +243,9 @@ impl ScalarUDFImpl for FloorFunc { ) }), - // Integer types + // Integer types (not reachable from SQL/SLT: floor() only accepts Float64/Float32/Decimal, + // so the RHS literal is always coerced to one of those before preimage runs; kept for + // programmatic use and unit tests) ScalarValue::Int8(Some(n)) => int_preimage_bounds(*n).map(|(lo, hi)| { (ScalarValue::Int8(Some(lo)), ScalarValue::Int8(Some(hi))) }), @@ -260,6 +260,9 @@ impl ScalarUDFImpl for FloorFunc { }), // Decimal types + // DECIMAL(precision, scale) where precision ≤ 38 -> Decimal128(precision, scale) + // DECIMAL(precision, scale) where precision > 38 -> Decimal256(precision, scale) + // Decimal32 and Decimal64 are unreachable from SQL/SLT. ScalarValue::Decimal32(Some(n), precision, scale) => { decimal_preimage_bounds::(*n, *precision, *scale).map( |(lo, hi)| { @@ -381,6 +384,10 @@ where } // Compute upper bound using checked addition + // Before preimage stage, the internal i128/i256(value) is validated based on the precision and scale. + // MAX_DECIMAL128_FOR_EACH_PRECISION and MAX_DECIMAL256_FOR_EACH_PRECISION are used to validate the internal i128/i256. + // Any invalid i128/i256 will not reach here. + // Therefore, the add_checked will always succeed if tested via SQL/SLT path. let upper = value.add_checked(one_scaled).ok()?; Some((value, upper)) @@ -511,36 +518,6 @@ mod tests { assert_preimage_none(ScalarValue::Int64(None)); } - #[test] - fn test_floor_preimage_invalid_inputs() { - let floor_func = FloorFunc::new(); - let info = SimplifyContext::default(); - - // Non-literal comparison value - let result = floor_func.preimage(&[col("x")], &col("y"), &info).unwrap(); - assert!( - matches!(result, PreimageResult::None), - "Expected None for non-literal" - ); - - // Wrong argument count (too many) - let lit = Expr::Literal(ScalarValue::Float64(Some(100.0)), None); - let result = floor_func - .preimage(&[col("x"), col("y")], &lit, &info) - .unwrap(); - assert!( - matches!(result, PreimageResult::None), - "Expected None for wrong arg count" - ); - - // Wrong argument count (zero) - let result = floor_func.preimage(&[], &lit, &info).unwrap(); - assert!( - matches!(result, PreimageResult::None), - "Expected None for zero args" - ); - } - // ============ Decimal32 Tests (mirrors float/int tests) ============ #[test] diff --git a/datafusion/sqllogictest/test_files/floor_preimage.slt b/datafusion/sqllogictest/test_files/floor_preimage.slt index 67840d362335..86d15ad1a63d 100644 --- a/datafusion/sqllogictest/test_files/floor_preimage.slt +++ b/datafusion/sqllogictest/test_files/floor_preimage.slt @@ -169,6 +169,19 @@ logical_plan 01)Filter: floor(test_data.float_val) = Float64(5.5) 02)--TableScan: test_data projection=[id, float_val, int_val, decimal_val] +# 8. Non-optimizable: extreme float literal (2^53) where n+1 loses precision, so preimage returns None +query TT +EXPLAIN SELECT * FROM test_data WHERE floor(float_val) = 9007199254740992; +---- +logical_plan +01)Filter: floor(test_data.float_val) = Float64(9007199254740992) +02)--TableScan: test_data projection=[id, float_val, int_val, decimal_val] + +# Data correctness: floor(col) = 2^53 returns no rows (no value in test_data has floor exactly 2^53) +query I rowsort +SELECT id FROM test_data WHERE floor(float_val) = 9007199254740992; +---- + ########## ## Other Comparison Operators ## From 48aebcb96db45198ad3e8e9ccd6532a83eca0e1d Mon Sep 17 00:00:00 2001 From: Devanshu Date: Tue, 3 Feb 2026 21:23:32 +0700 Subject: [PATCH 8/8] Macro to reduce code repeatition --- datafusion/functions/src/math/floor.rs | 102 +++++++++++-------------- 1 file changed, 46 insertions(+), 56 deletions(-) diff --git a/datafusion/functions/src/math/floor.rs b/datafusion/functions/src/math/floor.rs index 22db2ba62b3d..d4f25716ff7e 100644 --- a/datafusion/functions/src/math/floor.rs +++ b/datafusion/functions/src/math/floor.rs @@ -78,6 +78,42 @@ impl FloorFunc { } } +// ============ Macro for preimage bounds ============ +/// Generates the code to call the appropriate bounds function and wrap results. +macro_rules! preimage_bounds { + // Float types: call float_preimage_bounds and wrap in ScalarValue + (float: $variant:ident, $value:expr) => { + float_preimage_bounds($value).map(|(lo, hi)| { + ( + ScalarValue::$variant(Some(lo)), + ScalarValue::$variant(Some(hi)), + ) + }) + }; + + // Integer types: call int_preimage_bounds and wrap in ScalarValue + (int: $variant:ident, $value:expr) => { + int_preimage_bounds($value).map(|(lo, hi)| { + ( + ScalarValue::$variant(Some(lo)), + ScalarValue::$variant(Some(hi)), + ) + }) + }; + + // Decimal types: call decimal_preimage_bounds with precision/scale and wrap in ScalarValue + (decimal: $variant:ident, $decimal_type:ty, $value:expr, $precision:expr, $scale:expr) => { + decimal_preimage_bounds::<$decimal_type>($value, $precision, $scale).map( + |(lo, hi)| { + ( + ScalarValue::$variant(Some(lo), $precision, $scale), + ScalarValue::$variant(Some(hi), $precision, $scale), + ) + }, + ) + }; +} + impl ScalarUDFImpl for FloorFunc { fn as_any(&self) -> &dyn Any { self @@ -230,78 +266,32 @@ impl ScalarUDFImpl for FloorFunc { // Compute lower bound (N) and upper bound (N + 1) using helper functions let Some((lower, upper)) = (match lit_value { // Floating-point types - ScalarValue::Float64(Some(n)) => float_preimage_bounds(*n).map(|(lo, hi)| { - ( - ScalarValue::Float64(Some(lo)), - ScalarValue::Float64(Some(hi)), - ) - }), - ScalarValue::Float32(Some(n)) => float_preimage_bounds(*n).map(|(lo, hi)| { - ( - ScalarValue::Float32(Some(lo)), - ScalarValue::Float32(Some(hi)), - ) - }), + ScalarValue::Float64(Some(n)) => preimage_bounds!(float: Float64, *n), + ScalarValue::Float32(Some(n)) => preimage_bounds!(float: Float32, *n), // Integer types (not reachable from SQL/SLT: floor() only accepts Float64/Float32/Decimal, // so the RHS literal is always coerced to one of those before preimage runs; kept for // programmatic use and unit tests) - ScalarValue::Int8(Some(n)) => int_preimage_bounds(*n).map(|(lo, hi)| { - (ScalarValue::Int8(Some(lo)), ScalarValue::Int8(Some(hi))) - }), - ScalarValue::Int16(Some(n)) => int_preimage_bounds(*n).map(|(lo, hi)| { - (ScalarValue::Int16(Some(lo)), ScalarValue::Int16(Some(hi))) - }), - ScalarValue::Int32(Some(n)) => int_preimage_bounds(*n).map(|(lo, hi)| { - (ScalarValue::Int32(Some(lo)), ScalarValue::Int32(Some(hi))) - }), - ScalarValue::Int64(Some(n)) => int_preimage_bounds(*n).map(|(lo, hi)| { - (ScalarValue::Int64(Some(lo)), ScalarValue::Int64(Some(hi))) - }), + ScalarValue::Int8(Some(n)) => preimage_bounds!(int: Int8, *n), + ScalarValue::Int16(Some(n)) => preimage_bounds!(int: Int16, *n), + ScalarValue::Int32(Some(n)) => preimage_bounds!(int: Int32, *n), + ScalarValue::Int64(Some(n)) => preimage_bounds!(int: Int64, *n), // Decimal types // DECIMAL(precision, scale) where precision ≤ 38 -> Decimal128(precision, scale) // DECIMAL(precision, scale) where precision > 38 -> Decimal256(precision, scale) // Decimal32 and Decimal64 are unreachable from SQL/SLT. ScalarValue::Decimal32(Some(n), precision, scale) => { - decimal_preimage_bounds::(*n, *precision, *scale).map( - |(lo, hi)| { - ( - ScalarValue::Decimal32(Some(lo), *precision, *scale), - ScalarValue::Decimal32(Some(hi), *precision, *scale), - ) - }, - ) + preimage_bounds!(decimal: Decimal32, Decimal32Type, *n, *precision, *scale) } ScalarValue::Decimal64(Some(n), precision, scale) => { - decimal_preimage_bounds::(*n, *precision, *scale).map( - |(lo, hi)| { - ( - ScalarValue::Decimal64(Some(lo), *precision, *scale), - ScalarValue::Decimal64(Some(hi), *precision, *scale), - ) - }, - ) + preimage_bounds!(decimal: Decimal64, Decimal64Type, *n, *precision, *scale) } ScalarValue::Decimal128(Some(n), precision, scale) => { - decimal_preimage_bounds::(*n, *precision, *scale).map( - |(lo, hi)| { - ( - ScalarValue::Decimal128(Some(lo), *precision, *scale), - ScalarValue::Decimal128(Some(hi), *precision, *scale), - ) - }, - ) + preimage_bounds!(decimal: Decimal128, Decimal128Type, *n, *precision, *scale) } ScalarValue::Decimal256(Some(n), precision, scale) => { - decimal_preimage_bounds::(*n, *precision, *scale).map( - |(lo, hi)| { - ( - ScalarValue::Decimal256(Some(lo), *precision, *scale), - ScalarValue::Decimal256(Some(hi), *precision, *scale), - ) - }, - ) + preimage_bounds!(decimal: Decimal256, Decimal256Type, *n, *precision, *scale) } // Unsupported types