This is an automated email from the ASF dual-hosted git repository.
korlov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new 6a7a6e08cd2 IGNITE-25530 Sql. Explain. Confusing printout of Sort node
(#6044)
6a7a6e08cd2 is described below
commit 6a7a6e08cd238bef5a9f7967c3005da53aa168c0
Author: korlov42 <[email protected]>
AuthorDate: Tue Jun 17 11:35:08 2025 +0300
IGNITE-25530 Sql. Explain. Confusing printout of Sort node (#6044)
---
.../sql/group1/explain/sort_and_limit.test | 3 +-
.../internal/sql/engine/exec/ExecutionContext.java | 5 +--
.../sql/engine/exec/exp/RexToLixTranslator.java | 6 +--
.../internal/sql/engine/exec/rel/LimitNode.java | 2 +-
.../internal/sql/engine/exec/rel/SortNode.java | 28 +++++++++---
.../internal/sql/engine/rel/IgniteLimit.java | 11 ++++-
.../ignite/internal/sql/engine/rel/IgniteSort.java | 2 +-
.../sql/engine/rule/SortConverterRule.java | 52 +++++++++++++++++++++-
.../internal/sql/engine/util/IgniteMethod.java | 4 --
.../sql/engine/exec/rel/LimitExecutionTest.java | 16 +++++--
.../sql/engine/planner/LimitOffsetPlannerTest.java | 12 ++---
11 files changed, 106 insertions(+), 35 deletions(-)
diff --git
a/modules/sql-engine/src/integrationTest/sql/group1/explain/sort_and_limit.test
b/modules/sql-engine/src/integrationTest/sql/group1/explain/sort_and_limit.test
index c0c7e6fdadd..98ec081cb7d 100644
---
a/modules/sql-engine/src/integrationTest/sql/group1/explain/sort_and_limit.test
+++
b/modules/sql-engine/src/integrationTest/sql/group1/explain/sort_and_limit.test
@@ -118,8 +118,7 @@ Limit
est: (rows=30)
Sort
collation: [C1 ASC, C2 ASC]
- offset: 5
- fetch: 30
+ fetch: 35
est: (rows=35)
Project
fieldNames: [C1, C2]
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionContext.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionContext.java
index fcfb3e52c7c..76454b00d8d 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionContext.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionContext.java
@@ -20,7 +20,6 @@ package org.apache.ignite.internal.sql.engine.exec;
import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
import static org.apache.ignite.lang.ErrorGroups.Common.INTERNAL_ERR;
-import java.lang.reflect.Type;
import java.time.Clock;
import java.time.Instant;
import java.time.ZoneId;
@@ -301,14 +300,14 @@ public class ExecutionContext<RowT> implements
DataContext {
}
if (name.startsWith("?")) {
- return getParameter(name, null);
+ return getParameter(name);
} else {
return params.get(name);
}
}
/** Gets dynamic parameters by name. */
- public @Nullable Object getParameter(String name, @Nullable Type
storageType) {
+ private @Nullable Object getParameter(String name) {
assert name.startsWith("?") : name;
Object param = params.get(name);
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexToLixTranslator.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexToLixTranslator.java
index 5e7faeba382..e656eec2df8 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexToLixTranslator.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexToLixTranslator.java
@@ -93,7 +93,6 @@ import org.apache.calcite.sql.validate.SqlConformance;
import org.apache.calcite.util.BuiltInMethod;
import org.apache.calcite.util.ControlFlowException;
import org.apache.calcite.util.Pair;
-import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory;
import org.apache.ignite.internal.sql.engine.util.IgniteMethod;
import org.apache.ignite.internal.sql.engine.util.Primitives;
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -1555,9 +1554,8 @@ public class RexToLixTranslator implements
RexVisitor<RexToLixTranslator.Result>
Expressions.call(root, BuiltInMethod.DATA_CONTEXT_GET.method,
Expressions.constant("?" + dynamicParam.getIndex())),
storageType);*/
- final Type paramType = ((IgniteTypeFactory)
typeFactory).getResultClass(dynamicParam.getType());
- final Expression ctxGet = Expressions.call(root,
IgniteMethod.CONTEXT_GET_PARAMETER_VALUE.method(),
- Expressions.constant("?" + dynamicParam.getIndex()),
Expressions.constant(paramType));
+ final Expression ctxGet = Expressions.call(root,
BuiltInMethod.DATA_CONTEXT_GET.method,
+ Expressions.constant("?" + dynamicParam.getIndex()));
final Expression valueExpression = ConverterUtils.convert(ctxGet,
dynamicParam.getType());
final ParameterExpression valueVariable =
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/LimitNode.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/LimitNode.java
index e0069f46ca5..48f00e9026b 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/LimitNode.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/LimitNode.java
@@ -34,7 +34,7 @@ public class LimitNode<RowT> extends AbstractNode<RowT>
implements SingleNode<Ro
/** Fetch can be unset. */
private final boolean fetchUndefined;
- /** Already processed (pushed to upstream) rows count. */
+ /** Already processed (pushed to downstream) rows count. */
private long rowsProcessed;
/** Waiting results counter. */
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/SortNode.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/SortNode.java
index 07a66d3fa3e..bb93648eb87 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/SortNode.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/SortNode.java
@@ -44,7 +44,9 @@ public class SortNode<RowT> extends AbstractNode<RowT>
implements SingleNode<Row
private final PriorityQueue<RowT> rows;
/** SQL select limit. Negative if disabled. */
- private final long limit;
+ private final long fetch;
+
+ private final long offset;
/** Reverse-ordered rows in case of limited sort. */
private List<RowT> reversed;
@@ -60,13 +62,24 @@ public class SortNode<RowT> extends AbstractNode<RowT>
implements SingleNode<Row
public SortNode(ExecutionContext<RowT> ctx,
Comparator<RowT> comp,
long offset,
- long fetch) {
+ long fetch
+ ) {
super(ctx);
assert fetch == -1 || fetch >= 0;
assert offset >= 0;
- limit = fetch == -1 ? -1 : IgniteMath.addExact(fetch, offset);
+ // Offset must be set only together with fetch.
+ // This is limitation is current implementation, and SortConverterRule
+ // should not produce unsupported variant.
+ if (offset > 0 && fetch == -1) {
+ throw new AssertionError("Offset-only case is not supported by
Sort node");
+ }
+
+ this.fetch = fetch;
+ this.offset = offset;
+
+ long limit = fetch == -1 ? -1 : IgniteMath.addExact(fetch, offset);
if (limit < 1 || limit > Integer.MAX_VALUE) {
rows = new PriorityQueue<>(comp);
@@ -154,7 +167,8 @@ public class SortNode<RowT> extends AbstractNode<RowT>
implements SingleNode<Row
buf.app("class=").app(getClass().getSimpleName())
.app(", requested=").app(requested)
.app(", waiting=").app(waiting)
- .app(", limit=").app(limit);
+ .app(", fetch=").app(fetch)
+ .app(", offset=").app(offset);
}
private void flush() throws Exception {
@@ -165,12 +179,12 @@ public class SortNode<RowT> extends AbstractNode<RowT>
implements SingleNode<Row
inLoop = true;
try {
// Prepare final order (reversed).
- if (limit > 0 && !rows.isEmpty()) {
+ if (fetch > 0 && !rows.isEmpty()) {
if (reversed == null) {
reversed = new ArrayList<>(rows.size());
}
- while (!rows.isEmpty()) {
+ while (rows.size() > offset) {
reversed.add(rows.poll());
if (++processed >= inBufSize) {
@@ -181,6 +195,8 @@ public class SortNode<RowT> extends AbstractNode<RowT>
implements SingleNode<Row
}
}
+ rows.clear();
+
processed = 0;
}
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteLimit.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteLimit.java
index 55283d9df8b..27c0e17e053 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteLimit.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteLimit.java
@@ -176,12 +176,19 @@ public class IgniteLimit extends SingleRel implements
IgniteRel {
/** {@inheritDoc} */
@Override
public double estimateRowCount(RelMetadataQuery mq) {
- double inputRowCount = mq.getRowCount(getInput());
+ return estimateRowCount(mq.getRowCount(getInput()), offset, fetch);
+ }
+ /** Returns the estimated row count based on provided input and offset and
fetch attributes. */
+ public static double estimateRowCount(
+ double inputRowCount,
+ @Nullable RexNode offset,
+ @Nullable RexNode fetch
+ ) {
double lim = fetch != null ? doubleFromRex(fetch, inputRowCount *
FETCH_IS_PARAM_FACTOR) : inputRowCount;
double off = offset != null ? doubleFromRex(offset, inputRowCount *
OFFSET_IS_PARAM_FACTOR) : 0;
- return Math.max(0, Math.min(lim, inputRowCount - off));
+ return Math.max(1, Math.min(lim, inputRowCount - off));
}
/**
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteSort.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteSort.java
index 96caf9227af..6cdbdfb1413 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteSort.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteSort.java
@@ -151,7 +151,7 @@ public class IgniteSort extends Sort implements IgniteRel {
/** {@inheritDoc} */
@Override
public double estimateRowCount(RelMetadataQuery mq) {
- return memRows(mq.getRowCount(getInput()));
+ return IgniteLimit.estimateRowCount(mq.getRowCount(getInput()),
offset, fetch);
}
/** {@inheritDoc} */
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/SortConverterRule.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/SortConverterRule.java
index 37644d75351..21aa941db7c 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/SortConverterRule.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rule/SortConverterRule.java
@@ -17,6 +17,8 @@
package org.apache.ignite.internal.sql.engine.rule;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptRule;
@@ -27,11 +29,19 @@ 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.RexBuilder;
+import org.apache.calcite.rex.RexExecutor;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexUtil;
import org.apache.ignite.internal.sql.engine.rel.IgniteConvention;
import org.apache.ignite.internal.sql.engine.rel.IgniteLimit;
import org.apache.ignite.internal.sql.engine.rel.IgniteSort;
+import org.apache.ignite.internal.sql.engine.sql.fun.IgniteSqlOperatorTable;
import org.apache.ignite.internal.sql.engine.trait.IgniteDistributions;
+import org.apache.ignite.internal.util.ExceptionUtils;
+import org.apache.ignite.internal.util.IgniteUtils;
import org.immutables.value.Value;
+import org.jetbrains.annotations.Nullable;
/**
* Converter rule for sort operator.
@@ -79,8 +89,8 @@ public class SortConverterRule extends
RelRule<SortConverterRule.Config> {
cluster.traitSetOf(IgniteConvention.INSTANCE).replace(sort.getCollation()),
convert(sort.getInput(),
cluster.traitSetOf(IgniteConvention.INSTANCE)),
sort.getCollation(),
- sort.offset,
- sort.fetch
+ null,
+ createLimitForSort(cluster.getPlanner().getExecutor(),
cluster.getRexBuilder(), sort.offset, sort.fetch)
);
call.transformTo(
@@ -99,4 +109,42 @@ public class SortConverterRule extends
RelRule<SortConverterRule.Config> {
call.transformTo(new IgniteSort(cluster, outTraits, input,
sort.getCollation()));
}
}
+
+ private static @Nullable RexNode createLimitForSort(
+ @Nullable RexExecutor executor, RexBuilder builder, @Nullable
RexNode offset, @Nullable RexNode fetch
+ ) {
+ if (fetch == null) {
+ // Current implementation of SortNode cannot handle offset-only
case.
+ return null;
+ }
+
+ if (offset != null) {
+ boolean shouldTryToSimplify = RexUtil.isLiteral(fetch, false)
+ && RexUtil.isLiteral(offset, false);
+
+ fetch = builder.makeCall(IgniteSqlOperatorTable.PLUS, fetch,
offset);
+
+ if (shouldTryToSimplify && executor != null) {
+ try {
+ List<RexNode> result = new ArrayList<>();
+ executor.reduce(builder, List.of(fetch), result);
+
+ assert result.size() <= 1 : result;
+
+ if (result.size() == 1) {
+ fetch = result.get(0);
+ }
+ } catch (Exception ex) {
+ if (IgniteUtils.assertionsEnabled()) {
+ ExceptionUtils.sneakyThrow(ex);
+ }
+
+ // Just ignore the exception, we will deal with this
expression later again,
+ // and next time we might have all the required context to
evaluate it.
+ }
+ }
+ }
+
+ return fetch;
+ }
}
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteMethod.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteMethod.java
index b2b92e1d458..02483328fb7 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteMethod.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteMethod.java
@@ -20,7 +20,6 @@ package org.apache.ignite.internal.sql.engine.util;
import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
import java.lang.reflect.Method;
-import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Objects;
@@ -53,9 +52,6 @@ public enum IgniteMethod {
/** See {@link ExecutionContext#correlatedVariable(int)}. */
CONTEXT_GET_CORRELATED_VALUE(ExecutionContext.class, "correlatedVariable",
int.class),
- /** See {@link ExecutionContext#getParameter(String, Type)}. */
- CONTEXT_GET_PARAMETER_VALUE(ExecutionContext.class, "getParameter",
String.class, Type.class),
-
/** See {@link IgniteSqlDateTimeUtils#subtractTimeZoneOffset(long,
TimeZone)}. **/
SUBTRACT_TIMEZONE_OFFSET(IgniteSqlDateTimeUtils.class,
"subtractTimeZoneOffset", long.class, TimeZone.class),
diff --git
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/LimitExecutionTest.java
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/LimitExecutionTest.java
index a085c3abb43..035878365a6 100644
---
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/LimitExecutionTest.java
+++
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/LimitExecutionTest.java
@@ -18,6 +18,7 @@
package org.apache.ignite.internal.sql.engine.exec.rel;
import static
org.apache.ignite.internal.sql.engine.util.Commons.IN_BUFFER_SIZE;
+import static
org.apache.ignite.internal.testframework.IgniteTestUtils.assertThrowsWithCause;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -33,7 +34,17 @@ import org.junit.jupiter.api.Test;
/**
* Test LimitNode execution.
*/
+@SuppressWarnings({"ThrowableNotThrown", "ResultOfObjectAllocationIgnored",
"resource"})
public class LimitExecutionTest extends AbstractExecutionTest<Object[]> {
+ @Test
+ void offsetOnlyCaseIsNotSupportedBySortNode() {
+ assertThrowsWithCause(
+ () -> new SortNode<>(executionContext(),
LimitExecutionTest::compareArrays, 10, -1),
+ AssertionError.class,
+ "Offset-only case is not supported by Sort node"
+ );
+ }
+
/** Tests correct results fetched with Limit node. */
@Test
public void testLimit() {
@@ -61,13 +72,10 @@ public class LimitExecutionTest extends
AbstractExecutionTest<Object[]> {
int bufSize = IN_BUFFER_SIZE;
checkLimitSort(0, 1);
- checkLimitSort(1, 0);
checkLimitSort(1, 1);
checkLimitSort(0, bufSize);
- checkLimitSort(bufSize, 0);
checkLimitSort(bufSize, bufSize);
checkLimitSort(bufSize - 1, 1);
- checkLimitSort(2000, 0);
checkLimitSort(0, 3000);
checkLimitSort(2000, 3000);
}
@@ -99,7 +107,7 @@ public class LimitExecutionTest extends
AbstractExecutionTest<Object[]> {
sortNode.register(srcNode);
- for (int i = 0; i < offset + fetch; i++) {
+ for (int i = offset; i < offset + fetch; i++) {
assertTrue(rootNode.hasNext());
assertEquals(i, rootNode.next()[0]);
}
diff --git
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/LimitOffsetPlannerTest.java
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/LimitOffsetPlannerTest.java
index e844676cbf9..202a53f7808 100644
---
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/LimitOffsetPlannerTest.java
+++
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/LimitOffsetPlannerTest.java
@@ -88,8 +88,8 @@ public class LimitOffsetPlannerTest extends
AbstractPlannerTest {
isInstanceOf(IgniteLimit.class)
.and(input(isInstanceOf(IgniteExchange.class)
.and(input(isInstanceOf(IgniteSort.class)
- .and(s -> doubleFromRex(s.fetch, -1)
== 5.0)
- .and(s -> doubleFromRex(s.offset, -1)
== 10.0))))));
+ .and(s -> doubleFromRex(s.fetch, -1)
== 15.0)
+ .and(s -> s.offset == null))))));
// Same simple case but witout offset.
assertPlan("SELECT * FROM TEST ORDER BY ID LIMIT 5", publicSchema,
@@ -163,8 +163,8 @@ public class LimitOffsetPlannerTest extends
AbstractPlannerTest {
.and(input(isInstanceOf(IgniteExchange.class)
.and(e -> e.distribution() ==
IgniteDistributions.single())
.and(input(isInstanceOf(IgniteSort.class)
- .and(s -> doubleFromRex(s.offset, -1)
== 1)
- .and(s -> doubleFromRex(s.fetch, -1)
== 1)))))));
+ .and(s -> doubleFromRex(s.fetch, -1)
== 2)
+ .and(s -> s.offset == null)))))));
publicSchema = createSchemaWithTable(IgniteDistributions.random(),
"ID");
@@ -262,8 +262,8 @@ public class LimitOffsetPlannerTest extends
AbstractPlannerTest {
.and(input(isInstanceOf(IgniteExchange.class)
.and(input(
isInstanceOf(IgniteSort.class)
- .and(l ->
doubleFromRex(l.offset, -1) == 2.0)
- .and(l ->
doubleFromRex(l.fetch, -1) == 3.0)
+ .and(l -> l.offset ==
null)
+ .and(l ->
doubleFromRex(l.fetch, -1) == 5.0)
))
))
))