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

zstan 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 601c69f6a7b IGNITE-28844 Calcite. Improve type checking in LIMIT / 
OFFSET clauses (#13311)
601c69f6a7b is described below

commit 601c69f6a7bc9dca10b939f91912dfea33302f03
Author: Evgeniy Stanilovskiy <[email protected]>
AuthorDate: Thu Aug 6 12:21:46 2026 +0300

    IGNITE-28844 Calcite. Improve type checking in LIMIT / OFFSET clauses 
(#13311)
---
 .../query/calcite/exec/LogicalRelImplementor.java  |  55 ++++++++-
 .../query/calcite/exec/rel/AbstractNode.java       |   3 +
 .../rel/AbstractRightMaterializedJoinNode.java     |   3 -
 .../query/calcite/exec/rel/LimitNode.java          |  94 ++++++++------
 .../query/calcite/exec/rel/MergeJoinNode.java      |   3 -
 .../query/calcite/exec/rel/SortNode.java           |  37 +++---
 .../query/calcite/prepare/IgniteSqlValidator.java  | 105 ++++++++++------
 .../query/calcite/util/IgniteResource.java         |  13 +-
 .../calcite/exec/LogicalRelImplementorTest.java    |   2 +-
 .../query/calcite/exec/rel/LimitExecutionTest.java |   9 +-
 .../DynamicParametersIntegrationTest.java          |  10 ++
 .../integration/LimitOffsetIntegrationTest.java    |  20 +--
 .../integration/QueryMetadataIntegrationTest.java  |  58 ++++++---
 .../query/calcite/planner/AbstractPlannerTest.java | 137 +++++++++++++++++----
 .../planner/DynamicParametersPlannerTest.java      |  85 +++++++++++++
 .../query/calcite/planner/HashJoinPlannerTest.java |   4 +-
 .../apache/ignite/testsuites/PlannerTestSuite.java |   2 +
 modules/calcite/src/test/sql/order/test_limit.test |  62 +++++++++-
 18 files changed, 534 insertions(+), 168 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 1e93eecaf7c..64bcc6e6526 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
@@ -44,7 +44,9 @@ import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexUtil;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.mapping.IntPair;
+import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
 import org.apache.ignite.internal.processors.failure.FailureProcessor;
+import org.apache.ignite.internal.processors.query.IgniteSQLException;
 import org.apache.ignite.internal.processors.query.QueryUtils;
 import 
org.apache.ignite.internal.processors.query.calcite.exec.RowHandler.RowFactory;
 import 
org.apache.ignite.internal.processors.query.calcite.exec.exp.ExpressionFactory;
@@ -126,6 +128,8 @@ import 
org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribut
 import org.apache.ignite.internal.processors.query.calcite.trait.TraitUtils;
 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.processors.query.calcite.util.IgniteMath;
+import org.apache.ignite.internal.processors.query.calcite.util.IgniteResource;
 import org.apache.ignite.internal.processors.query.calcite.util.RexUtils;
 import org.apache.ignite.internal.util.typedef.F;
 import org.jetbrains.annotations.Nullable;
@@ -567,8 +571,8 @@ public class LogicalRelImplementor<Row> implements 
IgniteRelVisitor<Node<Row>> {
                 ctx,
                 rowType,
                 idxBndRel.first() ? cmp : cmp.reversed(),
-                null,
-                () -> 1
+                0,
+                1
             );
 
             sortNode.register(scanNode);
@@ -630,8 +634,8 @@ public class LogicalRelImplementor<Row> implements 
IgniteRelVisitor<Node<Row>> {
 
     /** {@inheritDoc} */
     @Override public Node<Row> visit(IgniteLimit rel) {
-        Supplier<Integer> offset = (rel.offset() == null) ? null : 
expressionFactory.execute(rel.offset());
-        Supplier<Integer> fetch = (rel.fetch() == null) ? null : 
expressionFactory.execute(rel.fetch());
+        long offset = validateAndGetOffset(rel.offset(), 
LimitNode.OFFSET_DEFAULT);
+        long fetch = validateAndGetFetch(rel.fetch(), LimitNode.FETCH_DEFAULT);
 
         LimitNode<Row> node = new LimitNode<>(ctx, rel.getRowType(), offset, 
fetch);
 
@@ -646,8 +650,8 @@ public class LogicalRelImplementor<Row> implements 
IgniteRelVisitor<Node<Row>> {
     @Override public Node<Row> visit(IgniteSort rel) {
         RelCollation collation = rel.getCollation();
 
-        Supplier<Integer> offset = (rel.offset == null) ? null : 
expressionFactory.execute(rel.offset);
-        Supplier<Integer> fetch = (rel.fetch == null) ? null : 
expressionFactory.execute(rel.fetch);
+        long offset = validateAndGetOffset(rel.offset, 
SortNode.OFFSET_DEFAULT);
+        long fetch = validateAndGetFetch(rel.fetch, SortNode.FETCH_DEFAULT);
 
         SortNode<Row> node = new SortNode<>(ctx, rel.getRowType(), 
expressionFactory.comparator(collation), offset,
             fetch);
@@ -659,6 +663,16 @@ public class LogicalRelImplementor<Row> implements 
IgniteRelVisitor<Node<Row>> {
         return node;
     }
 
+    /** */
+    private long validateAndGetOffset(RexNode node, long defaultVal) {
+        return node == null ? defaultVal : 
validateAndGetFetchOffsetParams(node, "offset");
+    }
+
+    /** */
+    private long validateAndGetFetch(RexNode node, long defaultVal) {
+        return node == null ? defaultVal : 
validateAndGetFetchOffsetParams(node, "fetch");
+    }
+
     /** {@inheritDoc} */
     @Override public Node<Row> visit(IgniteTableSpool rel) {
         TableSpoolNode<Row> node = new TableSpoolNode<>(ctx, rel.getRowType(), 
rel.readType == Spool.Type.LAZY);
@@ -1050,4 +1064,33 @@ public class LogicalRelImplementor<Row> implements 
IgniteRelVisitor<Node<Row>> {
             otherColMapping
         );
     }
+
+    /** */
+    private long validateAndGetFetchOffsetParams(RexNode node, String op) {
+        Supplier<Object> scalar = expressionFactory.execute(node);
+        Object param = scalar.get();
+
+        if (!(param instanceof Number)) {
+            String actual = param == null ? "null" : 
param.getClass().getSimpleName();
+            throw new 
IgniteSQLException(IgniteResource.INSTANCE.incorrectDynamicParameterType("BIGINT",
 actual).str(),
+                IgniteQueryErrorCode.UNEXPECTED_ELEMENT_TYPE);
+        }
+
+        long paramAsLong;
+
+        try {
+            paramAsLong = IgniteMath.convertToLongExact((Number)param);
+        }
+        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/exec/rel/AbstractNode.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/AbstractNode.java
index e426069a32d..d238c6576b8 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/AbstractNode.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/AbstractNode.java
@@ -37,6 +37,9 @@ import static 
org.apache.ignite.IgniteSystemProperties.IGNITE_CALCITE_EXEC_MODIF
  * Abstract node of execution tree.
  */
 public abstract class AbstractNode<Row> implements Node<Row> {
+    /** Special flag which marks that all the rows are received. */
+    static final int NOT_WAITING = -1;
+
     /** */
     public static final int IN_BUFFER_SIZE = 
IgniteSystemProperties.getInteger(IGNITE_CALCITE_EXEC_IN_BUFFER_SIZE, 512);
 
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/AbstractRightMaterializedJoinNode.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/AbstractRightMaterializedJoinNode.java
index 27c25f24e58..6e2017afd8d 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/AbstractRightMaterializedJoinNode.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/AbstractRightMaterializedJoinNode.java
@@ -29,9 +29,6 @@ public abstract class AbstractRightMaterializedJoinNode<Row> 
extends MemoryTrack
     /** */
     protected static final int HALF_BUF_SIZE = IN_BUFFER_SIZE >> 1;
 
-    /** Special flag which marks that all the rows are received. */
-    protected static final int NOT_WAITING = -1;
-
     /** */
     protected final Deque<Row> leftInBuf = new ArrayDeque<>(IN_BUFFER_SIZE);
 
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/LimitNode.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/LimitNode.java
index 9fa9d14090c..cee17d76289 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/LimitNode.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/LimitNode.java
@@ -17,46 +17,56 @@
 
 package org.apache.ignite.internal.processors.query.calcite.exec.rel;
 
-import java.util.function.Supplier;
 import org.apache.calcite.rel.type.RelDataType;
 import 
org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
+import org.apache.ignite.internal.processors.query.calcite.util.IgniteMath;
 import org.apache.ignite.internal.util.typedef.F;
-import org.jetbrains.annotations.Nullable;
 
 /** Offset, fetch|limit support node. */
 public class LimitNode<Row> extends AbstractNode<Row> implements 
SingleNode<Row>, Downstream<Row> {
-    /** Offset if its present, otherwise 0. */
-    private final int offset;
+    /** */
+    public static final long FETCH_DEFAULT = -1;
 
-    /** Fetch if its present, otherwise 0. */
-    private final int fetch;
+    /** */
+    public static final long OFFSET_DEFAULT = 0;
 
-    /** Already processed (pushed to upstream) rows count. */
-    private int rowsProcessed;
+    /** Offset param. */
+    private final long offset;
 
-    /** Fetch can be unset, in this case we need all rows. */
-    private @Nullable Supplier<Integer> fetchNode;
+    /** How many rows need to be processed, if {@code 0} it depends on {@link 
#rowsSummary}. */
+    private final long fetch;
+
+    /** Summary rows to process. */
+    private final long rowsSummary;
+
+    /** Already processed (pushed to downstream) rows count. */
+    private long rowsProcessed;
 
     /** Waiting results counter. */
     private int waiting;
 
+    /** Upper requested rows. */
+    private int requested;
+
     /**
      * Constructor.
      *
      * @param ctx Execution context.
      * @param rowType Row type.
+     * @param offset How many rows need to be skipped.
+     * @param fetch How many rows need to be processed, {@link #FETCH_DEFAULT} 
if param is undefined.
      */
     public LimitNode(
         ExecutionContext<Row> ctx,
         RelDataType rowType,
-        Supplier<Integer> offsetNode,
-        Supplier<Integer> fetchNode
+        long offset,
+        long fetch
     ) {
         super(ctx, rowType);
 
-        offset = offsetNode == null ? 0 : offsetNode.get();
-        fetch = fetchNode == null ? 0 : fetchNode.get();
-        this.fetchNode = fetchNode;
+        this.offset = offset;
+        rowsSummary = fetch == FETCH_DEFAULT ? Long.MAX_VALUE : 
IgniteMath.addExact(fetch, offset);
+        this.fetch = fetch == FETCH_DEFAULT ? 0 : fetch;
     }
 
     /** {@inheritDoc} */
@@ -64,19 +74,22 @@ public class LimitNode<Row> extends AbstractNode<Row> 
implements SingleNode<Row>
         assert !F.isEmpty(sources()) && sources().size() == 1;
         assert rowsCnt > 0;
 
-        if (fetchNone()) {
+        if (!hasMoreData()) {
             end();
 
             return;
         }
 
-        if (offset > 0 && rowsProcessed == 0)
-            rowsCnt = offset + rowsCnt;
+        assert requested == 0 : requested;
+        requested = rowsCnt;
 
-        waiting = rowsCnt;
+        if (fetch > 0) {
+            long remain = rowsSummary - rowsProcessed;
 
-        if (fetch > 0)
-            rowsCnt = Math.min(rowsCnt, (fetch + offset) - rowsProcessed);
+            rowsCnt = remain > rowsCnt ? rowsCnt : (int)remain;
+        }
+
+        waiting = rowsCnt;
 
         checkState();
 
@@ -85,38 +98,49 @@ public class LimitNode<Row> extends AbstractNode<Row> 
implements SingleNode<Row>
 
     /** {@inheritDoc} */
     @Override public void push(Row row) throws Exception {
-        if (waiting == -1)
+        if (waiting == NOT_WAITING)
             return;
 
-        ++rowsProcessed;
-
         --waiting;
 
-        checkState();
-
-        if (rowsProcessed > offset) {
-            if (fetchNode == null || (fetchNode != null && rowsProcessed <= 
fetch + offset))
-                downstream().push(row);
+        if (rowsProcessed >= offset && hasMoreData()) {
+            // This two rows can`t be swapped, cause if all requested rows 
have been pushed it will trigger further request call.
+            --requested;
+            downstream().push(row);
         }
 
-        if (fetch > 0 && rowsProcessed == fetch + offset && waiting > 0)
+        ++rowsProcessed;
+
+        // There several cases are possible:
+        //  1) requested = 512, limit = 1, offset = not defined: need to pass 
1 row and call end()
+        //  2) requested = 512, limit = 512, offset = not defined: just need 
to pass all rows without end() call
+        //  3) requested = 512, limit = 512, offset = 1: need to request 
initially 512 and further 1 row
+        if (!hasMoreData() && requested > 0)
             end();
+
+        if (waiting == 0 && requested > 0)
+            source().request(waiting = requested);
     }
 
     /** {@inheritDoc} */
     @Override public void end() throws Exception {
-        if (waiting == -1)
+        if (waiting == NOT_WAITING)
             return;
 
         assert downstream() != null;
 
-        waiting = -1;
+        waiting = NOT_WAITING;
+
+        if (requested > 0)
+            requested = 0;
 
         downstream().end();
     }
 
     /** {@inheritDoc} */
     @Override protected void rewindInternal() {
+        waiting = 0;
+        requested = 0;
         rowsProcessed = 0;
     }
 
@@ -128,8 +152,8 @@ public class LimitNode<Row> extends AbstractNode<Row> 
implements SingleNode<Row>
         return this;
     }
 
-    /** {@code True} if requested 0 results, or all already processed. */
-    private boolean fetchNone() {
-        return (fetchNode != null && fetch == 0) || (fetch > 0 && 
rowsProcessed == fetch + offset);
+    /** {@code True} If current rows processed is less than required or 
undefined. */
+    private boolean hasMoreData() {
+        return rowsProcessed < rowsSummary;
     }
 }
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/MergeJoinNode.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/MergeJoinNode.java
index 1c4d902c596..800e66e918b 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/MergeJoinNode.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/MergeJoinNode.java
@@ -35,9 +35,6 @@ public abstract class MergeJoinNode<Row> extends 
AbstractNode<Row> {
     /** */
     private static final int HALF_BUF_SIZE = IN_BUFFER_SIZE >> 1;
 
-    /** Special value to highlights that all row were received and we are not 
waiting any more. */
-    protected static final int NOT_WAITING = -1;
-
     /** */
     protected final Comparator<Row> comp;
 
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/SortNode.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/SortNode.java
index 1347fced4b6..d9180d25601 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/SortNode.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/SortNode.java
@@ -20,21 +20,26 @@ import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.List;
 import java.util.PriorityQueue;
-import java.util.function.Supplier;
 import org.apache.calcite.rel.type.RelDataType;
 import 
org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
+import org.apache.ignite.internal.processors.query.calcite.util.IgniteMath;
 import org.apache.ignite.internal.util.GridBoundedPriorityQueue;
 import org.apache.ignite.internal.util.typedef.F;
-import org.jetbrains.annotations.Nullable;
 
 /**
  * Sort node.
  */
 public class SortNode<Row> extends MemoryTrackingNode<Row> implements 
SingleNode<Row>, Downstream<Row> {
+    /** */
+    public static final long OFFSET_DEFAULT = 0;
+
+    /** */
+    public static final long FETCH_DEFAULT = -1;
+
     /** How many rows are requested by downstream. */
     private int requested;
 
-    /** How many rows are we waiting for from the upstream. {@code -1} means 
end of stream. */
+    /** How many rows are we waiting for from the upstream. {@link 
#NOT_WAITING} means end of stream. */
     private int waiting;
 
     /**  */
@@ -44,7 +49,7 @@ public class SortNode<Row> extends MemoryTrackingNode<Row> 
implements SingleNode
     private final PriorityQueue<Row> rows;
 
     /** SQL select limit. Negative if disabled. */
-    private final int limit;
+    private final long limit;
 
     /** Reverse-ordered rows in case of limited sort. */
     private List<Row> reversed;
@@ -53,26 +58,26 @@ public class SortNode<Row> extends MemoryTrackingNode<Row> 
implements SingleNode
      * @param ctx Execution context.
      * @param comp Rows comparator.
      * @param offset Offset.
-     * @param fetch Limit.
+     * @param fetch How many rows need to be processed, {@link #FETCH_DEFAULT} 
if param is undefined.
      */
     public SortNode(
         ExecutionContext<Row> ctx, RelDataType rowType,
         Comparator<Row> comp,
-        @Nullable Supplier<Integer> offset,
-        @Nullable Supplier<Integer> fetch
+        long offset,
+        long fetch
     ) {
         super(ctx, rowType);
 
-        assert fetch == null || fetch.get() >= 0;
-        assert offset == null || offset.get() >= 0;
+        assert fetch == FETCH_DEFAULT || fetch > 0 : "Unexpected fetch = " + 
fetch;
+        assert offset >= 0 : "Unexpected offset = " + offset;
 
-        limit = fetch == null ? -1 : fetch.get() + (offset == null ? 0 : 
offset.get());
+        limit = fetch == FETCH_DEFAULT ? -1 : (fetch > Long.MAX_VALUE - offset 
? -1 : fetch + offset);
 
-        if (limit < 0)
+        if (limit < 1 || limit > Integer.MAX_VALUE)
             rows = new PriorityQueue<>(comp);
         else {
-            rows = new GridBoundedPriorityQueue<>(limit, comp == null ? 
(Comparator<Row>)Comparator.reverseOrder()
-                : comp.reversed());
+            rows = new 
GridBoundedPriorityQueue<>(IgniteMath.convertToIntExact(limit), comp == null ?
+                (Comparator<Row>)Comparator.reverseOrder() : comp.reversed());
         }
     }
 
@@ -81,7 +86,7 @@ public class SortNode<Row> extends MemoryTrackingNode<Row> 
implements SingleNode
      * @param comp Rows comparator.
      */
     public SortNode(ExecutionContext<Row> ctx, RelDataType rowType, 
Comparator<Row> comp) {
-        this(ctx, rowType, comp, null, null);
+        this(ctx, rowType, comp, OFFSET_DEFAULT, FETCH_DEFAULT);
     }
 
     /** {@inheritDoc} */
@@ -150,7 +155,7 @@ public class SortNode<Row> extends MemoryTrackingNode<Row> 
implements SingleNode
 
         checkState();
 
-        waiting = -1;
+        waiting = NOT_WAITING;
 
         flush();
     }
@@ -160,7 +165,7 @@ public class SortNode<Row> extends MemoryTrackingNode<Row> 
implements SingleNode
         if (isClosed())
             return;
 
-        assert waiting == -1;
+        assert waiting == NOT_WAITING;
 
         int processed = 0;
 
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 1c7f00da265..cd044a629bd 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
@@ -29,6 +29,7 @@ import org.apache.calcite.prepare.CalciteCatalogReader;
 import org.apache.calcite.prepare.Prepare;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.runtime.Resources;
 import org.apache.calcite.sql.JoinConditionType;
 import org.apache.calcite.sql.JoinType;
 import org.apache.calcite.sql.SqlAggFunction;
@@ -61,6 +62,7 @@ import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.sql.validate.SelectScope;
 import org.apache.calcite.sql.validate.SqlQualified;
 import org.apache.calcite.sql.validate.SqlValidator;
+import org.apache.calcite.sql.validate.SqlValidatorException;
 import org.apache.calcite.sql.validate.SqlValidatorImpl;
 import org.apache.calcite.sql.validate.SqlValidatorNamespace;
 import org.apache.calcite.sql.validate.SqlValidatorScope;
@@ -74,8 +76,8 @@ 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.type.IgniteTypeFactory;
 import org.apache.ignite.internal.processors.query.calcite.type.OtherType;
+import org.apache.ignite.internal.processors.query.calcite.util.IgniteMath;
 import org.apache.ignite.internal.processors.query.calcite.util.IgniteResource;
-import org.apache.ignite.internal.util.typedef.F;
 import org.immutables.value.Value;
 import org.jetbrains.annotations.Nullable;
 
@@ -84,9 +86,6 @@ import static org.apache.calcite.util.Static.RESOURCE;
 /** Validator. */
 @Value.Enclosing
 public class IgniteSqlValidator extends SqlValidatorImpl {
-    /** Decimal of Integer.MAX_VALUE for fetch/offset bounding. */
-    private static final BigDecimal DEC_INT_MAX = 
BigDecimal.valueOf(Integer.MAX_VALUE);
-
     /** **/
     private static final int MAX_LENGTH_OF_ALIASES = 256;
 
@@ -241,10 +240,74 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
 
     /** {@inheritDoc} */
     @Override protected void validateSelect(SqlSelect select, RelDataType 
targetRowType) {
-        checkIntegerLimit(select.getFetch(), "fetch / limit");
-        checkIntegerLimit(select.getOffset(), "offset");
-
         super.validateSelect(select, targetRowType);
+
+        validateFetchOffset(select.getFetch(), "fetch / limit");
+        validateFetchOffset(select.getOffset(), "offset");
+    }
+
+    /**
+     * Validate fetch/offset params restrictions.
+     *
+     * @param n          Node to check.
+     * @param clauseName Clause name.
+     */
+    private void validateFetchOffset(@Nullable SqlNode n, String clauseName) {
+        if (n == null)
+            return;
+
+        if (n instanceof SqlLiteral) {
+            BigDecimal offsetFetchLimit = ((SqlLiteral)n).bigDecimalValue();
+
+            checkLimitOffset(offsetFetchLimit, n, clauseName);
+        }
+        else if (n instanceof SqlDynamicParam dynamicParam) {
+            // Dynamic parameters are nullable.
+            RelDataType expectType = 
typeFactory.createTypeWithNullability(typeFactory.createSqlType(SqlTypeName.DECIMAL),
 true);
+
+            if (definedDynParam(dynamicParam)) {
+                Object param = parameters[dynamicParam.getIndex()];
+
+                if (!(param instanceof Number)) {
+                    Resources.ExInst<SqlValidatorException> err;
+
+                    if (param == null)
+                        err = 
IgniteResource.INSTANCE.incorrectDynamicParameterType(SqlTypeName.BIGINT.toString(),
 "null");
+                    else {
+                        SqlTypeName paramType = 
typeFactory().createType(param.getClass()).getSqlTypeName();
+                        err = 
IgniteResource.INSTANCE.incorrectDynamicParameterType(SqlTypeName.BIGINT.toString(),
 paramType.getName());
+                    }
+
+                    throw newValidationError(n, err);
+                }
+                else
+                    checkLimitOffset((Number)param, n, clauseName);
+
+                setValidatedNodeType(dynamicParam, expectType);
+            }
+            else {
+                expectType = 
typeFactory.createTypeWithNullability(typeFactory.createSqlType(SqlTypeName.BIGINT),
 true);
+                setValidatedNodeType(dynamicParam, expectType);
+            }
+        }
+    }
+
+    /** Returns {@code true} if the given dynamic parameter has value set. */
+    private boolean definedDynParam(SqlDynamicParam param) {
+        return param.getIndex() < parameters.length;
+    }
+
+    /** */
+    private void checkLimitOffset(Number offsetFetchLimit, SqlNode n, String 
nodeName) {
+        try {
+            long res = IgniteMath.convertToLongExact(offsetFetchLimit);
+
+            if (res < 0)
+                throw newValidationError(n, 
IgniteResource.INSTANCE.illegalFetchLimit(nodeName));
+        }
+        catch (ArithmeticException e) {
+            throw newValidationError(n, 
IgniteResource.INSTANCE.illegalFetchLimit(nodeName));
+        }
     }
 
     /** {@inheritDoc} */
@@ -261,34 +324,6 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
         super.validateNamespace(namespace, targetRowType);
     }
 
-    /**
-     * @param n Node to check limit.
-     * @param nodeName Node name.
-     */
-    private void checkIntegerLimit(SqlNode n, String nodeName) {
-        if (n instanceof SqlLiteral) {
-            BigDecimal offFetchLimit = ((SqlLiteral)n).bigDecimalValue();
-
-            if (offFetchLimit.compareTo(DEC_INT_MAX) > 0 || 
offFetchLimit.compareTo(BigDecimal.ZERO) < 0)
-                throw newValidationError(n, 
IgniteResource.INSTANCE.correctIntegerLimit(nodeName));
-        }
-        else if (n instanceof SqlDynamicParam) {
-            // will fail in params check.
-            if (F.isEmpty(parameters))
-                return;
-
-            int idx = ((SqlDynamicParam)n).getIndex();
-
-            if (idx < parameters.length) {
-                Object param = parameters[idx];
-                if (parameters[idx] instanceof Integer) {
-                    if ((Integer)param < 0)
-                        throw newValidationError(n, 
IgniteResource.INSTANCE.correctIntegerLimit(nodeName));
-                }
-            }
-        }
-    }
-
     /** {@inheritDoc} */
     @Override public void validateCall(SqlCall call, SqlValidatorScope scope) {
         if (call.getKind() == SqlKind.AS) {
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/IgniteResource.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/IgniteResource.java
index df74d56a916..8dff42046dc 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/IgniteResource.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/IgniteResource.java
@@ -39,11 +39,6 @@ public interface IgniteResource {
     @Resources.BaseMessage("Illegal aggregate function. {0} is unsupported at 
the moment.")
     Resources.ExInst<SqlValidatorException> 
unsupportedAggregationFunction(String a0);
 
-    /** */
-    @Resources.BaseMessage("Illegal value of {0}. The value must be positive 
and less than Integer.MAX_VALUE " +
-        "(" + Integer.MAX_VALUE + ")." )
-    Resources.ExInst<SqlValidatorException> correctIntegerLimit(String a0);
-
     /** */
     @Resources.BaseMessage("Option ''{0}'' has already been defined")
     Resources.ExInst<SqlValidatorException> optionAlreadyDefined(String 
optName);
@@ -84,4 +79,12 @@ public interface IgniteResource {
     /** */
     @Resources.BaseMessage("Operator ''CAST'' supports only the parameters: 
value and target type.")
     Resources.ExInst<SqlValidatorException> invalidCastParameters();
+
+    /** */
+    @Resources.BaseMessage("Illegal value of {0}. The value must be 
non-negative and less than or equal to " + Long.MAX_VALUE)
+    Resources.ExInst<SqlValidatorException> illegalFetchLimit(String a0);
+
+    /** */
+    @Resources.BaseMessage("Incorrect type of a dynamic parameter. Expected 
<{0}> but got <{1}>")
+    Resources.ExInst<SqlValidatorException> 
incorrectDynamicParameterType(String expected, String actual);
 }
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementorTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementorTest.java
index 06a00e8622f..3e87461ea0b 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementorTest.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementorTest.java
@@ -196,7 +196,7 @@ public class LogicalRelImplementorTest extends 
GridCommonAbstractTest {
         node = relImplementor.visit(idxScan);
 
         assertTrue(node instanceof SortNode);
-        assertEquals(1, (int)U.field(node, "limit"));
+        assertEquals(1L, (long)U.field(node, "limit"));
         assertTrue(node.sources() != null && node.sources().size() == 1);
         assertTrue(node.sources().get(0) instanceof ScanNode);
     }
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/LimitExecutionTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/LimitExecutionTest.java
index 6dd3c7b36cf..fda6deaacc9 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/LimitExecutionTest.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/LimitExecutionTest.java
@@ -52,6 +52,7 @@ public class LimitExecutionTest extends AbstractExecutionTest 
{
     /** Tests Sort node can limit its output when fetch param is set. */
     @Test
     public void testSortLimit() {
+        checkLimitSort(0, 0);
         checkLimitSort(0, 1);
         checkLimitSort(1, 0);
         checkLimitSort(1, 1);
@@ -78,10 +79,10 @@ public class LimitExecutionTest extends 
AbstractExecutionTest {
 
         RootNode<Object[]> rootNode = new RootNode<>(ctx, rowType);
 
-        SortNode<Object[]> sortNode = new SortNode<>(ctx, rowType, 
F::compareArrays, () -> offset,
-            fetch == 0 ? null : () -> fetch);
+        SortNode<Object[]> sortNode = new SortNode<>(ctx, rowType, 
F::compareArrays, offset,
+            fetch == 0 ? SortNode.FETCH_DEFAULT : fetch);
 
-        List<Object[]> data = IntStream.range(0, SourceNode.IN_BUFFER_SIZE + 
fetch + offset).boxed()
+        List<Object[]> data = IntStream.range(0, IN_BUFFER_SIZE + fetch + 
offset).boxed()
             .map(i -> new Object[] {i}).collect(Collectors.toList());
         Collections.shuffle(data);
 
@@ -109,7 +110,7 @@ public class LimitExecutionTest extends 
AbstractExecutionTest {
         RelDataType rowType = TypeUtils.createRowType(tf, int.class);
 
         RootNode<Object[]> rootNode = new RootNode<>(ctx, rowType);
-        LimitNode<Object[]> limitNode = new LimitNode<>(ctx, rowType, () -> 
offset, fetch == 0 ? null : () -> fetch);
+        LimitNode<Object[]> limitNode = new LimitNode<>(ctx, rowType, offset, 
fetch == 0 ? LimitNode.FETCH_DEFAULT : fetch);
         SourceNode srcNode = new SourceNode(ctx, rowType);
 
         rootNode.register(limitNode);
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 daa39e374a0..0b68a9773ac 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
@@ -150,6 +150,16 @@ public class DynamicParametersIntegrationTest extends 
AbstractBasicIntegrationTe
         assertQuery("SELECT name LIKE '%' || ? || '%' FROM person where name 
is not null").withParams("go")
             
.returns(true).returns(false).returns(false).returns(false).check();
 
+        assertQuery("SELECT id FROM person ORDER BY id LIMIT 
?").withParams(1).returns(0).check();
+        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)
             .returns(0).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 428d4d273d9..6beccead712 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
@@ -17,7 +17,7 @@
 
 package org.apache.ignite.internal.processors.query.calcite.integration;
 
-import java.math.BigDecimal;
+import java.math.BigInteger;
 import java.util.Arrays;
 import java.util.List;
 import org.apache.calcite.sql.validate.SqlValidatorException;
@@ -104,15 +104,15 @@ public class LimitOffsetIntegrationTest extends 
AbstractBasicIntegrationTransact
     /** Tests correctness of fetch / offset params. */
     @Test
     public void testInvalidLimitOffset() {
-        String bigInt = BigDecimal.valueOf(10000000000L).toString();
+        BigInteger moreThanMaxLong = 
BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE);
 
-        assertThrows("SELECT * FROM TEST_REPL OFFSET " + bigInt + " ROWS",
+        assertThrows("SELECT * FROM TEST_REPL OFFSET " + moreThanMaxLong + " 
ROWS",
             SqlValidatorException.class, "Illegal value of offset");
 
-        assertThrows("SELECT * FROM TEST_REPL FETCH FIRST " + bigInt + " ROWS 
ONLY",
+        assertThrows("SELECT * FROM TEST_REPL FETCH FIRST " + moreThanMaxLong 
+ " ROWS ONLY",
             SqlValidatorException.class, "Illegal value of fetch / limit");
 
-        assertThrows("SELECT * FROM TEST_REPL LIMIT " + bigInt,
+        assertThrows("SELECT * FROM TEST_REPL LIMIT " + moreThanMaxLong,
             SqlValidatorException.class, "Illegal value of fetch / limit");
 
         assertThrows("SELECT * FROM TEST_REPL OFFSET -1 ROWS FETCH FIRST -1 
ROWS ONLY",
@@ -123,16 +123,6 @@ public class LimitOffsetIntegrationTest extends 
AbstractBasicIntegrationTransact
 
         assertThrows("SELECT * FROM TEST_REPL OFFSET 2+1 ROWS",
             IgniteSQLException.class, null);
-
-        // Check with parameters
-        assertThrows("SELECT * FROM TEST_REPL OFFSET ? ROWS FETCH FIRST ? ROWS 
ONLY",
-            SqlValidatorException.class, "Illegal value of fetch / limit", -1, 
-1);
-
-        assertThrows("SELECT * FROM TEST_REPL OFFSET ? ROWS",
-            SqlValidatorException.class, "Illegal value of offset", -1);
-
-        assertThrows("SELECT * FROM TEST_REPL FETCH FIRST ? ROWS ONLY",
-            SqlValidatorException.class, "Illegal value of fetch / limit", -1);
     }
 
     /**
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/QueryMetadataIntegrationTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/QueryMetadataIntegrationTest.java
index 36ee4dc647a..5947b3cc8db 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/QueryMetadataIntegrationTest.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/QueryMetadataIntegrationTest.java
@@ -23,9 +23,12 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.function.Consumer;
+import org.apache.calcite.rel.type.RelDataTypeSystem;
+import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.ignite.internal.IgniteEx;
 import org.apache.ignite.internal.processors.query.GridQueryFieldMetadata;
 import org.apache.ignite.internal.processors.query.QueryEngine;
+import 
org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeSystem;
 import org.apache.ignite.internal.processors.query.calcite.util.Commons;
 import org.apache.ignite.internal.util.typedef.T2;
 import org.junit.Test;
@@ -40,9 +43,9 @@ import static 
org.apache.calcite.rel.type.RelDataType.SCALE_NOT_SPECIFIED;
 public class QueryMetadataIntegrationTest extends AbstractBasicIntegrationTest 
{
     /** */
     @Test
-    public void testJoin() throws Exception {
-        executeSql("CREATE TABLE tbl1 (id DECIMAL(10, 2), val VARCHAR, val2 
BIGINT, ts TIMESTAMP(14), PRIMARY KEY(id, val))");
-        executeSql("CREATE TABLE tbl2 (id DECIMAL(10, 2) NOT NULL, val 
VARCHAR, val2 BIGINT, ts TIMESTAMP(14), " +
+    public void testJoin() {
+        sql("CREATE TABLE tbl1 (id DECIMAL(10, 2), val VARCHAR, val2 BIGINT, 
ts TIMESTAMP(14), PRIMARY KEY(id, val))");
+        sql("CREATE TABLE tbl2 (id DECIMAL(10, 2) NOT NULL, val VARCHAR, val2 
BIGINT, ts TIMESTAMP(14), " +
             "PRIMARY KEY(id, val))");
 
         checker("select * from tbl1 inner join tbl2 on (tbl1.id > tbl2.id and 
tbl1.id <> ?) " +
@@ -67,9 +70,9 @@ public class QueryMetadataIntegrationTest extends 
AbstractBasicIntegrationTest {
 
     /** */
     @Test
-    public void testMultipleConditions() throws Exception {
-        executeSql("CREATE TABLE tbl (id BIGINT, val VARCHAR, PRIMARY 
KEY(id))");
-        executeSql("CREATE INDEX tbl_val_idx ON tbl(val)");
+    public void testMultipleConditions() {
+        sql("CREATE TABLE tbl (id BIGINT, val VARCHAR, PRIMARY KEY(id))");
+        sql("CREATE INDEX tbl_val_idx ON tbl(val)");
 
         checker("select * from tbl where id in (?, ?) or (id > ? and id <= ?) 
or (val <> ?)")
             .addMeta(
@@ -87,9 +90,9 @@ public class QueryMetadataIntegrationTest extends 
AbstractBasicIntegrationTest {
 
     /** */
     @Test
-    public void testMultipleQueries() throws Exception {
-        executeSql("CREATE TABLE tbl (id BIGINT, val VARCHAR, PRIMARY 
KEY(id))");
-        executeSql("CREATE INDEX tbl_val_idx ON tbl(val)");
+    public void testMultipleQueries() {
+        sql("CREATE TABLE tbl (id BIGINT, val VARCHAR, PRIMARY KEY(id))");
+        sql("CREATE INDEX tbl_val_idx ON tbl(val)");
 
         checker("insert into tbl(id, val) values (?, ?); select * from tbl 
where id > ?")
             .addMeta(
@@ -111,9 +114,30 @@ public class QueryMetadataIntegrationTest extends 
AbstractBasicIntegrationTest {
 
     /** */
     @Test
-    public void testDml() throws Exception {
-        executeSql("CREATE TABLE tbl1 (id BIGINT, val VARCHAR, PRIMARY 
KEY(id))");
-        executeSql("CREATE TABLE tbl2 (id BIGINT, val VARCHAR, PRIMARY 
KEY(id))");
+    public void testLimitOffsetParameterMetadata() {
+        RelDataTypeSystem typeSys = IgniteTypeSystem.INSTANCE;
+
+        for (String qry : new String[] {
+            "SELECT 1 LIMIT ?",
+            "SELECT 1 OFFSET ?",
+            "SELECT 1 FETCH FIRST ? ROWS ONLY"
+        }) {
+            checker(qry)
+                .addMeta(
+                    builder -> builder
+                        .add(null, null, int.class, "1", 
typeSys.getDefaultPrecision(SqlTypeName.INTEGER), 0, false),
+                    builder -> builder
+                        .add(null, null, Long.class, "?0", 
typeSys.getDefaultPrecision(SqlTypeName.BIGINT), 0, true)
+                )
+                .check();
+        }
+    }
+
+    /** */
+    @Test
+    public void testDml() {
+        sql("CREATE TABLE tbl1 (id BIGINT, val VARCHAR, PRIMARY KEY(id))");
+        sql("CREATE TABLE tbl2 (id BIGINT, val VARCHAR, PRIMARY KEY(id))");
 
         checker("insert into tbl1(id, val) values (?, ?)")
             .addMeta(
@@ -150,8 +174,8 @@ public class QueryMetadataIntegrationTest extends 
AbstractBasicIntegrationTest {
 
     /** */
     @Test
-    public void testDdl() throws Exception {
-        executeSql("CREATE TABLE tbl1 (id BIGINT, val VARCHAR, PRIMARY 
KEY(id))");
+    public void testDdl() {
+        sql("CREATE TABLE tbl1 (id BIGINT, val VARCHAR, PRIMARY KEY(id))");
 
         checker("CREATE TABLE tbl2 (id BIGINT, val VARCHAR, PRIMARY KEY(id))")
             .addMeta(builder -> {}, builder -> {})
@@ -166,8 +190,8 @@ public class QueryMetadataIntegrationTest extends 
AbstractBasicIntegrationTest {
 
     /** */
     @Test
-    public void testExplain() throws Exception {
-        executeSql("CREATE TABLE tbl (id BIGINT, val VARCHAR, PRIMARY 
KEY(id))");
+    public void testExplain() {
+        sql("CREATE TABLE tbl (id BIGINT, val VARCHAR, PRIMARY KEY(id))");
 
         checker("explain plan for select * from tbl where id in (?, ?) or (id 
> ? and id <= ?) or (val <> ?)")
             .addMeta(
@@ -227,7 +251,7 @@ public class QueryMetadataIntegrationTest extends 
AbstractBasicIntegrationTest {
         }
 
         /** */
-        public void check() throws Exception {
+        public void check() {
             List<List<GridQueryFieldMetadata>> actualRsMeta = 
qryEngine.resultSetMetaData(null, schema, sql);
             List<List<GridQueryFieldMetadata>> actualParamMeta = 
qryEngine.parameterMetaData(null, schema, sql);
 
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractPlannerTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractPlannerTest.java
index b49af08b5c7..3da09de1218 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractPlannerTest.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractPlannerTest.java
@@ -220,26 +220,31 @@ public abstract class AbstractPlannerTest extends 
GridCommonAbstractTest {
         @Nullable RelOptListener planLsnr,
         String... disabledRules
     ) {
-        return plannerCtx(sql, Collections.singleton(publicSchema), planLsnr, 
disabledRules);
+        return plannerCtx(sql, Collections.singleton(publicSchema), planLsnr, 
null, ImmutableSet.copyOf(disabledRules));
     }
 
     /** */
-    protected PlanningContext plannerCtx(
+    private PlanningContext plannerCtx(
         String sql,
         Collection<IgniteSchema> schemas,
         @Nullable RelOptListener planLsnr,
-        String... disabledRules
+        Collection<Object> params,
+        Collection<String> disabledRules
     ) {
-        PlanningContext ctx = PlanningContext.builder()
+        PlanningContext.Builder ctxBuilder = PlanningContext.builder()
             .parentContext(Contexts.of(baseQueryContext(schemas), planLsnr))
-            .query(sql)
-            .build();
+            .query(sql);
+
+        if (params != null)
+            ctxBuilder.parameters(params.toArray(Object[]::new));
+
+        PlanningContext ctx = ctxBuilder.build();
 
         IgnitePlanner planner = ctx.planner();
 
         assertNotNull(planner);
 
-        planner.addDisabledRules(ImmutableSet.copyOf(disabledRules));
+        planner.addDisabledRules(disabledRules);
 
         return ctx;
     }
@@ -438,6 +443,16 @@ public abstract class AbstractPlannerTest extends 
GridCommonAbstractTest {
         return table;
     }
 
+    /** */
+    @SuppressWarnings("ThrowableNotThrown")
+    void assertThrows(
+        TestPlanningContextBuilder ctxBuilder,
+        Class<? extends Throwable> cls,
+        @Nullable String msg
+    ) {
+        GridTestUtils.assertThrows(null, () -> assertPlan(ctxBuilder, rel -> 
true), cls, msg);
+    }
+
     /** */
     protected <T extends RelNode> void assertPlan(
         String sql,
@@ -445,7 +460,9 @@ public abstract class AbstractPlannerTest extends 
GridCommonAbstractTest {
         Predicate<T> predicate,
         String... disabledRules
     ) throws Exception {
-        assertPlan(sql, schema, null, predicate, disabledRules);
+        TestPlanningContextBuilder builder = 
contextBuilder().query(sql).schema(schema).disabledRules(disabledRules);
+
+        assertPlan(builder, predicate);
     }
 
     /** */
@@ -455,20 +472,33 @@ public abstract class AbstractPlannerTest extends 
GridCommonAbstractTest {
         Predicate<T> predicate,
         String... disabledRules
     ) throws Exception {
-        assertPlan(sql, schemas, null, predicate, disabledRules);
+        TestPlanningContextBuilder builder = 
contextBuilder().query(sql).schemas(schemas).disabledRules(disabledRules);
+
+        assertPlan(builder, predicate);
     }
 
     /** */
     protected <T extends RelNode> void assertPlan(
         String sql,
-        Collection<IgniteSchema> schemas,
-        @Nullable RelOptListener planLsnr,
+        IgniteSchema schema,
+        RelOptListener planLsnr,
         Predicate<T> predicate,
         String... disabledRules
     ) throws Exception {
-        IgniteRel plan = physicalPlan(plannerCtx(sql, schemas, planLsnr, 
disabledRules));
+        TestPlanningContextBuilder builder = 
contextBuilder().query(sql).schema(schema).disabledRules(disabledRules)
+            .planListener(planLsnr);
+
+        assertPlan(builder, predicate);
+    }
+
+    /** */
+    protected <T extends RelNode> void assertPlan(
+        TestPlanningContextBuilder ctxBuilder,
+        Predicate<T> predicate
+    ) throws Exception {
+        IgniteRel plan = physicalPlan(ctxBuilder.build());
 
-        checkSplitAndSerialization(plan, schemas);
+        checkSplitAndSerialization(plan, ctxBuilder.schemas);
 
         if (!predicate.test((T)plan)) {
             String invalidPlanMsg = "Invalid plan (" + lastErrorMsg + "):\n" +
@@ -478,17 +508,6 @@ public abstract class AbstractPlannerTest extends 
GridCommonAbstractTest {
         }
     }
 
-    /** */
-    protected <T extends RelNode> void assertPlan(
-        String sql,
-        IgniteSchema schema,
-        @Nullable RelOptListener planLsnr,
-        Predicate<T> predicate,
-        String... disabledRules
-    ) throws Exception {
-        assertPlan(sql, Collections.singletonList(schema), planLsnr, 
predicate, disabledRules);
-    }
-
     /**
      * Predicate builder for "Instance of class" condition.
      */
@@ -805,4 +824,74 @@ public abstract class AbstractPlannerTest extends 
GridCommonAbstractTest {
             return true;
         }
     }
+
+    /** Test planning context builder. */
+    public class TestPlanningContextBuilder {
+        /** */
+        private String query;
+
+        /** */
+        private Collection<IgniteSchema> schemas;
+
+        /** */
+        private Collection<Object> params = List.of();
+
+        /** */
+        private Collection<String> disabledRules = List.of();
+
+        /** */
+        @Nullable private RelOptListener planListener;
+
+        /** */
+        public TestPlanningContextBuilder query(String qry) {
+            query = qry;
+            return this;
+        }
+
+        /** */
+        public TestPlanningContextBuilder schema(IgniteSchema schemas) {
+            this.schemas = List.of(schemas);
+            return this;
+        }
+
+        /** */
+        public TestPlanningContextBuilder schemas(Collection<IgniteSchema> 
schemas) {
+            this.schemas = List.copyOf(schemas);
+            return this;
+        }
+
+        /** */
+        public TestPlanningContextBuilder params(Collection<Object> params) {
+            this.params = List.copyOf(params);
+            return this;
+        }
+
+        /** */
+        public TestPlanningContextBuilder params(Object... params) {
+            this.params = Arrays.asList(params);
+            return this;
+        }
+
+        /** */
+        public TestPlanningContextBuilder disabledRules(String... rules) {
+            disabledRules = List.of(rules);
+            return this;
+        }
+
+        /** */
+        public TestPlanningContextBuilder planListener(@Nullable 
RelOptListener planListener) {
+            this.planListener = planListener;
+            return this;
+        }
+
+        /** */
+        PlanningContext build() {
+            return plannerCtx(query, schemas, planListener, params, 
disabledRules);
+        }
+    }
+
+    /** */
+    public TestPlanningContextBuilder contextBuilder() {
+        return new TestPlanningContextBuilder();
+    }
 }
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/DynamicParametersPlannerTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/DynamicParametersPlannerTest.java
new file mode 100644
index 00000000000..e27d9eb468c
--- /dev/null
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/DynamicParametersPlannerTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.planner;
+
+import java.math.BigInteger;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
+import org.junit.Test;
+
+import static 
org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions.single;
+
+/** */
+public class DynamicParametersPlannerTest extends AbstractPlannerTest {
+    /** Dynamic parameters in LIMIT / OFFSET. */
+    @Test
+    public void testLimitOffset() throws Exception {
+        IgniteSchema schema = createSchema(createTable("T1", single(), "c1", 
Integer.class));
+
+        TestPlanningContextBuilder builder = contextBuilder().query("SELECT * 
FROM t1 LIMIT ?").schema(schema);
+
+        assertPlan(builder.params(Long.MAX_VALUE), rel -> true);
+
+        // Count of dynamic parameters need to be invalidated, remove it 
after: IGNITE-28906
+        assertPlan(builder.params(Long.MAX_VALUE, -1), rel -> true);
+
+        assertThrows(builder.params("a"), IgniteException.class,
+            "Incorrect type of a dynamic parameter. Expected <BIGINT> but got 
<VARCHAR>");
+
+        BigInteger moreThanMaxLong = 
BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE);
+
+        assertThrows(builder.params(moreThanMaxLong), IgniteException.class,
+            "Illegal value of fetch / limit");
+
+        assertThrows(builder.params(-1), IgniteException.class,
+            "Illegal value of fetch / limit");
+
+        assertThrows(builder.params((Object)null), IgniteException.class,
+            "Incorrect type of a dynamic parameter. Expected <BIGINT> but got 
<null>");
+
+        // OFFSET.
+        builder.query("SELECT * FROM t1 OFFSET ?");
+
+        assertThrows(builder.params(moreThanMaxLong), IgniteException.class,
+            "Illegal value of offset");
+
+        assertThrows(builder.params(-1), IgniteException.class,
+            "Illegal value of offset");
+
+        assertThrows(builder.params((Object)null), IgniteException.class,
+            "Incorrect type of a dynamic parameter. Expected <BIGINT> but got 
<null>");
+
+        // OFFSET Alternate syntax.
+        builder.query("SELECT * FROM t1 OFFSET ? ROWS");
+
+        assertThrows(builder.params(moreThanMaxLong), IgniteException.class,
+            "Illegal value of offset");
+
+        assertThrows(builder.params(-1), IgniteException.class,
+            "Illegal value of offset");
+
+        assertThrows(builder.params((Object)null), IgniteException.class,
+            "Incorrect type of a dynamic parameter. Expected <BIGINT> but got 
<null>");
+
+        // Expression.
+        builder.query("SELECT * FROM TEST_REPL OFFSET 2+? ROWS");
+
+        assertThrows(builder, IgniteException.class,
+            "Encountered \" \"+\"");
+    }
+}
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/HashJoinPlannerTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/HashJoinPlannerTest.java
index 97a8689535e..55140639755 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/HashJoinPlannerTest.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/HashJoinPlannerTest.java
@@ -28,10 +28,10 @@ import 
org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
 import 
org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.internal.CU;
+import org.apache.ignite.testframework.GridTestUtils;
 import org.junit.Test;
 
 import static org.apache.calcite.rel.RelFieldCollation.Direction.ASCENDING;
-import static org.apache.ignite.testframework.GridTestUtils.assertThrows;
 
 /** */
 public class HashJoinPlannerTest extends AbstractPlannerTest {
@@ -148,7 +148,7 @@ public class HashJoinPlannerTest extends 
AbstractPlannerTest {
                 if (canBePlanned)
                     assertPlan(sql0, schema, 
nodeOrAnyChild(isInstanceOf(IgniteHashJoin.class)), DISABLED_RULES);
                 else {
-                    assertThrows(null, () -> physicalPlan(sql0, schema, 
DISABLED_RULES), CannotPlanException.class,
+                    GridTestUtils.assertThrows(null, () -> physicalPlan(sql0, 
schema, DISABLED_RULES), CannotPlanException.class,
                         "There are not enough rules");
                 }
             }
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java
 
b/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java
index 7daf90205dd..be2d0283c15 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java
@@ -23,6 +23,7 @@ import 
org.apache.ignite.internal.processors.query.calcite.planner.AggregatePlan
 import 
org.apache.ignite.internal.processors.query.calcite.planner.CorrelatedNestedLoopJoinPlannerTest;
 import 
org.apache.ignite.internal.processors.query.calcite.planner.CorrelatedSubqueryPlannerTest;
 import 
org.apache.ignite.internal.processors.query.calcite.planner.DataTypesPlannerTest;
+import 
org.apache.ignite.internal.processors.query.calcite.planner.DynamicParametersPlannerTest;
 import 
org.apache.ignite.internal.processors.query.calcite.planner.HashAggregatePlannerTest;
 import 
org.apache.ignite.internal.processors.query.calcite.planner.HashIndexSpoolPlannerTest;
 import 
org.apache.ignite.internal.processors.query.calcite.planner.HashJoinPlannerTest;
@@ -96,6 +97,7 @@ import org.junit.runners.Suite;
     AbstractPlannerUtilityTest.class,
 
     HintsTestSuite.class,
+    DynamicParametersPlannerTest.class,
 })
 public class PlannerTestSuite {
 }
diff --git a/modules/calcite/src/test/sql/order/test_limit.test 
b/modules/calcite/src/test/sql/order/test_limit.test
index 2c2c3d34db9..4cdfe041e35 100644
--- a/modules/calcite/src/test/sql/order/test_limit.test
+++ b/modules/calcite/src/test/sql/order/test_limit.test
@@ -17,9 +17,67 @@ SELECT a FROM test ORDER BY a LIMIT 1
 ----
 11
 
-# LIMIT with non-scalar should fail
+# decimal limit
+query I
+SELECT a FROM test ORDER BY a LIMIT 1.2
+----
+11
+
+# decimal limit
+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
+SELECT a FROM test ORDER BY a FETCH FIRST 0 ROWS ONLY
+----
+
+# decimal limit
+query I
+SELECT a FROM test ORDER BY a LIMIT 1.2
+----
+11
+
+# decimal limit
+query I
+SELECT a FROM test ORDER BY a FETCH FIRST 1.2 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
+----
+12
+
+# Unexpected literal
+statement error
+SELECT a FROM test ORDER BY a FETCH FIRST '1'
+
+# More than max of big integer
+statement error
+SELECT a FROM test LIMIT 9223372036854775808
+
+# More than max of big integer
+statement error
+SELECT a FROM test ORDER BY a FETCH FIRST 9223372036854775808 ROWS ONLY
+
+# Unexpected operation
+statement error
+SELECT a FROM test ORDER BY a FETCH FIRST 1 + 1 ROWS ONLY
+
 statement error
-SELECT a FROM test LIMIT a
+SELECT a FROM test ORDER BY a FETCH FIRST -1 ROWS ONLY
 
 # LIMIT with non-scalar operation should also fail
 statement error

Reply via email to