Skip to content

IGNITE-28844 Calcite. Improve type checking in LIMIT / OFFSET clauses - #13311

Open
zstan wants to merge 5 commits into
apache:masterfrom
zstan:ignite-28844
Open

IGNITE-28844 Calcite. Improve type checking in LIMIT / OFFSET clauses#13311
zstan wants to merge 5 commits into
apache:masterfrom
zstan:ignite-28844

Conversation

@zstan

@zstan zstan commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR (“IGNITE-28844 Calcite. Improve type checking in LIMIT / OFFSET clauses”) tightens validation for LIMIT / OFFSET values (including dynamic parameters) and aligns execution nodes to accept long-based fetch/offset values, with accompanying tests.

Changes:

  • Added stricter validator checks for LIMIT/OFFSET literals and dynamic parameters (numeric-only, long-range, non-negative).
  • Introduced a planner-test helper (StatementChecker) and added new planner/integration tests for dynamic parameters in LIMIT/OFFSET.
  • Updated execution nodes (LimitNode, SortNode) and implementor wiring to use long fetch/offset values and centralize “illegal fetch/offset” error reporting.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
modules/calcite/src/test/sql/order/test_limit.test Adds negative SQL cases for invalid FETCH FIRST / LIMIT values and expressions.
modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java Registers the new dynamic-parameters planner test in the suite.
modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/StatementChecker.java Adds a new test helper for validating statements/plans (currently has critical correctness gaps).
modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/DynamicParametersPlannerTest.java Adds planner-level tests for dynamic parameter typing/range checks in LIMIT/OFFSET.
modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/AbstractPlannerTest.java Adds checkStatement(...) helper and PlanChecker implementation to drive planner validation in tests.
modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DynamicParametersIntegrationTest.java Adds integration coverage for LIMIT ? with integer and BigDecimal parameters.
modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/LimitExecutionTest.java Updates execution tests to new LimitNode/SortNode APIs (currently inconsistent for “unlimited” fetch).
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/IgniteResource.java Adds new validator error messages for illegal fetch/offset and dynamic parameter type mismatch.
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteSqlValidator.java Implements the new fetch/offset validation logic for literals and dynamic params.
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/SortNode.java Switches limited-sort parameter types to long and adjusts bounded/unbounded buffer selection.
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/LimitNode.java Switches limit/offset parameter types to long and updates request/push logic for new semantics.
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/AbstractNode.java Introduces a shared NOT_WAITING sentinel constant.
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java Wires runtime fetch/offset evaluation and validation into LimitNode / SortNode construction.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 9 comments.

Comment thread modules/calcite/src/test/sql/order/test_limit.test
@tkalkirill

Copy link
Copy Markdown
Contributor

Hi, regarding https://issues.apache.org/jira/browse/CALCITE-7624, I implemented support for BigDecimal for the OFFSET/FETCH and LIMIT in #13375.

private final long fetch;

/** Fetch can be unset. */
private final boolean fetchUndefined;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add long limit = fetch == -1 ? Long.MAX_VALUE : IgniteMath.addExact(fetch, offset) instead. To execute IgniteMath.addExact(fetch, offset) only once (and not for each request and each push), to simplify hasMoreData (rowsProcessed < limit).

private final boolean fetchUndefined;

/** Already processed (pushed to upstream) rows count. */
private int rowsProcessed;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comparing int with long, can overflow

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment should be started with an uppercase letter.

*/
private void checkIntegerLimit(SqlNode n, String nodeName) {
private void invalidateFetchOffset(@Nullable SqlNode n, String nodeName) {
if (n == null) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant braces

* @param nodeName Node name.
*/
private void checkIntegerLimit(SqlNode n, String nodeName) {
private void invalidateFetchOffset(@Nullable SqlNode n, String nodeName) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

invalidate - means make something invalid. Here we validate parameters, let's use validateor check.

}

/** */
protected <T extends RelNode> void assertPlan(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant method. Used in assertThrows, but it's better to write own assertThrows method with parameters like PlannerContext or PlannerContextBuilder.


/** */
@SuppressWarnings("ThrowableNotThrown")
static void assertThrows(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's rewrite to assertThrows(PlannerContext, cls, msg) or assertThrows(PlannerContextBuilder, cls, msg) and use physicalPlan(...) method inside (not assertPlan)

TestPlanningContextBuilder ctxBuilder,
Predicate<T> predicate
) throws Exception {
IgniteRel plan = physicalPlan(plannerCtx(ctxBuilder.query, ctxBuilder.schemas, ctxBuilder.planListener,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move plannerCtx(...) to TestPlanningContextBuilder.build() method.

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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant change

limit = fetch == -1 ? -1 : (fetch > Long.MAX_VALUE - offset ? -1 : fetch + offset);

if (limit < 0)
if (limit < 1 || limit > Integer.MAX_VALUE)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we rich this line with limit == 0? If yes, it's more correct to use `limit <= 1' or add other logic to return empty result

@@ -84,9 +87,6 @@
/** Validator. */
@Value.Enclosing
public class IgniteSqlValidator extends SqlValidatorImpl {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should use SqlTypeName.DECIMAL, similar to the CALCITE-7624?
org.apache.calcite.sql.validate.SqlValidatorImpl#handleOffsetFetch

A question regarding rounding might arise; we can use the Calcite org.apache.calcite.adapter.enumerable.FetchOffsetRoundingPolicy interface, and it will handle rounding when creating nodes.

Object param = parameters[dynamicParam.getIndex()];

if (!(param instanceof Number)) {
SqlTypeName expectType = SqlTypeName.BIGINT;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we support the SqlTypeName.DECIMAL as in Calcite, then it will need to be fixed.

try {
long res = IgniteMath.convertToLongExact(offsetFetchLimit);

if (res < 0)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we support the SqlTypeName.DECIMAL as in Calcite, then it will need to be fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants