IGNITE-28844 Calcite. Improve type checking in LIMIT / OFFSET clauses - #13311
IGNITE-28844 Calcite. Improve type checking in LIMIT / OFFSET clauses#13311zstan wants to merge 5 commits into
Conversation
There was a problem hiding this comment.
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/OFFSETliterals and dynamic parameters (numeric-only, long-range, non-negative). - Introduced a planner-test helper (
StatementChecker) and added new planner/integration tests for dynamic parameters inLIMIT/OFFSET. - Updated execution nodes (
LimitNode,SortNode) and implementor wiring to uselongfetch/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.
|
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; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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) { |
| * @param nodeName Node name. | ||
| */ | ||
| private void checkIntegerLimit(SqlNode n, String nodeName) { | ||
| private void invalidateFetchOffset(@Nullable SqlNode n, String nodeName) { |
There was a problem hiding this comment.
invalidate - means make something invalid. Here we validate parameters, let's use validateor check.
| } | ||
|
|
||
| /** */ | ||
| protected <T extends RelNode> void assertPlan( |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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; |
| limit = fetch == -1 ? -1 : (fetch > Long.MAX_VALUE - offset ? -1 : fetch + offset); | ||
|
|
||
| if (limit < 0) | ||
| if (limit < 1 || limit > Integer.MAX_VALUE) |
There was a problem hiding this comment.
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 { | |||
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
If we support the SqlTypeName.DECIMAL as in Calcite, then it will need to be fixed.
https://issues.apache.org/jira/browse/IGNITE-28844