This is an automated email from the ASF dual-hosted git repository.
mbudiu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/main by this push:
new 8d3cb822fd [CALCITE-2067] RexBuilder can't handle NaN,Infinity double
constants
8d3cb822fd is described below
commit 8d3cb822fd6180b4d6a338b439f647f0c734e55c
Author: Mihai Budiu <[email protected]>
AuthorDate: Wed Jan 31 04:01:20 2024 -0800
[CALCITE-2067] RexBuilder can't handle NaN,Infinity double constants
Signed-off-by: Mihai Budiu <[email protected]>
---
.../calcite/adapter/arrow/ArrowTranslator.java | 2 +
.../calcite/adapter/arrow/ArrowAdapterTest.java | 2 +-
.../apache/calcite/rel/rel2sql/SqlImplementor.java | 2 +-
.../java/org/apache/calcite/rex/RexBuilder.java | 38 +++++++++--
.../java/org/apache/calcite/rex/RexLiteral.java | 79 +++++++++++++++-------
.../java/org/apache/calcite/tools/RelBuilder.java | 2 +-
.../main/java/org/apache/calcite/util/Util.java | 19 ++++++
.../calcite/rel/rel2sql/RelToSqlConverterTest.java | 6 +-
.../org/apache/calcite/rex/RexExecutorTest.java | 6 +-
.../java/org/apache/calcite/test/JdbcTest.java | 2 +-
.../org/apache/calcite/test/RelOptRulesTest.java | 27 ++++++++
.../java/org/apache/calcite/util/UtilTest.java | 27 ++++++++
.../org/apache/calcite/test/RelOptRulesTest.xml | 64 ++++++++++++++----
.../calcite/test/TypeCoercionConverterTest.xml | 12 ++--
core/src/test/resources/sql/cast.iq | 2 +-
.../calcite/adapter/druid/DruidExpressions.java | 6 +-
.../org/apache/calcite/test/DruidAdapter2IT.java | 16 +++--
.../org/apache/calcite/test/DruidAdapterIT.java | 8 +--
.../org/apache/calcite/piglet/PigRelExVisitor.java | 8 ++-
.../java/org/apache/calcite/test/PigRelExTest.java | 5 +-
.../java/org/apache/calcite/test/PigRelOpTest.java | 2 +-
site/_docs/history.md | 11 +++
.../java/org/apache/calcite/util/TestUtil.java | 7 ++
23 files changed, 280 insertions(+), 73 deletions(-)
diff --git
a/arrow/src/main/java/org/apache/calcite/adapter/arrow/ArrowTranslator.java
b/arrow/src/main/java/org/apache/calcite/adapter/arrow/ArrowTranslator.java
index 0015471d14..d531e333cf 100644
--- a/arrow/src/main/java/org/apache/calcite/adapter/arrow/ArrowTranslator.java
+++ b/arrow/src/main/java/org/apache/calcite/adapter/arrow/ArrowTranslator.java
@@ -228,6 +228,8 @@ class ArrowTranslator {
}
} else if (String.class.equals(literal.getClass())) {
return "string";
+ } else if (literal instanceof Double) {
+ return "float";
}
throw new UnsupportedOperationException("Unsupported literal " + literal);
}
diff --git
a/arrow/src/test/java/org/apache/calcite/adapter/arrow/ArrowAdapterTest.java
b/arrow/src/test/java/org/apache/calcite/adapter/arrow/ArrowAdapterTest.java
index d48576a117..349d85a4c4 100644
--- a/arrow/src/test/java/org/apache/calcite/adapter/arrow/ArrowAdapterTest.java
+++ b/arrow/src/test/java/org/apache/calcite/adapter/arrow/ArrowAdapterTest.java
@@ -373,7 +373,7 @@ class ArrowAdapterTest {
String sql = "select * from arrowdata\n"
+ " where \"floatField\"=15.0";
String plan = "PLAN=ArrowToEnumerableConverter\n"
- + " ArrowFilter(condition=[=(CAST($2):DOUBLE, 15.0)])\n"
+ + " ArrowFilter(condition=[=(CAST($2):DOUBLE, 15.0E0)])\n"
+ " ArrowTableScan(table=[[ARROW, ARROWDATA]], fields=[[0, 1, 2,
3]])\n\n";
String result = "intField=15; stringField=15; floatField=15.0;
longField=15\n";
diff --git
a/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
b/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
index 8b26d90bf7..0884ca94cb 100644
--- a/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
+++ b/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
@@ -1498,7 +1498,7 @@ public abstract class SqlImplementor {
case EXACT_NUMERIC: {
if (SqlTypeName.APPROX_TYPES.contains(typeName)) {
return SqlLiteral.createApproxNumeric(
- castNonNull(literal.getValueAs(BigDecimal.class)).toPlainString(),
POS);
+ castNonNull(literal.getValueAs(Double.class)).toString(), POS);
} else {
return SqlLiteral.createExactNumeric(
castNonNull(literal.getValueAs(BigDecimal.class)).toPlainString(),
POS);
diff --git a/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
b/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
index 5cc4b21170..3ebabd164c 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
@@ -1437,7 +1437,21 @@ public class RexBuilder {
public RexLiteral makeApproxLiteral(@Nullable BigDecimal bd, RelDataType
type) {
assert SqlTypeFamily.APPROXIMATE_NUMERIC.getTypeNames().contains(
type.getSqlTypeName());
- return makeLiteral(bd, type, SqlTypeName.DOUBLE);
+ return makeLiteral(bd != null ? bd.doubleValue() : null, type,
SqlTypeName.DOUBLE);
+ }
+
+ /**
+ * Creates an approximate numeric literal (double or float)
+ * from a Double value.
+ *
+ * @param val literal value
+ * @param type approximate numeric type
+ * @return new literal
+ */
+ public RexLiteral makeApproxLiteral(Double val, RelDataType type) {
+ assert SqlTypeFamily.APPROXIMATE_NUMERIC.getTypeNames().contains(
+ type.getSqlTypeName());
+ return makeLiteral(val, type, SqlTypeName.DOUBLE);
}
/**
@@ -2013,7 +2027,10 @@ public class RexBuilder {
case FLOAT:
case REAL:
case DOUBLE:
- return makeApproxLiteral((BigDecimal) value, type);
+ if (value instanceof Double) {
+ return makeApproxLiteral((Double) value, type);
+ }
+ return makeApproxLiteral(((BigDecimal) value).doubleValue(), type);
case BOOLEAN:
return (Boolean) value ? booleanTrue : booleanFalse;
case TIME:
@@ -2159,13 +2176,24 @@ public class RexBuilder {
type.getSqlTypeName());
return new BigDecimal(((Number) o).longValue());
case REAL:
- case FLOAT:
- case DOUBLE:
if (o instanceof BigDecimal) {
return o;
}
- return new BigDecimal(((Number) o).doubleValue(), MathContext.DECIMAL64)
+ // Float values are stored as Doubles
+ if (o instanceof Float) {
+ return ((Float) o).doubleValue();
+ }
+ if (o instanceof Double) {
+ return o;
+ }
+ return new BigDecimal(((Number) o).doubleValue(), MathContext.DECIMAL32)
.stripTrailingZeros();
+ case FLOAT:
+ case DOUBLE:
+ if (o instanceof Double) {
+ return o;
+ }
+ return ((Number) o).doubleValue();
case CHAR:
case VARCHAR:
if (o instanceof NlsString) {
diff --git a/core/src/main/java/org/apache/calcite/rex/RexLiteral.java
b/core/src/main/java/org/apache/calcite/rex/RexLiteral.java
index fd34c99674..1d95b0b3ed 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexLiteral.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexLiteral.java
@@ -55,6 +55,7 @@ import org.locationtech.jts.geom.Geometry;
import java.io.PrintWriter;
import java.math.BigDecimal;
+import java.math.MathContext;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
@@ -112,9 +113,11 @@ import static java.util.Objects.requireNonNull;
* <td>{@link BigDecimal}</td>
* </tr>
* <tr>
- * <td>{@link SqlTypeName#DOUBLE}</td>
+ * <td>{@link SqlTypeName#DOUBLE},
+ * {@link SqlTypeName#REAL},
+ * {@link SqlTypeName#FLOAT}</td>
* <td>Approximate number, for example <code>6.023E-23</code>.</td>
- * <td>{@link BigDecimal}</td>
+ * <td>{@link Double}.</td>
* </tr>
* <tr>
* <td>{@link SqlTypeName#DATE}</td>
@@ -193,7 +196,7 @@ public class RexLiteral extends RexNode {
/**
* The value of this literal. Must be consistent with its type, as per
* {@link #valueMatchesType}. For example, you can't store an
- * {@link Integer} value here just because you feel like it -- all numbers
are
+ * {@link Integer} value here just because you feel like it -- all exact
numbers are
* represented by a {@link BigDecimal}. But since this field is private, it
* doesn't really matter how the values are stored.
*/
@@ -204,12 +207,9 @@ public class RexLiteral extends RexNode {
*/
private final RelDataType type;
- // TODO jvs 26-May-2006: Use SqlTypeFamily instead; it exists
- // for exactly this purpose (to avoid the confusion which results
- // from overloading SqlTypeName).
/**
* An indication of the broad type of this literal -- even if its type isn't
- * a SQL type. Sometimes this will be different than the SQL type; for
+ * a SQL type. Sometimes this will be different from the SQL type; for
* example, all exact numbers, including integers have typeName
* {@link SqlTypeName#DECIMAL}. See {@link #valueMatchesType} for the
* definitive story.
@@ -294,10 +294,10 @@ public class RexLiteral extends RexNode {
}
/**
- * Returns true if {@link RexDigestIncludeType#OPTIONAL} digest would
include data type.
+ * Returns whether {@link RexDigestIncludeType} digest would include data
type.
*
* @see RexCall#computeDigest(boolean)
- * @return true if {@link RexDigestIncludeType#OPTIONAL} digest would
include data type
+ * @return whether {@link RexDigestIncludeType} digest would include data
type
*/
@RequiresNonNull("type")
RexDigestIncludeType digestIncludesType(
@@ -328,11 +328,12 @@ public class RexLiteral extends RexNode {
}
// fall through
case DECIMAL:
+ case BIGINT:
+ return value instanceof BigDecimal;
case DOUBLE:
case FLOAT:
case REAL:
- case BIGINT:
- return value instanceof BigDecimal;
+ return value instanceof Double;
case DATE:
return value instanceof DateString;
case TIME:
@@ -660,8 +661,14 @@ public class RexLiteral extends RexNode {
break;
case DOUBLE:
case FLOAT:
- assert value instanceof BigDecimal;
- sb.append(Util.toScientificNotation((BigDecimal) value));
+ if (value instanceof BigDecimal) {
+ sb.append(Util.toScientificNotation((BigDecimal) value));
+ } else {
+ assert value instanceof Double;
+ Double d = (Double) value;
+ String repr = Util.toScientificNotation(d);
+ sb.append(repr);
+ }
break;
case BIGINT:
assert value instanceof BigDecimal;
@@ -1075,22 +1082,48 @@ public class RexLiteral extends RexNode {
case BIGINT:
case INTEGER:
case SMALLINT:
- case TINYINT:
- case DOUBLE:
- case REAL:
- case FLOAT:
+ case TINYINT: {
+ BigDecimal bd = (BigDecimal) value;
if (clazz == Long.class) {
- return clazz.cast(((BigDecimal) value).longValue());
+ return clazz.cast(bd.longValue());
} else if (clazz == Integer.class) {
- return clazz.cast(((BigDecimal) value).intValue());
+ return clazz.cast(bd.intValue());
} else if (clazz == Short.class) {
- return clazz.cast(((BigDecimal) value).shortValue());
+ return clazz.cast(bd.shortValue());
} else if (clazz == Byte.class) {
- return clazz.cast(((BigDecimal) value).byteValue());
+ return clazz.cast(bd.byteValue());
} else if (clazz == Double.class) {
- return clazz.cast(((BigDecimal) value).doubleValue());
+ return clazz.cast(bd.doubleValue());
} else if (clazz == Float.class) {
- return clazz.cast(((BigDecimal) value).floatValue());
+ return clazz.cast(bd.floatValue());
+ }
+ break;
+ }
+ case DOUBLE:
+ case REAL:
+ case FLOAT:
+ if (value instanceof Double) {
+ Double d = (Double) value;
+ if (clazz == Long.class) {
+ return clazz.cast(d.longValue());
+ } else if (clazz == Integer.class) {
+ return clazz.cast(d.intValue());
+ } else if (clazz == Short.class) {
+ return clazz.cast(d.shortValue());
+ } else if (clazz == Byte.class) {
+ return clazz.cast(d.byteValue());
+ } else if (clazz == Double.class) {
+ // Cast still needed, since the Java compiler does not understand
+ // that T is double.
+ return clazz.cast(d);
+ } else if (clazz == Float.class) {
+ return clazz.cast(d.floatValue());
+ } else if (clazz == BigDecimal.class) {
+ // This particular conversion is lossy, since in general BigDecimal
cannot
+ // represent accurately FP values. However, this is the best we can
do.
+ // This conversion used to be in RexBuilder, used when creating a
RexLiteral.
+ return clazz.cast(new BigDecimal(d,
MathContext.DECIMAL64).stripTrailingZeros());
+ }
}
break;
case DATE:
diff --git a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
index 86472f9d22..4c7357da81 100644
--- a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
+++ b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
@@ -480,7 +480,7 @@ public class RelBuilder {
return rexBuilder.makeExactLiteral((BigDecimal) value);
} else if (value instanceof Float || value instanceof Double) {
return rexBuilder.makeApproxLiteral(
- BigDecimal.valueOf(((Number) value).doubleValue()));
+ ((Number) value).doubleValue(),
getTypeFactory().createSqlType(SqlTypeName.DOUBLE));
} else if (value instanceof Number) {
return rexBuilder.makeExactLiteral(
BigDecimal.valueOf(((Number) value).longValue()));
diff --git a/core/src/main/java/org/apache/calcite/util/Util.java
b/core/src/main/java/org/apache/calcite/util/Util.java
index a3fcafaa24..b9b8e2085b 100644
--- a/core/src/main/java/org/apache/calcite/util/Util.java
+++ b/core/src/main/java/org/apache/calcite/util/Util.java
@@ -534,6 +534,21 @@ public class Util {
pw.println();
}
+ /**
+ * Formats a double value to a String ensuring that the output
+ * is in scientific notation if the value is not "special".
+ * (Special values include infinities and NaN.)
+ */
+ public static String toScientificNotation(Double d) {
+ String repr = Double.toString(d);
+ if (!repr.toLowerCase(Locale.ENGLISH).contains("e")
+ && !d.isInfinite()
+ && !d.isNaN()) {
+ repr += "E0";
+ }
+ return repr;
+ }
+
/**
* Formats a {@link BigDecimal} value to a string in scientific notation For
* example<br>
@@ -558,6 +573,10 @@ public class Util {
int len = unscaled.length();
int scale = bd.scale();
int e = len - scale - 1;
+ if (bd.stripTrailingZeros().equals(BigDecimal.ZERO)) {
+ // Without this adjustment 0.0 generates 0E-1
+ e = 0;
+ }
StringBuilder ret = new StringBuilder();
if (bd.signum() < 0) {
diff --git
a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
index 74f4450df4..6d2612b329 100644
---
a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
+++
b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
@@ -407,7 +407,7 @@ class RelToSqlConverterTest {
+ "where \"net_weight\" <> 10 or \"net_weight\" is null";
final String expected = "SELECT \"product_id\", \"shelf_width\"\n"
+ "FROM \"foodmart\".\"product\"\n"
- + "WHERE \"net_weight\" <> 10 OR \"net_weight\" IS NULL";
+ + "WHERE \"net_weight\" <> CAST(10 AS DOUBLE) OR \"net_weight\" IS
NULL";
sql(query).ok(expected);
}
@@ -4391,7 +4391,7 @@ class RelToSqlConverterTest {
+ " select \"product_id\", 0 as \"net_weight\"\n"
+ " from \"sales_fact_1997\") t0";
final String expected = "SELECT SUM(CASE WHEN \"product_id\" = 0"
- + " THEN \"net_weight\" ELSE 0 END) AS \"NET_WEIGHT\"\n"
+ + " THEN \"net_weight\" ELSE 0E0 END) AS \"NET_WEIGHT\"\n"
+ "FROM (SELECT \"product_id\", \"net_weight\"\n"
+ "FROM \"foodmart\".\"product\"\n"
+ "UNION ALL\n"
@@ -6507,7 +6507,7 @@ class RelToSqlConverterTest {
+ "PATTERN (\"STRT\" \"DOWN\" + \"UP\" +)\n"
+ "DEFINE "
+ "\"DOWN\" AS PREV(\"DOWN\".\"net_weight\", 0) = "
- + "0 OR PREV(\"DOWN\".\"net_weight\", 0) = 1, "
+ + "CAST(0 AS DOUBLE) OR PREV(\"DOWN\".\"net_weight\", 0) = CAST(1 AS
DOUBLE), "
+ "\"UP\" AS PREV(\"UP\".\"net_weight\", 0) > "
+ "PREV(\"UP\".\"net_weight\", 1))";
sql(sql).ok(expected);
diff --git a/core/src/test/java/org/apache/calcite/rex/RexExecutorTest.java
b/core/src/test/java/org/apache/calcite/rex/RexExecutorTest.java
index 8387269782..5586f2388f 100644
--- a/core/src/test/java/org/apache/calcite/rex/RexExecutorTest.java
+++ b/core/src/test/java/org/apache/calcite/rex/RexExecutorTest.java
@@ -389,9 +389,11 @@ class RexExecutorTest {
final RexCall first =
(RexCall) rexBuilder.makeCall(SqlStdOperatorTable.LN,
rexBuilder.makeLiteral(3, integer, true));
+ // Division by zero causes an exception during evaluation
final RexCall second =
- (RexCall) rexBuilder.makeCall(SqlStdOperatorTable.LN,
- rexBuilder.makeLiteral(-2, integer, true));
+ (RexCall) rexBuilder.makeCall(SqlStdOperatorTable.DIVIDE_INTEGER,
+ rexBuilder.makeLiteral(-2, integer, true),
+ rexBuilder.makeLiteral(0, integer, true));
executor.reduce(rexBuilder, ImmutableList.of(first, second),
reducedValues);
assertThat(reducedValues, hasSize(2));
diff --git a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
index 4eeb666d4f..4f4838d7ab 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
@@ -389,7 +389,7 @@ public class JdbcTest {
+ "expr#7=[null:JavaType(class java.lang.Integer)], "
+ "empid=[$t3], deptno=[$t4], name=[$t5], salary=[$t6], "
+ "commission=[$t7])\n"
- + " EnumerableValues(tuples=[[{ 'Fred', 56, 123.4 }]])\n";
+ + " EnumerableValues(tuples=[[{ 'Fred', 56,
123.4000015258789E0 }]])\n";
assertThat(resultSet.getString(1), isLinux(expected));
// With named columns
diff --git a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
index 6c672b72ab..cb15194c03 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -3341,6 +3341,33 @@ class RelOptRulesTest extends RelOptTestBase {
.check();
}
+ /** Test case for <a
href="https://issues.apache.org/jira/browse/CALCITE-2067">
+ * [CALCITE-2067] RexLiteral cannot represent accurately floating point
values,
+ * including NaN, Infinity</a>. */
+ @Test public void testDoubleReduction() {
+ // Without the fix for CALCITE-2067 the result returned below is
+ // 1008618.49. Ironically, that result is more accurate; however
+ // it is not the result returned by the pow() function, which is
+ // 1008618.4899999999
+ final String sql = "SELECT power(1004.3, 2)";
+ sql(sql)
+ .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS)
+ .check();
+ }
+
+ /** Test case for <a
href="https://issues.apache.org/jira/browse/CALCITE-2067">
+ * [CALCITE-2067] RexLiteral cannot represent accurately floating point
values,
+ * including NaN, Infinity</a>. */
+ @Test public void testDoubleReduction2() {
+ // Without the fix for CALCITE-2067 the following expression is not
+ // reduced to Infinity, since Infinity cannot be represented
+ // as a BigDecimal value.
+ final String sql2 = "SELECT 1.0 / 0.0e0";
+ sql(sql2)
+ .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS)
+ .check();
+ }
+
/** Tests that {@link UnionMergeRule} does nothing if its arguments have
* are different set operators, {@link Union} and {@link Intersect}. */
@Test void testMergeSetOpMixed() {
diff --git a/core/src/test/java/org/apache/calcite/util/UtilTest.java
b/core/src/test/java/org/apache/calcite/util/UtilTest.java
index fbca0845a4..94456f89ed 100644
--- a/core/src/test/java/org/apache/calcite/util/UtilTest.java
+++ b/core/src/test/java/org/apache/calcite/util/UtilTest.java
@@ -108,6 +108,7 @@ import java.util.function.UnaryOperator;
import static org.apache.calcite.test.Matchers.isLinux;
import static org.apache.calcite.util.ReflectUtil.isStatic;
+import static org.apache.calcite.util.TestUtil.assertThatScientific;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.anyOf;
@@ -161,6 +162,10 @@ class UtilTest {
@Test void testScientificNotation() {
BigDecimal bd;
+ bd = new BigDecimal("0.0");
+ TestUtil.assertEqualsVerbose(
+ "0E0",
+ Util.toScientificNotation(bd));
bd = new BigDecimal("0.001234");
TestUtil.assertEqualsVerbose(
"1.234E-3",
@@ -209,6 +214,28 @@ class UtilTest {
Util.toScientificNotation(bd));
}
+ @Test void testDoubleScientificNotation() {
+ assertThatScientific("0.001234", is("0.001234E0"));
+ assertThatScientific("0.001", is("0.001E0"));
+ assertThatScientific("-0.001", is("-0.001E0"));
+ assertThatScientific("1", is("1.0E0"));
+ assertThatScientific("-1", is("-1.0E0"));
+ assertThatScientific("1.0", is("1.0E0"));
+ assertThatScientific("12345", is("12345.0E0"));
+ assertThatScientific("12345.00", is("12345.0E0"));
+ assertThatScientific("12345.001", is("12345.001E0"));
+
+ // test truncate
+ assertThatScientific("1.23456789012345678901", is("1.2345678901234567E0"));
+ assertThatScientific("-1.23456789012345678901",
is("-1.2345678901234567E0"));
+
+ // special values
+ assertThatScientific("Infinity", is("Infinity"));
+ assertThatScientific("-Infinity", is("-Infinity"));
+ assertThatScientific("NaN", is("NaN"));
+ assertThatScientific("-0.0", is("-0.0E0"));
+ }
+
@Test void testToJavaId() throws UnsupportedEncodingException {
assertEquals(
"ID$0$foo",
diff --git
a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
index 0ebdb9b469..ec4d69b9c0 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -1566,7 +1566,7 @@ case when cast(ename as double) < 5 then 0.0
</Resource>
<Resource name="planBefore">
<![CDATA[
-LogicalProject(T=[CASE(<(CAST(CASE(>($1, 'abc'), $1,
null:VARCHAR(20))):DOUBLE, 5), 0.0:DOUBLE, CASE(IS NOT NULL(CAST(CASE(>($1,
'abc'), $1, null:VARCHAR(20))):DOUBLE), CAST(CAST(CASE(>($1, 'abc'), $1,
null:VARCHAR(20))):DOUBLE):DOUBLE NOT NULL, 1.0:DOUBLE))])
+LogicalProject(T=[CASE(<(CAST(CASE(>($1, 'abc'), $1,
null:VARCHAR(20))):DOUBLE, 5), 0.0E0:DOUBLE, CASE(IS NOT NULL(CAST(CASE(>($1,
'abc'), $1, null:VARCHAR(20))):DOUBLE), CAST(CAST(CASE(>($1, 'abc'), $1,
null:VARCHAR(20))):DOUBLE):DOUBLE NOT NULL, 1.0E0:DOUBLE))])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
]]>
</Resource>
@@ -3026,6 +3026,40 @@ LogicalAggregate(group=[{0}], EXPR$1=[SUM($3)],
EXPR$2=[MIN($4)], EXPR$3=[COUNT(
LogicalFilter(condition=[false])
LogicalAggregate(group=[{}], agg#0=[COUNT()])
LogicalTableScan(table=[[scott, EMP]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testDoubleReduction">
+ <Resource name="sql">
+ <![CDATA[SELECT power(1004.3, 2)]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+LogicalProject(EXPR$0=[POWER(1004.3:DECIMAL(5, 1), 2)])
+ LogicalValues(tuples=[[{ 0 }]])
+]]>
+ </Resource>
+ <Resource name="planAfter">
+ <![CDATA[
+LogicalProject(EXPR$0=[1008618.4899999999E0:DOUBLE])
+ LogicalValues(tuples=[[{ 0 }]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testDoubleReduction2">
+ <Resource name="sql">
+ <![CDATA[SELECT 1.0 / 0.0e0]]>
+ </Resource>
+ <Resource name="planBefore">
+ <![CDATA[
+LogicalProject(EXPR$0=[/(1.0:DECIMAL(2, 1), 0.0E0:DOUBLE)])
+ LogicalValues(tuples=[[{ 0 }]])
+]]>
+ </Resource>
+ <Resource name="planAfter">
+ <![CDATA[
+LogicalProject(EXPR$0=[Infinity:DOUBLE])
+ LogicalValues(tuples=[[{ 0 }]])
]]>
</Resource>
</TestCase>
@@ -12257,13 +12291,13 @@ from emp]]>
</Resource>
<Resource name="planBefore">
<![CDATA[
-LogicalProject(NEWCOL=[CASE(false, 2.1:FLOAT, 1:FLOAT)])
+LogicalProject(NEWCOL=[CASE(false, CAST(2.1:DECIMAL(2, 1)):FLOAT NOT NULL,
CAST(1):FLOAT NOT NULL)])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
]]>
</Resource>
<Resource name="planAfter">
<![CDATA[
-LogicalProject(NEWCOL=[1E0:FLOAT])
+LogicalProject(NEWCOL=[1.0E0:FLOAT])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
]]>
</Resource>
@@ -16484,9 +16518,9 @@ LogicalProject(DEPTNO=[$0], NAME=[$1])
LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
LogicalAggregate(group=[{0}])
LogicalUnion(all=[true])
- LogicalValues(tuples=[[{ 12 }]])
- LogicalValues(tuples=[[{ 34 }]])
- LogicalValues(tuples=[[{ 56.4 }]])
+ LogicalValues(tuples=[[{ 12.0E0 }]])
+ LogicalValues(tuples=[[{ 34.0E0 }]])
+ LogicalValues(tuples=[[{ 56.4E0 }]])
]]>
</Resource>
<Resource name="planAfter">
@@ -16496,7 +16530,7 @@ LogicalProject(DEPTNO=[$0], NAME=[$1])
LogicalProject(DEPTNO=[$0], NAME=[$1], DEPTNO0=[CAST($0):DOUBLE NOT NULL])
LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
LogicalAggregate(group=[{0}])
- LogicalValues(tuples=[[{ 12 }, { 34 }, { 56.4 }]])
+ LogicalValues(tuples=[[{ 12.0E0 }, { 34.0E0 }, { 56.4E0 }]])
]]>
</Resource>
</TestCase>
@@ -16514,15 +16548,15 @@ LogicalProject(EXPR$0=[CAST(OR(AND(IS NOT NULL($6),
<>($2, 0)), AND(<($3, $2), n
LogicalAggregate(group=[{}], agg#0=[COUNT()], agg#1=[COUNT($0)])
LogicalProject(EXPR$0=[$0], $f1=[true])
LogicalUnion(all=[true])
- LogicalValues(tuples=[[{ 12 }]])
- LogicalValues(tuples=[[{ 34 }]])
- LogicalValues(tuples=[[{ 56.4 }]])
+ LogicalValues(tuples=[[{ 12.0E0 }]])
+ LogicalValues(tuples=[[{ 34.0E0 }]])
+ LogicalValues(tuples=[[{ 56.4E0 }]])
LogicalAggregate(group=[{0}], agg#0=[MIN($1)])
LogicalProject(EXPR$0=[$0], $f1=[true])
LogicalUnion(all=[true])
- LogicalValues(tuples=[[{ 12 }]])
- LogicalValues(tuples=[[{ 34 }]])
- LogicalValues(tuples=[[{ 56.4 }]])
+ LogicalValues(tuples=[[{ 12.0E0 }]])
+ LogicalValues(tuples=[[{ 34.0E0 }]])
+ LogicalValues(tuples=[[{ 56.4E0 }]])
]]>
</Resource>
<Resource name="planAfter">
@@ -16534,10 +16568,10 @@ LogicalProject(EXPR$0=[CAST(OR(AND(IS NOT NULL($6),
<>($2, 0)), AND(<($3, $2), n
LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
LogicalAggregate(group=[{}], agg#0=[COUNT()], agg#1=[COUNT($0)])
LogicalProject(EXPR$0=[$0], $f1=[true])
- LogicalValues(tuples=[[{ 12 }, { 34 }, { 56.4 }]])
+ LogicalValues(tuples=[[{ 12.0E0 }, { 34.0E0 }, { 56.4E0 }]])
LogicalAggregate(group=[{0}], agg#0=[MIN($1)])
LogicalProject(EXPR$0=[$0], $f1=[true])
- LogicalValues(tuples=[[{ 12 }, { 34 }, { 56.4 }]])
+ LogicalValues(tuples=[[{ 12.0E0 }, { 34.0E0 }, { 56.4E0 }]])
]]>
</Resource>
</TestCase>
diff --git
a/core/src/test/resources/org/apache/calcite/test/TypeCoercionConverterTest.xml
b/core/src/test/resources/org/apache/calcite/test/TypeCoercionConverterTest.xml
index c88d38118c..c5476150a7 100644
---
a/core/src/test/resources/org/apache/calcite/test/TypeCoercionConverterTest.xml
+++
b/core/src/test/resources/org/apache/calcite/test/TypeCoercionConverterTest.xml
@@ -158,11 +158,11 @@ LogicalTableModify(table=[[CATALOG, SALES, T1]],
operation=[INSERT], flattened=[
LogicalUnion(all=[false])
LogicalUnion(all=[false])
LogicalUnion(all=[false])
- LogicalValues(tuples=[[{ 'a', 1, 1, 0, 0, 0, 0, 2021-11-28 00:00:00,
2021-11-28, X'0a', false }]])
- LogicalValues(tuples=[[{ 'b', 2, 2, 0, 0, 0, 0, 2021-11-28 00:00:00,
2021-11-28, X'0a', false }]])
- LogicalValues(tuples=[[{ 'c', 3, 3, 0, 0, 0, 0, 2021-11-28 00:00:00,
2021-11-28, X'0a', false }]])
- LogicalValues(tuples=[[{ 'd', 4, 4, 0, 0, 0, 0, 2021-11-28 00:00:00,
2021-11-28, X'0a', false }]])
- LogicalValues(tuples=[[{ 'e', 5, 5, 0, 0, 0, 0, 2021-11-28 00:00:00,
2021-11-28, X'0a', false }]])
+ LogicalValues(tuples=[[{ 'a', 1, 1, 0, 0.0E0, 0.0E0, 0, 2021-11-28
00:00:00, 2021-11-28, X'0a', false }]])
+ LogicalValues(tuples=[[{ 'b', 2, 2, 0, 0.0E0, 0.0E0, 0, 2021-11-28
00:00:00, 2021-11-28, X'0a', false }]])
+ LogicalValues(tuples=[[{ 'c', 3, 3, 0, 0.0E0, 0.0E0, 0, 2021-11-28
00:00:00, 2021-11-28, X'0a', false }]])
+ LogicalValues(tuples=[[{ 'd', 4, 4, 0, 0.0E0, 0.0E0, 0, 2021-11-28
00:00:00, 2021-11-28, X'0a', false }]])
+ LogicalValues(tuples=[[{ 'e', 5, 5, 0, 0.0E0, 0.0E0, 0, 2021-11-28
00:00:00, 2021-11-28, X'0a', false }]])
]]>
</Resource>
</TestCase>
@@ -173,7 +173,7 @@ LogicalTableModify(table=[[CATALOG, SALES, T1]],
operation=[INSERT], flattened=[
<Resource name="plan">
<![CDATA[
LogicalTableModify(table=[[CATALOG, SALES, T1]], operation=[INSERT],
flattened=[false])
- LogicalValues(tuples=[[{ 'a', 1, 1, 0, 0, 0, 0, 2021-11-28 00:00:00,
2021-11-28, X'0a', false }, { 'b', 2, 2, 0, 0, 0, 0, 2021-11-28 00:00:00,
2021-11-28, X'0a', false }, { 'c', 3, 3, 0, 0, 0, 0, 2021-11-28 00:00:00,
2021-11-28, X'0a', false }, { 'd', 4, 4, 0, 0, 0, 0, 2021-11-28 00:00:00,
2021-11-28, X'0a', false }, { 'e', 5, 5, 0, 0, 0, 0, 2021-11-28 00:00:00,
2021-11-28, X'0a', false }]])
+ LogicalValues(tuples=[[{ 'a', 1, 1, 0, 0.0E0, 0.0E0, 0, 2021-11-28 00:00:00,
2021-11-28, X'0a', false }, { 'b', 2, 2, 0, 0.0E0, 0.0E0, 0, 2021-11-28
00:00:00, 2021-11-28, X'0a', false }, { 'c', 3, 3, 0, 0.0E0, 0.0E0, 0,
2021-11-28 00:00:00, 2021-11-28, X'0a', false }, { 'd', 4, 4, 0, 0.0E0, 0.0E0,
0, 2021-11-28 00:00:00, 2021-11-28, X'0a', false }, { 'e', 5, 5, 0, 0.0E0,
0.0E0, 0, 2021-11-28 00:00:00, 2021-11-28, X'0a', false }]])
]]>
</Resource>
</TestCase>
diff --git a/core/src/test/resources/sql/cast.iq
b/core/src/test/resources/sql/cast.iq
index b615bc7aff..a24e1a4234 100644
--- a/core/src/test/resources/sql/cast.iq
+++ b/core/src/test/resources/sql/cast.iq
@@ -131,7 +131,7 @@ values cast('-123.45' as double);
(1 row)
!ok
-EnumerableValues(tuples=[[{ -1.2345E2 }]])
+EnumerableValues(tuples=[[{ -123.45E0 }]])
!plan
values cast('false' as boolean);
diff --git
a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidExpressions.java
b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidExpressions.java
index 73045d208c..cbdf146ea1 100644
--- a/druid/src/main/java/org/apache/calcite/adapter/druid/DruidExpressions.java
+++ b/druid/src/main/java/org/apache/calcite/adapter/druid/DruidExpressions.java
@@ -33,6 +33,7 @@ import com.google.common.primitives.Chars;
import org.checkerframework.checker.nullness.qual.Nullable;
+import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -136,8 +137,11 @@ public class DruidExpressions {
// deal with this for now
return null;
} else if (SqlTypeName.NUMERIC_TYPES.contains(sqlTypeName)) {
+ // This conversion is lossy for Double values.
+ // However, Druid does not support floating point literal values
+ // if they are formatted using scientific notation.
return DruidExpressions.numberLiteral(
- requireNonNull((Number) RexLiteral.value(rexNode)));
+ requireNonNull((RexLiteral) rexNode).getValueAs(BigDecimal.class));
} else if (SqlTypeFamily.INTERVAL_DAY_TIME == sqlTypeName.getFamily()) {
// Calcite represents DAY-TIME intervals in milliseconds.
final long milliseconds =
diff --git a/druid/src/test/java/org/apache/calcite/test/DruidAdapter2IT.java
b/druid/src/test/java/org/apache/calcite/test/DruidAdapter2IT.java
index 8c522b06a0..a44add8094 100644
--- a/druid/src/test/java/org/apache/calcite/test/DruidAdapter2IT.java
+++ b/druid/src/test/java/org/apache/calcite/test/DruidAdapter2IT.java
@@ -2634,7 +2634,10 @@ public class DruidAdapter2IT {
+ "from \"foodmart\" "
+ "where cast(\"product_id\" as double) = 1016.0";
final String plan = "PLAN=EnumerableInterpreter\n"
- + " DruidQuery(table=[[foodmart, foodmart]],
intervals=[[1900-01-09T00:00:00.000Z/2992-01-10T00:00:00.000Z]],
filter=[=(CAST($1):DOUBLE, 1016.0)], projects=[[$91]], groups=[{}],
aggs=[[SUM($0)]])";
+ + " DruidQuery(table=[[foodmart, foodmart]], "
+ + "intervals=[[1900-01-09T00:00:00.000Z/2992-01-10T00:00:00.000Z]], "
+ + "filter=[=(CAST($1):DOUBLE, 1016.0E0)], "
+ + "projects=[[$91]], groups=[{}], aggs=[[SUM($0)]])";
final String druidQuery =
"{'queryType':'timeseries','dataSource':'foodmart','descending':false,'granularity':'all',"
+
"'filter':{'type':'bound','dimension':'product_id','lower':'1016.0',"
@@ -2660,7 +2663,10 @@ public class DruidAdapter2IT {
+ "from \"foodmart\" "
+ "where cast(\"product_id\" as double) <> 1016.0";
final String plan = "PLAN=EnumerableInterpreter\n"
- + " DruidQuery(table=[[foodmart, foodmart]],
intervals=[[1900-01-09T00:00:00.000Z/2992-01-10T00:00:00.000Z]],
filter=[<>(CAST($1):DOUBLE, 1016.0)], projects=[[$91]], groups=[{}],
aggs=[[SUM($0)]])";
+ + " DruidQuery(table=[[foodmart, foodmart]], "
+ + "intervals=[[1900-01-09T00:00:00.000Z/2992-01-10T00:00:00.000Z]], "
+ + "filter=[<>(CAST($1):DOUBLE, 1016.0E0)], "
+ + "projects=[[$91]], groups=[{}], aggs=[[SUM($0)]])";
final String druidQuery =
"{'queryType':'timeseries','dataSource':'foodmart','descending':false,'granularity':'all',"
+
"'filter':{'type':'not','field':{'type':'bound','dimension':'product_id','"
@@ -3078,7 +3084,7 @@ public class DruidAdapter2IT {
final String plan = "PLAN=EnumerableInterpreter\n"
+ " DruidQuery(table=[[foodmart, foodmart]], "
+ "intervals=[[1900-01-09T00:00:00.000Z/2992-01-10T00:00:00.000Z]],"
- + " filter=[=(FLOOR($90), 23)], groups=[{}], aggs=[[COUNT()]]";
+ + " filter=[=(FLOOR($90), 23.0E0)], groups=[{}], aggs=[[COUNT()]]";
sql(sql)
.returnsOrdered("EXPR$0=2")
.explainContains(plan)
@@ -3299,8 +3305,8 @@ public class DruidAdapter2IT {
.returnsOrdered("EXPR$0=2")
.explainContains("PLAN=EnumerableInterpreter\n"
+ " DruidQuery(table=[[foodmart, foodmart]],
intervals=[[1900-01-09T00:00:00"
- + ".000Z/2992-01-10T00:00:00.000Z]], filter=[AND(>(SIN($91),
9.129452507276277E-1), >"
- + "(COS($90), 4.08082061813392E-1), =(FLOOR(TAN($91)), 2),
<(ABS(-(TAN($91), /(SIN"
+ + ".000Z/2992-01-10T00:00:00.000Z]], filter=[AND(>(SIN($91),
0.9129452507276277E0), >"
+ + "(COS($90), 0.40808206181339196E0), =(FLOOR(TAN($91)), 2.0E0),
<(ABS(-(TAN($91), /(SIN"
+ "($91), COS($91)))), 1.0E-6))], groups=[{}], aggs=[[COUNT()]])");
}
diff --git a/druid/src/test/java/org/apache/calcite/test/DruidAdapterIT.java
b/druid/src/test/java/org/apache/calcite/test/DruidAdapterIT.java
index 200c93efe5..9e140f3293 100644
--- a/druid/src/test/java/org/apache/calcite/test/DruidAdapterIT.java
+++ b/druid/src/test/java/org/apache/calcite/test/DruidAdapterIT.java
@@ -3124,7 +3124,7 @@ public class DruidAdapterIT {
+ "where cast(\"product_id\" as double) = 1016.0";
final String plan = "PLAN=EnumerableInterpreter\n"
+ " DruidQuery(table=[[foodmart, foodmart]],
intervals=[[1900-01-09T00:00:00.000Z/2992-01-10T00:00:00.000Z]], "
- + "filter=[=(CAST($1):DOUBLE, 1016.0)], projects=[[$91]], groups=[{}],
aggs=[[SUM($0)]])";
+ + "filter=[=(CAST($1):DOUBLE, 1016.0E0)], projects=[[$91]],
groups=[{}], aggs=[[SUM($0)]])";
final String druidQuery =
"{'queryType':'timeseries','dataSource':'foodmart','descending':false,'granularity':'all',"
+
"'filter':{'type':'bound','dimension':'product_id','lower':'1016.0',"
@@ -3151,7 +3151,7 @@ public class DruidAdapterIT {
+ "where cast(\"product_id\" as double) <> 1016.0";
final String plan = "PLAN=EnumerableInterpreter\n"
+ " DruidQuery(table=[[foodmart, foodmart]],
intervals=[[1900-01-09T00:00:00.000Z/2992-01-10T00:00:00.000Z]], "
- + "filter=[<>(CAST($1):DOUBLE, 1016.0)], projects=[[$91]],
groups=[{}], aggs=[[SUM($0)]])";
+ + "filter=[<>(CAST($1):DOUBLE, 1016.0E0)], projects=[[$91]],
groups=[{}], aggs=[[SUM($0)]])";
final String druidQuery =
"{'queryType':'timeseries','dataSource':'foodmart','descending':false,'granularity':'all',"
+
"'filter':{'type':'not','field':{'type':'bound','dimension':'product_id','"
@@ -3725,7 +3725,7 @@ public class DruidAdapterIT {
final String plan = "PLAN=EnumerableInterpreter\n"
+ " DruidQuery(table=[[foodmart, foodmart]], "
+ "intervals=[[1900-01-09T00:00:00.000Z/2992-01-10T00:00:00.000Z]],"
- + " filter=[=(FLOOR($90), 23)], groups=[{}], aggs=[[COUNT()]]";
+ + " filter=[=(FLOOR($90), 23.0E0)], groups=[{}], aggs=[[COUNT()]]";
sql(sql, FOODMART)
.returnsOrdered("EXPR$0=2")
.explainContains(plan)
@@ -3957,7 +3957,7 @@ public class DruidAdapterIT {
.explainContains("PLAN=EnumerableInterpreter\n"
+ " DruidQuery(table=[[foodmart, foodmart]], "
+
"intervals=[[1900-01-09T00:00:00.000Z/2992-01-10T00:00:00.000Z]], "
- + "filter=[AND(>(SIN($91), 9.129452507276277E-1), >(COS($90),
4.08082061813392E-1), =(FLOOR(TAN($91)), 2), "
+ + "filter=[AND(>(SIN($91), 0.9129452507276277E0), >(COS($90),
0.40808206181339196E0), =(FLOOR(TAN($91)), 2.0E0), "
+ "<(ABS(-(TAN($91), /(SIN($91), COS($91)))), 1.0E-6))], "
+ "groups=[{}], aggs=[[COUNT()]])");
}
diff --git
a/piglet/src/main/java/org/apache/calcite/piglet/PigRelExVisitor.java
b/piglet/src/main/java/org/apache/calcite/piglet/PigRelExVisitor.java
index c00289f728..49a748e5b2 100644
--- a/piglet/src/main/java/org/apache/calcite/piglet/PigRelExVisitor.java
+++ b/piglet/src/main/java/org/apache/calcite/piglet/PigRelExVisitor.java
@@ -256,8 +256,12 @@ class PigRelExVisitor extends LogicalExpressionVisitor {
final RexNode operand = stack.pop();
if (operand instanceof RexLiteral) {
final Comparable value = ((RexLiteral) operand).getValue();
- assert value instanceof BigDecimal;
- stack.push(builder.literal(((BigDecimal) value).negate()));
+ if (value instanceof BigDecimal) {
+ stack.push(builder.literal(((BigDecimal) value).negate()));
+ } else {
+ assert value instanceof Double;
+ stack.push(builder.literal(- (Double) value));
+ }
} else {
stack.push(builder.call(SqlStdOperatorTable.UNARY_MINUS, operand));
}
diff --git a/piglet/src/test/java/org/apache/calcite/test/PigRelExTest.java
b/piglet/src/test/java/org/apache/calcite/test/PigRelExTest.java
index b36f684fa5..5d56694c04 100644
--- a/piglet/src/test/java/org/apache/calcite/test/PigRelExTest.java
+++ b/piglet/src/test/java/org/apache/calcite/test/PigRelExTest.java
@@ -79,7 +79,10 @@ class PigRelExTest extends PigRelTestBase {
}
@Test void testConstantFloat() {
- checkTranslation(".1E6 == -2.3", inTree("=(1E5:DOUBLE, -2.3:DECIMAL(2,
1))"));
+ // Add a variable d in the expression to prevent it from being simplified
to "false".
+ checkTranslation(".1E6 == -2.3 + d",
+ // Validator converts -2.3 from DECIMAL to DOUBLE
+ inTree("=(100000.0E0, +(-2.3E0, $3))"));
}
@Test void testConstantString() {
diff --git a/piglet/src/test/java/org/apache/calcite/test/PigRelOpTest.java
b/piglet/src/test/java/org/apache/calcite/test/PigRelOpTest.java
index 9b5709184a..95de883788 100644
--- a/piglet/src/test/java/org/apache/calcite/test/PigRelOpTest.java
+++ b/piglet/src/test/java/org/apache/calcite/test/PigRelOpTest.java
@@ -227,7 +227,7 @@ class PigRelOpTest extends PigRelTestBase {
+ "A = LOAD 'scott.DEPT' as (DEPTNO:int, DNAME:chararray,
LOC:CHARARRAY);\n"
+ "B = SAMPLE A 0.5;\n";
final String plan = ""
- + "LogicalFilter(condition=[<(RAND(), 5E-1)])\n"
+ + "LogicalFilter(condition=[<(RAND(), 0.5E0)])\n"
+ " LogicalTableScan(table=[[scott, DEPT]])\n";
final String sql = ""
+ "SELECT *\n"
diff --git a/site/_docs/history.md b/site/_docs/history.md
index e341ac57ec..e395d8b6ab 100644
--- a/site/_docs/history.md
+++ b/site/_docs/history.md
@@ -53,6 +53,17 @@ using JDK/OpenJDK versions 8 to 23;
Guava versions 21.0 to 33.3.0-jre;
other software versions as specified in gradle.properties.
+* [a <href="https://issues.apache.org/jira/browse/CALCITE-2067">]
+ **RexLiteral cannot represent accurately floating point values,
+ including NaN, Infinity**. This fix changes the way RexLiteral
+ represents floating point values. Previously floating point values
+ were encoded into BigDecimal values. This caused precision loss
+ when representing the results of simplifying expressions whose
+ results are floating point. With this change RexLiteral uses
+ internally a Java Double value to represent a SQL DOUBLE, FLOAT, or
+ REAL value. The result of RexLiteral.getValue() accordingly changes
+ type in this case.
+
#### New features
{: #new-features-1-38-0}
diff --git a/testkit/src/main/java/org/apache/calcite/util/TestUtil.java
b/testkit/src/main/java/org/apache/calcite/util/TestUtil.java
index 4987235f19..3c2917ffea 100644
--- a/testkit/src/main/java/org/apache/calcite/util/TestUtil.java
+++ b/testkit/src/main/java/org/apache/calcite/util/TestUtil.java
@@ -35,8 +35,10 @@ import java.util.regex.Pattern;
import static org.apache.calcite.util.Util.first;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.fail;
+import static java.lang.Double.parseDouble;
import static java.lang.Integer.parseInt;
import static java.util.Objects.requireNonNull;
@@ -93,6 +95,11 @@ public abstract class TestUtil {
+ toJavaString(actual) + '\n');
}
+ public static void assertThatScientific(String value,
org.hamcrest.Matcher<String> matcher) {
+ double d = parseDouble(value);
+ assertThat(Util.toScientificNotation(d), matcher);
+ }
+
/**
* Converts a string (which may contain quotes and newlines) into a java
* literal.