This is an automated email from the ASF dual-hosted git repository.
tkalkirill pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/master by this push:
new 2a37fcb3a1b IGNITE-28896 SQL Calcite: Adjust fractional value handling
in FETCH, OFFSET, and LIMIT (#13375)
2a37fcb3a1b is described below
commit 2a37fcb3a1b4d3c8970f0913fb99bf16750f6097
Author: Kirill Tkalenko <[email protected]>
AuthorDate: Sat Aug 8 09:31:59 2026 +0300
IGNITE-28896 SQL Calcite: Adjust fractional value handling in FETCH,
OFFSET, and LIMIT (#13375)
---
.../query/calcite/exec/LogicalRelImplementor.java | 20 ++++----
.../query/calcite/prepare/IgniteSqlValidator.java | 11 +++--
.../query/calcite/type/IgniteTypeSystem.java | 6 +++
.../processors/query/calcite/util/IgniteMath.java | 19 ++++++--
.../DynamicParametersIntegrationTest.java | 54 ++++++++++++++++++++--
.../integration/LimitOffsetIntegrationTest.java | 31 +++++++++++--
modules/calcite/src/test/sql/order/test_limit.test | 14 +++++-
.../src/test/sql/types/decimal/test_decimal.test | 4 +-
.../test/sql/types/decimal/test_decimal_ops.test | 4 +-
9 files changed, 130 insertions(+), 33 deletions(-)
diff --git
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java
index 64bcc6e6526..b5c1c06f1ce 100644
---
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java
+++
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java
@@ -17,6 +17,8 @@
package org.apache.ignite.internal.processors.query.calcite.exec;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
@@ -571,7 +573,7 @@ public class LogicalRelImplementor<Row> implements
IgniteRelVisitor<Node<Row>> {
ctx,
rowType,
idxBndRel.first() ? cmp : cmp.reversed(),
- 0,
+ SortNode.OFFSET_DEFAULT,
1
);
@@ -1076,21 +1078,17 @@ public class LogicalRelImplementor<Row> implements
IgniteRelVisitor<Node<Row>> {
IgniteQueryErrorCode.UNEXPECTED_ELEMENT_TYPE);
}
- long paramAsLong;
-
try {
- paramAsLong = IgniteMath.convertToLongExact((Number)param);
+ BigDecimal paramAsDecimal =
IgniteMath.convertToBigDecimal((Number)param);
+
+ if (paramAsDecimal.signum() < 0)
+ throw new IllegalArgumentException("Negative value for " + op);
+
+ return IgniteMath.convertToLongExact(paramAsDecimal,
RoundingMode.DOWN);
}
catch (RuntimeException ex) {
throw new
IgniteSQLException(IgniteResource.INSTANCE.illegalFetchLimit(op).str(),
IgniteQueryErrorCode.UNEXPECTED_ELEMENT_TYPE, ex);
}
-
- if (paramAsLong < 0) {
- throw new
IgniteSQLException(IgniteResource.INSTANCE.illegalFetchLimit(op).str(),
- IgniteQueryErrorCode.UNEXPECTED_ELEMENT_TYPE);
- }
-
- return paramAsLong;
}
}
diff --git
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteSqlValidator.java
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteSqlValidator.java
index cd044a629bd..22d9be1f083 100644
---
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteSqlValidator.java
+++
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteSqlValidator.java
@@ -18,6 +18,7 @@
package org.apache.ignite.internal.processors.query.calcite.prepare;
import java.math.BigDecimal;
+import java.math.RoundingMode;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
@@ -300,12 +301,14 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
/** */
private void checkLimitOffset(Number offsetFetchLimit, SqlNode n, String
nodeName) {
try {
- long res = IgniteMath.convertToLongExact(offsetFetchLimit);
+ BigDecimal val = IgniteMath.convertToBigDecimal(offsetFetchLimit);
- if (res < 0)
- throw newValidationError(n,
IgniteResource.INSTANCE.illegalFetchLimit(nodeName));
+ if (val.signum() < 0)
+ throw new IllegalArgumentException("Negative value for " +
nodeName);
+
+ IgniteMath.convertToLongExact(val, RoundingMode.DOWN);
}
- catch (ArithmeticException e) {
+ catch (RuntimeException e) {
throw newValidationError(n,
IgniteResource.INSTANCE.illegalFetchLimit(nodeName));
}
}
diff --git
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/type/IgniteTypeSystem.java
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/type/IgniteTypeSystem.java
index a0500966cf6..8161f63ba11 100644
---
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/type/IgniteTypeSystem.java
+++
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/type/IgniteTypeSystem.java
@@ -19,6 +19,7 @@ package
org.apache.ignite.internal.processors.query.calcite.type;
import java.io.Serializable;
import java.math.BigDecimal;
+import java.math.RoundingMode;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rel.type.RelDataTypeSystem;
@@ -126,4 +127,9 @@ public class IgniteTypeSystem extends RelDataTypeSystemImpl
implements Serializa
@Override public boolean shouldConvertRaggedUnionTypesToVarying() {
return true;
}
+
+ /** {@inheritDoc} */
+ @Override public RoundingMode roundingMode() {
+ return RoundingMode.HALF_UP;
+ }
}
diff --git
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/IgniteMath.java
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/IgniteMath.java
index 020b747a45a..1aeaf2716fe 100644
---
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/IgniteMath.java
+++
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/IgniteMath.java
@@ -21,6 +21,7 @@ import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import org.apache.calcite.sql.type.SqlTypeName;
+import
org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeSystem;
import static org.apache.calcite.sql.type.SqlTypeName.BIGINT;
import static org.apache.calcite.sql.type.SqlTypeName.INTEGER;
@@ -72,7 +73,7 @@ public class IgniteMath {
private static final double BYTE_MIN_EXT = Byte.MIN_VALUE - 1d;
/** */
- public static final RoundingMode NUMERIC_ROUNDING_MODE =
RoundingMode.HALF_UP;
+ public static final RoundingMode NUMERIC_ROUNDING_MODE =
IgniteTypeSystem.INSTANCE.roundingMode();
/** Returns the sum of its arguments, throwing an exception if the result
overflows an {@code long}. */
public static long addExact(long x, long y) {
@@ -267,7 +268,12 @@ public class IgniteMath {
/** Cast value to {@code long}, throwing an exception if the result
overflows an {@code long}. */
public static long convertToLongExact(Number x) {
- x = round(x);
+ return convertToLongExact(x, NUMERIC_ROUNDING_MODE);
+ }
+
+ /** Cast value to {@code long}, throwing an exception if the result
overflows an {@code long}. */
+ public static long convertToLongExact(Number x, RoundingMode roundingMode)
{
+ x = round(x, roundingMode);
checkNumberLongBounds(BIGINT, x);
@@ -411,11 +417,16 @@ public class IgniteMath {
/** */
private static double extendToRound(double x) {
- return x < 0.0d ? x - 0.5d : x + 0.5d;
+ return round(x).doubleValue();
+ }
+
+ /** */
+ private static BigDecimal round(Number x, RoundingMode roundingMode) {
+ return convertToBigDecimal(x).setScale(0, roundingMode);
}
/** */
private static BigDecimal round(Number x) {
- return convertToBigDecimal(x).setScale(0, NUMERIC_ROUNDING_MODE);
+ return round(x, NUMERIC_ROUNDING_MODE);
}
}
diff --git
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DynamicParametersIntegrationTest.java
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DynamicParametersIntegrationTest.java
index 0b68a9773ac..45f6db66f08 100644
---
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DynamicParametersIntegrationTest.java
+++
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DynamicParametersIntegrationTest.java
@@ -154,10 +154,6 @@ public class DynamicParametersIntegrationTest extends
AbstractBasicIntegrationTe
assertQuery("SELECT id FROM person ORDER BY id LIMIT
?").withParams(1D).returns(0).check();
assertQuery("SELECT id FROM person ORDER BY id LIMIT
?").withParams(1F).returns(0).check();
assertQuery("SELECT id FROM person ORDER BY id LIMIT
?").withParams(1L).returns(0).check();
- assertQuery("SELECT id FROM person ORDER BY id LIMIT
?").withParams(1.4).returns(0).check();
- assertQuery("SELECT id FROM person ORDER BY id LIMIT
?").withParams(1.5).returns(0).returns(1).check();
- assertQuery("SELECT id FROM person ORDER BY id LIMIT
?").withParams(1.6).returns(0).returns(1).check();
-
assertQuery("SELECT id FROM person ORDER BY id LIMIT
?").withParams(new BigDecimal(1)).returns(0).check();
assertQuery("SELECT id FROM person WHERE name LIKE ? ORDER BY id LIMIT
?").withParams("I%", 1)
@@ -170,6 +166,56 @@ public class DynamicParametersIntegrationTest extends
AbstractBasicIntegrationTe
.returns(3).returns(4).check();
}
+ /** */
+ @Test
+ public void testFractionalLimitOffset() {
+ createAndPopulateTable();
+
+ assertQuery("SELECT id FROM person ORDER BY id LIMIT
?").withParams(0.5).resultSize(0).check();
+ assertQuery("SELECT id FROM person ORDER BY id LIMIT
?").withParams(1.4).returns(0).check();
+ assertQuery("SELECT id FROM person ORDER BY id LIMIT
?").withParams(1.6).returns(0).check();
+ assertThrowsSqlException("SELECT id FROM person ORDER BY id LIMIT ?",
null, BigDecimal.valueOf(-1.5));
+ assertThrowsSqlException("SELECT id FROM person ORDER BY id LIMIT ?",
null, BigDecimal.valueOf(-0.5));
+
+ assertQuery("SELECT id FROM person ORDER BY id FETCH FIRST ? ROWS
ONLY")
+ .withParams(BigDecimal.valueOf(0.5))
+ .resultSize(0)
+ .check();
+ assertQuery("SELECT id FROM person ORDER BY id FETCH FIRST ? ROWS
ONLY")
+ .withParams(BigDecimal.valueOf(1.3))
+ .returns(0)
+ .check();
+ assertQuery("SELECT id FROM person ORDER BY id FETCH FIRST ? ROWS
ONLY")
+ .withParams(BigDecimal.valueOf(1.6))
+ .returns(0)
+ .check();
+ assertThrowsSqlException("SELECT id FROM person ORDER BY id FETCH
FIRST ? ROWS ONLY", null, BigDecimal.valueOf(-1.5));
+ assertThrowsSqlException("SELECT id FROM person ORDER BY id FETCH
FIRST ? ROWS ONLY", null, BigDecimal.valueOf(-0.5));
+
+ assertQuery("SELECT id FROM person ORDER BY id OFFSET ? ROWS")
+ .withParams(BigDecimal.valueOf(0.5))
+ .returns(0)
+ .returns(1)
+ .returns(2)
+ .returns(3)
+ .returns(4)
+ .check();
+ assertQuery("SELECT id FROM person ORDER BY id OFFSET ? ROWS")
+ .withParams(BigDecimal.valueOf(2.3))
+ .returns(2)
+ .returns(3)
+ .returns(4)
+ .check();
+ assertQuery("SELECT id FROM person ORDER BY id OFFSET ? ROWS")
+ .withParams(BigDecimal.valueOf(2.6))
+ .returns(2)
+ .returns(3)
+ .returns(4)
+ .check();
+ assertThrowsSqlException("SELECT id FROM person ORDER BY id OFFSET ?
ROWS", null, BigDecimal.valueOf(-0.5));
+ assertThrowsSqlException("SELECT id FROM person ORDER BY id OFFSET ?
ROWS", null, BigDecimal.valueOf(-1.5));
+ }
+
/** Tests the same query with different type of parameters to cover case
with check right plans cache work. **/
@Test
public void testWithDifferentParametersTypes() {
diff --git
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/LimitOffsetIntegrationTest.java
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/LimitOffsetIntegrationTest.java
index 6beccead712..04eeb5e543e 100644
---
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/LimitOffsetIntegrationTest.java
+++
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/LimitOffsetIntegrationTest.java
@@ -93,12 +93,12 @@ public class LimitOffsetIntegrationTest extends
AbstractBasicIntegrationTransact
/** */
@Test
- public void testNestedLimitOffsetWithUnion() {
- sql("INSERT into TEST_REPL VALUES (1, 'a'), (2, 'b'), (3, 'c'), (4,
'd')");
+ public void testNestedLimitOffsetWithUnion() throws Exception {
+ fillCache(cacheRepl, 4);
- assertQuery("(SELECT id FROM TEST_REPL WHERE id = 2) UNION ALL " +
+ assertQuery("(SELECT id FROM TEST_REPL WHERE id = 1) UNION ALL " +
"SELECT id FROM (select id from (SELECT id FROM TEST_REPL OFFSET
2) order by id OFFSET 1)"
- ).returns(2).returns(4).check();
+ ).returns(1).returns(3).check();
}
/** Tests correctness of fetch / offset params. */
@@ -121,10 +121,33 @@ public class LimitOffsetIntegrationTest extends
AbstractBasicIntegrationTransact
assertThrows("SELECT * FROM TEST_REPL OFFSET -1 ROWS",
IgniteSQLException.class, null);
+ assertThrowsSqlException("SELECT * FROM TEST_REPL OFFSET -1.5 ROWS",
null);
+
+ assertThrowsSqlException("SELECT * FROM TEST_REPL OFFSET -0.5 ROWS",
null);
+
assertThrows("SELECT * FROM TEST_REPL OFFSET 2+1 ROWS",
IgniteSQLException.class, null);
}
+ /** */
+ @Test
+ public void testFractionalLimitOffset() throws Exception {
+ fillCache(cacheRepl, 4);
+
+ assertQuery("SELECT id FROM TEST_REPL ORDER BY id LIMIT 0.5").check();
+ assertQuery("SELECT id FROM TEST_REPL ORDER BY id LIMIT
1.2").returns(0).check();
+ assertQuery("SELECT id FROM TEST_REPL ORDER BY id LIMIT
1.5").returns(0).check();
+
+ assertQuery("SELECT id FROM TEST_REPL ORDER BY id FETCH FIRST 0.5 ROWS
ONLY").check();
+ assertQuery("SELECT id FROM TEST_REPL ORDER BY id FETCH FIRST 1.3 ROWS
ONLY").returns(0).check();
+ assertQuery("SELECT id FROM TEST_REPL ORDER BY id FETCH FIRST 1.6 ROWS
ONLY").returns(0).check();
+
+ assertQuery("SELECT id FROM TEST_REPL ORDER BY id OFFSET 0.5 ROWS")
+ .returns(0).returns(1).returns(2).returns(3).check();
+ assertQuery("SELECT id FROM TEST_REPL ORDER BY id OFFSET 2.3
ROWS").returns(2).returns(3).check();
+ assertQuery("SELECT id FROM TEST_REPL ORDER BY id OFFSET 2.6
ROWS").returns(2).returns(3).check();
+ }
+
/**
*
*/
diff --git a/modules/calcite/src/test/sql/order/test_limit.test
b/modules/calcite/src/test/sql/order/test_limit.test
index 4cdfe041e35..00ce8f9822d 100644
--- a/modules/calcite/src/test/sql/order/test_limit.test
+++ b/modules/calcite/src/test/sql/order/test_limit.test
@@ -28,14 +28,12 @@ query I
SELECT a FROM test ORDER BY a LIMIT 1.5
----
11
-12
# decimal limit
query I
SELECT a FROM test ORDER BY a LIMIT 1.6
----
11
-12
# decimal limit
query I
@@ -54,6 +52,18 @@ SELECT a FROM test ORDER BY a FETCH FIRST 1.2 ROWS ONLY
----
11
+# decimal limit
+query I
+SELECT a FROM test ORDER BY a FETCH FIRST 1.5 ROWS ONLY
+----
+11
+
+# decimal limit
+query I
+SELECT a FROM test ORDER BY a FETCH FIRST 1.6 ROWS ONLY
+----
+11
+
# decimal offset/limit
query I
SELECT a FROM test ORDER BY a OFFSET 1.1 ROWS FETCH FIRST 1.1 ROWS ONLY
diff --git a/modules/calcite/src/test/sql/types/decimal/test_decimal.test
b/modules/calcite/src/test/sql/types/decimal/test_decimal.test
index d0733c8f175..c3cb24fcf17 100644
--- a/modules/calcite/src/test/sql/types/decimal/test_decimal.test
+++ b/modules/calcite/src/test/sql/types/decimal/test_decimal.test
@@ -15,7 +15,7 @@ DECIMAL(32767, 0)
query II
SELECT '0.1'::DECIMAL::VARCHAR, '922337203685478.758'::DECIMAL::VARCHAR;
----
-0 922337203685478
+0 922337203685479
# test basic string conversions
query II
@@ -27,7 +27,7 @@ SELECT '0.1'::DECIMAL(1,1)::VARCHAR,
'922337203685478.758'::DECIMAL(18,3)::VARCH
query II
SELECT '-0.1'::DECIMAL::VARCHAR, '-922337203685478.758'::DECIMAL::VARCHAR;
----
-0 -922337203685478
+0 -922337203685479
# negative values
query II
diff --git a/modules/calcite/src/test/sql/types/decimal/test_decimal_ops.test
b/modules/calcite/src/test/sql/types/decimal/test_decimal_ops.test
index 94ab2cd0ceb..86c529e269a 100644
--- a/modules/calcite/src/test/sql/types/decimal/test_decimal_ops.test
+++ b/modules/calcite/src/test/sql/types/decimal/test_decimal_ops.test
@@ -154,7 +154,7 @@ SELECT ROUND('100.3'::DECIMAL), ROUND('-127012.3'::DECIMAL)
query II
SELECT ROUND('10.5'::DECIMAL), ROUND('-10.5'::DECIMAL)
----
-10 -10
+11 -11
query II
SELECT ROUND('10.5'::DECIMAL(3,1)), ROUND('-10.5'::DECIMAL(3,1))
@@ -252,4 +252,4 @@ SELECT ROUND('1049578239572094512.32415'::DECIMAL(30,10),
0)::VARCHAR,
query I
SELECT (SELECT '1.0'::DECIMAL(2,1));
----
-1.0
\ No newline at end of file
+1.0