This is an automated email from the ASF dual-hosted git repository.

tkalkirill pushed a commit to branch ignite-28971
in repository https://gitbox.apache.org/repos/asf/ignite.git

commit ef7140019687d987205af07cb32198e5b6a9b201
Author: Kirill Tkalenko <[email protected]>
AuthorDate: Wed Aug 12 13:31:53 2026 +0300

    IGNITE-28971 Wip
---
 .../query/calcite/exec/LogicalRelImplementor.java  |  6 +++-
 .../query/calcite/prepare/IgnitePlanner.java       | 17 ++++++++++--
 .../query/calcite/prepare/IgniteSqlValidator.java  | 22 ++++++++++-----
 .../calcite/sql/IgniteSqlPaginationPolicy.java     | 32 ++++++++++++++++++++++
 .../DynamicParametersIntegrationTest.java          |  9 +++---
 .../integration/LimitOffsetIntegrationTest.java    | 12 ++++----
 .../OperatorsExtensionIntegrationTest.java         | 20 ++++++++++++++
 modules/calcite/src/test/sql/order/test_limit.test |  4 +++
 8 files changed, 101 insertions(+), 21 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 b5c1c06f1ce..71af0b907d2 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
@@ -125,6 +125,7 @@ import 
org.apache.ignite.internal.processors.query.calcite.rule.LogicalScanConve
 import 
org.apache.ignite.internal.processors.query.calcite.schema.CacheTableDescriptor;
 import org.apache.ignite.internal.processors.query.calcite.schema.IgniteIndex;
 import org.apache.ignite.internal.processors.query.calcite.schema.IgniteTable;
+import 
org.apache.ignite.internal.processors.query.calcite.sql.IgniteSqlPaginationPolicy;
 import org.apache.ignite.internal.processors.query.calcite.trait.Destination;
 import 
org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribution;
 import org.apache.ignite.internal.processors.query.calcite.trait.TraitUtils;
@@ -1084,7 +1085,10 @@ public class LogicalRelImplementor<Row> implements 
IgniteRelVisitor<Node<Row>> {
             if (paramAsDecimal.signum() < 0)
                 throw new IllegalArgumentException("Negative value for " + op);
 
-            return IgniteMath.convertToLongExact(paramAsDecimal, 
RoundingMode.DOWN);
+            IgniteSqlPaginationPolicy pagPlc = 
ctx.unwrap(IgniteSqlPaginationPolicy.class);
+            RoundingMode roundingMode = pagPlc == null ? 
IgniteMath.NUMERIC_ROUNDING_MODE : pagPlc.roundingMode();
+
+            return IgniteMath.convertToLongExact(paramAsDecimal, roundingMode);
         }
         catch (RuntimeException ex) {
             throw new 
IgniteSQLException(IgniteResource.INSTANCE.illegalFetchLimit(op).str(),
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgnitePlanner.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgnitePlanner.java
index 553ca3ba89a..2a73046f941 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgnitePlanner.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgnitePlanner.java
@@ -91,6 +91,7 @@ import 
org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
 import org.apache.ignite.internal.processors.query.IgniteSQLException;
 import 
org.apache.ignite.internal.processors.query.calcite.metadata.IgniteMetadata;
 import 
org.apache.ignite.internal.processors.query.calcite.metadata.RelMetadataQueryEx;
+import 
org.apache.ignite.internal.processors.query.calcite.sql.IgniteSqlPaginationPolicy;
 import 
org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
 import org.apache.ignite.internal.processors.query.calcite.util.Commons;
 import org.apache.ignite.internal.util.typedef.F;
@@ -365,7 +366,7 @@ public class IgnitePlanner implements Planner, 
RelOptTable.ViewExpander {
         }
 
         CalciteCatalogReader catalogReader = 
this.catalogReader.withSchemaPath(schemaPath);
-        SqlValidator validator = new IgniteSqlValidator(operatorTbl, 
catalogReader, typeFactory, validatorCfg, ctx.parameters());
+        SqlValidator validator = createSqlValidator(catalogReader);
         SqlToRelConverter sqlToRelConverter = sqlToRelConverter(validator, 
catalogReader, sqlToRelConverterCfg);
         RelRoot root = sqlToRelConverter.convertQuery(sqlNode, true, false);
         root = root.withRel(sqlToRelConverter.decorrelate(sqlNode, root.rel));
@@ -426,7 +427,7 @@ public class IgnitePlanner implements Planner, 
RelOptTable.ViewExpander {
     /** */
     private SqlValidator validator() {
         if (validator == null)
-            validator = new IgniteSqlValidator(operatorTbl, catalogReader, 
typeFactory, validatorCfg, ctx.parameters());
+            validator = createSqlValidator(catalogReader);
 
         return validator;
     }
@@ -798,4 +799,16 @@ public class IgnitePlanner implements Planner, 
RelOptTable.ViewExpander {
             super.checkCancel();
         }
     }
+
+    /** */
+    private SqlValidator createSqlValidator(CalciteCatalogReader 
catalogReader) {
+        return new IgniteSqlValidator(
+            operatorTbl,
+            catalogReader,
+            typeFactory,
+            validatorCfg,
+            ctx.parameters(),
+            ctx.unwrap(IgniteSqlPaginationPolicy.class)
+        );
+    }
 }
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 22d9be1f083..57ceb927de0 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
@@ -75,6 +75,7 @@ import 
org.apache.ignite.internal.processors.query.calcite.schema.CacheTableDesc
 import 
org.apache.ignite.internal.processors.query.calcite.schema.IgniteCacheTable;
 import org.apache.ignite.internal.processors.query.calcite.schema.IgniteTable;
 import 
org.apache.ignite.internal.processors.query.calcite.sql.IgniteSqlDecimalLiteral;
+import 
org.apache.ignite.internal.processors.query.calcite.sql.IgniteSqlPaginationPolicy;
 import 
org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
 import org.apache.ignite.internal.processors.query.calcite.type.OtherType;
 import org.apache.ignite.internal.processors.query.calcite.util.IgniteMath;
@@ -113,25 +114,31 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
     /** */
     private final RelDataType nullType;
 
+    /** */
+    private final @Nullable IgniteSqlPaginationPolicy pagPlc;
+
     /**
      * Creates a validator.
      *
-     * @param opTab         Operator table
-     * @param catalogReader Catalog reader
-     * @param typeFactory   Type factory
-     * @param cfg           Config
-     * @param parameters    Dynamic parameters
+     * @param opTab Operator table.
+     * @param catalogReader Catalog reader.
+     * @param typeFactory Type factory.
+     * @param cfg Config.
+     * @param parameters Dynamic parameters.
+     * @param pagPlc Pagination policy.
      */
     public IgniteSqlValidator(
         SqlOperatorTable opTab,
         CalciteCatalogReader catalogReader,
         IgniteTypeFactory typeFactory,
         SqlValidator.Config cfg,
-        @Nullable Object[] parameters
+        @Nullable Object[] parameters,
+        @Nullable IgniteSqlPaginationPolicy pagPlc
     ) {
         super(opTab, catalogReader, typeFactory, cfg);
 
         this.parameters = parameters;
+        this.pagPlc = pagPlc;
 
         nullType = typeFactory.createSqlType(SqlTypeName.NULL);
     }
@@ -306,7 +313,8 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
             if (val.signum() < 0)
                 throw new IllegalArgumentException("Negative value for " + 
nodeName);
 
-            IgniteMath.convertToLongExact(val, RoundingMode.DOWN);
+            RoundingMode roundingMode = pagPlc == null ? 
IgniteMath.NUMERIC_ROUNDING_MODE : pagPlc.roundingMode();
+            IgniteMath.convertToLongExact(val, roundingMode);
         }
         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/sql/IgniteSqlPaginationPolicy.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/IgniteSqlPaginationPolicy.java
new file mode 100644
index 00000000000..47eb4d15393
--- /dev/null
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/IgniteSqlPaginationPolicy.java
@@ -0,0 +1,32 @@
+/*
+ * 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.sql;
+
+import java.math.RoundingMode;
+import org.apache.calcite.plan.Context;
+import org.apache.calcite.tools.Frameworks;
+
+/**
+ * Defines a policy for processing values of SQL pagination clauses: LIMIT, 
FETCH, and OFFSET.
+ *
+ * <p>Custom instance can be supplied through {@link 
Frameworks.ConfigBuilder#context(Context)}.</p>
+ */
+@FunctionalInterface
+public interface IgniteSqlPaginationPolicy {
+    /** Returns the rounding mode for FETCH, LIMIT and OFFSET values. */
+    RoundingMode roundingMode();
+}
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 45f6db66f08..cea49a96c10 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
@@ -171,15 +171,15 @@ public class DynamicParametersIntegrationTest extends 
AbstractBasicIntegrationTe
     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(0.5).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.6).returns(0).check();
+        assertQuery("SELECT id FROM person ORDER BY id LIMIT 
?").withParams(1.6).returns(0).returns(1).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)
+            .returns(0)
             .check();
         assertQuery("SELECT id FROM person ORDER BY id FETCH FIRST ? ROWS 
ONLY")
             .withParams(BigDecimal.valueOf(1.3))
@@ -188,13 +188,13 @@ public class DynamicParametersIntegrationTest extends 
AbstractBasicIntegrationTe
         assertQuery("SELECT id FROM person ORDER BY id FETCH FIRST ? ROWS 
ONLY")
             .withParams(BigDecimal.valueOf(1.6))
             .returns(0)
+            .returns(1)
             .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)
@@ -208,7 +208,6 @@ public class DynamicParametersIntegrationTest extends 
AbstractBasicIntegrationTe
             .check();
         assertQuery("SELECT id FROM person ORDER BY id OFFSET ? ROWS")
             .withParams(BigDecimal.valueOf(2.6))
-            .returns(2)
             .returns(3)
             .returns(4)
             .check();
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 04eeb5e543e..74b15d6e476 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
@@ -134,18 +134,18 @@ public class LimitOffsetIntegrationTest extends 
AbstractBasicIntegrationTransact
     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 
0.5").returns(0).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 LIMIT 
1.5").returns(0).returns(1).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 0.5 ROWS 
ONLY").returns(0).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 FETCH FIRST 1.6 ROWS 
ONLY").returns(0).returns(1).check();
 
         assertQuery("SELECT id FROM TEST_REPL ORDER BY id OFFSET 0.5 ROWS")
-            .returns(0).returns(1).returns(2).returns(3).check();
+            .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();
+        assertQuery("SELECT id FROM TEST_REPL ORDER BY id OFFSET 2.6 
ROWS").returns(3).check();
     }
 
     /**
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/OperatorsExtensionIntegrationTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/OperatorsExtensionIntegrationTest.java
index 4d4e402f1e5..0ad8cd90e5c 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/OperatorsExtensionIntegrationTest.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/OperatorsExtensionIntegrationTest.java
@@ -17,6 +17,7 @@
 package org.apache.ignite.internal.processors.query.calcite.integration;
 
 import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.sql.Timestamp;
 import java.util.HashSet;
 import java.util.List;
@@ -64,6 +65,7 @@ import 
org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.Accumula
 import 
org.apache.ignite.internal.processors.query.calcite.prepare.IgniteConvertletTable;
 import 
org.apache.ignite.internal.processors.query.calcite.prepare.IgniteSqlNodeRewriter;
 import 
org.apache.ignite.internal.processors.query.calcite.prepare.IgniteSqlValidator;
+import 
org.apache.ignite.internal.processors.query.calcite.sql.IgniteSqlPaginationPolicy;
 import 
org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
 import org.apache.ignite.plugin.AbstractTestPluginProvider;
 import org.apache.ignite.plugin.PluginContext;
@@ -93,6 +95,7 @@ public class OperatorsExtensionIntegrationTest extends 
AbstractBasicIntegrationT
                                     .withSqlNodeRewriter(new SqlRewriter()))
                             .context(Contexts.chain(
                                 
CalciteQueryProcessor.FRAMEWORK_CONFIG.getContext(),
+                                Contexts.of((IgniteSqlPaginationPolicy)() -> 
RoundingMode.DOWN),
                                 Contexts.of(new 
AccumulatorFactoryProviderImpl())))
                             .build();
 
@@ -178,6 +181,23 @@ public class OperatorsExtensionIntegrationTest extends 
AbstractBasicIntegrationT
             .check();
     }
 
+    /** */
+    @Test
+    public void testPaginationRoundingPolicy() {
+        assertQuery("SELECT x FROM (VALUES (0), (1), (2)) t(x) ORDER BY x 
LIMIT 1.9")
+            .returns(0)
+            .check();
+
+        assertQuery("SELECT x FROM (VALUES (0), (1), (2)) t(x) ORDER BY x 
FETCH FIRST 1.9 ROWS ONLY")
+            .returns(0)
+            .check();
+
+        assertQuery("SELECT x FROM (VALUES (0), (1), (2)) t(x) ORDER BY x 
OFFSET 1.9 ROWS")
+            .returns(1)
+            .returns(2)
+            .check();
+    }
+
     /** Rewrites LTRIM with 2 parameters. */
     public static SqlCall rewriteLtrim(SqlValidator validator, SqlCall call) {
         if (call.operandCount() != 2)
diff --git a/modules/calcite/src/test/sql/order/test_limit.test 
b/modules/calcite/src/test/sql/order/test_limit.test
index 00ce8f9822d..f7fa98caf70 100644
--- a/modules/calcite/src/test/sql/order/test_limit.test
+++ b/modules/calcite/src/test/sql/order/test_limit.test
@@ -28,12 +28,14 @@ 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
@@ -57,12 +59,14 @@ query I
 SELECT a FROM test ORDER BY a FETCH FIRST 1.5 ROWS ONLY
 ----
 11
+12
 
 # decimal limit
 query I
 SELECT a FROM test ORDER BY a FETCH FIRST 1.6 ROWS ONLY
 ----
 11
+12
 
 # decimal offset/limit
 query I

Reply via email to