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 74620272892 IGNITE-27701 Sql. Wrong result is returned for query with
LEFT JOIN (#7501)
74620272892 is described below
commit 746202728927d18f06dfae2690521c7f61a19fb0
Author: korlov42 <[email protected]>
AuthorDate: Mon Feb 2 14:45:17 2026 +0200
IGNITE-27701 Sql. Wrong result is returned for query with LEFT JOIN (#7501)
---
.../ignite/internal/sql/engine/ItJoinTest.java | 13 ++++++
.../internal/sql/engine/exec/rel/HashJoinNode.java | 46 +++++++++++++++++++---
.../engine/exec/rel/AbstractJoinExecutionTest.java | 4 +-
3 files changed, 56 insertions(+), 7 deletions(-)
diff --git
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItJoinTest.java
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItJoinTest.java
index 5c77a2aa502..b68e87f5d09 100644
---
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItJoinTest.java
+++
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItJoinTest.java
@@ -1181,4 +1181,17 @@ public class ItJoinTest extends BaseSqlIntegrationTest {
+ " MATCH_CONDITION t2.t < t1.t\n"
+ " ON t1.k = t2.k"));
}
+
+ @ParameterizedTest
+ @EnumSource(mode = Mode.EXCLUDE, names = {"CORRELATED", "MERGE"})
+ void testLeftJoinWithDuplicatesAndAlwaysFalseNonEquiCondition(JoinType
joinType) {
+ assertQuery("SELECT t1.id FROM t1 LEFT JOIN t1 AS t2 ON t1.c1 = t2.c1
AND t1.id < 0", joinType)
+ .returns(0)
+ .returns(1)
+ .returns(2)
+ .returns(3)
+ .returns(4)
+ .returns(5)
+ .check();
+ }
}
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/HashJoinNode.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/HashJoinNode.java
index 394a63c832a..b3aa1a23b7a 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/HashJoinNode.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/rel/HashJoinNode.java
@@ -251,6 +251,8 @@ public abstract class HashJoinNode<RowT> extends
AbstractRightMaterializedJoinNo
private final RowFactory<RowT> rightRowFactory;
private final SqlJoinProjection outputProjection;
+ private boolean matched;
+
/**
* Creates HashJoinNode for LEFT OUTER JOIN operator.
*
@@ -272,6 +274,14 @@ public abstract class HashJoinNode<RowT> extends
AbstractRightMaterializedJoinNo
this.rightRowFactory = rightRowFactory;
}
+ /** {@inheritDoc} */
+ @Override
+ protected void rewindInternal() {
+ matched = false;
+
+ super.rewindInternal();
+ }
+
/** {@inheritDoc} */
@Override
protected void join() throws Exception {
@@ -284,7 +294,22 @@ public abstract class HashJoinNode<RowT> extends
AbstractRightMaterializedJoinNo
// Proceed with next left row, if previous was fully
processed.
if (!rightIt.hasNext()) {
+ // If this is not the first iteration and row
wasn't previously matched,
+ // then before advancing to the next one we should
emit current as non-matched.
+ if (left != null && !matched) {
+ --requested;
+ RowT row = outputProjection.project(context(),
left, rightRowFactory.create());
+ downstream().push(row);
+
+ left = null;
+ }
+
+ if (leftInBuf.isEmpty()) {
+ break;
+ }
+
left = leftInBuf.remove();
+ matched = false;
Collection<RowT> rightRows = lookup(left);
@@ -310,9 +335,10 @@ public abstract class HashJoinNode<RowT> extends
AbstractRightMaterializedJoinNo
RowT right = rightIt.next();
if (checkNonEquiCondition &&
!nonEquiCondition.test(left, right)) {
- right = rightRowFactory.create();
+ continue;
}
+ matched = true;
--requested;
RowT row = outputProjection.project(context(),
left, right);
@@ -320,7 +346,11 @@ public abstract class HashJoinNode<RowT> extends
AbstractRightMaterializedJoinNo
}
}
- if (!rightIt.hasNext()) {
+ // Postpone nullification of `left` row if no match
was found.
+ // Left join should emit any non matched row from left
side, but
+ // by this point we may run out of `requested` quota.
Therefore we
+ // handle this case during next iteration.
+ if (!rightIt.hasNext() && matched) {
left = null;
}
}
@@ -357,7 +387,9 @@ public abstract class HashJoinNode<RowT> extends
AbstractRightMaterializedJoinNo
) {
super(ctx, joinInfo, nonEquiCondition);
- assert nonEquiCondition == null : "Non equi condition is not
supported in RIGHT join";
+ if (nonEquiCondition != null) {
+ throw new IllegalStateException("Non equi condition is not
supported in RIGHT join");
+ }
this.outputProjection = outputProjection;
this.leftRowFactory = leftRowFactory;
@@ -514,7 +546,9 @@ public abstract class HashJoinNode<RowT> extends
AbstractRightMaterializedJoinNo
) {
super(ctx, joinInfo, nonEquiCondition);
- assert nonEquiCondition == null : "Non equi condition is not
supported in FULL OUTER join";
+ if (nonEquiCondition != null) {
+ throw new IllegalStateException("Non equi condition is not
supported in FULL OUTER join");
+ }
this.outputProjection = outputProjection;
this.leftRowFactory = leftRowFactory;
@@ -750,7 +784,9 @@ public abstract class HashJoinNode<RowT> extends
AbstractRightMaterializedJoinNo
) {
super(ctx, joinInfo, nonEquiCondition);
- assert nonEquiCondition == null : "Non equi condition is not
supported in ANTI join";
+ if (nonEquiCondition != null) {
+ throw new IllegalStateException("Non equi condition is not
supported in ANTI join");
+ }
}
/** {@inheritDoc} */
diff --git
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/AbstractJoinExecutionTest.java
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/AbstractJoinExecutionTest.java
index 93525d533d1..d82e120e6b4 100644
---
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/AbstractJoinExecutionTest.java
+++
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/rel/AbstractJoinExecutionTest.java
@@ -518,7 +518,7 @@ public abstract class AbstractJoinExecutionTest extends
AbstractExecutionTest<Ob
}
@ParameterizedTest
- @EnumSource(value = JoinRelType.class, names = {"INNER", "SEMI"})
+ @EnumSource(value = JoinRelType.class, names = {"INNER", "SEMI", "LEFT"})
void nonEquiJoinWithDifferentBufferSize(JoinRelType joinType) {
int buffSize = 1;
validateNonEquiJoin(executionContext(buffSize), joinType, 0, 0);
@@ -620,7 +620,7 @@ public abstract class AbstractJoinExecutionTest extends
AbstractExecutionTest<Ob
(l, r) -> false,
() -> IntStream.range(0, leftSize).mapToObj(i ->
person).iterator(),
() -> IntStream.range(0, rightSize).mapToObj(i ->
department).iterator(),
- 0
+ joinType == LEFT ? leftSize : 0
);
}