This is an automated email from the ASF dual-hosted git repository.
ppa pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new c0a5d0a5ac8 IGNITE-26613 Sql. Use compare() in binary comparisons for
floating point types (#7948)
c0a5d0a5ac8 is described below
commit c0a5d0a5ac8891f2ef4d5aa44746b523114f31d4
Author: Pavel Pereslegin <[email protected]>
AuthorDate: Fri Apr 17 14:43:27 2026 +0300
IGNITE-26613 Sql. Use compare() in binary comparisons for floating point
types (#7948)
---
.../internal/sql/engine/ItFloatingPointTest.java | 128 ++++++++++++++-----
.../sql/engine/exec/exp/IgniteExpressions.java | 40 ++++++
.../internal/sql/engine/externalize/RelJson.java | 18 ++-
.../exec/exp/SqlExpressionFactoryImplTest.java | 140 +++++++++++++++++++++
.../engine/expressions/IgnitePredicateTest.java | 22 +++-
...pecialFloatingPointValuesSerializationTest.java | 95 ++++++++++++++
6 files changed, 408 insertions(+), 35 deletions(-)
diff --git
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItFloatingPointTest.java
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItFloatingPointTest.java
index c27fee5c259..b759dff61d6 100644
---
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItFloatingPointTest.java
+++
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItFloatingPointTest.java
@@ -17,17 +17,14 @@
package org.apache.ignite.internal.sql.engine;
-import static java.util.stream.Collectors.toList;
import static org.apache.ignite.internal.TestWrappers.unwrapIgniteImpl;
import static
org.apache.ignite.internal.sql.engine.util.QueryChecker.containsIndexScan;
import static
org.apache.ignite.internal.sql.engine.util.QueryChecker.containsIndexScanIgnoreBounds;
import static
org.apache.ignite.internal.sql.engine.util.QueryChecker.containsTableScan;
-import static org.hamcrest.MatcherAssert.assertThat;
import java.util.List;
import org.apache.ignite.Ignite;
import org.apache.ignite.internal.app.IgniteImpl;
-import org.hamcrest.Matchers;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -205,15 +202,15 @@ public class ItFloatingPointTest extends
BaseSqlMultiStatementTest {
assertQuery("SELECT fn FROM test WHERE fn =
'-Infinity'::FLOAT").returns(Float.NEGATIVE_INFINITY).check();
assertQuery("SELECT f FROM test WHERE f =
'+Infinity'::FLOAT").returns(Float.POSITIVE_INFINITY).check();
assertQuery("SELECT fn FROM test WHERE fn =
'+Infinity'::FLOAT").returns(Float.POSITIVE_INFINITY).check();
- assertQuery("SELECT f FROM test WHERE f =
'NaN'::FLOAT").returnNothing().check(); // NaN never equals
- assertQuery("SELECT fn FROM test WHERE fn =
'NaN'::FLOAT").returnNothing().check(); // NaN never equals
+ assertQuery("SELECT f FROM test WHERE f =
'NaN'::FLOAT").returns(Float.NaN).check();
+ assertQuery("SELECT fn FROM test WHERE fn =
'NaN'::FLOAT").returns(Float.NaN).check();
assertQuery("SELECT d FROM test WHERE d =
'-Infinity'::DOUBLE").returns(Double.NEGATIVE_INFINITY).check();
assertQuery("SELECT dn FROM test WHERE dn =
'-Infinity'::DOUBLE").returns(Double.NEGATIVE_INFINITY).check();
assertQuery("SELECT d FROM test WHERE d =
'+Infinity'::DOUBLE").returns(Double.POSITIVE_INFINITY).check();
assertQuery("SELECT dn FROM test WHERE dn =
'+Infinity'::DOUBLE").returns(Double.POSITIVE_INFINITY).check();
- assertQuery("SELECT d FROM test WHERE d =
'NaN'::DOUBLE").returnNothing().check(); // NaN never equals
- assertQuery("SELECT dn FROM test WHERE dn =
'NaN'::DOUBLE").returnNothing().check(); // NaN never equals
+ assertQuery("SELECT d FROM test WHERE d =
'NaN'::DOUBLE").returns(Double.NaN).check();
+ assertQuery("SELECT dn FROM test WHERE dn =
'NaN'::DOUBLE").returns(Double.NaN).check();
}
@Test
@@ -228,6 +225,7 @@ public class ItFloatingPointTest extends
BaseSqlMultiStatementTest {
.returns(-1.0f)
.returns(1.0f)
.returns(Float.POSITIVE_INFINITY)
+ .returns(Float.NaN)
.check();
assertQuery("SELECT /*+ FORCE_INDEX(test_fn_idx) */ fn FROM test
WHERE fn > ?")
.withParam(Float.NEGATIVE_INFINITY)
@@ -238,6 +236,7 @@ public class ItFloatingPointTest extends
BaseSqlMultiStatementTest {
.returns(-1.0f)
.returns(1.0f)
.returns(Float.POSITIVE_INFINITY)
+ .returns(Float.NaN)
.check();
assertQuery("SELECT /*+ FORCE_INDEX(test_f_idx) */ f FROM test
WHERE f > ?")
.withParam(Float.NaN)
@@ -255,6 +254,7 @@ public class ItFloatingPointTest extends
BaseSqlMultiStatementTest {
.returns(-0.0d).returns(0.0d)
.returns(-1.0d).returns(1.0d)
.returns(Double.POSITIVE_INFINITY)
+ .returns(Double.NaN)
.check();
assertQuery("SELECT /*+ FORCE_INDEX(test_dn_idx) */ dn FROM test
WHERE dn > '-Infinity'::DOUBLE")
.matches(containsIndexScan("PUBLIC", "TEST",
"TEST_DN_IDX"))
@@ -264,6 +264,7 @@ public class ItFloatingPointTest extends
BaseSqlMultiStatementTest {
.returns(-1.0d)
.returns(1.0d)
.returns(Double.POSITIVE_INFINITY)
+ .returns(Double.NaN)
.check();
assertQuery("SELECT /*+ FORCE_INDEX(test_d_idx) */ d FROM test
WHERE d > 'NaN'::DOUBLE")
.matches(containsIndexScan("PUBLIC", "TEST", "TEST_D_IDX"))
@@ -280,43 +281,69 @@ public class ItFloatingPointTest extends
BaseSqlMultiStatementTest {
assertQuery("SELECT /*+ FORCE_INDEX(test_f_idx) */ f FROM test
WHERE f < ?")
.withParam(+0.0f)
.matches(containsIndexScan("PUBLIC", "TEST", "TEST_F_IDX"))
+ .returns(-0.0f)
.returns(-1.0f)
.returns(Float.NEGATIVE_INFINITY)
.check();
assertQuery("SELECT /*+ FORCE_INDEX(test_fn_idx) */ fn FROM test
WHERE fn < ?")
.withParam(+0.0f)
.matches(containsIndexScan("PUBLIC", "TEST",
"TEST_FN_IDX"))
+ .returns(-0.0f)
.returns(-1.0f)
.returns(Float.NEGATIVE_INFINITY)
.check();
assertQuery("SELECT /*+ FORCE_INDEX(test_f_idx) */ f FROM test
WHERE f < ?")
.withParam(Float.NaN)
.matches(containsIndexScan("PUBLIC", "TEST", "TEST_F_IDX"))
- .returnNothing()
+ .returns(Float.NEGATIVE_INFINITY)
+ .returns(-0.0f)
+ .returns(0.0f)
+ .returns(-1.0f)
+ .returns(1.0f)
+ .returns(Float.POSITIVE_INFINITY)
.check();
assertQuery("SELECT /*+ FORCE_INDEX(test_fn_idx) */ fn FROM test
WHERE fn < ?")
.withParam(Float.NaN)
.matches(containsIndexScan("PUBLIC", "TEST",
"TEST_FN_IDX"))
- .returnNothing()
+ .returns(Float.NEGATIVE_INFINITY)
+ .returns(-0.0f)
+ .returns(0.0f)
+ .returns(0.0f)
+ .returns(-1.0f)
+ .returns(1.0f)
+ .returns(Float.POSITIVE_INFINITY)
.check();
assertQuery("SELECT /*+ FORCE_INDEX(test_d_idx) */ d FROM test
WHERE d < +0.0::DOUBLE")
.matches(containsIndexScan("PUBLIC", "TEST", "TEST_D_IDX"))
+ .returns(-0.0d)
.returns(-1.0d)
.returns(Double.NEGATIVE_INFINITY)
.check();
assertQuery("SELECT /*+ FORCE_INDEX(test_dn_idx) */ dn FROM test
WHERE dn < +0.0::DOUBLE")
.matches(containsIndexScan("PUBLIC", "TEST",
"TEST_DN_IDX"))
+ .returns(-0.0d)
.returns(-1.0d)
.returns(Double.NEGATIVE_INFINITY)
.check();
assertQuery("SELECT /*+ FORCE_INDEX(test_d_idx) */ d FROM test
WHERE d < '-NaN'::DOUBLE")
.matches(containsIndexScan("PUBLIC", "TEST", "TEST_D_IDX"))
- .returnNothing()
+ .returns(Double.NEGATIVE_INFINITY)
+ .returns(-0.0d)
+ .returns(0.0d)
+ .returns(-1.0d)
+ .returns(1.0d)
+ .returns(Double.POSITIVE_INFINITY)
.check();
assertQuery("SELECT /*+ FORCE_INDEX(test_dn_idx) */ dn FROM test
WHERE dn < '-NaN'::DOUBLE")
.matches(containsIndexScan("PUBLIC", "TEST",
"TEST_DN_IDX"))
- .returnNothing()
+ .returns(Double.NEGATIVE_INFINITY)
+ .returns(-0.0d)
+ .returns(0.0d)
+ .returns(0.0d)
+ .returns(-1.0d)
+ .returns(1.0d)
+ .returns(Double.POSITIVE_INFINITY)
.check();
}
}
@@ -326,13 +353,18 @@ public class ItFloatingPointTest extends
BaseSqlMultiStatementTest {
{ // Greater-than
assertQuery("SELECT /*+ NO_INDEX */ f FROM test WHERE f >
-0.0::FLOAT")
.matches(containsTableScan("PUBLIC", "TEST"))
+ .returns(0.0f)
.returns(1.0f)
.returns(Float.POSITIVE_INFINITY)
+ .returns(Float.NaN)
.check();
- assertQuery("SELECT /*+ NO_INDEX */ fn FROM test WHERE fn >
-0.0::FLOAT")
+ assertQuery("SELECT /*+ NO_INDEX */ id, fn FROM test WHERE fn >
-0.0::FLOAT")
.matches(containsTableScan("PUBLIC", "TEST"))
- .returns(1.0f)
- .returns(Float.POSITIVE_INFINITY)
+ .returns(0, 0.0f)
+ .returns(5, 0.0f)
+ .returns(7, 1.0f)
+ .returns(2, Float.POSITIVE_INFINITY)
+ .returns(3, Float.NaN)
.check();
assertQuery("SELECT /*+ NO_INDEX */ f FROM test WHERE f > ?")
.withParam(Float.NaN)
@@ -347,13 +379,18 @@ public class ItFloatingPointTest extends
BaseSqlMultiStatementTest {
assertQuery("SELECT /*+ NO_INDEX */ d FROM test WHERE d >
-0.0::DOUBLE")
.matches(containsTableScan("PUBLIC", "TEST"))
+ .returns(0.0d)
.returns(1.0d)
.returns(Double.POSITIVE_INFINITY)
+ .returns(Double.NaN)
.check();
- assertQuery("SELECT /*+ NO_INDEX */ dn FROM test WHERE dn >
-0.0::DOUBLE")
+ assertQuery("SELECT /*+ NO_INDEX */ id, dn FROM test WHERE dn >
-0.0::DOUBLE")
.matches(containsTableScan("PUBLIC", "TEST"))
- .returns(1.0d)
- .returns(Double.POSITIVE_INFINITY)
+ .returns(0, 0.0d)
+ .returns(5, 0.0d)
+ .returns(7, 1.0d)
+ .returns(2, Double.POSITIVE_INFINITY)
+ .returns(3, Double.NaN)
.check();
assertQuery("SELECT /*+ NO_INDEX */ d FROM test WHERE d >
'NaN'::DOUBLE")
.matches(containsTableScan("PUBLIC", "TEST"))
@@ -368,42 +405,68 @@ public class ItFloatingPointTest extends
BaseSqlMultiStatementTest {
{ // Lesser-than
assertQuery("SELECT /*+ NO_INDEX */ f FROM test WHERE f <
+0.0::FLOAT")
.matches(containsTableScan("PUBLIC", "TEST"))
+ .returns(-0.0f)
.returns(-1.0f)
.returns(Float.NEGATIVE_INFINITY)
.check();
assertQuery("SELECT /*+ NO_INDEX */ fn FROM test WHERE fn <
+0.0::FLOAT")
.matches(containsTableScan("PUBLIC", "TEST"))
+ .returns(-0.0f)
.returns(-1.0f)
.returns(Float.NEGATIVE_INFINITY)
.check();
assertQuery("SELECT /*+ NO_INDEX */ f FROM test WHERE f < ?")
.withParam(Float.NaN)
.matches(containsTableScan("PUBLIC", "TEST"))
- .returnNothing()
+ .returns(Float.NEGATIVE_INFINITY)
+ .returns(-1.0f)
+ .returns(-0.0f)
+ .returns(0.0f)
+ .returns(1.0f)
+ .returns(Float.POSITIVE_INFINITY)
.check();
assertQuery("SELECT /*+ NO_INDEX */ fn FROM test WHERE fn < ?")
.withParam(Float.NaN)
.matches(containsTableScan("PUBLIC", "TEST"))
- .returnNothing()
+ .returns(Float.NEGATIVE_INFINITY)
+ .returns(-1.0f)
+ .returns(-0.0f)
+ .returns(0.0f)
+ .returns(0.0f)
+ .returns(1.0f)
+ .returns(Float.POSITIVE_INFINITY)
.check();
assertQuery("SELECT /*+ NO_INDEX */ d FROM test WHERE d <
+0.0::DOUBLE")
.matches(containsTableScan("PUBLIC", "TEST"))
+ .returns(-0.0d)
.returns(-1.0d)
.returns(Double.NEGATIVE_INFINITY)
.check();
assertQuery("SELECT /*+ NO_INDEX */ dn FROM test WHERE dn <
+0.0::DOUBLE")
.matches(containsTableScan("PUBLIC", "TEST"))
+ .returns(-0.0d)
.returns(-1.0d)
.returns(Double.NEGATIVE_INFINITY)
.check();
assertQuery("SELECT /*+ NO_INDEX */ d FROM test WHERE d <
'NaN'::DOUBLE")
.matches(containsTableScan("PUBLIC", "TEST"))
- .returnNothing()
+ .returns(Double.NEGATIVE_INFINITY)
+ .returns(-1.0d)
+ .returns(-0.0d)
+ .returns(0.0d)
+ .returns(1.0d)
+ .returns(Double.POSITIVE_INFINITY)
.check();
assertQuery("SELECT /*+ NO_INDEX */ dn FROM test WHERE dn <
'NaN'::DOUBLE")
.matches(containsTableScan("PUBLIC", "TEST"))
- .returnNothing()
+ .returns(Double.NEGATIVE_INFINITY)
+ .returns(-1.0d)
+ .returns(-0.0d)
+ .returns(0.0d)
+ .returns(0.0d)
+ .returns(1.0d)
+ .returns(Double.POSITIVE_INFINITY)
.check();
}
}
@@ -424,27 +487,32 @@ public class ItFloatingPointTest extends
BaseSqlMultiStatementTest {
.check();
assertQuery("SELECT f FROM test WHERE f IS DISTINCT FROM
'-0.0'::FLOAT")
+ .returns(0.0f) // 0.0f <> -0.0f
.returns(1.0f).returns(-1.0f)
.returns(Float.NEGATIVE_INFINITY).returns(Float.POSITIVE_INFINITY)
.returns(Float.NaN)
.returns((Object) null)
.check();
assertQuery("SELECT d FROM test WHERE d IS DISTINCT FROM
'-0.0'::DOUBLE")
+ .returns(0.0d) // 0.0d <> -0.0d
.returns(1.0d).returns(-1.0d)
.returns(Double.NEGATIVE_INFINITY).returns(Double.POSITIVE_INFINITY)
.returns(Double.NaN)
.returns((Object) null)
.check();
- List<Float> floats = sql("SELECT f FROM test WHERE f IS DISTINCT FROM
'NaN'::FLOAT")
-
.stream().flatMap(List::stream).map(Float.class::cast).collect(toList());
- assertThat(floats, Matchers.hasSize(8));
- assertThat(floats, Matchers.hasItem(Float.NaN)); // NaN not equal to
NaN
-
- List<Double> doubles = sql("SELECT d FROM test WHERE d IS DISTINCT
FROM 'NaN'::DOUBLE")
-
.stream().flatMap(List::stream).map(Double.class::cast).collect(toList());
- assertThat(doubles, Matchers.hasSize(8));
- assertThat(doubles, Matchers.hasItem(Double.NaN)); // NaN not equal to
NaN
+ assertQuery("SELECT f FROM test WHERE f IS DISTINCT FROM 'NaN'::FLOAT")
+ .returns(0.0f).returns(-0.0f)
+ .returns(1.0f).returns(-1.0f)
+
.returns(Float.NEGATIVE_INFINITY).returns(Float.POSITIVE_INFINITY)
+ .returns((Object) null)
+ .check();
+ assertQuery("SELECT d FROM test WHERE d IS DISTINCT FROM
'NaN'::DOUBLE")
+ .returns(0.0d).returns(-0.0d)
+ .returns(1.0d).returns(-1.0d)
+
.returns(Double.NEGATIVE_INFINITY).returns(Double.POSITIVE_INFINITY)
+ .returns((Object) null)
+ .check();
}
@Test
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteExpressions.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteExpressions.java
index 0be9c1f0735..808fc7fcaa6 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteExpressions.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteExpressions.java
@@ -18,6 +18,7 @@
package org.apache.ignite.internal.sql.engine.exec.exp;
import java.lang.reflect.Type;
+import java.util.EnumSet;
import org.apache.calcite.linq4j.tree.Expression;
import org.apache.calcite.linq4j.tree.ExpressionType;
import org.apache.calcite.linq4j.tree.Expressions;
@@ -29,6 +30,16 @@ import org.jetbrains.annotations.Nullable;
/** Calcite liq4j expressions customized for Ignite. */
public class IgniteExpressions {
+ /** Comparison expression types that need special handling for
floating-point types. */
+ private static final EnumSet<ExpressionType>
COMPARISON_OR_EQUALS_OPERATORS = EnumSet.of(
+ ExpressionType.Equal,
+ ExpressionType.NotEqual,
+ ExpressionType.LessThan,
+ ExpressionType.LessThanOrEqual,
+ ExpressionType.GreaterThan,
+ ExpressionType.GreaterThanOrEqual
+ );
+
/** Make binary expression with arithmetic operations override. */
public static Expression makeBinary(ExpressionType binaryType, Expression
left, Expression right) {
switch (binaryType) {
@@ -41,6 +52,14 @@ public class IgniteExpressions {
case Divide:
return divideExact(left, right);
default:
+ if (COMPARISON_OR_EQUALS_OPERATORS.contains(binaryType)) {
+ Expression floatingPointCmp =
compareFloatingPoint(binaryType, left, right);
+
+ if (floatingPointCmp != null) {
+ return floatingPointCmp;
+ }
+ }
+
return Expressions.makeBinary(binaryType, left, right);
}
}
@@ -182,4 +201,25 @@ public class IgniteExpressions {
return Double.TYPE;
}
}
+
+ /**
+ * Generates a comparison expression for floating-point types using {@link
Float#compare(float, float)}
+ * or {@link Double#compare(double, double)} instead of primitive
comparison operators.
+ */
+ private static @Nullable Expression compareFloatingPoint(ExpressionType
binaryType, Expression left, Expression right) {
+ Type leftType = left.getType();
+ Type rightType = right.getType();
+
+ boolean isFloat = leftType == Float.TYPE || rightType == Float.TYPE;
+ boolean isDouble = leftType == Double.TYPE || rightType == Double.TYPE;
+
+ if (!isFloat && !isDouble) {
+ return null;
+ }
+
+ Class<?> targetClass = isDouble ? Double.class : Float.class;
+ Expression cmp = Expressions.call(targetClass, "compare", left, right);
+
+ return Expressions.makeBinary(binaryType, cmp,
Expressions.constant(0));
+ }
}
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/externalize/RelJson.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/externalize/RelJson.java
index b9365cc2c71..40d51acb091 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/externalize/RelJson.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/externalize/RelJson.java
@@ -420,7 +420,19 @@ class RelJson {
RexLiteral literal = (RexLiteral) node;
Object value = literal.getValue3();
map = map();
- map.put("literal", toJson(value));
+ // Convert the approximate numeric negative zero (-0.0) value
to a string to preserve the minus sign,
+ // otherwise due to USE_BIG_DECIMAL_FOR_FLOATS jackson will
create a BigDecimal from a double (-0.0)
+ // and this will result in the loss of the minus sign.
+ // Other special values +Infinity/-Infinity/NaN are serialized
by jackson as strings.
+ // The value in RexLiteral for approximate types is always of
type Double (see RexLiteral#valueMatchesType).
+ if (value instanceof Double
+ && isApproximateNumeric(node.getType())
+ && isNegativeZero((Double) value)) {
+ map.put("literal", "-0.0");
+ } else {
+ map.put("literal", toJson(value));
+ }
+
map.put("type", toJson(node.getType()));
return map;
@@ -1062,4 +1074,8 @@ class RelJson {
}
return list;
}
+
+ private static boolean isNegativeZero(double d) {
+ return d == 0.0 && Double.doubleToRawLongBits(d) != 0;
+ }
}
diff --git
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/exp/SqlExpressionFactoryImplTest.java
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/exp/SqlExpressionFactoryImplTest.java
index d8379a5330a..107808b4fa9 100644
---
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/exp/SqlExpressionFactoryImplTest.java
+++
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/exp/SqlExpressionFactoryImplTest.java
@@ -48,6 +48,7 @@ import org.apache.calcite.rex.RexInputRef;
import org.apache.calcite.rex.RexLiteral;
import org.apache.calcite.rex.RexLocalRef;
import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.type.BasicSqlType;
import org.apache.calcite.sql.type.SqlTypeName;
@@ -828,6 +829,145 @@ public class SqlExpressionFactoryImplTest extends
BaseIgniteAbstractTest {
);
}
+ @ParameterizedTest
+ @MethodSource("approximateTypeSpecialValuesComparisonArgs")
+ public void testApproximateTypeSpecialValuesComparison(
+ SqlTypeName type,
+ SqlOperator op,
+ Number left,
+ Number right,
+ boolean expected
+ ) {
+ RexBuilder rexBuilder = Commons.rexBuilder();
+ IgniteTypeFactory tf = Commons.typeFactory();
+
+ RelDataType colType = tf.createSqlType(type);
+ RelDataType rowType = new Builder(tf)
+ .add("c1", colType)
+ .add("c2", colType)
+ .build();
+
+ RexInputRef ref1 = rexBuilder.makeInputRef(rowType, 0);
+ RexInputRef ref2 = rexBuilder.makeInputRef(rowType, 1);
+ RexNode filter = rexBuilder.makeCall(op, List.of(ref1, ref2));
+
+ SqlPredicate predicate = expFactory.predicate(filter, rowType);
+ assertEquals(expected, predicate.test(ctx, new Object[]{left, right}),
+ "Failed for " + left + " " + op.getName() + " " + right);
+ }
+
+ private static List<Arguments>
approximateTypeSpecialValuesComparisonArgs() {
+ List<Arguments> args = new ArrayList<>();
+
+ args.addAll(makeApproximateTypeSpecialValuesComparisonArgs(
+ SqlTypeName.FLOAT,
+ Float.NaN,
+ Float.POSITIVE_INFINITY,
+ Float.NEGATIVE_INFINITY,
+ 1.0f,
+ 0.0f,
+ -0.0f
+ ));
+ args.addAll(makeApproximateTypeSpecialValuesComparisonArgs(
+ SqlTypeName.DOUBLE,
+ Double.NaN,
+ Double.POSITIVE_INFINITY,
+ Double.NEGATIVE_INFINITY,
+ 1.0d,
+ 0.0d,
+ -0.0d
+ ));
+
+ return args;
+ }
+
+ private static List<Arguments>
makeApproximateTypeSpecialValuesComparisonArgs(
+ SqlTypeName type,
+ Number nan,
+ Number posInf,
+ Number negInf,
+ Number one,
+ Number zero,
+ Number negZero
+ ) {
+ List<Arguments> args = new ArrayList<>();
+
+ // NaN = NaN : true
+ args.add(Arguments.of(type, SqlStdOperatorTable.EQUALS, nan, nan,
true));
+ // NaN = 1.0 : false
+ args.add(Arguments.of(type, SqlStdOperatorTable.EQUALS, nan, one,
false));
+ // 1.0 = NaN : false
+ args.add(Arguments.of(type, SqlStdOperatorTable.EQUALS, one, nan,
false));
+ // -0.0 = -0.0 : true
+ args.add(Arguments.of(type, SqlStdOperatorTable.EQUALS, negZero,
negZero, true));
+ // -0.0 = 0.0 : false
+ args.add(Arguments.of(type, SqlStdOperatorTable.EQUALS, negZero, zero,
false));
+ // 1.0 = 1.0 : true
+ args.add(Arguments.of(type, SqlStdOperatorTable.EQUALS, one, one,
true));
+
+ // NaN <> NaN should be false
+ args.add(Arguments.of(type, SqlStdOperatorTable.NOT_EQUALS, nan, nan,
false));
+ // NaN <> 1.0 should be true
+ args.add(Arguments.of(type, SqlStdOperatorTable.NOT_EQUALS, nan, one,
true));
+ // -0.0 <> -0.0 : false
+ args.add(Arguments.of(type, SqlStdOperatorTable.NOT_EQUALS, negZero,
negZero, false));
+ // -0.0 <> 0.0 : true
+ args.add(Arguments.of(type, SqlStdOperatorTable.NOT_EQUALS, negZero,
zero, true));
+
+ // NaN > any non-NaN value : true
+ args.add(Arguments.of(type, SqlStdOperatorTable.GREATER_THAN, nan,
one, true));
+ args.add(Arguments.of(type, SqlStdOperatorTable.GREATER_THAN, nan,
posInf, true));
+ // any non-NaN > NaN : false
+ args.add(Arguments.of(type, SqlStdOperatorTable.GREATER_THAN, one,
nan, false));
+ // NaN > NaN : false
+ args.add(Arguments.of(type, SqlStdOperatorTable.GREATER_THAN, nan,
nan, false));
+ // -0.0 > -0.0 : false
+ args.add(Arguments.of(type, SqlStdOperatorTable.GREATER_THAN, negZero,
negZero, false));
+ // -0.0 > 0.0 : false
+ args.add(Arguments.of(type, SqlStdOperatorTable.GREATER_THAN, negZero,
zero, false));
+ // 0.0 > -0.0 : true
+ args.add(Arguments.of(type, SqlStdOperatorTable.GREATER_THAN, zero,
negZero, true));
+
+ // NaN >= NaN : true
+ args.add(Arguments.of(type, SqlStdOperatorTable.GREATER_THAN_OR_EQUAL,
nan, nan, true));
+ // 1.0 >= NaN : false
+ args.add(Arguments.of(type, SqlStdOperatorTable.GREATER_THAN_OR_EQUAL,
one, nan, false));
+ // -0.0 >= -0.0 : true
+ args.add(Arguments.of(type, SqlStdOperatorTable.GREATER_THAN_OR_EQUAL,
negZero, negZero, true));
+ // -0.0 >= 0.0 : false
+ args.add(Arguments.of(type, SqlStdOperatorTable.GREATER_THAN_OR_EQUAL,
negZero, zero, false));
+ // 0.0 >= -0.0 : true
+ args.add(Arguments.of(type, SqlStdOperatorTable.GREATER_THAN_OR_EQUAL,
zero, negZero, true));
+
+ // 1.0 < NaN : true
+ args.add(Arguments.of(type, SqlStdOperatorTable.LESS_THAN, one, nan,
true));
+ // NaN < 1.0 : false
+ args.add(Arguments.of(type, SqlStdOperatorTable.LESS_THAN, nan, one,
false));
+ // NaN < NaN : false
+ args.add(Arguments.of(type, SqlStdOperatorTable.LESS_THAN, nan, nan,
false));
+ // -0.0 < -0.0 : false
+ args.add(Arguments.of(type, SqlStdOperatorTable.LESS_THAN, negZero,
negZero, false));
+ // -0.0 < 0.0 : true
+ args.add(Arguments.of(type, SqlStdOperatorTable.LESS_THAN, negZero,
zero, true));
+ // 0.0 < -0.0 : false
+ args.add(Arguments.of(type, SqlStdOperatorTable.LESS_THAN, zero,
negZero, false));
+
+ // NaN <= NaN : true
+ args.add(Arguments.of(type, SqlStdOperatorTable.LESS_THAN_OR_EQUAL,
nan, nan, true));
+ // -0.0 <= -0.0 : true
+ args.add(Arguments.of(type, SqlStdOperatorTable.LESS_THAN_OR_EQUAL,
negZero, negZero, true));
+ // -0.0 <= 0.0 : true
+ args.add(Arguments.of(type, SqlStdOperatorTable.LESS_THAN_OR_EQUAL,
negZero, zero, true));
+ // 0.0 <= -0.0 : false
+ args.add(Arguments.of(type, SqlStdOperatorTable.LESS_THAN_OR_EQUAL,
zero, negZero, false));
+
+ args.add(Arguments.of(type, SqlStdOperatorTable.EQUALS, posInf,
posInf, true));
+ args.add(Arguments.of(type, SqlStdOperatorTable.LESS_THAN, negInf,
posInf, true));
+ args.add(Arguments.of(type, SqlStdOperatorTable.EQUALS, zero, zero,
true));
+
+ return args;
+ }
+
private static List<Arguments> rowSourceTestArgs() {
EnumSet<ColumnType> ignoredTypes = EnumSet.of(
// TODO https://issues.apache.org/jira/browse/IGNITE-17373
diff --git
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/expressions/IgnitePredicateTest.java
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/expressions/IgnitePredicateTest.java
index 14af0f06750..7e2d188ceff 100644
---
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/expressions/IgnitePredicateTest.java
+++
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/expressions/IgnitePredicateTest.java
@@ -378,18 +378,20 @@ class IgnitePredicateTest extends
AbstractExpressionFactoryTest {
IgnitePredicate predicate = factory.predicate("val > 0",
inputType);
assertThat(predicate.test(context, row(-100.5f)), is(false));
+ assertThat(predicate.test(context, row(-0.0f)), is(false));
assertThat(predicate.test(context, row(0.0f)), is(false));
assertThat(predicate.test(context, row(100.5f)), is(true));
assertThat(predicate.test(context, row(null)), is(false));
assertThat(predicate.test(context, row(Float.POSITIVE_INFINITY)),
is(true));
assertThat(predicate.test(context, row(Float.NEGATIVE_INFINITY)),
is(false));
- assertThat(predicate.test(context, row(Float.NaN)), is(false));
+ assertThat(predicate.test(context, row(Float.NaN)), is(true));
}
{
IgnitePredicate predicate = factory.predicate("val <> 0",
inputType);
assertThat(predicate.test(context, row(-100.5f)), is(true));
+ assertThat(predicate.test(context, row(-0.0f)), is(true));
assertThat(predicate.test(context, row(0.0f)), is(false));
assertThat(predicate.test(context, row(100.5f)), is(true));
assertThat(predicate.test(context, row(null)), is(false));
@@ -402,6 +404,7 @@ class IgnitePredicateTest extends
AbstractExpressionFactoryTest {
IgnitePredicate predicate = factory.predicate("val <= 0",
inputType);
assertThat(predicate.test(context, row(-100.5f)), is(true));
+ assertThat(predicate.test(context, row(-0.0f)), is(true));
assertThat(predicate.test(context, row(0.0f)), is(true));
assertThat(predicate.test(context, row(100.5f)), is(false));
assertThat(predicate.test(context, row(null)), is(false));
@@ -414,6 +417,7 @@ class IgnitePredicateTest extends
AbstractExpressionFactoryTest {
IgnitePredicate predicate = factory.predicate("val IS NULL",
inputType);
assertThat(predicate.test(context, row(-100.5f)), is(false));
+ assertThat(predicate.test(context, row(-0.0f)), is(false));
assertThat(predicate.test(context, row(0.0f)), is(false));
assertThat(predicate.test(context, row(100.5f)), is(false));
assertThat(predicate.test(context, row(null)), is(true));
@@ -426,6 +430,7 @@ class IgnitePredicateTest extends
AbstractExpressionFactoryTest {
IgnitePredicate predicate = factory.predicate("val IS NOT NULL",
inputType);
assertThat(predicate.test(context, row(-100.5f)), is(true));
+ assertThat(predicate.test(context, row(-0.0f)), is(true));
assertThat(predicate.test(context, row(0.0f)), is(true));
assertThat(predicate.test(context, row(100.5f)), is(true));
assertThat(predicate.test(context, row(null)), is(false));
@@ -438,6 +443,7 @@ class IgnitePredicateTest extends
AbstractExpressionFactoryTest {
IgnitePredicate predicate = factory.predicate("val < 0.1",
inputType);
assertThat(predicate.test(context, row(-100.5f)), is(true));
+ assertThat(predicate.test(context, row(-0.0f)), is(true));
assertThat(predicate.test(context, row(0.0f)), is(true));
assertThat(predicate.test(context, row(100.5f)), is(false));
assertThat(predicate.test(context, row(null)), is(false));
@@ -450,12 +456,13 @@ class IgnitePredicateTest extends
AbstractExpressionFactoryTest {
IgnitePredicate predicate = factory.predicate("val > 0.1",
inputType);
assertThat(predicate.test(context, row(-100.5f)), is(false));
+ assertThat(predicate.test(context, row(-0.0f)), is(false));
assertThat(predicate.test(context, row(0.0f)), is(false));
assertThat(predicate.test(context, row(100.5f)), is(true));
assertThat(predicate.test(context, row(null)), is(false));
assertThat(predicate.test(context, row(Float.POSITIVE_INFINITY)),
is(true));
assertThat(predicate.test(context, row(Float.NEGATIVE_INFINITY)),
is(false));
- assertThat(predicate.test(context, row(Float.NaN)), is(false));
+ assertThat(predicate.test(context, row(Float.NaN)), is(true));
}
}
@@ -469,18 +476,20 @@ class IgnitePredicateTest extends
AbstractExpressionFactoryTest {
IgnitePredicate predicate = factory.predicate("val > 0",
inputType);
assertThat(predicate.test(context, row(-100.5)), is(false));
+ assertThat(predicate.test(context, row(-0.0d)), is(false));
assertThat(predicate.test(context, row(0.0)), is(false));
assertThat(predicate.test(context, row(100.5)), is(true));
assertThat(predicate.test(context, row(null)), is(false));
assertThat(predicate.test(context, row(Double.POSITIVE_INFINITY)),
is(true));
assertThat(predicate.test(context, row(Double.NEGATIVE_INFINITY)),
is(false));
- assertThat(predicate.test(context, row(Double.NaN)), is(false));
+ assertThat(predicate.test(context, row(Double.NaN)), is(true));
}
{
IgnitePredicate predicate = factory.predicate("val <> 0",
inputType);
assertThat(predicate.test(context, row(-100.5)), is(true));
+ assertThat(predicate.test(context, row(-0.0d)), is(true));
assertThat(predicate.test(context, row(0.0)), is(false));
assertThat(predicate.test(context, row(100.5)), is(true));
assertThat(predicate.test(context, row(null)), is(false));
@@ -493,6 +502,7 @@ class IgnitePredicateTest extends
AbstractExpressionFactoryTest {
IgnitePredicate predicate = factory.predicate("val <= 0",
inputType);
assertThat(predicate.test(context, row(-100.5)), is(true));
+ assertThat(predicate.test(context, row(-0.0d)), is(true));
assertThat(predicate.test(context, row(0.0)), is(true));
assertThat(predicate.test(context, row(100.5)), is(false));
assertThat(predicate.test(context, row(null)), is(false));
@@ -505,6 +515,7 @@ class IgnitePredicateTest extends
AbstractExpressionFactoryTest {
IgnitePredicate predicate = factory.predicate("val IS NULL",
inputType);
assertThat(predicate.test(context, row(-100.5)), is(false));
+ assertThat(predicate.test(context, row(-0.0d)), is(false));
assertThat(predicate.test(context, row(0.0)), is(false));
assertThat(predicate.test(context, row(100.5)), is(false));
assertThat(predicate.test(context, row(null)), is(true));
@@ -517,6 +528,7 @@ class IgnitePredicateTest extends
AbstractExpressionFactoryTest {
IgnitePredicate predicate = factory.predicate("val IS NOT NULL",
inputType);
assertThat(predicate.test(context, row(-100.5)), is(true));
+ assertThat(predicate.test(context, row(-0.0d)), is(true));
assertThat(predicate.test(context, row(0.0)), is(true));
assertThat(predicate.test(context, row(100.5)), is(true));
assertThat(predicate.test(context, row(null)), is(false));
@@ -529,6 +541,7 @@ class IgnitePredicateTest extends
AbstractExpressionFactoryTest {
IgnitePredicate predicate = factory.predicate("val < 0.1",
inputType);
assertThat(predicate.test(context, row(-100.5)), is(true));
+ assertThat(predicate.test(context, row(-0.0d)), is(true));
assertThat(predicate.test(context, row(0.0)), is(true));
assertThat(predicate.test(context, row(100.5)), is(false));
assertThat(predicate.test(context, row(null)), is(false));
@@ -541,12 +554,13 @@ class IgnitePredicateTest extends
AbstractExpressionFactoryTest {
IgnitePredicate predicate = factory.predicate("val > 0.1",
inputType);
assertThat(predicate.test(context, row(-100.5)), is(false));
+ assertThat(predicate.test(context, row(-0.0d)), is(false));
assertThat(predicate.test(context, row(0.0)), is(false));
assertThat(predicate.test(context, row(100.5)), is(true));
assertThat(predicate.test(context, row(null)), is(false));
assertThat(predicate.test(context, row(Double.POSITIVE_INFINITY)),
is(true));
assertThat(predicate.test(context, row(Double.NEGATIVE_INFINITY)),
is(false));
- assertThat(predicate.test(context, row(Double.NaN)), is(false));
+ assertThat(predicate.test(context, row(Double.NaN)), is(true));
}
}
diff --git
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/SpecialFloatingPointValuesSerializationTest.java
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/SpecialFloatingPointValuesSerializationTest.java
new file mode 100644
index 00000000000..6a506dc320b
--- /dev/null
+++
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/SpecialFloatingPointValuesSerializationTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.
+ */
+
+package org.apache.ignite.internal.sql.engine.planner;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import java.util.List;
+import java.util.stream.Stream;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.ignite.internal.sql.engine.rel.IgniteRel;
+import
org.apache.ignite.internal.sql.engine.rel.ProjectableFilterableTableScan;
+import org.apache.ignite.internal.sql.engine.rel.explain.ExplainUtils;
+import org.apache.ignite.internal.sql.engine.schema.IgniteSchema;
+import org.apache.ignite.internal.sql.engine.trait.IgniteDistributions;
+import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * Test to verify that special floating point values are correctly serialized
and deserialized in the planner.
+ */
+public class SpecialFloatingPointValuesSerializationTest extends
AbstractPlannerTest {
+ @ParameterizedTest
+ @MethodSource("specialValuesArgs")
+ void testSpecialValues(SqlTypeName typeName, String condition, String
expectedCompiledCondition) throws Exception {
+ String sql = "SELECT c1 FROM test WHERE " + condition;
+ RelDataType sqlType = TYPE_FACTORY.createSqlType(typeName);
+
+ IgniteSchema schema = createSchemaFrom(
+ tableBuilder ->
+ tableBuilder.name("TEST")
+ .addColumn("C1",
IgniteTypeFactory.relDataTypeToNative(sqlType))
+ .size(400)
+ .distribution(IgniteDistributions.single())
+ );
+
+ IgniteRel plan = physicalPlan(sql, List.of(schema), null, List.of(),
null);
+
+ checkSplitAndSerialization(plan, List.of(schema));
+
+ log.info("statement: {}\n{}", sql, ExplainUtils.toString(plan));
+
+ var tableScan = (ProjectableFilterableTableScan) plan;
+ RexNode rexNode = tableScan.condition();
+
+ assertThat(rexNode, notNullValue());
+ assertThat(rexNode.toString(), equalTo(expectedCompiledCondition));
+ }
+
+ private static Stream<Arguments> specialValuesArgs() {
+ return Stream.of(
+ Arguments.of(SqlTypeName.REAL, "c1 = -0.0::REAL", "=($t0,
-0.0E0)"),
+ Arguments.of(SqlTypeName.REAL, "c1 = '-0.0'::REAL", "=($t0,
-0.0E0)"),
+ Arguments.of(SqlTypeName.REAL, "c1 = 0.0::REAL", "=($t0,
0.0E0)"),
+ Arguments.of(SqlTypeName.REAL, "c1 = '0.0'::REAL", "=($t0,
0.0E0)"),
+ Arguments.of(SqlTypeName.REAL, "c1 = 'NaN'::REAL", "=($t0,
NaN)"),
+ Arguments.of(SqlTypeName.REAL, "c1 = 'Infinity'::REAL",
"=($t0, Infinity)"),
+ Arguments.of(SqlTypeName.REAL, "c1 = '-Infinity'::REAL",
"=($t0, -Infinity)"),
+ Arguments.of(SqlTypeName.FLOAT, "c1 = -0.0::FLOAT",
"=(CAST($t0):FLOAT, -0.0E0)"),
+ Arguments.of(SqlTypeName.FLOAT, "c1 = '-0.0'::FLOAT",
"=(CAST($t0):FLOAT, -0.0E0)"),
+ Arguments.of(SqlTypeName.FLOAT, "c1 = 0.0::FLOAT",
"=(CAST($t0):FLOAT, 0.0E0)"),
+ Arguments.of(SqlTypeName.FLOAT, "c1 = '0.0'::FLOAT",
"=(CAST($t0):FLOAT, 0.0E0)"),
+ Arguments.of(SqlTypeName.FLOAT, "c1 = 'NaN'::FLOAT",
"=(CAST($t0):FLOAT, NaN)"),
+ Arguments.of(SqlTypeName.FLOAT, "c1 = 'Infinity'::FLOAT",
"=(CAST($t0):FLOAT, Infinity)"),
+ Arguments.of(SqlTypeName.FLOAT, "c1 = '-Infinity'::FLOAT",
"=(CAST($t0):FLOAT, -Infinity)"),
+ Arguments.of(SqlTypeName.DOUBLE, "c1 = -0.0::DOUBLE", "=($t0,
-0.0E0)"),
+ Arguments.of(SqlTypeName.DOUBLE, "c1 = '-0.0'::DOUBLE",
"=($t0, -0.0E0)"),
+ Arguments.of(SqlTypeName.DOUBLE, "c1 = 0.0::DOUBLE", "=($t0,
0.0E0)"),
+ Arguments.of(SqlTypeName.DOUBLE, "c1 = '0.0'::DOUBLE", "=($t0,
0.0E0)"),
+ Arguments.of(SqlTypeName.DOUBLE, "c1 = 'NaN'::DOUBLE", "=($t0,
NaN)"),
+ Arguments.of(SqlTypeName.DOUBLE, "c1 = 'Infinity'::DOUBLE",
"=($t0, Infinity)"),
+ Arguments.of(SqlTypeName.DOUBLE, "c1 = '-Infinity'::DOUBLE",
"=($t0, -Infinity)")
+ );
+ }
+}