Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -205,15 +202,15 @@ void testEqualityComparison() {
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
Expand All @@ -228,6 +225,7 @@ void testNonEqualityComparisonWithIndexScan() {
.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)
Expand All @@ -238,6 +236,7 @@ void testNonEqualityComparisonWithIndexScan() {
.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)
Expand All @@ -255,6 +254,7 @@ void testNonEqualityComparisonWithIndexScan() {
.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"))
Expand All @@ -264,6 +264,7 @@ void testNonEqualityComparisonWithIndexScan() {
.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"))
Expand All @@ -280,43 +281,69 @@ void testNonEqualityComparisonWithIndexScan() {
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();
}
}
Expand All @@ -326,13 +353,18 @@ void testNonEqualityComparisonWithTableScan() {
{ // 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)
Expand All @@ -347,13 +379,18 @@ void testNonEqualityComparisonWithTableScan() {

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"))
Expand All @@ -368,42 +405,68 @@ void testNonEqualityComparisonWithTableScan() {
{ // 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();
}
}
Expand All @@ -424,27 +487,32 @@ void testIsDistinctFrom() {
.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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -29,6 +30,16 @@

/** 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) {
Expand All @@ -41,6 +52,14 @@ public static Expression makeBinary(ExpressionType binaryType, Expression left,
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);
}
}
Expand Down Expand Up @@ -182,4 +201,25 @@ private static Type larger(Type type0, Type type1) {
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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,19 @@ private Object toJson(RexNode node) {
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;
Expand Down Expand Up @@ -1062,4 +1074,8 @@ private List<RexNode> toRexList(RelInput relInput, List<?> operands) {
}
return list;
}

private static boolean isNegativeZero(double d) {
return d == 0.0 && Double.doubleToRawLongBits(d) != 0;
}
}
Loading