Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
*/
public abstract class AbstractNode<Row> implements Node<Row> {
/** */
protected static final int IN_BUFFER_SIZE = IgniteSystemProperties.getInteger(IGNITE_CALCITE_EXEC_IN_BUFFER_SIZE, 512);
public static final int IN_BUFFER_SIZE = IgniteSystemProperties.getInteger(IGNITE_CALCITE_EXEC_IN_BUFFER_SIZE, 512);

/** */
protected static final int MODIFY_BATCH_SIZE = IgniteSystemProperties.getInteger(IGNITE_CALCITE_EXEC_MODIFY_BATCH_SIZE, 100);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ public static <Row> CollectNode<Row> createCountCollector(ExecutionContext<Row>

if (waiting == 0)
source().request(waiting = IN_BUFFER_SIZE);

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.

Brw. Why di we reques more than even rowsCnt (in case of few rowsCnt)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This node always produce one row. But can collect it from unlimited data set, so it's worth to request from input more rows than requested from downstream.

else if (waiting < 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.

It look like delayed downstream().end(). Why not in own void end()?

@alex-plekhanov alex-plekhanov Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Because we must keep invariant: "end downstream only if any rows is requested". If we request initially 1 row, push that row on "end()", we can't futher end downstream, since no rows more requested and it can lead to assertions. This case is shown in the new CollectIntegrationTest

requested = 0;

downstream().end();
}
}

/** {@inheritDoc} */
Expand All @@ -138,6 +143,7 @@ public static <Row> CollectNode<Row> createCountCollector(ExecutionContext<Row>
@Override public void end() throws Exception {
assert downstream() != null;
assert waiting > 0;
assert requested > 0;

checkState();

Expand All @@ -146,10 +152,13 @@ public static <Row> CollectNode<Row> createCountCollector(ExecutionContext<Row>
if (isClosed())
return;

requested--;

downstream().push(collector.get());

if (requested > 0) {
requested = 0;

downstream().push(collector.get());
downstream().end();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,8 @@ private void onRequest() throws Exception {
break;

case END:
requested = 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.

New testRequestRowsAfterInputEnds() test seems to work without any changes here. Is it an optimization? Do we need extra execution tests or at least comments?

@alex-plekhanov alex-plekhanov Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, new tests only cover CollectNode and SortAggregateNode changes. Changes to CNLJ and SortNode are made to be consistent with other code. We have a lot of nodes with:

requested = 0;
downstream().end();

Sequential calls. These 4 nodes are only nodes with different logic. Change in SortNode it's not a bugfix, it's just code consistency, but CNLJ without requested = 0 can lead to the same problems as CollectNode and SortAggregateNode. I think it's possible to write reproducer for CNLJ, but maybe 2 tests is enoght to prove that requested = 0 should always be placed before downstream().end()?


downstream().end();
break;

Expand Down Expand Up @@ -338,8 +340,11 @@ private void onEndLeft() throws Exception {

state = State.END;

if (requested > 0)
if (requested > 0) {
requested = 0;

downstream().end();
}
}
else {
prepareCorrelations();
Expand Down Expand Up @@ -469,8 +474,9 @@ private void join() throws Exception {

state = State.END;

if (requested > 0)
downstream().end();
requested = 0;

downstream().end();

return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,11 @@ public SortAggregateNode(

source().request(IN_BUFFER_SIZE);
}
else if (waiting < 0)
else if (waiting < 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.

Same. Looks like a delayed end(). Why not in own end(). It it some CLN node behaviour trait?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

See https://github.com/apache/ignite/pull/13407/changes#r3674976662 comment. It's common behaviour for all nodes.

requested = 0;

downstream().end();
}
}

/** {@inheritDoc} */
Expand Down Expand Up @@ -163,8 +166,11 @@ else if (waiting < 0)
doPush();
}

if (requested > 0)
if (requested > 0) {
requested = 0;

downstream().end();
}

grp = null;
prevRow = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,10 +205,11 @@ private void flush() throws Exception {
}

if (reversed == null ? rows.isEmpty() : reversed.isEmpty()) {
if (requested > 0)
downstream().end();
if (requested > 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.

The new tests testRequestRowsAfterInputEnds() and CollectIntegrationTest seem to work witout this change. Why? Maybe we should add/fix some executions tests too.

@alex-plekhanov alex-plekhanov Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This change is only for code consistency. No bug here.

requested = 0;

requested = 0;
downstream().end();
}
}
}
finally {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* 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.integration;

import org.apache.ignite.internal.processors.query.calcite.exec.rel.AbstractNode;
import org.junit.Test;

/**
* Integration test for collect node.
*/
public class CollectIntegrationTest extends AbstractBasicIntegrationTest {

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.

One dedicated with-join test only for row in-out and buffer processing. Based on a plan which can variate. Maybe we need a special node execution test, WDYT?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

But this integration test shows that problem is not artifitial

/**
* Tests that collect node correctly handles the case when downstream requests
* limited number of rows, where collect must push one row and then
* properly terminate downstream.
*/
@Test
public void testRequestLimitedRowsCountFromCollect() {
sql("CREATE TABLE t(a INT)");

sql("INSERT INTO t (a) VALUES (?)", 0);

String sql = "SELECT /*+ CNL_JOIN */ ARRAY(SELECT a FROM t) FROM t LIMIT 1";

assertQuery(sql).resultSize(1).check();

/**
* The data source size of (buffer size + 1) is used to ensure that multiple batches are needed
* on right hand of CNLJ to process all input rows, in this case left hand is not requested
* immediately after endLeft() call.
*/
for (int i = 1; i < AbstractNode.IN_BUFFER_SIZE + 1; i++)
sql("INSERT INTO t (a) VALUES (?)", i);

assertQuery(sql).resultSize(1).check();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.apache.ignite.cache.QueryIndexType;
import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.internal.processors.query.calcite.exec.rel.AbstractNode;
import org.apache.ignite.internal.util.typedef.F;
import org.junit.Test;

Expand Down Expand Up @@ -155,6 +156,31 @@ public void testNullsReordering() {
.check();
}

/**
* Tests that sort aggregate node correctly handles the case when input data
* ends exactly when the requested number of rows is satisfied.
*/
@Test
public void testRequestRowsAfterInputEnds() {

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.

It actually works witout fixes in the ALL and RANDOM tx. modes. Maybe we need assumeThat()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

For ALL and RANDOM plan is not the same as for NONE, for this reason plan checkers (matches method) are disabled for ALL and RANDOM. But result still should be valid for these tx modes even with another plan, so I think test is also helpful for these modes

/**
* With input rows count equals to the buffer size, the last row completes both
* the input data and the requested count in the same cycle. This triggers
* a synchronous request() call from within push() to fill the buffer, and
* the node must properly handle the termination on the subsequent request()
* call rather than on end().
*/
int bufSize = AbstractNode.IN_BUFFER_SIZE;

sql("CREATE TABLE t0(a INTEGER PRIMARY KEY, b INTEGER) WITH template=replicated," + atomicity());

for (int i = 0; i < bufSize; i++)
sql("INSERT INTO t0 VALUES (?, ?)", i, i);

assertQuery("SELECT t1.a FROM t0 AS t1 JOIN (SELECT a, count(a) FROM t0 GROUP BY a) AS t2 ON t1.a = t2.a")
.resultSize(bufSize)
.check();
}

/**
* @param c Cache.
* @param rows Rows count.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.apache.ignite.internal.processors.query.calcite.integration.CalciteBasicSecondaryIndexIntegrationTest;
import org.apache.ignite.internal.processors.query.calcite.integration.CalciteErrorHandlilngIntegrationTest;
import org.apache.ignite.internal.processors.query.calcite.integration.CalcitePlanningDumpTest;
import org.apache.ignite.internal.processors.query.calcite.integration.CollectIntegrationTest;
import org.apache.ignite.internal.processors.query.calcite.integration.CorrelatesIntegrationTest;
import org.apache.ignite.internal.processors.query.calcite.integration.DataTypesTest;
import org.apache.ignite.internal.processors.query.calcite.integration.DateTimeTest;
Expand Down Expand Up @@ -187,6 +188,7 @@
SelectByKeyFieldTest.class,
WindowIntegrationTest.class,
CalciteMessageUnmarshalThreadIntegrationTest.class,
CollectIntegrationTest.class,
})
public class IntegrationTestSuite {
}
Loading