This is an automated email from the ASF dual-hosted git repository.
tkalkirill pushed a commit to branch ignite-28739-new
in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/ignite-28739-new by this push:
new bbbb52ca7df IGNITE-28739 Wip
bbbb52ca7df is described below
commit bbbb52ca7dfae0b92c5bc567addaa6fe4307445f
Author: Kirill Tkalenko <[email protected]>
AuthorDate: Tue Aug 11 16:39:11 2026 +0300
IGNITE-28739 Wip
---
modules/calcite/pom.xml | 11 +++--
.../src/main/codegen/includes/parserImpls.ftl | 2 +-
.../codegen/patches/select-fetch-expression.patch | 26 ----------
.../calcite/prepare/IgniteRelFieldTrimmer.java | 6 ++-
.../query/calcite/prepare/IgniteSqlValidator.java | 26 ++++++++--
.../query/calcite/rule/SortConverterRule.java | 56 +++++++++++++++++++++-
.../integration/LimitOffsetIntegrationTest.java | 30 ++++++++++++
.../calcite/planner/LimitOffsetPlannerTest.java | 50 +++++++++++++++++++
8 files changed, 167 insertions(+), 40 deletions(-)
diff --git a/modules/calcite/pom.xml b/modules/calcite/pom.xml
index 7bfedc5caa8..53ea643b2ef 100644
--- a/modules/calcite/pom.xml
+++ b/modules/calcite/pom.xml
@@ -352,11 +352,12 @@
data="tdd(${project.build.directory}/codegen/config.fmpp), default:
tdd(${project.build.directory}/codegen/default_config.fmpp)"
/>
<!-- TODO:
https://issues.apache.org/jira/browse/CALCITE-7592
- Remove this workaround and
select-fetch-expression.patch after upgrading to Calcite 1.43. -->
- <patch
-
dir="${project.build.directory}/generated-sources/fmpp"
-
patchfile="${project.build.directory}/codegen/patches/select-fetch-expression.patch"
- strip="1"
+ Remove this workaround after upgrading to
Calcite 1.43. -->
+ <replace
+
file="${project.build.directory}/generated-sources/fmpp/javacc/Parser.jj"
+ token="<FETCH> ( <FIRST> |
<NEXT> ) offsetFetch[1] = UnsignedNumericLiteralOrParam()"
+ value="<FETCH> ( <FIRST> |
<NEXT> ) offsetFetch[1] = FetchCount()"
+ failOnNoReplacements="true"
/>
</target>
</configuration>
diff --git a/modules/calcite/src/main/codegen/includes/parserImpls.ftl
b/modules/calcite/src/main/codegen/includes/parserImpls.ftl
index c2cf7559328..4a05872bbf3 100644
--- a/modules/calcite/src/main/codegen/includes/parserImpls.ftl
+++ b/modules/calcite/src/main/codegen/includes/parserImpls.ftl
@@ -818,7 +818,7 @@ SqlDrop SqlDropView(Span s, boolean replace) :
}
// TODO: https://issues.apache.org/jira/browse/CALCITE-7592
-// Remove this method and select-fetch-expression.patch after upgrading to
Calcite 1.43.
+// Remove this method and the corresponding replacement in pom.xml after
upgrading to Calcite 1.43.
JAVACODE
SqlNode FetchCount() {
SqlNode e;
diff --git
a/modules/calcite/src/main/codegen/patches/select-fetch-expression.patch
b/modules/calcite/src/main/codegen/patches/select-fetch-expression.patch
deleted file mode 100644
index 60ec72ffb85..00000000000
--- a/modules/calcite/src/main/codegen/patches/select-fetch-expression.patch
+++ /dev/null
@@ -1,26 +0,0 @@
-#
-# 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.
-#
---- a/javacc/Parser.jj
-+++ b/javacc/Parser.jj
-@@ -765,7 +765,7 @@
- {
- // SQL:2008-style syntax. "OFFSET ... FETCH ...".
- // If you specify both LIMIT and FETCH, FETCH wins.
-- <FETCH> ( <FIRST> | <NEXT> ) offsetFetch[1] =
UnsignedNumericLiteralOrParam()
-+ <FETCH> ( <FIRST> | <NEXT> ) offsetFetch[1] = FetchCount()
- ( <ROW> | <ROWS> ) <ONLY>
- }
diff --git
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteRelFieldTrimmer.java
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteRelFieldTrimmer.java
index b1b4f97e866..61bfd0fca80 100644
---
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteRelFieldTrimmer.java
+++
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteRelFieldTrimmer.java
@@ -71,10 +71,12 @@ public class IgniteRelFieldTrimmer extends RelFieldTrimmer {
if (newInput == input && inputMapping.isIdentity() &&
fieldsUsed.cardinality() == fieldCnt)
return result(sort, Mappings.createIdentity(fieldCnt));
+ RelCollation newCollation = RexUtil.apply(inputMapping, collation);
+
RelNode newSort = sort.copy(
- sort.getTraitSet(),
+ sort.getTraitSet().replace(newCollation),
newInput,
- RexUtil.apply(inputMapping, collation),
+ newCollation,
sort.offset,
sort.fetch
);
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 6386f346369..58ad3ce7acd 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
@@ -257,6 +257,9 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
if (fetch == null)
return;
+ if (SqlUtil.isNullLiteral(fetch, true))
+ throw newValidationError(fetch,
IgniteResource.INSTANCE.illegalFetchLimit(clauseName));
+
validateFetchExpression(fetch, clauseName);
deriveDynamicParameterTypes(fetch);
@@ -268,19 +271,34 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
validateFetchOffset(fetch, clauseName);
}
- /** Reject column references in a fetch expression. */
+ /** Reject column references, aggregate functions, and window functions in
a fetch expression. */
// TODO: https://issues.apache.org/jira/browse/CALCITE-7592
// Remove this method after upgrading to Calcite 1.43.
private void validateFetchExpression(SqlNode node, String clauseName) {
- if (node instanceof SqlIdentifier)
- throw newValidationError(node,
IgniteResource.INSTANCE.illegalFetchLimit(clauseName));
+ if (node instanceof SqlIdentifier) {
+ if (makeNullaryCall((SqlIdentifier)node) == null)
+ throw newValidationError(node,
IgniteResource.INSTANCE.illegalFetchLimit(clauseName));
+
+ return;
+ }
if (node instanceof SqlNodeList) {
for (SqlNode child : (SqlNodeList)node)
validateFetchExpression(child, clauseName);
}
else if (node instanceof SqlCall) {
- for (SqlNode child : ((SqlCall)node).getOperandList()) {
+ SqlCall call = (SqlCall)node;
+
+ if (call.isA(SqlKind.QUERY))
+ throw newValidationError(call,
IgniteResource.INSTANCE.illegalFetchLimit(clauseName));
+
+ if (call.getKind() == SqlKind.OVER)
+ throw newValidationError(call,
RESOURCE.windowedAggregateIllegalInClause(clauseName));
+
+ if (call.getOperator().isAggregator())
+ throw newValidationError(call,
RESOURCE.aggregateIllegalInClause(clauseName));
+
+ for (SqlNode child : call.getOperandList()) {
if (child != null)
validateFetchExpression(child, clauseName);
}
diff --git
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/SortConverterRule.java
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/SortConverterRule.java
index a3020b4c4d4..0883c7d46c5 100644
---
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/SortConverterRule.java
+++
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/SortConverterRule.java
@@ -17,6 +17,10 @@
package org.apache.ignite.internal.processors.query.calcite.rule;
import com.google.common.collect.ImmutableMap;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptRule;
import org.apache.calcite.plan.RelOptRuleCall;
@@ -26,10 +30,16 @@ import org.apache.calcite.rel.RelCollations;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.Sort;
import org.apache.calcite.rel.logical.LogicalSort;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexDynamicParam;
+import org.apache.calcite.rex.RexLiteral;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexUtil;
import
org.apache.ignite.internal.processors.query.calcite.rel.IgniteConvention;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteLimit;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteSort;
import
org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions;
+import org.apache.ignite.internal.processors.query.calcite.util.RexUtils;
import org.immutables.value.Value;
/**
@@ -74,13 +84,15 @@ public class SortConverterRule extends
RelRule<SortConverterRule.Config> {
sort.fetch));
}
else {
+ boolean pushLimit = canPushLimit(sort);
+
RelNode igniteSort = new IgniteSort(
cluster,
cluster.traitSetOf(IgniteConvention.INSTANCE).replace(sort.getCollation()),
convert(sort.getInput(),
cluster.traitSetOf(IgniteConvention.INSTANCE)),
sort.getCollation(),
- sort.offset,
- sort.fetch,
+ pushLimit ? sort.offset : null,
+ pushLimit ? sort.fetch : null,
false
);
@@ -101,4 +113,44 @@ public class SortConverterRule extends
RelRule<SortConverterRule.Config> {
call.transformTo(new IgniteSort(cluster, outTraits, input,
sort.getCollation(), false));
}
}
+
+ /** Returns {@code true} if FETCH can be safely pushed to the sort. */
+ private static boolean canPushLimit(Sort sort) {
+ RexNode fetch = sort.fetch;
+
+ // A non-deterministic FETCH must be evaluated only once by the outer
IgniteLimit.
+ if (!RexUtil.isDeterministic(fetch))
+ return false;
+
+ if (!RexUtil.isConstant(fetch) || containsDynamicParameter(fetch))
+ return false;
+
+ List<RexNode> reducedFetch = new ArrayList<>(1);
+
+
RexUtils.executor(sort.getCluster()).reduce(sort.getCluster().getRexBuilder(),
+ Collections.singletonList(fetch), reducedFetch);
+
+ if (!(reducedFetch.get(0) instanceof RexLiteral))
+ return false;
+
+ BigDecimal fetchValue =
((RexLiteral)reducedFetch.get(0)).getValueAs(BigDecimal.class);
+
+ // SortNode does not accept zero FETCH; the outer IgniteLimit handles
it.
+ return fetchValue != null && fetchValue.compareTo(BigDecimal.ONE) >= 0;
+ }
+
+ /** Returns {@code true} if the expression contains a dynamic parameter. */
+ private static boolean containsDynamicParameter(RexNode node) {
+ if (node instanceof RexDynamicParam)
+ return true;
+
+ if (node instanceof RexCall) {
+ for (RexNode operand : ((RexCall)node).getOperands()) {
+ if (containsDynamicParameter(operand))
+ return true;
+ }
+ }
+
+ return false;
+ }
}
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 434c16afff0..909064f7f54 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
@@ -211,11 +211,16 @@ public class LimitOffsetIntegrationTest extends
AbstractBasicIntegrationTransact
assertThrowsSqlException("SELECT * FROM TEST_REPL FETCH FIRST ('abc')
ROWS ONLY", null);
assertThrowsSqlException("SELECT * FROM TEST_REPL FETCH FIRST
(SUBSTRING('abc', 1, 1)) ROWS ONLY", null);
+ assertThrowsSqlException("SELECT * FROM TEST_REPL FETCH FIRST
(CAST(NULL AS INTEGER)) ROWS ONLY", null);
assertThrowsSqlException("SELECT * FROM TEST_REPL FETCH FIRST (id)
ROWS ONLY", null);
assertThrowsSqlException("SELECT * FROM TEST_REPL FETCH FIRST (id + 1)
ROWS ONLY", null);
assertThrowsSqlException("SELECT * FROM TEST_REPL FETCH FIRST
(ABS(id)) ROWS ONLY", null);
+ assertThrowsSqlException("SELECT * FROM TEST_REPL FETCH FIRST (SUM(1))
ROWS ONLY", null);
+ assertThrowsSqlException("SELECT * FROM TEST_REPL FETCH FIRST
(ROW_NUMBER() OVER ()) ROWS ONLY", null);
+ assertThrowsSqlException("SELECT * FROM TEST_REPL FETCH FIRST
(ABS((SELECT 1))) ROWS ONLY", null);
+
assertThrowsSqlException("SELECT * FROM TEST_REPL FETCH FIRST SQRT(4)
ROWS ONLY", null);
}
@@ -263,6 +268,14 @@ public class LimitOffsetIntegrationTest extends
AbstractBasicIntegrationTransact
.returns(1)
.returns(2)
.check();
+
+ assertQuery("SELECT id FROM TEST_REPL ORDER BY id FETCH FIRST
(EXTRACT(YEAR FROM CURRENT_DATE)) ROWS ONLY")
+ .returns(0)
+ .returns(1)
+ .returns(2)
+ .returns(3)
+ .returns(4)
+ .check();
}
/** */
@@ -289,6 +302,23 @@ public class LimitOffsetIntegrationTest extends
AbstractBasicIntegrationTransact
.check();
}
+ /** */
+ @Test
+ public void testFetchExpressionWithoutPushDown() throws Exception {
+ fillCache(cacheRepl, 5);
+
+ assertQuery("SELECT id FROM TEST_REPL ORDER BY id OFFSET 1 ROWS "
+ + "FETCH FIRST (ABS(0.5)) ROWS ONLY")
+ .resultSize(0)
+ .check();
+
+ assertQuery("SELECT id FROM TEST_REPL ORDER BY id OFFSET 1 ROWS "
+ + "FETCH FIRST (RAND_INTEGER(1) + 2) ROWS ONLY")
+ .returns(1)
+ .returns(2)
+ .check();
+ }
+
/**
* @param c Cache.
* @param rows Rows count.
diff --git
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/LimitOffsetPlannerTest.java
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/LimitOffsetPlannerTest.java
index cbde3437299..ed244db9178 100644
---
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/LimitOffsetPlannerTest.java
+++
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/LimitOffsetPlannerTest.java
@@ -17,9 +17,14 @@
package org.apache.ignite.internal.processors.query.calcite.planner;
+import org.apache.calcite.rel.RelRoot;
+import org.apache.calcite.rel.core.Sort;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.util.ImmutableIntList;
+import
org.apache.ignite.internal.processors.query.calcite.prepare.IgnitePlanner;
+import
org.apache.ignite.internal.processors.query.calcite.prepare.PlanningContext;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteExchange;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteLimit;
@@ -61,6 +66,51 @@ public class LimitOffsetPlannerTest extends
AbstractPlannerTest {
.and(hasChildThat(isInstanceOf(IgniteSort.class)))));
}
+ /** */
+ @Test
+ public void testFetchExpressionPushDown() throws Exception {
+ IgniteSchema publicSchema =
createSchemaWithTable(IgniteDistributions.random());
+
+ assertPlan("SELECT * FROM TEST ORDER BY ID FETCH FIRST (ABS(-2)) ROWS
ONLY", publicSchema,
+ isInstanceOf(IgniteLimit.class)
+ .and(input(isInstanceOf(IgniteExchange.class)
+ .and(input(isInstanceOf(IgniteSort.class)
+ .and(sort -> sort.fetch != null))))));
+
+ assertPlan("SELECT * FROM TEST ORDER BY ID OFFSET 1 ROWS "
+ + "FETCH FIRST (ABS(0.5)) ROWS ONLY", publicSchema,
+ isInstanceOf(IgniteLimit.class)
+ .and(limit -> limit.offset() != null && limit.fetch() != null)
+ .and(input(isInstanceOf(IgniteExchange.class)
+ .and(input(isInstanceOf(IgniteSort.class)
+ .and(sort -> sort.offset == null && sort.fetch ==
null))))));
+
+ assertPlan("SELECT * FROM TEST ORDER BY ID OFFSET 1 ROWS "
+ + "FETCH FIRST (RAND_INTEGER(1) + 2) ROWS ONLY", publicSchema,
+ isInstanceOf(IgniteLimit.class)
+ .and(limit -> limit.offset() != null && limit.fetch() != null)
+ .and(input(isInstanceOf(IgniteExchange.class)
+ .and(input(isInstanceOf(IgniteSort.class)
+ .and(sort -> sort.offset == null && sort.fetch ==
null))))));
+ }
+
+ /** */
+ @Test
+ public void testFetchExpressionFieldTrimmingPreservesCollationTrait()
throws Exception {
+ IgniteSchema publicSchema =
createSchemaWithTable(IgniteDistributions.random());
+ PlanningContext ctx = plannerCtx("SELECT val FROM (SELECT * FROM TEST
ORDER BY val "
+ + "FETCH FIRST (1 + 1) ROWS ONLY)", publicSchema);
+
+ try (IgnitePlanner planner = ctx.planner()) {
+ SqlNode sql = planner.validate(planner.parse(ctx.query()));
+ RelRoot root = planner.trimUnusedFields(planner.rel(sql));
+ Sort sort = findFirstNode(root.rel, byClass(Sort.class));
+
+ assertNotNull(sort);
+ assertEquals(sort.getCollation(),
sort.getTraitSet().getCollation());
+ }
+ }
+
/** */
@Test
public void testNestedLimitOffsetWithUnion() throws Exception {