diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/type/IgniteTypeFactory.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/type/IgniteTypeFactory.java index 744fea9c05251..00de072af2120 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/type/IgniteTypeFactory.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/type/IgniteTypeFactory.java @@ -250,7 +250,18 @@ else if (type instanceof IgniteCustomType) if (types.size() == 1 || allEquals(types)) return F.first(types); - return super.leastRestrictive(types); + RelDataType res = super.leastRestrictive(types); + + // Calcite compares approximate numerics by their precisions. While FLOAT has the same precision as DOUBLE, the + // least restrictive may variate between them and issue FLOAT instead of DOUBLE. DOUBLE is more preferable. + if (res != null && res.getSqlTypeName() == SqlTypeName.FLOAT && types.size() > 1) { + for (RelDataType type : types) { + if (type.getSqlTypeName() == SqlTypeName.DOUBLE && type.getPrecision() >= res.getPrecision()) + return type; + } + } + + return res; } /** {@inheritDoc} */ diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/NumericTypesPrecisionsTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/NumericTypesPrecisionsTest.java new file mode 100644 index 0000000000000..e582e24b72ac8 --- /dev/null +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/NumericTypesPrecisionsTest.java @@ -0,0 +1,202 @@ +/* + * 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.processors.query.calcite.exec; + +import java.util.Arrays; +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rel.type.RelDataTypeSystem; +import org.apache.calcite.sql.type.SqlTypeName; +import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory; +import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeSystem; +import org.apache.ignite.internal.processors.query.calcite.util.Commons; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** Test for numeric types precisions. */ +public class NumericTypesPrecisionsTest { + /** */ + private static final IgniteTypeFactory TYPE_FACTORY = Commons.typeFactory(); + + /** */ + private static final int STRING_PRECISION = 65536; + + /** */ + private static final int DECIMAL_PRECISION = 32767; + + /** */ + private static final int DECIMAL_SCALE = 32767; + + /** */ + private final RelDataTypeSystem typeSys = IgniteTypeSystem.INSTANCE; + + /** */ + private static final RelDataType TINYINT = TYPE_FACTORY.createSqlType(SqlTypeName.TINYINT); + + /** */ + private static final RelDataType SMALLINT = TYPE_FACTORY.createSqlType(SqlTypeName.SMALLINT); + + /** */ + private static final RelDataType INTEGER = TYPE_FACTORY.createSqlType(SqlTypeName.INTEGER); + + /** */ + private static final RelDataType REAL = TYPE_FACTORY.createSqlType(SqlTypeName.REAL); + + /** */ + private static final RelDataType FLOAT = TYPE_FACTORY.createSqlType(SqlTypeName.FLOAT); + + /** */ + private static final RelDataType DOUBLE = TYPE_FACTORY.createSqlType(SqlTypeName.DOUBLE); + + /** */ + private static final RelDataType DECIMAL = TYPE_FACTORY.createSqlType(SqlTypeName.DECIMAL, 1000, 10); + + /** */ + private static final RelDataType BIGINT = TYPE_FACTORY.createSqlType(SqlTypeName.BIGINT); + + /** */ + private static final RelDataType[] TEST_SUITE = new RelDataType[] {TINYINT, SMALLINT, INTEGER, REAL, FLOAT, DOUBLE, DECIMAL, BIGINT}; + + /** */ + @Test + public void testLeastRestrictiveTinyInt() { + RelDataType[] expected = new RelDataType[] {TINYINT, SMALLINT, INTEGER, REAL, FLOAT, DOUBLE, DECIMAL, BIGINT}; + + doTestExpectedLeastRestrictive(TINYINT, expected); + } + + /** */ + @Test + public void testLeastRestrictiveSmallInt() { + RelDataType[] expected = new RelDataType[] {SMALLINT, SMALLINT, INTEGER, REAL, FLOAT, DOUBLE, DECIMAL, BIGINT}; + + doTestExpectedLeastRestrictive(SMALLINT, expected); + } + + /** */ + @Test + public void testLeastRestrictiveInteger() { + RelDataType[] expected = new RelDataType[] {INTEGER, INTEGER, INTEGER, REAL, FLOAT, DOUBLE, DECIMAL, BIGINT}; + + doTestExpectedLeastRestrictive(INTEGER, expected); + } + + /** */ + @Test + public void testLeastRestrictiveFloat() { + RelDataType[] expected = new RelDataType[] {FLOAT, FLOAT, FLOAT, FLOAT, FLOAT, DOUBLE, DOUBLE, FLOAT}; + + doTestExpectedLeastRestrictive(FLOAT, expected); + } + + /** */ + @Test + public void testLeastRestrictiveDouble() { + RelDataType[] expected = new RelDataType[] {DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE}; + + doTestExpectedLeastRestrictive(DOUBLE, expected); + } + + /** */ + @Test + public void testLeastRestrictiveDecimal() { + RelDataType[] expected = new RelDataType[] {DECIMAL, DECIMAL, DECIMAL, DOUBLE, DOUBLE, DOUBLE, DECIMAL, DECIMAL}; + + doTestExpectedLeastRestrictive(DECIMAL, expected); + } + + /** */ + @Test + public void testLeastRestrictiveBigInt() { + RelDataType[] expected = new RelDataType[] {BIGINT, BIGINT, BIGINT, REAL, FLOAT, DOUBLE, DECIMAL, BIGINT}; + + doTestExpectedLeastRestrictive(BIGINT, expected); + } + + /** This test is mostly for tracking possible chages with Calcite's version updates. */ + @Test + public void testMaxPrecision() { + assertEquals(STRING_PRECISION, typeSys.getMaxPrecision(SqlTypeName.CHAR)); + assertEquals(STRING_PRECISION, typeSys.getMaxPrecision(SqlTypeName.VARCHAR)); + assertEquals(STRING_PRECISION, typeSys.getMaxPrecision(SqlTypeName.BINARY)); + assertEquals(STRING_PRECISION, typeSys.getMaxPrecision(SqlTypeName.VARBINARY)); + + assertEquals(3, typeSys.getMaxPrecision(SqlTypeName.TINYINT)); + assertEquals(5, typeSys.getMaxPrecision(SqlTypeName.SMALLINT)); + assertEquals(10, typeSys.getMaxPrecision(SqlTypeName.INTEGER)); + assertEquals(19, typeSys.getMaxPrecision(SqlTypeName.BIGINT)); + assertEquals(7, typeSys.getMaxPrecision(SqlTypeName.REAL)); + assertEquals(15, typeSys.getMaxPrecision(SqlTypeName.FLOAT)); + assertEquals(15, typeSys.getMaxPrecision(SqlTypeName.DOUBLE)); + assertEquals(DECIMAL_PRECISION, typeSys.getMaxPrecision(SqlTypeName.DECIMAL)); + + assertEquals(0, typeSys.getMaxPrecision(SqlTypeName.DATE)); + assertEquals(3, typeSys.getMaxPrecision(SqlTypeName.TIME)); + assertEquals(3, typeSys.getMaxPrecision(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE)); + assertEquals(3, typeSys.getMaxPrecision(SqlTypeName.TIMESTAMP)); + assertEquals(3, typeSys.getMaxPrecision(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE)); + } + + /** This test is mostly for tracking possible chages with Calcite's version updates. */ + @Test + public void testDefaultPrecision() { + assertEquals(1, typeSys.getDefaultPrecision(SqlTypeName.CHAR)); + assertEquals(RelDataType.PRECISION_NOT_SPECIFIED, typeSys.getDefaultPrecision(SqlTypeName.VARCHAR)); + assertEquals(1, typeSys.getDefaultPrecision(SqlTypeName.BINARY)); + assertEquals(RelDataType.PRECISION_NOT_SPECIFIED, typeSys.getDefaultPrecision(SqlTypeName.VARBINARY)); + + assertEquals(3, typeSys.getDefaultPrecision(SqlTypeName.TINYINT)); + assertEquals(5, typeSys.getDefaultPrecision(SqlTypeName.SMALLINT)); + assertEquals(10, typeSys.getDefaultPrecision(SqlTypeName.INTEGER)); + assertEquals(19, typeSys.getDefaultPrecision(SqlTypeName.BIGINT)); + assertEquals(7, typeSys.getDefaultPrecision(SqlTypeName.REAL)); + assertEquals(15, typeSys.getDefaultPrecision(SqlTypeName.FLOAT)); + assertEquals(15, typeSys.getDefaultPrecision(SqlTypeName.DOUBLE)); + assertEquals(DECIMAL_PRECISION, typeSys.getDefaultPrecision(SqlTypeName.DECIMAL)); + + assertEquals(0, typeSys.getDefaultPrecision(SqlTypeName.DATE)); + assertEquals(0, typeSys.getDefaultPrecision(SqlTypeName.TIME)); + assertEquals(3, typeSys.getDefaultPrecision(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE)); + assertEquals(3, typeSys.getDefaultPrecision(SqlTypeName.TIMESTAMP)); + assertEquals(0, typeSys.getDefaultPrecision(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE)); + } + + + /** This test is mostly for tracking possible chages with Calcite's version updates. */ + @Test + public void testMaxNumericPrecision() { + assertEquals(DECIMAL_PRECISION, typeSys.getMaxNumericPrecision()); + } + + /** This test is mostly for tracking possible chages with Calcite's version updates. */ + @Test + public void testMaxNumericScale() { + assertEquals(DECIMAL_SCALE, typeSys.getMaxNumericScale()); + } + + /** */ + private static void doTestExpectedLeastRestrictive(RelDataType testType, RelDataType[] expectedLeast) { + assert expectedLeast.length == TEST_SUITE.length; + + for (int i = 0; i < TEST_SUITE.length; ++i) { + RelDataType actualType = TYPE_FACTORY.leastRestrictive(Arrays.asList(testType, TEST_SUITE[i])); + + assertEquals("leastRestrictive(" + testType + ", " + TEST_SUITE[i] + ")", expectedLeast[i], actualType); + } + } +} diff --git a/modules/calcite/src/test/java/org/apache/ignite/testsuites/IgniteCalciteTestSuite.java b/modules/calcite/src/test/java/org/apache/ignite/testsuites/IgniteCalciteTestSuite.java index ed7ad797597d0..e71990733db80 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/testsuites/IgniteCalciteTestSuite.java +++ b/modules/calcite/src/test/java/org/apache/ignite/testsuites/IgniteCalciteTestSuite.java @@ -20,6 +20,7 @@ import org.apache.ignite.internal.processors.query.calcite.QueryCheckerTest; import org.apache.ignite.internal.processors.query.calcite.exec.ClosableIteratorsHolderTest; import org.apache.ignite.internal.processors.query.calcite.exec.LogicalRelImplementorTest; +import org.apache.ignite.internal.processors.query.calcite.exec.NumericTypesPrecisionsTest; import org.apache.ignite.internal.processors.query.calcite.exec.exp.IgniteSqlFunctionsTest; import org.apache.ignite.internal.processors.query.calcite.exec.tracker.MemoryTrackerTest; import org.apache.ignite.internal.processors.query.calcite.message.CalciteCommunicationMessageSerializationTest; @@ -47,6 +48,8 @@ ScriptTestSuite.class, CalciteCommunicationMessageSerializationTest.class, + + NumericTypesPrecisionsTest.class, }) public class IgniteCalciteTestSuite { }