This is an automated email from the ASF dual-hosted git repository.

gian pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git


The following commit(s) were added to refs/heads/master by this push:
     new c96b215dd67 SortMerge join support for IS NOT DISTINCT FROM. (#16003)
c96b215dd67 is described below

commit c96b215dd67fad883b1f6651ed568426aae7070f
Author: Gian Merlino <[email protected]>
AuthorDate: Tue Mar 19 12:02:13 2024 -0700

    SortMerge join support for IS NOT DISTINCT FROM. (#16003)
    
    * SortMerge join support for IS NOT DISTINCT FROM.
    
    The patch adds a "requiredNonNullKeyParts" field to the sortMerge
    processor, which has the list of key parts that must be nonnull for
    an equijoin condition to match. Conditions with SQL "=" are present in
    the list; conditions with SQL "IS NOT DISTINCT FROM" are absent from
    the list.
    
    * Fix test.
    
    * Update javadoc.
---
 .../apache/druid/msq/querykit/DataSourcePlan.java  |   8 +-
 .../common/SortMergeJoinFrameProcessor.java        |  22 +-
 .../common/SortMergeJoinFrameProcessorFactory.java |  25 +++
 .../SortMergeJoinFrameProcessorFactoryTest.java    | 241 ++++++++++++++++++++
 .../common/SortMergeJoinFrameProcessorTest.java    | 242 +++++++++++++++++++++
 .../druid/frame/key/FrameComparisonWidget.java     |  15 +-
 .../druid/frame/key/FrameComparisonWidgetImpl.java |  25 ++-
 .../frame/key/FrameComparisonWidgetImplTest.java   |  17 +-
 .../druid/sql/calcite/planner/JoinAlgorithm.java   |  17 --
 .../druid/sql/calcite/CalciteJoinQueryTest.java    |  22 +-
 10 files changed, 576 insertions(+), 58 deletions(-)

diff --git 
a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/DataSourcePlan.java
 
b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/DataSourcePlan.java
index 566b084ad36..56fae646a4a 100644
--- 
a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/DataSourcePlan.java
+++ 
b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/DataSourcePlan.java
@@ -333,19 +333,15 @@ public class DataSourcePlan
   /**
    * Checks if the sortMerge algorithm can execute a particular join condition.
    *
-   * Two checks:
-   * (1) join condition on two tables "table1" and "table2" is of the form
+   * One check: join condition on two tables "table1" and "table2" is of the 
form
    * table1.columnA = table2.columnA && table1.columnB = table2.columnB && ....
-   *
-   * (2) join condition uses equals, not IS NOT DISTINCT FROM [sortMerge 
processor does not currently implement
-   * IS NOT DISTINCT FROM]
    */
   private static boolean canUseSortMergeJoin(JoinConditionAnalysis 
joinConditionAnalysis)
   {
     return joinConditionAnalysis
         .getEquiConditions()
         .stream()
-        .allMatch(equality -> equality.getLeftExpr().isIdentifier() && 
!equality.isIncludeNull());
+        .allMatch(equality -> equality.getLeftExpr().isIdentifier());
   }
 
   /**
diff --git 
a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/common/SortMergeJoinFrameProcessor.java
 
b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/common/SortMergeJoinFrameProcessor.java
index 4b3854883a2..0fd85c6d082 100644
--- 
a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/common/SortMergeJoinFrameProcessor.java
+++ 
b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/common/SortMergeJoinFrameProcessor.java
@@ -138,6 +138,7 @@ public class SortMergeJoinFrameProcessor implements 
FrameProcessor<Object>
       FrameWriterFactory frameWriterFactory,
       String rightPrefix,
       List<List<KeyColumn>> keyColumns,
+      int[] requiredNonNullKeyParts,
       JoinType joinType,
       long maxBufferedBytes
   )
@@ -148,8 +149,8 @@ public class SortMergeJoinFrameProcessor implements 
FrameProcessor<Object>
     this.rightPrefix = rightPrefix;
     this.joinType = joinType;
     this.trackers = ImmutableList.of(
-        new Tracker(left, keyColumns.get(LEFT), maxBufferedBytes),
-        new Tracker(right, keyColumns.get(RIGHT), maxBufferedBytes)
+        new Tracker(left, keyColumns.get(LEFT), requiredNonNullKeyParts, 
maxBufferedBytes),
+        new Tracker(right, keyColumns.get(RIGHT), requiredNonNullKeyParts, 
maxBufferedBytes)
     );
     this.maxBufferedBytes = maxBufferedBytes;
   }
@@ -195,7 +196,7 @@ public class SortMergeJoinFrameProcessor implements 
FrameProcessor<Object>
 
       // Two rows match if the keys compare equal _and_ neither key has a null 
component. (x JOIN y ON x.a = y.a does
       // not match rows where "x.a" is null.)
-      final boolean marksMatch = markCmp == 0 && 
trackers.get(LEFT).hasCompletelyNonNullMark();
+      final boolean marksMatch = markCmp == 0 && 
trackers.get(LEFT).markHasRequiredNonNullKeyParts();
 
       // If marked keys are equal on both sides ("marksMatch"), at least one 
side needs to have a complete set of rows
       // for the marked key. Check if this is true, otherwise call nextAwait 
to read more data.
@@ -446,7 +447,7 @@ public class SortMergeJoinFrameProcessor implements 
FrameProcessor<Object>
   /**
    * Compares the marked rows of the two {@link #trackers}. This method 
returns 0 if both sides are null, even
    * though this is not considered a match by join semantics. Therefore, it is 
important to also check
-   * {@link Tracker#hasCompletelyNonNullMark()}.
+   * {@link Tracker#markHasRequiredNonNullKeyParts()}.
    *
    * @return negative if {@link #LEFT} key is earlier, positive if {@link 
#RIGHT} key is earlier, zero if the keys
    * are the same. Returns zero even if a key component is null, even though 
this is not considered a match by
@@ -549,6 +550,7 @@ public class SortMergeJoinFrameProcessor implements 
FrameProcessor<Object>
     private final List<FrameHolder> holders = new ArrayList<>();
     private final ReadableInput input;
     private final List<KeyColumn> keyColumns;
+    private final int[] requiredNonNullKeyParts;
     private final long maxBytesBuffered;
 
     // markFrame and markRow are the first frame and row with the current key.
@@ -561,10 +563,16 @@ public class SortMergeJoinFrameProcessor implements 
FrameProcessor<Object>
     // done indicates that no more data is available in the channel.
     private boolean done;
 
-    public Tracker(ReadableInput input, List<KeyColumn> keyColumns, long 
maxBytesBuffered)
+    public Tracker(
+        final ReadableInput input,
+        final List<KeyColumn> keyColumns,
+        final int[] requiredNonNullKeyParts,
+        final long maxBytesBuffered
+    )
     {
       this.input = input;
       this.keyColumns = keyColumns;
+      this.requiredNonNullKeyParts = requiredNonNullKeyParts;
       this.maxBytesBuffered = maxBytesBuffered;
     }
 
@@ -686,9 +694,9 @@ public class SortMergeJoinFrameProcessor implements 
FrameProcessor<Object>
     /**
      * Whether this tracker has a marked row that is completely nonnull.
      */
-    public boolean hasCompletelyNonNullMark()
+    public boolean markHasRequiredNonNullKeyParts()
     {
-      return hasMark() && 
holders.get(markFrame).comparisonWidget.isCompletelyNonNullKey(markRow);
+      return hasMark() && 
holders.get(markFrame).comparisonWidget.hasNonNullKeyParts(markRow, 
requiredNonNullKeyParts);
     }
 
     /**
diff --git 
a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/common/SortMergeJoinFrameProcessorFactory.java
 
b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/common/SortMergeJoinFrameProcessorFactory.java
index ef4d9f280a9..7a81c59cc11 100644
--- 
a/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/common/SortMergeJoinFrameProcessorFactory.java
+++ 
b/extensions-core/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/common/SortMergeJoinFrameProcessorFactory.java
@@ -28,6 +28,8 @@ import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
 import org.apache.druid.frame.key.KeyColumn;
 import org.apache.druid.frame.key.KeyOrder;
 import org.apache.druid.frame.processor.FrameProcessor;
@@ -142,6 +144,7 @@ public class SortMergeJoinFrameProcessorFactory extends 
BaseFrameProcessorFactor
 
     // Compute key columns.
     final List<List<KeyColumn>> keyColumns = toKeyColumns(condition);
+    final int[] requiredNonNullKeyParts = toRequiredNonNullKeyParts(condition);
 
     // Stitch up the inputs and validate each input channel signature.
     // If validateInputFrameSignatures fails, it's a precondition violation: 
this class somehow got bad inputs.
@@ -180,6 +183,7 @@ public class SortMergeJoinFrameProcessorFactory extends 
BaseFrameProcessorFactor
               
stageDefinition.createFrameWriterFactory(outputChannel.getFrameMemoryAllocator()),
               rightPrefix,
               keyColumns,
+              requiredNonNullKeyParts,
               joinType,
               frameContext.memoryParameters().getSortMergeJoinMemory()
           );
@@ -217,6 +221,27 @@ public class SortMergeJoinFrameProcessorFactory extends 
BaseFrameProcessorFactor
     return retVal;
   }
 
+  /**
+   * Extracts a list of key parts that must be nonnull from a {@link 
JoinConditionAnalysis}. These are equality
+   * conditions for which {@link Equality#isIncludeNull()} is false.
+   *
+   * The condition must have been validated by {@link 
#validateCondition(JoinConditionAnalysis)}.
+   */
+  public static int[] toRequiredNonNullKeyParts(final JoinConditionAnalysis 
condition)
+  {
+    final IntList retVal = new 
IntArrayList(condition.getEquiConditions().size());
+
+    final List<Equality> equiConditions = condition.getEquiConditions();
+    for (int i = 0; i < equiConditions.size(); i++) {
+      Equality equiCondition = equiConditions.get(i);
+      if (!equiCondition.isIncludeNull()) {
+        retVal.add(i);
+      }
+    }
+
+    return retVal.toArray(new int[0]);
+  }
+
   /**
    * Validates that a join condition can be handled by this processor. Returns 
the condition if it can be handled.
    * Throws {@link IllegalArgumentException} if the condition cannot be 
handled.
diff --git 
a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/querykit/common/SortMergeJoinFrameProcessorFactoryTest.java
 
b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/querykit/common/SortMergeJoinFrameProcessorFactoryTest.java
new file mode 100644
index 00000000000..d1b2730bd0c
--- /dev/null
+++ 
b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/querykit/common/SortMergeJoinFrameProcessorFactoryTest.java
@@ -0,0 +1,241 @@
+/*
+ * 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.druid.msq.querykit.common;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.druid.frame.key.KeyColumn;
+import org.apache.druid.frame.key.KeyOrder;
+import org.apache.druid.math.expr.ExprMacroTable;
+import org.apache.druid.segment.join.JoinConditionAnalysis;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class SortMergeJoinFrameProcessorFactoryTest
+{
+  @Test
+  public void test_validateCondition()
+  {
+    Assert.assertNotNull(
+        SortMergeJoinFrameProcessorFactory.validateCondition(
+            JoinConditionAnalysis.forExpression("1", "j.", 
ExprMacroTable.nil())
+        )
+    );
+
+    Assert.assertNotNull(
+        SortMergeJoinFrameProcessorFactory.validateCondition(
+            JoinConditionAnalysis.forExpression("x == \"j.y\"", "j.", 
ExprMacroTable.nil())
+        )
+    );
+
+    Assert.assertNotNull(
+        SortMergeJoinFrameProcessorFactory.validateCondition(
+            JoinConditionAnalysis.forExpression("1", "j.", 
ExprMacroTable.nil())
+        )
+    );
+
+    Assert.assertNotNull(
+        SortMergeJoinFrameProcessorFactory.validateCondition(
+            JoinConditionAnalysis.forExpression("x == \"j.y\" && a == 
\"j.b\"", "j.", ExprMacroTable.nil())
+        )
+    );
+
+    Assert.assertNotNull(
+        SortMergeJoinFrameProcessorFactory.validateCondition(
+            JoinConditionAnalysis.forExpression(
+                "notdistinctfrom(x, \"j.y\") && a == \"j.b\"",
+                "j.",
+                ExprMacroTable.nil()
+            )
+        )
+    );
+
+    Assert.assertThrows(
+        IllegalArgumentException.class,
+        () -> SortMergeJoinFrameProcessorFactory.validateCondition(
+            JoinConditionAnalysis.forExpression("x == y", "j.", 
ExprMacroTable.nil())
+        )
+    );
+
+    Assert.assertThrows(
+        IllegalArgumentException.class,
+        () -> SortMergeJoinFrameProcessorFactory.validateCondition(
+            JoinConditionAnalysis.forExpression("x + 1 == \"j.y\"", "j.", 
ExprMacroTable.nil())
+        )
+    );
+  }
+
+  @Test
+  public void test_toKeyColumns()
+  {
+    Assert.assertEquals(
+        ImmutableList.of(
+            ImmutableList.of(new KeyColumn("x", KeyOrder.ASCENDING)),
+            ImmutableList.of(new KeyColumn("y", KeyOrder.ASCENDING))
+        ),
+        SortMergeJoinFrameProcessorFactory.toKeyColumns(
+            JoinConditionAnalysis.forExpression(
+                "x == \"j.y\"",
+                "j.",
+                ExprMacroTable.nil()
+            )
+        )
+    );
+
+    Assert.assertEquals(
+        ImmutableList.of(
+            ImmutableList.of(),
+            ImmutableList.of()
+        ),
+        SortMergeJoinFrameProcessorFactory.toKeyColumns(
+            JoinConditionAnalysis.forExpression(
+                "1",
+                "j.",
+                ExprMacroTable.nil()
+            )
+        )
+    );
+
+    Assert.assertEquals(
+        ImmutableList.of(
+            ImmutableList.of(new KeyColumn("x", KeyOrder.ASCENDING), new 
KeyColumn("a", KeyOrder.ASCENDING)),
+            ImmutableList.of(new KeyColumn("y", KeyOrder.ASCENDING), new 
KeyColumn("b", KeyOrder.ASCENDING))
+        ),
+        SortMergeJoinFrameProcessorFactory.toKeyColumns(
+            JoinConditionAnalysis.forExpression(
+                "x == \"j.y\" && a == \"j.b\"",
+                "j.",
+                ExprMacroTable.nil()
+            )
+        )
+    );
+
+    Assert.assertEquals(
+        ImmutableList.of(
+            ImmutableList.of(new KeyColumn("x", KeyOrder.ASCENDING), new 
KeyColumn("a", KeyOrder.ASCENDING)),
+            ImmutableList.of(new KeyColumn("y", KeyOrder.ASCENDING), new 
KeyColumn("b", KeyOrder.ASCENDING))
+        ),
+        SortMergeJoinFrameProcessorFactory.toKeyColumns(
+            JoinConditionAnalysis.forExpression(
+                "x == \"j.y\" && notdistinctfrom(a, \"j.b\")",
+                "j.",
+                ExprMacroTable.nil()
+            )
+        )
+    );
+
+    Assert.assertEquals(
+        ImmutableList.of(
+            ImmutableList.of(new KeyColumn("x", KeyOrder.ASCENDING), new 
KeyColumn("a", KeyOrder.ASCENDING)),
+            ImmutableList.of(new KeyColumn("y", KeyOrder.ASCENDING), new 
KeyColumn("b", KeyOrder.ASCENDING))
+        ),
+        SortMergeJoinFrameProcessorFactory.toKeyColumns(
+            JoinConditionAnalysis.forExpression(
+                "notdistinctfrom(x, \"j.y\") && a == \"j.b\"",
+                "j.",
+                ExprMacroTable.nil()
+            )
+        )
+    );
+
+    Assert.assertEquals(
+        ImmutableList.of(
+            ImmutableList.of(new KeyColumn("x", KeyOrder.ASCENDING), new 
KeyColumn("a", KeyOrder.ASCENDING)),
+            ImmutableList.of(new KeyColumn("y", KeyOrder.ASCENDING), new 
KeyColumn("b", KeyOrder.ASCENDING))
+        ),
+        SortMergeJoinFrameProcessorFactory.toKeyColumns(
+            JoinConditionAnalysis.forExpression(
+                "notdistinctfrom(x, \"j.y\") && notdistinctfrom(a, \"j.b\")",
+                "j.",
+                ExprMacroTable.nil()
+            )
+        )
+    );
+  }
+
+  @Test
+  public void test_toRequiredNonNullKeyParts()
+  {
+    Assert.assertArrayEquals(
+        new int[0],
+        SortMergeJoinFrameProcessorFactory.toRequiredNonNullKeyParts(
+            JoinConditionAnalysis.forExpression(
+                "1",
+                "j.",
+                ExprMacroTable.nil()
+            )
+        )
+    );
+
+    Assert.assertArrayEquals(
+        new int[]{0},
+        SortMergeJoinFrameProcessorFactory.toRequiredNonNullKeyParts(
+            JoinConditionAnalysis.forExpression(
+                "x == \"j.y\"",
+                "j.",
+                ExprMacroTable.nil()
+            )
+        )
+    );
+
+    Assert.assertArrayEquals(
+        new int[]{0, 1},
+        SortMergeJoinFrameProcessorFactory.toRequiredNonNullKeyParts(
+            JoinConditionAnalysis.forExpression(
+                "x == \"j.y\" && a == \"j.b\"",
+                "j.",
+                ExprMacroTable.nil()
+            )
+        )
+    );
+
+    Assert.assertArrayEquals(
+        new int[]{0},
+        SortMergeJoinFrameProcessorFactory.toRequiredNonNullKeyParts(
+            JoinConditionAnalysis.forExpression(
+                "x == \"j.y\" && notdistinctfrom(a, \"j.b\")",
+                "j.",
+                ExprMacroTable.nil()
+            )
+        )
+    );
+
+    Assert.assertArrayEquals(
+        new int[]{1},
+        SortMergeJoinFrameProcessorFactory.toRequiredNonNullKeyParts(
+            JoinConditionAnalysis.forExpression(
+                "notdistinctfrom(x, \"j.y\") && a == \"j.b\"",
+                "j.",
+                ExprMacroTable.nil()
+            )
+        )
+    );
+
+    Assert.assertArrayEquals(
+        new int[0],
+        SortMergeJoinFrameProcessorFactory.toRequiredNonNullKeyParts(
+            JoinConditionAnalysis.forExpression(
+                "notdistinctfrom(x, \"j.y\") && notdistinctfrom(a, \"j.b\")",
+                "j.",
+                ExprMacroTable.nil()
+            )
+        )
+    );
+  }
+}
diff --git 
a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/querykit/common/SortMergeJoinFrameProcessorTest.java
 
b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/querykit/common/SortMergeJoinFrameProcessorTest.java
index 4b750f167d8..20e4d487107 100644
--- 
a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/querykit/common/SortMergeJoinFrameProcessorTest.java
+++ 
b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/querykit/common/SortMergeJoinFrameProcessorTest.java
@@ -164,6 +164,7 @@ public class SortMergeJoinFrameProcessorTest extends 
InitializedNullHandlingTest
             ImmutableList.of(new KeyColumn("countryIsoCode", 
KeyOrder.ASCENDING)),
             ImmutableList.of(new KeyColumn("countryIsoCode", 
KeyOrder.ASCENDING))
         ),
+        new int[]{0},
         JoinType.LEFT,
         MAX_BUFFERED_BYTES
     );
@@ -209,6 +210,7 @@ public class SortMergeJoinFrameProcessorTest extends 
InitializedNullHandlingTest
             ImmutableList.of(new KeyColumn("countryIsoCode", 
KeyOrder.ASCENDING)),
             ImmutableList.of(new KeyColumn("countryIsoCode", 
KeyOrder.ASCENDING))
         ),
+        new int[]{0},
         JoinType.LEFT,
         MAX_BUFFERED_BYTES
     );
@@ -285,6 +287,7 @@ public class SortMergeJoinFrameProcessorTest extends 
InitializedNullHandlingTest
             ImmutableList.of(new KeyColumn("countryIsoCode", 
KeyOrder.ASCENDING)),
             ImmutableList.of(new KeyColumn("countryIsoCode", 
KeyOrder.ASCENDING))
         ),
+        new int[]{0},
         JoinType.INNER,
         MAX_BUFFERED_BYTES
     );
@@ -326,6 +329,7 @@ public class SortMergeJoinFrameProcessorTest extends 
InitializedNullHandlingTest
             ImmutableList.of(new KeyColumn("countryIsoCode", 
KeyOrder.ASCENDING)),
             ImmutableList.of(new KeyColumn("countryIsoCode", 
KeyOrder.ASCENDING))
         ),
+        new int[]{0},
         JoinType.LEFT,
         MAX_BUFFERED_BYTES
     );
@@ -397,6 +401,7 @@ public class SortMergeJoinFrameProcessorTest extends 
InitializedNullHandlingTest
         makeFrameWriterFactory(joinSignature),
         "j0.",
         ImmutableList.of(Collections.emptyList(), Collections.emptyList()),
+        new int[0],
         JoinType.INNER,
         MAX_BUFFERED_BYTES
     );
@@ -510,6 +515,7 @@ public class SortMergeJoinFrameProcessorTest extends 
InitializedNullHandlingTest
                 new KeyColumn("regionIsoCode", KeyOrder.ASCENDING)
             )
         ),
+        new int[]{0, 1},
         JoinType.LEFT,
         MAX_BUFFERED_BYTES
     );
@@ -589,6 +595,7 @@ public class SortMergeJoinFrameProcessorTest extends 
InitializedNullHandlingTest
             ImmutableList.of(new KeyColumn("regionIsoCode", 
KeyOrder.ASCENDING)),
             ImmutableList.of(new KeyColumn("regionIsoCode", 
KeyOrder.ASCENDING))
         ),
+        new int[]{0},
         JoinType.RIGHT,
         MAX_BUFFERED_BYTES
     );
@@ -671,6 +678,7 @@ public class SortMergeJoinFrameProcessorTest extends 
InitializedNullHandlingTest
             ImmutableList.of(new KeyColumn("regionIsoCode", 
KeyOrder.ASCENDING)),
             ImmutableList.of(new KeyColumn("regionIsoCode", 
KeyOrder.ASCENDING))
         ),
+        new int[]{0},
         JoinType.FULL,
         MAX_BUFFERED_BYTES
     );
@@ -716,6 +724,165 @@ public class SortMergeJoinFrameProcessorTest extends 
InitializedNullHandlingTest
     assertResult(processor, outputChannel.readable(), joinSignature, 
expectedRows);
   }
 
+  @Test
+  public void testInnerJoinRegionCodeOnly() throws Exception
+  {
+    // This join generates duplicates.
+
+    final ReadableInput factChannel =
+        buildFactInput(
+            ImmutableList.of(
+                new KeyColumn("regionIsoCode", KeyOrder.ASCENDING),
+                new KeyColumn("page", KeyOrder.ASCENDING)
+            )
+        );
+
+    final ReadableInput regionsChannel =
+        buildRegionsInput(
+            ImmutableList.of(
+                new KeyColumn("regionIsoCode", KeyOrder.ASCENDING),
+                new KeyColumn("countryIsoCode", KeyOrder.ASCENDING)
+            )
+        );
+
+    final BlockingQueueFrameChannel outputChannel = 
BlockingQueueFrameChannel.minimal();
+
+    final RowSignature joinSignature =
+        RowSignature.builder()
+                    .add("j0.page", ColumnType.STRING)
+                    .add("regionName", ColumnType.STRING)
+                    .add("j0.countryIsoCode", ColumnType.STRING)
+                    .build();
+
+    final SortMergeJoinFrameProcessor processor = new 
SortMergeJoinFrameProcessor(
+        regionsChannel,
+        factChannel,
+        outputChannel.writable(),
+        makeFrameWriterFactory(joinSignature),
+        "j0.",
+        ImmutableList.of(
+            ImmutableList.of(new KeyColumn("regionIsoCode", 
KeyOrder.ASCENDING)),
+            ImmutableList.of(new KeyColumn("regionIsoCode", 
KeyOrder.ASCENDING))
+        ),
+        new int[]{0},
+        JoinType.INNER,
+        MAX_BUFFERED_BYTES
+    );
+
+    final List<List<Object>> expectedRows = Arrays.asList(
+        Arrays.asList("유희왕 GX", "Seoul", "KR"),
+        Arrays.asList("青野武", "Tōkyō", "JP"),
+        Arrays.asList("Алиса в Зазеркалье", "Finnmark Fylke", "NO"),
+        Arrays.asList("Saison 9 de Secret Story", "Val d'Oise", "FR"),
+        Arrays.asList("Cream Soda", "Ainigriv", "SU"),
+        Arrays.asList("Carlo Curti", "California", "US"),
+        Arrays.asList("Otjiwarongo Airport", "California", "US"),
+        Arrays.asList("President of India", "California", "US"),
+        Arrays.asList("Mathis Bolly", "Mexico City", "MX"),
+        Arrays.asList("Gabinete Ministerial de Rafael Correa", "Provincia del 
Guayas", "EC"),
+        Arrays.asList("Diskussion:Sebastian Schulz", "Hesse", "DE"),
+        Arrays.asList("Glasgow", "Kingston upon Hull", "GB"),
+        Arrays.asList("History of Fourems", "Fourems Province", "MMMM"),
+        Arrays.asList("DirecTV", "North Carolina", "US"),
+        Arrays.asList("Peremptory norm", "New South Wales", "AU"),
+        Arrays.asList("Didier Leclair", "Ontario", "CA"),
+        Arrays.asList("Sarah Michelle Gellar", "Ontario", "CA"),
+        Arrays.asList("Les Argonautes", "Quebec", "CA"),
+        Arrays.asList("Golpe de Estado en Chile de 1973", "Santiago 
Metropolitan", "CL"),
+        Arrays.asList("Wendigo", "Departamento de San Salvador", "SV"),
+        Arrays.asList("Giusy Ferreri discography", "Provincia di Varese", 
"IT"),
+        Arrays.asList("Giusy Ferreri discography", "Virginia", "IT"),
+        Arrays.asList("Old Anatolian Turkish", "Provincia di Varese", "US"),
+        Arrays.asList("Old Anatolian Turkish", "Virginia", "US"),
+        Arrays.asList("Roma-Bangkok", "Provincia di Varese", "IT"),
+        Arrays.asList("Roma-Bangkok", "Virginia", "IT")
+    );
+
+    assertResult(processor, outputChannel.readable(), joinSignature, 
expectedRows);
+  }
+
+  @Test
+  public void testInnerJoinRegionCodeOnlyIsNotDistinctFrom() throws Exception
+  {
+    // This join generates duplicates.
+
+    final ReadableInput factChannel =
+        buildFactInput(
+            ImmutableList.of(
+                new KeyColumn("regionIsoCode", KeyOrder.ASCENDING),
+                new KeyColumn("page", KeyOrder.ASCENDING)
+            )
+        );
+
+    final ReadableInput regionsChannel =
+        buildRegionsInput(
+            ImmutableList.of(
+                new KeyColumn("regionIsoCode", KeyOrder.ASCENDING),
+                new KeyColumn("countryIsoCode", KeyOrder.ASCENDING)
+            )
+        );
+
+    final BlockingQueueFrameChannel outputChannel = 
BlockingQueueFrameChannel.minimal();
+
+    final RowSignature joinSignature =
+        RowSignature.builder()
+                    .add("j0.page", ColumnType.STRING)
+                    .add("regionName", ColumnType.STRING)
+                    .add("j0.countryIsoCode", ColumnType.STRING)
+                    .build();
+
+    final SortMergeJoinFrameProcessor processor = new 
SortMergeJoinFrameProcessor(
+        regionsChannel,
+        factChannel,
+        outputChannel.writable(),
+        makeFrameWriterFactory(joinSignature),
+        "j0.",
+        ImmutableList.of(
+            ImmutableList.of(new KeyColumn("regionIsoCode", 
KeyOrder.ASCENDING)),
+            ImmutableList.of(new KeyColumn("regionIsoCode", 
KeyOrder.ASCENDING))
+        ),
+        new int[0], // empty array: act as if IS NOT DISTINCT FROM
+        JoinType.INNER,
+        MAX_BUFFERED_BYTES
+    );
+
+    final List<List<Object>> expectedRows = Arrays.asList(
+        Arrays.asList("Agama mossambica", "Nulland", null),
+        Arrays.asList("Apamea abruzzorum", "Nulland", null),
+        Arrays.asList("Atractus flammigerus", "Nulland", null),
+        Arrays.asList("Rallicula", "Nulland", null),
+        Arrays.asList("Talk:Oswald Tilghman", "Nulland", null),
+        Arrays.asList("유희왕 GX", "Seoul", "KR"),
+        Arrays.asList("青野武", "Tōkyō", "JP"),
+        Arrays.asList("Алиса в Зазеркалье", "Finnmark Fylke", "NO"),
+        Arrays.asList("Saison 9 de Secret Story", "Val d'Oise", "FR"),
+        Arrays.asList("Cream Soda", "Ainigriv", "SU"),
+        Arrays.asList("Carlo Curti", "California", "US"),
+        Arrays.asList("Otjiwarongo Airport", "California", "US"),
+        Arrays.asList("President of India", "California", "US"),
+        Arrays.asList("Mathis Bolly", "Mexico City", "MX"),
+        Arrays.asList("Gabinete Ministerial de Rafael Correa", "Provincia del 
Guayas", "EC"),
+        Arrays.asList("Diskussion:Sebastian Schulz", "Hesse", "DE"),
+        Arrays.asList("Glasgow", "Kingston upon Hull", "GB"),
+        Arrays.asList("History of Fourems", "Fourems Province", "MMMM"),
+        Arrays.asList("DirecTV", "North Carolina", "US"),
+        Arrays.asList("Peremptory norm", "New South Wales", "AU"),
+        Arrays.asList("Didier Leclair", "Ontario", "CA"),
+        Arrays.asList("Sarah Michelle Gellar", "Ontario", "CA"),
+        Arrays.asList("Les Argonautes", "Quebec", "CA"),
+        Arrays.asList("Golpe de Estado en Chile de 1973", "Santiago 
Metropolitan", "CL"),
+        Arrays.asList("Wendigo", "Departamento de San Salvador", "SV"),
+        Arrays.asList("Giusy Ferreri discography", "Provincia di Varese", 
"IT"),
+        Arrays.asList("Giusy Ferreri discography", "Virginia", "IT"),
+        Arrays.asList("Old Anatolian Turkish", "Provincia di Varese", "US"),
+        Arrays.asList("Old Anatolian Turkish", "Virginia", "US"),
+        Arrays.asList("Roma-Bangkok", "Provincia di Varese", "IT"),
+        Arrays.asList("Roma-Bangkok", "Virginia", "IT")
+    );
+
+    assertResult(processor, outputChannel.readable(), joinSignature, 
expectedRows);
+  }
+
   @Test
   public void testLeftJoinCountryNumber() throws Exception
   {
@@ -750,6 +917,7 @@ public class SortMergeJoinFrameProcessorTest extends 
InitializedNullHandlingTest
             ImmutableList.of(new KeyColumn("countryNumber", 
KeyOrder.ASCENDING)),
             ImmutableList.of(new KeyColumn("countryNumber", 
KeyOrder.ASCENDING))
         ),
+        new int[]{0},
         JoinType.LEFT,
         MAX_BUFFERED_BYTES
     );
@@ -844,6 +1012,7 @@ public class SortMergeJoinFrameProcessorTest extends 
InitializedNullHandlingTest
             ImmutableList.of(new KeyColumn("countryNumber", 
KeyOrder.ASCENDING)),
             ImmutableList.of(new KeyColumn("countryNumber", 
KeyOrder.ASCENDING))
         ),
+        new int[]{0},
         JoinType.RIGHT,
         MAX_BUFFERED_BYTES
     );
@@ -938,6 +1107,75 @@ public class SortMergeJoinFrameProcessorTest extends 
InitializedNullHandlingTest
             ImmutableList.of(new KeyColumn("countryIsoCode", 
KeyOrder.ASCENDING)),
             ImmutableList.of(new KeyColumn("countryIsoCode", 
KeyOrder.ASCENDING))
         ),
+        new int[]{0},
+        JoinType.INNER,
+        MAX_BUFFERED_BYTES
+    );
+
+    final List<List<Object>> expectedRows = Arrays.asList(
+        Arrays.asList("Peremptory norm", "AU", "AU", "Australia", 0L),
+        Arrays.asList("Didier Leclair", "CA", "CA", "Canada", 1L),
+        Arrays.asList("Les Argonautes", "CA", "CA", "Canada", 1L),
+        Arrays.asList("Sarah Michelle Gellar", "CA", "CA", "Canada", 1L),
+        Arrays.asList("Golpe de Estado en Chile de 1973", "CL", "CL", "Chile", 
2L),
+        Arrays.asList("Diskussion:Sebastian Schulz", "DE", "DE", "Germany", 
3L),
+        Arrays.asList("Gabinete Ministerial de Rafael Correa", "EC", "EC", 
"Ecuador", 4L),
+        Arrays.asList("Saison 9 de Secret Story", "FR", "FR", "France", 5L),
+        Arrays.asList("Glasgow", "GB", "GB", "United Kingdom", 6L),
+        Arrays.asList("Giusy Ferreri discography", "IT", "IT", "Italy", 7L),
+        Arrays.asList("Roma-Bangkok", "IT", "IT", "Italy", 7L),
+        Arrays.asList("青野武", "JP", "JP", "Japan", 8L),
+        Arrays.asList("유희왕 GX", "KR", "KR", "Republic of Korea", 9L),
+        Arrays.asList("History of Fourems", "MMMM", "MMMM", "Fourems", 205L),
+        Arrays.asList("Mathis Bolly", "MX", "MX", "Mexico", 10L),
+        Arrays.asList("Алиса в Зазеркалье", "NO", "NO", "Norway", 11L),
+        Arrays.asList("Cream Soda", "SU", "SU", "States United", 15L),
+        Arrays.asList("Wendigo", "SV", "SV", "El Salvador", 12L),
+        Arrays.asList("Carlo Curti", "US", "US", "United States", 13L),
+        Arrays.asList("DirecTV", "US", "US", "United States", 13L),
+        Arrays.asList("Old Anatolian Turkish", "US", "US", "United States", 
13L),
+        Arrays.asList("Otjiwarongo Airport", "US", "US", "United States", 13L),
+        Arrays.asList("President of India", "US", "US", "United States", 13L)
+    );
+
+    assertResult(processor, outputChannel.readable(), joinSignature, 
expectedRows);
+  }
+
+  @Test
+  public void testInnerJoinCountryIsoCodeNotDistinctFrom() throws Exception
+  {
+    final ReadableInput factChannel = buildFactInput(
+        ImmutableList.of(
+            new KeyColumn("countryIsoCode", KeyOrder.ASCENDING),
+            new KeyColumn("page", KeyOrder.ASCENDING)
+        )
+    );
+
+    final ReadableInput countriesChannel =
+        buildCountriesInput(ImmutableList.of(new KeyColumn("countryIsoCode", 
KeyOrder.ASCENDING)));
+
+    final BlockingQueueFrameChannel outputChannel = 
BlockingQueueFrameChannel.minimal();
+
+    final RowSignature joinSignature =
+        RowSignature.builder()
+                    .add("page", ColumnType.STRING)
+                    .add("countryIsoCode", ColumnType.STRING)
+                    .add("j0.countryIsoCode", ColumnType.STRING)
+                    .add("j0.countryName", ColumnType.STRING)
+                    .add("j0.countryNumber", ColumnType.LONG)
+                    .build();
+
+    final SortMergeJoinFrameProcessor processor = new 
SortMergeJoinFrameProcessor(
+        factChannel,
+        countriesChannel,
+        outputChannel.writable(),
+        makeFrameWriterFactory(joinSignature),
+        "j0.",
+        ImmutableList.of(
+            ImmutableList.of(new KeyColumn("countryIsoCode", 
KeyOrder.ASCENDING)),
+            ImmutableList.of(new KeyColumn("countryIsoCode", 
KeyOrder.ASCENDING))
+        ),
+        new int[0], // empty array: act as if IS NOT DISTINCT FROM
         JoinType.INNER,
         MAX_BUFFERED_BYTES
     );
@@ -1005,6 +1243,7 @@ public class SortMergeJoinFrameProcessorTest extends 
InitializedNullHandlingTest
             ImmutableList.of(new KeyColumn("countryIsoCode", 
KeyOrder.ASCENDING)),
             ImmutableList.of(new KeyColumn("countryIsoCode", 
KeyOrder.ASCENDING))
         ),
+        new int[]{0},
         JoinType.INNER,
         1
     );
@@ -1072,6 +1311,7 @@ public class SortMergeJoinFrameProcessorTest extends 
InitializedNullHandlingTest
             ImmutableList.of(new KeyColumn("countryIsoCode", 
KeyOrder.ASCENDING)),
             ImmutableList.of(new KeyColumn("countryIsoCode", 
KeyOrder.ASCENDING))
         ),
+        new int[]{0},
         JoinType.INNER,
         1
     );
@@ -1128,6 +1368,7 @@ public class SortMergeJoinFrameProcessorTest extends 
InitializedNullHandlingTest
             ImmutableList.of(new KeyColumn("channel", KeyOrder.ASCENDING)),
             ImmutableList.of(new KeyColumn("channel", KeyOrder.ASCENDING))
         ),
+        new int[]{0},
         JoinType.INNER,
         MAX_BUFFERED_BYTES
     );
@@ -1182,6 +1423,7 @@ public class SortMergeJoinFrameProcessorTest extends 
InitializedNullHandlingTest
             ImmutableList.of(new KeyColumn("channel", KeyOrder.ASCENDING)),
             ImmutableList.of(new KeyColumn("channel", KeyOrder.ASCENDING))
         ),
+        new int[]{0},
         JoinType.INNER,
         1
     );
diff --git 
a/processing/src/main/java/org/apache/druid/frame/key/FrameComparisonWidget.java
 
b/processing/src/main/java/org/apache/druid/frame/key/FrameComparisonWidget.java
index 710bfc3b5c9..913d775b80b 100644
--- 
a/processing/src/main/java/org/apache/druid/frame/key/FrameComparisonWidget.java
+++ 
b/processing/src/main/java/org/apache/druid/frame/key/FrameComparisonWidget.java
@@ -36,18 +36,25 @@ public interface FrameComparisonWidget
   RowKey readKey(int row);
 
   /**
-   * Whether a particular row has no null fields in its comparison key.
+   * Whether particular key parts in a particular row are non-null.
    *
    * When {@link 
org.apache.druid.common.config.NullHandling#replaceWithDefault()}, default 
values (like empty strings
    * and numeric zeroes) are considered null for purposes of this method. This 
behavior is inherited from
    * {@link org.apache.druid.frame.field.FieldReader#isNull(Memory, long)} and 
enables join code to behave
    * similarly in MSQ and native queries.
+   *
+   * @param row      row number
+   * @param keyParts parts to check
    */
-  boolean isCompletelyNonNullKey(int row);
+  boolean hasNonNullKeyParts(int row, int[] keyParts);
 
   /**
    * Compare a specific row of this frame to the provided key. The key must 
have been created with sortColumns
    * that match the ones used to create this widget, or else results are 
undefined.
+   *
+   * Comparison considers null to be equal to null. Callers that need to 
determine if key parts are null
+   * (perhaps because they *don't* want to consider null to be equal to null) 
should use
+   * {@link #hasNonNullKeyParts(int, int[])} to check the relevant parts.
    */
   int compare(int row, RowKey key);
 
@@ -55,6 +62,10 @@ public interface FrameComparisonWidget
    * Compare a specific row of this frame to a specific row of another frame. 
The other frame must have the same
    * sort key, or else results are undefined. The other frame may be the same 
object as this frame; for example,
    * this is used by {@link org.apache.druid.frame.write.FrameSort} to sort 
frames in-place.
+   *
+   * Comparison considers null to be equal to null. Callers that need to 
determine if key parts are null
+   * (perhaps because they *don't* want to consider null to be equal to null) 
should use
+   * {@link #hasNonNullKeyParts(int, int[])} to check the relevant parts.
    */
   int compare(int row, FrameComparisonWidget otherWidget, int otherRow);
 }
diff --git 
a/processing/src/main/java/org/apache/druid/frame/key/FrameComparisonWidgetImpl.java
 
b/processing/src/main/java/org/apache/druid/frame/key/FrameComparisonWidgetImpl.java
index 873c9ef9243..d7008fcab45 100644
--- 
a/processing/src/main/java/org/apache/druid/frame/key/FrameComparisonWidgetImpl.java
+++ 
b/processing/src/main/java/org/apache/druid/frame/key/FrameComparisonWidgetImpl.java
@@ -28,6 +28,7 @@ import org.apache.druid.frame.read.FrameReader;
 import org.apache.druid.frame.read.FrameReaderUtils;
 import org.apache.druid.frame.write.FrameWriterUtils;
 import org.apache.druid.frame.write.RowBasedFrameWriter;
+import org.apache.druid.java.util.common.IAE;
 import org.apache.druid.java.util.common.ISE;
 import org.apache.druid.segment.column.RowSignature;
 
@@ -138,21 +139,29 @@ public class FrameComparisonWidgetImpl implements 
FrameComparisonWidget
   }
 
   @Override
-  public boolean isCompletelyNonNullKey(int row)
+  public boolean hasNonNullKeyParts(int row, int[] keyParts)
   {
-    if (keyFieldCount == 0) {
+    if (keyParts.length == 0) {
       return true;
     }
 
     final long rowPosition = getRowPositionInDataRegion(row);
-    long keyFieldPosition = rowPosition + (long) signature.size() * 
Integer.BYTES;
 
-    for (int i = 0; i < keyFieldCount; i++) {
-      final boolean isNull = keyFieldReaders.get(i).isNull(dataRegion, 
keyFieldPosition);
-      if (isNull) {
-        return false;
+    for (int i : keyParts) {
+      if (i < 0 || i >= keyFieldCount) {
+        throw new IAE("Invalid key part[%d]", i);
+      }
+
+      final long keyFieldPosition;
+
+      if (i == 0) {
+        keyFieldPosition = rowPosition + (long) signature.size() * 
Integer.BYTES;
       } else {
-        keyFieldPosition = rowPosition + dataRegion.getInt(rowPosition + 
(long) i * Integer.BYTES);
+        keyFieldPosition = rowPosition + dataRegion.getInt(rowPosition + 
(long) (i - 1) * Integer.BYTES);
+      }
+
+      if (keyFieldReaders.get(i).isNull(dataRegion, keyFieldPosition)) {
+        return false;
       }
     }
 
diff --git 
a/processing/src/test/java/org/apache/druid/frame/key/FrameComparisonWidgetImplTest.java
 
b/processing/src/test/java/org/apache/druid/frame/key/FrameComparisonWidgetImplTest.java
index 9dbb718fea2..64556c6775b 100644
--- 
a/processing/src/test/java/org/apache/druid/frame/key/FrameComparisonWidgetImplTest.java
+++ 
b/processing/src/test/java/org/apache/druid/frame/key/FrameComparisonWidgetImplTest.java
@@ -83,9 +83,14 @@ public class FrameComparisonWidgetImplTest extends 
InitializedNullHandlingTest
     final FrameComparisonWidget widget = createComparisonWidget(keyColumns);
 
     for (int i = 0; i < frame.numRows(); i++) {
-      final boolean isPartiallyNull =
-          
Arrays.stream(RowKeyComparatorTest.ALL_KEY_OBJECTS.get(i)).limit(3).anyMatch(Objects::isNull);
-      Assert.assertEquals(isPartiallyNull, !widget.isCompletelyNonNullKey(i));
+      final boolean isAllNonNull =
+          
Arrays.stream(RowKeyComparatorTest.ALL_KEY_OBJECTS.get(i)).limit(3).allMatch(Objects::nonNull);
+
+      // null key part, if any, is always the second one (1)
+      Assert.assertTrue(widget.hasNonNullKeyParts(i, new int[0]));
+      Assert.assertTrue(widget.hasNonNullKeyParts(i, new int[]{0, 2}));
+      Assert.assertEquals(isAllNonNull, widget.hasNonNullKeyParts(i, new 
int[]{0, 1, 2}));
+      Assert.assertEquals(isAllNonNull, widget.hasNonNullKeyParts(i, new 
int[]{1}));
     }
   }
 
@@ -102,9 +107,9 @@ public class FrameComparisonWidgetImplTest extends 
InitializedNullHandlingTest
     final FrameComparisonWidget widget = createComparisonWidget(keyColumns);
 
     for (int i = 0; i < frame.numRows(); i++) {
-      final boolean isPartiallyNull =
-          
Arrays.stream(RowKeyComparatorTest.ALL_KEY_OBJECTS.get(i)).anyMatch(Objects::isNull);
-      Assert.assertEquals(isPartiallyNull, !widget.isCompletelyNonNullKey(i));
+      final boolean isAllNonNull =
+          
Arrays.stream(RowKeyComparatorTest.ALL_KEY_OBJECTS.get(i)).allMatch(Objects::nonNull);
+      Assert.assertEquals(isAllNonNull, widget.hasNonNullKeyParts(i, new 
int[]{0, 1, 2, 3}));
     }
   }
 
diff --git 
a/sql/src/main/java/org/apache/druid/sql/calcite/planner/JoinAlgorithm.java 
b/sql/src/main/java/org/apache/druid/sql/calcite/planner/JoinAlgorithm.java
index 6e6c872594f..2e53795c4ff 100644
--- a/sql/src/main/java/org/apache/druid/sql/calcite/planner/JoinAlgorithm.java
+++ b/sql/src/main/java/org/apache/druid/sql/calcite/planner/JoinAlgorithm.java
@@ -33,12 +33,6 @@ public enum JoinAlgorithm
     {
       return false;
     }
-
-    @Override
-    public boolean canHandleLeftExpressions()
-    {
-      return true;
-    }
   },
   SORT_MERGE("sortMerge") {
     @Override
@@ -46,12 +40,6 @@ public enum JoinAlgorithm
     {
       return true;
     }
-
-    @Override
-    public boolean canHandleLeftExpressions()
-    {
-      return false;
-    }
   };
 
   private final String id;
@@ -84,11 +72,6 @@ public enum JoinAlgorithm
    */
   public abstract boolean requiresSubquery();
 
-  /**
-   * Whether this join algorithm is able to handle left-hand side expressions.
-   */
-  public abstract boolean canHandleLeftExpressions();
-
   @Override
   public String toString()
   {
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteJoinQueryTest.java 
b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteJoinQueryTest.java
index 04727d7f86a..93ce1a7d102 100644
--- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteJoinQueryTest.java
+++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteJoinQueryTest.java
@@ -3863,13 +3863,16 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
                 .context(queryContext)
                 .build()
         ),
-        ImmutableList.of(
-            new Object[]{"", ""},
-            new Object[]{"10.1", "10.1"},
-            new Object[]{"2", "2"},
-            new Object[]{"1", "1"},
-            new Object[]{"def", "def"},
-            new Object[]{"abc", "abc"}
+        sortIfSortBased(
+            ImmutableList.of(
+                new Object[]{"", ""},
+                new Object[]{"10.1", "10.1"},
+                new Object[]{"2", "2"},
+                new Object[]{"1", "1"},
+                new Object[]{"def", "def"},
+                new Object[]{"abc", "abc"}
+            ),
+            0
         )
     );
   }
@@ -3879,11 +3882,6 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
   @ParameterizedTest(name = "{0}")
   public void testInnerJoinSubqueryWithSelectorFilter(Map<String, Object> 
queryContext)
   {
-    if (isSortBasedJoin()) {
-      // Cannot handle the [l1.k = 'abc'] condition.
-      msqIncompatible();
-    }
-
     // Cannot vectorize due to 'concat' expression.
     cannotVectorize();
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to