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

cheddar 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 65c3b4d31aa Support join in decoupled mode (#15957)
65c3b4d31aa is described below

commit 65c3b4d31aa4aba775fc78d5dbd3facbbc7c8776
Author: Zoltan Haindrich <k...@rxd.hu>
AuthorDate: Wed Mar 6 02:10:13 2024 +0100

    Support join in decoupled mode (#15957)
    
    * plan join(s) in decoupled mode
    * configure DecoupledPlanningCalciteJoinQueryTest
            the test has 593 cases; however there are quite a few parameterized
            from the 107 methods annotated with @Test - 42 is not yet working
     * replace the isRoot hack in DruidQueryGenerator with a logic that instead 
looks ahead for the next node; and doesn't let the previous node do the Project 
- this makes it plan more likely than the existing planner
---
 sql/pom.xml                                        |  10 ++
 .../druid/sql/calcite/planner/QueryHandler.java    |   3 +-
 .../planner/querygen/DruidQueryGenerator.java      | 137 ++++++++++++---------
 ...utDescProducer.java => SourceDescProducer.java} |  20 ++-
 .../druid/sql/calcite/rel/DruidJoinQueryRel.java   |  96 ++++++++++-----
 .../druid/sql/calcite/rel/logical/DruidJoin.java   |  79 ++++++++++++
 .../sql/calcite/rel/logical/DruidTableScan.java    |   8 +-
 .../druid/sql/calcite/rel/logical/DruidUnion.java  |  20 +--
 .../druid/sql/calcite/rel/logical/DruidValues.java |   8 +-
 .../druid/sql/calcite/rule/DruidJoinRule.java      |  57 ++++++---
 .../sql/calcite/rule/logical/DruidJoinRule.java    |  76 ++++++++++++
 .../calcite/rule/logical/DruidLogicalRules.java    |   7 ++
 .../druid/sql/calcite/BaseCalciteQueryTest.java    |  22 ++--
 .../druid/sql/calcite/CalciteJoinQueryTest.java    |  71 ++++++++++-
 .../apache/druid/sql/calcite/CalciteQueryTest.java |  26 ++--
 ... => DecoupledPlanningCalciteJoinQueryTest.java} |  19 ++-
 .../calcite/DecoupledPlanningCalciteQueryTest.java |   2 +-
 .../DecoupledPlanningCalciteUnionQueryTest.java    |   2 +-
 .../druid/sql/calcite/DecoupledTestConfig.java     |  40 +++++-
 .../apache/druid/sql/calcite/NotYetSupported.java  |  58 ++++++++-
 .../druid/sql/calcite/SqlTestFrameworkConfig.java  |   9 +-
 .../apache/druid/sql/http/ResultFormatTest.java    |  28 ++---
 22 files changed, 610 insertions(+), 188 deletions(-)

diff --git a/sql/pom.xml b/sql/pom.xml
index 7b062b11834..1941df02be5 100644
--- a/sql/pom.xml
+++ b/sql/pom.xml
@@ -207,6 +207,16 @@
       <artifactId>junit-vintage-engine</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter-params</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-lang3</artifactId>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>pl.pragmatists</groupId>
       <artifactId>JUnitParams</artifactId>
diff --git 
a/sql/src/main/java/org/apache/druid/sql/calcite/planner/QueryHandler.java 
b/sql/src/main/java/org/apache/druid/sql/calcite/planner/QueryHandler.java
index 3e7f58ec067..dfbded5281b 100644
--- a/sql/src/main/java/org/apache/druid/sql/calcite/planner/QueryHandler.java
+++ b/sql/src/main/java/org/apache/druid/sql/calcite/planner/QueryHandler.java
@@ -71,6 +71,7 @@ import org.apache.druid.sql.calcite.rel.DruidQuery;
 import org.apache.druid.sql.calcite.rel.DruidRel;
 import org.apache.druid.sql.calcite.rel.DruidUnionRel;
 import org.apache.druid.sql.calcite.rel.logical.DruidLogicalConvention;
+import org.apache.druid.sql.calcite.rel.logical.DruidLogicalNode;
 import org.apache.druid.sql.calcite.run.EngineFeature;
 import org.apache.druid.sql.calcite.run.QueryMaker;
 import org.apache.druid.sql.calcite.table.DruidTable;
@@ -561,7 +562,7 @@ public abstract class QueryHandler extends 
SqlStatementHandler.BaseStatementHand
           newRoot
       );
 
-      DruidQueryGenerator generator = new DruidQueryGenerator(plannerContext, 
newRoot, rexBuilder);
+      DruidQueryGenerator generator = new DruidQueryGenerator(plannerContext, 
(DruidLogicalNode) newRoot, rexBuilder);
       DruidQuery baseQuery = generator.buildQuery();
       try {
         log.info(
diff --git 
a/sql/src/main/java/org/apache/druid/sql/calcite/planner/querygen/DruidQueryGenerator.java
 
b/sql/src/main/java/org/apache/druid/sql/calcite/planner/querygen/DruidQueryGenerator.java
index d10c9d3a65b..0047cc0ad4d 100644
--- 
a/sql/src/main/java/org/apache/druid/sql/calcite/planner/querygen/DruidQueryGenerator.java
+++ 
b/sql/src/main/java/org/apache/druid/sql/calcite/planner/querygen/DruidQueryGenerator.java
@@ -31,25 +31,28 @@ import org.apache.druid.error.DruidException;
 import org.apache.druid.query.QueryDataSource;
 import org.apache.druid.sql.calcite.planner.PlannerContext;
 import 
org.apache.druid.sql.calcite.planner.querygen.DruidQueryGenerator.PDQVertexFactory.PDQVertex;
-import 
org.apache.druid.sql.calcite.planner.querygen.InputDescProducer.InputDesc;
+import 
org.apache.druid.sql.calcite.planner.querygen.SourceDescProducer.SourceDesc;
 import org.apache.druid.sql.calcite.rel.DruidQuery;
 import org.apache.druid.sql.calcite.rel.PartialDruidQuery;
 import org.apache.druid.sql.calcite.rel.PartialDruidQuery.Stage;
+import org.apache.druid.sql.calcite.rel.logical.DruidAggregate;
+import org.apache.druid.sql.calcite.rel.logical.DruidLogicalNode;
+import org.apache.druid.sql.calcite.rel.logical.DruidSort;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
+import java.util.Stack;
 
 /**
- * Converts a DAG of {@link 
org.apache.druid.sql.calcite.rel.logical.DruidLogicalNode} convention to a 
native
- * {@link DruidQuery} for execution.
+ * Converts a DAG of {@link DruidLogicalNode} convention to a native {@link 
DruidQuery} for execution.
  */
 public class DruidQueryGenerator
 {
-  private final RelNode relRoot;
+  private final DruidLogicalNode relRoot;
   private final PDQVertexFactory vertexFactory;
 
-  public DruidQueryGenerator(PlannerContext plannerContext, RelNode relRoot, 
RexBuilder rexBuilder)
+  public DruidQueryGenerator(PlannerContext plannerContext, DruidLogicalNode 
relRoot, RexBuilder rexBuilder)
   {
     this.relRoot = relRoot;
     this.vertexFactory = new PDQVertexFactory(plannerContext, rexBuilder);
@@ -57,28 +60,34 @@ public class DruidQueryGenerator
 
   public DruidQuery buildQuery()
   {
-    Vertex vertex = buildVertexFor(relRoot, true);
+    Stack<DruidLogicalNode> stack = new Stack<>();
+    stack.push(relRoot);
+    Vertex vertex = buildVertexFor(stack);
     return vertex.buildQuery(true);
   }
 
-  private Vertex buildVertexFor(RelNode node, boolean isRoot)
+  private Vertex buildVertexFor(Stack<DruidLogicalNode> stack)
   {
     List<Vertex> newInputs = new ArrayList<>();
-    for (RelNode input : node.getInputs()) {
-      newInputs.add(buildVertexFor(input, false));
+
+    for (RelNode input : stack.peek().getInputs()) {
+      stack.push((DruidLogicalNode) input);
+      newInputs.add(buildVertexFor(stack));
+      stack.pop();
     }
-    Vertex vertex = processNodeWithInputs(node, newInputs, isRoot);
+    Vertex vertex = processNodeWithInputs(stack, newInputs);
     return vertex;
   }
 
-  private Vertex processNodeWithInputs(RelNode node, List<Vertex> newInputs, 
boolean isRoot)
+  private Vertex processNodeWithInputs(Stack<DruidLogicalNode> stack, 
List<Vertex> newInputs)
   {
-    if (node instanceof InputDescProducer) {
+    DruidLogicalNode node = stack.peek();
+    if (node instanceof SourceDescProducer) {
       return vertexFactory.createVertex(PartialDruidQuery.create(node), 
newInputs);
     }
     if (newInputs.size() == 1) {
       Vertex inputVertex = newInputs.get(0);
-      Optional<Vertex> newVertex = inputVertex.extendWith(node, isRoot);
+      Optional<Vertex> newVertex = inputVertex.extendWith(stack);
       if (newVertex.isPresent()) {
         return newVertex.get();
       }
@@ -86,7 +95,7 @@ public class DruidQueryGenerator
           PartialDruidQuery.createOuterQuery(((PDQVertex) 
inputVertex).partialDruidQuery),
           ImmutableList.of(inputVertex)
       );
-      newVertex = inputVertex.extendWith(node, false);
+      newVertex = inputVertex.extendWith(stack);
       if (newVertex.isPresent()) {
         return newVertex.get();
       }
@@ -107,21 +116,21 @@ public class DruidQueryGenerator
     /**
      * Extends the current vertex to include the specified parent.
      */
-    Optional<Vertex> extendWith(RelNode parentNode, boolean isRoot);
+    Optional<Vertex> extendWith(Stack<DruidLogicalNode> stack);
 
     /**
-     * Decides wether this {@link Vertex} can be unwrapped into an {@link 
InputDesc}.
+     * Decides wether this {@link Vertex} can be unwrapped into an {@link 
SourceDesc}.
      */
-    boolean canUnwrapInput();
+    boolean canUnwrapSourceDesc();
 
     /**
-     * Unwraps this {@link Vertex} into an {@link InputDesc}.
+     * Unwraps this {@link Vertex} into an {@link SourceDesc}.
      *
-     * Unwraps the input of this vertex - if it doesn't do anything beyond 
reading its input.
+     * Unwraps the source of this vertex - if it doesn't do anything beyond 
reading its input.
      *
      * @throws DruidException if unwrap is not possible.
      */
-    InputDesc unwrapInputDesc();
+    SourceDesc unwrapSourceDesc();
   }
 
   /**
@@ -157,10 +166,10 @@ public class DruidQueryGenerator
       @Override
       public DruidQuery buildQuery(boolean topLevel)
       {
-        InputDesc input = getInput();
+        SourceDesc source = getSource();
         return partialDruidQuery.build(
-            input.dataSource,
-            input.rowSignature,
+            source.dataSource,
+            source.rowSignature,
             plannerContext,
             rexBuilder,
             !topLevel
@@ -168,39 +177,39 @@ public class DruidQueryGenerator
       }
 
       /**
-       * Creates the {@link InputDesc} for the current {@link Vertex}.
+       * Creates the {@link SourceDesc} for the current {@link Vertex}.
        */
-      private InputDesc getInput()
+      private SourceDesc getSource()
       {
-        List<InputDesc> inputDescs = new ArrayList<>();
+        List<SourceDesc> sourceDescs = new ArrayList<>();
         for (Vertex inputVertex : inputs) {
-          final InputDesc desc;
-          if (inputVertex.canUnwrapInput()) {
-            desc = inputVertex.unwrapInputDesc();
+          final SourceDesc desc;
+          if (inputVertex.canUnwrapSourceDesc()) {
+            desc = inputVertex.unwrapSourceDesc();
           } else {
             DruidQuery inputQuery = inputVertex.buildQuery(false);
-            desc = new InputDesc(new QueryDataSource(inputQuery.getQuery()), 
inputQuery.getOutputRowSignature());
+            desc = new SourceDesc(new QueryDataSource(inputQuery.getQuery()), 
inputQuery.getOutputRowSignature());
           }
-          inputDescs.add(desc);
+          sourceDescs.add(desc);
         }
         RelNode scan = partialDruidQuery.getScan();
-        if (scan instanceof InputDescProducer) {
-          InputDescProducer inp = (InputDescProducer) scan;
-          return inp.getInputDesc(plannerContext, inputDescs);
+        if (scan instanceof SourceDescProducer) {
+          SourceDescProducer inp = (SourceDescProducer) scan;
+          return inp.getSourceDesc(plannerContext, sourceDescs);
         }
         if (inputs.size() == 1) {
-          return inputDescs.get(0);
+          return sourceDescs.get(0);
         }
-        throw DruidException.defensive("Unable to create InputDesc for 
Operator [%s]", scan);
+        throw DruidException.defensive("Unable to create SourceDesc for 
Operator [%s]", scan);
       }
 
       /**
        * Extends the the current partial query with the new parent if possible.
        */
       @Override
-      public Optional<Vertex> extendWith(RelNode parentNode, boolean isRoot)
+      public Optional<Vertex> extendWith(Stack<DruidLogicalNode> stack)
       {
-        Optional<PartialDruidQuery> newPartialQuery = 
extendPartialDruidQuery(parentNode, isRoot);
+        Optional<PartialDruidQuery> newPartialQuery = 
extendPartialDruidQuery(stack);
         if (!newPartialQuery.isPresent()) {
           return Optional.empty();
         }
@@ -210,65 +219,81 @@ public class DruidQueryGenerator
       /**
        * Merges the given {@link RelNode} into the current {@link 
PartialDruidQuery}.
        */
-      private Optional<PartialDruidQuery> extendPartialDruidQuery(RelNode 
parentNode, boolean isRoot)
+      private Optional<PartialDruidQuery> 
extendPartialDruidQuery(Stack<DruidLogicalNode> stack)
       {
-        if (accepts(parentNode, Stage.WHERE_FILTER, Filter.class)) {
+        DruidLogicalNode parentNode = stack.peek();
+        if (accepts(stack, Stage.WHERE_FILTER, Filter.class)) {
           PartialDruidQuery newPartialQuery = 
partialDruidQuery.withWhereFilter((Filter) parentNode);
           return Optional.of(newPartialQuery);
         }
-        if (accepts(parentNode, Stage.SELECT_PROJECT, Project.class)) {
+        if (accepts(stack, Stage.SELECT_PROJECT, Project.class)) {
           PartialDruidQuery newPartialQuery = 
partialDruidQuery.withSelectProject((Project) parentNode);
           return Optional.of(newPartialQuery);
         }
-        if (accepts(parentNode, Stage.AGGREGATE, Aggregate.class)) {
+        if (accepts(stack, Stage.AGGREGATE, Aggregate.class)) {
           PartialDruidQuery newPartialQuery = 
partialDruidQuery.withAggregate((Aggregate) parentNode);
           return Optional.of(newPartialQuery);
         }
-        if (accepts(parentNode, Stage.AGGREGATE_PROJECT, Project.class) && 
isRoot) {
+        if (accepts(stack, Stage.AGGREGATE_PROJECT, Project.class)) {
           PartialDruidQuery newPartialQuery = 
partialDruidQuery.withAggregateProject((Project) parentNode);
           return Optional.of(newPartialQuery);
         }
-        if (accepts(parentNode, Stage.HAVING_FILTER, Filter.class)) {
+        if (accepts(stack, Stage.HAVING_FILTER, Filter.class)) {
           PartialDruidQuery newPartialQuery = 
partialDruidQuery.withHavingFilter((Filter) parentNode);
           return Optional.of(newPartialQuery);
         }
-        if (accepts(parentNode, Stage.SORT, Sort.class)) {
+        if (accepts(stack, Stage.SORT, Sort.class)) {
           PartialDruidQuery newPartialQuery = 
partialDruidQuery.withSort((Sort) parentNode);
           return Optional.of(newPartialQuery);
         }
-        if (accepts(parentNode, Stage.SORT_PROJECT, Project.class)) {
+        if (accepts(stack, Stage.SORT_PROJECT, Project.class)) {
           PartialDruidQuery newPartialQuery = 
partialDruidQuery.withSortProject((Project) parentNode);
           return Optional.of(newPartialQuery);
         }
-        if (accepts(parentNode, Stage.WINDOW, Window.class)) {
+        if (accepts(stack, Stage.WINDOW, Window.class)) {
           PartialDruidQuery newPartialQuery = 
partialDruidQuery.withWindow((Window) parentNode);
           return Optional.of(newPartialQuery);
         }
-        if (accepts(parentNode, Stage.WINDOW_PROJECT, Project.class)) {
+        if (accepts(stack, Stage.WINDOW_PROJECT, Project.class)) {
           PartialDruidQuery newPartialQuery = 
partialDruidQuery.withWindowProject((Project) parentNode);
           return Optional.of(newPartialQuery);
         }
         return Optional.empty();
       }
 
-      private boolean accepts(RelNode node, Stage whereFilter, Class<? extends 
RelNode> class1)
+      private boolean accepts(Stack<DruidLogicalNode> stack, Stage stage, 
Class<? extends RelNode> clazz)
       {
-        return partialDruidQuery.canAccept(whereFilter) && 
class1.isInstance(node);
+        DruidLogicalNode currentNode = stack.peek();
+        if (Project.class == clazz && stack.size() >= 2) {
+          // peek at parent and postpone project for next query stage
+          DruidLogicalNode parentNode = stack.get(stack.size() - 2);
+          if (stage.ordinal() > Stage.AGGREGATE.ordinal()
+              && parentNode instanceof DruidAggregate
+              && !partialDruidQuery.canAccept(Stage.AGGREGATE)) {
+            return false;
+          }
+          if (stage.ordinal() > Stage.SORT.ordinal()
+              && parentNode instanceof DruidSort
+              && !partialDruidQuery.canAccept(Stage.SORT)) {
+            return false;
+          }
+        }
+        return partialDruidQuery.canAccept(stage) && 
clazz.isInstance(currentNode);
       }
 
       @Override
-      public InputDesc unwrapInputDesc()
+      public SourceDesc unwrapSourceDesc()
       {
-        if (canUnwrapInput()) {
+        if (canUnwrapSourceDesc()) {
           DruidQuery q = buildQuery(false);
-          InputDesc origInput = getInput();
-          return new InputDesc(origInput.dataSource, 
q.getOutputRowSignature());
+          SourceDesc origInput = getSource();
+          return new SourceDesc(origInput.dataSource, 
q.getOutputRowSignature());
         }
-        throw DruidException.defensive("Can't unwrap input of vertex[%s]", 
partialDruidQuery);
+        throw DruidException.defensive("Can't unwrap source of vertex[%s]", 
partialDruidQuery);
       }
 
       @Override
-      public boolean canUnwrapInput()
+      public boolean canUnwrapSourceDesc()
       {
         if (partialDruidQuery.stage() == Stage.SCAN) {
           return true;
diff --git 
a/sql/src/main/java/org/apache/druid/sql/calcite/planner/querygen/InputDescProducer.java
 
b/sql/src/main/java/org/apache/druid/sql/calcite/planner/querygen/SourceDescProducer.java
similarity index 68%
rename from 
sql/src/main/java/org/apache/druid/sql/calcite/planner/querygen/InputDescProducer.java
rename to 
sql/src/main/java/org/apache/druid/sql/calcite/planner/querygen/SourceDescProducer.java
index 412ac4d1a28..5e2fa2dc4d0 100644
--- 
a/sql/src/main/java/org/apache/druid/sql/calcite/planner/querygen/InputDescProducer.java
+++ 
b/sql/src/main/java/org/apache/druid/sql/calcite/planner/querygen/SourceDescProducer.java
@@ -22,6 +22,7 @@ package org.apache.druid.sql.calcite.planner.querygen;
 import org.apache.druid.query.DataSource;
 import org.apache.druid.segment.column.RowSignature;
 import org.apache.druid.sql.calcite.planner.PlannerContext;
+import org.apache.druid.sql.calcite.rel.VirtualColumnRegistry;
 
 import java.util.List;
 
@@ -30,24 +31,31 @@ import java.util.List;
  *
  * Example: TableScan ; Union; Join.
  */
-public interface InputDescProducer
+public interface SourceDescProducer
 {
   /**
    * Utility class to input related things details.
    *
    * Main reason to have this was that {@link DataSource} doesn't contain the 
{@link RowSignature}.
    */
-  class InputDesc
+  class SourceDesc
   {
-    public DataSource dataSource;
-    public RowSignature rowSignature;
+    public final DataSource dataSource;
+    public final RowSignature rowSignature;
+    public final VirtualColumnRegistry virtualColumnRegistry;
 
-    public InputDesc(DataSource dataSource, RowSignature rowSignature)
+    public SourceDesc(DataSource dataSource, RowSignature rowSignature)
+    {
+      this(dataSource, rowSignature, null);
+    }
+
+    public SourceDesc(DataSource dataSource, RowSignature rowSignature, 
VirtualColumnRegistry virtualColumnRegistry)
     {
       this.dataSource = dataSource;
       this.rowSignature = rowSignature;
+      this.virtualColumnRegistry = virtualColumnRegistry;
     }
   }
 
-  InputDesc getInputDesc(PlannerContext plannerContext, List<InputDesc> 
inputs);
+  SourceDesc getSourceDesc(PlannerContext plannerContext, List<SourceDesc> 
sources);
 }
diff --git 
a/sql/src/main/java/org/apache/druid/sql/calcite/rel/DruidJoinQueryRel.java 
b/sql/src/main/java/org/apache/druid/sql/calcite/rel/DruidJoinQueryRel.java
index 6a8f1529966..677a697a52a 100644
--- a/sql/src/main/java/org/apache/druid/sql/calcite/rel/DruidJoinQueryRel.java
+++ b/sql/src/main/java/org/apache/druid/sql/calcite/rel/DruidJoinQueryRel.java
@@ -55,9 +55,11 @@ import org.apache.druid.sql.calcite.expression.Expressions;
 import org.apache.druid.sql.calcite.planner.Calcites;
 import org.apache.druid.sql.calcite.planner.PlannerConfig;
 import org.apache.druid.sql.calcite.planner.PlannerContext;
+import 
org.apache.druid.sql.calcite.planner.querygen.SourceDescProducer.SourceDesc;
 import org.apache.druid.sql.calcite.table.RowSignatures;
 
 import javax.annotation.Nullable;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
@@ -142,19 +144,13 @@ public class DruidJoinQueryRel extends 
DruidRel<DruidJoinQueryRel>
     );
   }
 
-  @Override
-  public DruidQuery toDruidQuery(final boolean finalizeAggregations)
+  private SourceDesc buildLeftSourceDesc()
   {
+    final SourceDesc leftDesc;
     final DruidRel<?> leftDruidRel = (DruidRel<?>) left;
     final DruidQuery leftQuery = 
Preconditions.checkNotNull(leftDruidRel.toDruidQuery(false), "leftQuery");
     final RowSignature leftSignature = leftQuery.getOutputRowSignature();
     final DataSource leftDataSource;
-
-    final DruidRel<?> rightDruidRel = (DruidRel<?>) right;
-    final DruidQuery rightQuery = 
Preconditions.checkNotNull(rightDruidRel.toDruidQuery(false), "rightQuery");
-    final RowSignature rightSignature = rightQuery.getOutputRowSignature();
-    final DataSource rightDataSource;
-
     if (computeLeftRequiresSubquery(getPlannerContext(), leftDruidRel)) {
       leftDataSource = new QueryDataSource(leftQuery.getQuery());
       if (leftFilter != null) {
@@ -163,37 +159,54 @@ public class DruidJoinQueryRel extends 
DruidRel<DruidJoinQueryRel>
     } else {
       leftDataSource = leftQuery.getDataSource();
     }
+    leftDesc = new SourceDesc(leftDataSource, leftSignature);
+    return leftDesc;
+  }
 
+  private SourceDesc buildRightSourceDesc()
+  {
+    final SourceDesc rightDesc;
+    final DruidRel<?> rightDruidRel = (DruidRel<?>) right;
+    final DruidQuery rightQuery = 
Preconditions.checkNotNull(rightDruidRel.toDruidQuery(false), "rightQuery");
+    final RowSignature rightSignature = rightQuery.getOutputRowSignature();
+    final DataSource rightDataSource;
     if (computeRightRequiresSubquery(getPlannerContext(), rightDruidRel)) {
       rightDataSource = new QueryDataSource(rightQuery.getQuery());
     } else {
       rightDataSource = rightQuery.getDataSource();
     }
+    rightDesc = new SourceDesc(rightDataSource, rightSignature);
+    return rightDesc;
+  }
 
-
+  public static SourceDesc buildJoinSourceDesc(final SourceDesc leftDesc, 
final SourceDesc rightDesc, PlannerContext plannerContext, Join joinRel, Filter 
leftFilter)
+  {
     final Pair<String, RowSignature> prefixSignaturePair = 
computeJoinRowSignature(
-        leftSignature,
-        rightSignature,
-        findExistingJoinPrefixes(leftDataSource, rightDataSource)
+        leftDesc.rowSignature,
+        rightDesc.rowSignature,
+        findExistingJoinPrefixes(leftDesc.dataSource, rightDesc.dataSource)
     );
 
+    String prefix = prefixSignaturePair.lhs;
+    RowSignature signature = prefixSignaturePair.rhs;
+
     VirtualColumnRegistry virtualColumnRegistry = VirtualColumnRegistry.create(
-        prefixSignaturePair.rhs,
-        getPlannerContext().getExpressionParser(),
-        
getPlannerContext().getPlannerConfig().isForceExpressionVirtualColumns()
+        signature,
+        plannerContext.getExpressionParser(),
+        plannerContext.getPlannerConfig().isForceExpressionVirtualColumns()
     );
-    
getPlannerContext().setJoinExpressionVirtualColumnRegistry(virtualColumnRegistry);
+    
plannerContext.setJoinExpressionVirtualColumnRegistry(virtualColumnRegistry);
 
     // Generate the condition for this join as a Druid expression.
     final DruidExpression condition = Expressions.toDruidExpression(
-        getPlannerContext(),
-        prefixSignaturePair.rhs,
+        plannerContext,
+        signature,
         joinRel.getCondition()
     );
 
     // Unsetting it to avoid any VC Registry leaks incase there are multiple 
druid quries for the SQL
     // It should be fixed soon with changes in interface for 
SqlOperatorConversion and Expressions bridge class
-    getPlannerContext().setJoinExpressionVirtualColumnRegistry(null);
+    plannerContext.setJoinExpressionVirtualColumnRegistry(null);
 
     // DruidJoinRule should not have created us if "condition" is null. Check 
defensively anyway, which also
     // quiets static code analysis.
@@ -201,25 +214,40 @@ public class DruidJoinQueryRel extends 
DruidRel<DruidJoinQueryRel>
       throw new CannotBuildQueryException(joinRel, joinRel.getCondition());
     }
 
-    return partialQuery.build(
-        JoinDataSource.create(
-            leftDataSource,
-            rightDataSource,
-            prefixSignaturePair.lhs,
-            JoinConditionAnalysis.forExpression(
-                condition.getExpression(),
-                getPlannerContext().parseExpression(condition.getExpression()),
-                prefixSignaturePair.lhs
-            ),
-            toDruidJoinType(joinRel.getJoinType()),
-            getDimFilter(getPlannerContext(), leftSignature, leftFilter),
-            getPlannerContext().getJoinableFactoryWrapper()
+    JoinDataSource joinDataSource = JoinDataSource.create(
+        leftDesc.dataSource,
+        rightDesc.dataSource,
+        prefix,
+        JoinConditionAnalysis.forExpression(
+            condition.getExpression(),
+            plannerContext.parseExpression(condition.getExpression()),
+            prefix
         ),
-        prefixSignaturePair.rhs,
+        toDruidJoinType(joinRel.getJoinType()),
+        getDimFilter(plannerContext, leftDesc.rowSignature, leftFilter),
+        plannerContext.getJoinableFactoryWrapper()
+    );
+
+    SourceDesc sourceDesc = new SourceDesc(joinDataSource, signature, 
virtualColumnRegistry);
+    return sourceDesc;
+  }
+
+
+  @Override
+  public DruidQuery toDruidQuery(final boolean finalizeAggregations)
+  {
+    final SourceDesc leftDesc = buildLeftSourceDesc();
+    final SourceDesc rightDesc = buildRightSourceDesc();
+
+    SourceDesc sourceDesc = buildJoinSourceDesc(leftDesc, rightDesc, 
getPlannerContext(), joinRel, leftFilter);
+
+    return partialQuery.build(
+        sourceDesc.dataSource,
+        sourceDesc.rowSignature,
         getPlannerContext(),
         getCluster().getRexBuilder(),
         finalizeAggregations,
-        virtualColumnRegistry
+        sourceDesc.virtualColumnRegistry
     );
   }
 
diff --git 
a/sql/src/main/java/org/apache/druid/sql/calcite/rel/logical/DruidJoin.java 
b/sql/src/main/java/org/apache/druid/sql/calcite/rel/logical/DruidJoin.java
new file mode 100644
index 00000000000..5d531bfde80
--- /dev/null
+++ b/sql/src/main/java/org/apache/druid/sql/calcite/rel/logical/DruidJoin.java
@@ -0,0 +1,79 @@
+/*
+ * 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.sql.calcite.rel.logical;
+
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptPlanner;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.CorrelationId;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.hint.RelHint;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rex.RexNode;
+import org.apache.druid.sql.calcite.planner.PlannerContext;
+import org.apache.druid.sql.calcite.planner.querygen.SourceDescProducer;
+import org.apache.druid.sql.calcite.rel.DruidJoinQueryRel;
+
+import java.util.List;
+import java.util.Set;
+
+public class DruidJoin extends Join implements DruidLogicalNode, 
SourceDescProducer
+{
+  public DruidJoin(RelOptCluster cluster,
+      RelTraitSet traitSet,
+      List<RelHint> hints,
+      RelNode left,
+      RelNode right,
+      RexNode condition,
+      Set<CorrelationId> variablesSet,
+      JoinRelType joinType)
+  {
+    super(cluster, traitSet, hints, left, right, condition, variablesSet, 
joinType);
+  }
+
+  @Override
+  public Join copy(
+      RelTraitSet traitSet,
+      RexNode conditionExpr,
+      RelNode left,
+      RelNode right,
+      JoinRelType joinType,
+      boolean semiJoinDone)
+  {
+    return new DruidJoin(getCluster(), traitSet, hints, left, right, 
conditionExpr, variablesSet, joinType);
+  }
+
+  @Override
+  public RelOptCost computeSelfCost(RelOptPlanner planner, RelMetadataQuery mq)
+  {
+    return planner.getCostFactory().makeCost(mq.getRowCount(this), 0, 0);
+  }
+
+  @Override
+  public SourceDesc getSourceDesc(PlannerContext plannerContext, 
List<SourceDesc> sources)
+  {
+    SourceDesc leftDesc = sources.get(0);
+    SourceDesc rightDesc = sources.get(1);
+    return DruidJoinQueryRel.buildJoinSourceDesc(leftDesc, rightDesc, 
plannerContext, this, null);
+  }
+}
diff --git 
a/sql/src/main/java/org/apache/druid/sql/calcite/rel/logical/DruidTableScan.java
 
b/sql/src/main/java/org/apache/druid/sql/calcite/rel/logical/DruidTableScan.java
index b3bc5ba782a..45d97b04f32 100644
--- 
a/sql/src/main/java/org/apache/druid/sql/calcite/rel/logical/DruidTableScan.java
+++ 
b/sql/src/main/java/org/apache/druid/sql/calcite/rel/logical/DruidTableScan.java
@@ -34,7 +34,7 @@ import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.schema.Table;
 import org.apache.druid.sql.calcite.planner.PlannerContext;
-import org.apache.druid.sql.calcite.planner.querygen.InputDescProducer;
+import org.apache.druid.sql.calcite.planner.querygen.SourceDescProducer;
 import org.apache.druid.sql.calcite.table.DruidTable;
 
 import java.util.List;
@@ -42,7 +42,7 @@ import java.util.List;
 /**
  * {@link DruidLogicalNode} convention node for {@link TableScan} plan node.
  */
-public class DruidTableScan extends TableScan implements DruidLogicalNode, 
InputDescProducer
+public class DruidTableScan extends TableScan implements DruidLogicalNode, 
SourceDescProducer
 {
   public DruidTableScan(
       RelOptCluster cluster,
@@ -98,10 +98,10 @@ public class DruidTableScan extends TableScan implements 
DruidLogicalNode, Input
   }
 
   @Override
-  public InputDesc getInputDesc(PlannerContext plannerContext, List<InputDesc> 
inputs)
+  public SourceDesc getSourceDesc(PlannerContext plannerContext, 
List<SourceDesc> sources)
   {
     final DruidTable druidTable = getDruidTable();
-    return new InputDesc(druidTable.getDataSource(), 
druidTable.getRowSignature());
+    return new SourceDesc(druidTable.getDataSource(), 
druidTable.getRowSignature());
   }
 
   private DruidTable getDruidTable()
diff --git 
a/sql/src/main/java/org/apache/druid/sql/calcite/rel/logical/DruidUnion.java 
b/sql/src/main/java/org/apache/druid/sql/calcite/rel/logical/DruidUnion.java
index daab1708cb0..96981a751e6 100644
--- a/sql/src/main/java/org/apache/druid/sql/calcite/rel/logical/DruidUnion.java
+++ b/sql/src/main/java/org/apache/druid/sql/calcite/rel/logical/DruidUnion.java
@@ -35,11 +35,11 @@ import org.apache.druid.query.TableDataSource;
 import org.apache.druid.query.UnionDataSource;
 import org.apache.druid.segment.column.RowSignature;
 import org.apache.druid.sql.calcite.planner.PlannerContext;
-import org.apache.druid.sql.calcite.planner.querygen.InputDescProducer;
+import org.apache.druid.sql.calcite.planner.querygen.SourceDescProducer;
 import java.util.ArrayList;
 import java.util.List;
 
-public class DruidUnion extends Union implements DruidLogicalNode, 
InputDescProducer
+public class DruidUnion extends Union implements DruidLogicalNode, 
SourceDescProducer
 {
   public DruidUnion(
       RelOptCluster cluster,
@@ -64,26 +64,26 @@ public class DruidUnion extends Union implements 
DruidLogicalNode, InputDescProd
   }
 
   @Override
-  public InputDesc getInputDesc(PlannerContext plannerContext, List<InputDesc> 
inputs)
+  public SourceDesc getSourceDesc(PlannerContext plannerContext, 
List<SourceDesc> sources)
   {
     List<DataSource> dataSources = new ArrayList<>();
     RowSignature signature = null;
-    for (InputDesc inputDesc : inputs) {
-      checkDataSourceSupported(inputDesc.dataSource);
-      dataSources.add(inputDesc.dataSource);
+    for (SourceDesc sourceDesc : sources) {
+      checkDataSourceSupported(sourceDesc.dataSource);
+      dataSources.add(sourceDesc.dataSource);
       if (signature == null) {
-        signature = inputDesc.rowSignature;
+        signature = sourceDesc.rowSignature;
       } else {
-        if (!signature.equals(inputDesc.rowSignature)) {
+        if (!signature.equals(sourceDesc.rowSignature)) {
           throw DruidException.defensive(
               "Row signature mismatch in Union inputs [%s] and [%s]",
               signature,
-              inputDesc.rowSignature
+              sourceDesc.rowSignature
           );
         }
       }
     }
-    return new InputDesc(new UnionDataSource(dataSources), signature);
+    return new SourceDesc(new UnionDataSource(dataSources), signature);
   }
 
   private void checkDataSourceSupported(DataSource dataSource)
diff --git 
a/sql/src/main/java/org/apache/druid/sql/calcite/rel/logical/DruidValues.java 
b/sql/src/main/java/org/apache/druid/sql/calcite/rel/logical/DruidValues.java
index fea4e5f610d..c6fa2180161 100644
--- 
a/sql/src/main/java/org/apache/druid/sql/calcite/rel/logical/DruidValues.java
+++ 
b/sql/src/main/java/org/apache/druid/sql/calcite/rel/logical/DruidValues.java
@@ -32,7 +32,7 @@ import org.apache.calcite.rex.RexLiteral;
 import org.apache.druid.query.InlineDataSource;
 import org.apache.druid.segment.column.RowSignature;
 import org.apache.druid.sql.calcite.planner.PlannerContext;
-import org.apache.druid.sql.calcite.planner.querygen.InputDescProducer;
+import org.apache.druid.sql.calcite.planner.querygen.SourceDescProducer;
 import org.apache.druid.sql.calcite.rel.CostEstimates;
 import org.apache.druid.sql.calcite.rule.DruidLogicalValuesRule;
 import org.apache.druid.sql.calcite.table.InlineTable;
@@ -44,7 +44,7 @@ import java.util.stream.Collectors;
 /**
  * {@link DruidLogicalNode} convention node for {@link LogicalValues} plan 
node.
  */
-public class DruidValues extends LogicalValues implements DruidLogicalNode, 
InputDescProducer
+public class DruidValues extends LogicalValues implements DruidLogicalNode, 
SourceDescProducer
 {
 
   private InlineTable inlineTable;
@@ -72,12 +72,12 @@ public class DruidValues extends LogicalValues implements 
DruidLogicalNode, Inpu
   }
 
   @Override
-  public InputDesc getInputDesc(PlannerContext plannerContext, List<InputDesc> 
inputs)
+  public SourceDesc getSourceDesc(PlannerContext plannerContext, 
List<SourceDesc> sources)
   {
     if (inlineTable == null) {
       inlineTable = buildInlineTable(plannerContext);
     }
-    return new InputDesc(inlineTable.getDataSource(), 
inlineTable.getRowSignature());
+    return new SourceDesc(inlineTable.getDataSource(), 
inlineTable.getRowSignature());
   }
 
   private InlineTable buildInlineTable(PlannerContext plannerContext)
diff --git 
a/sql/src/main/java/org/apache/druid/sql/calcite/rule/DruidJoinRule.java 
b/sql/src/main/java/org/apache/druid/sql/calcite/rule/DruidJoinRule.java
index d5a307bc54f..66f5accfcc6 100644
--- a/sql/src/main/java/org/apache/druid/sql/calcite/rule/DruidJoinRule.java
+++ b/sql/src/main/java/org/apache/druid/sql/calcite/rule/DruidJoinRule.java
@@ -20,6 +20,7 @@
 package org.apache.druid.sql.calcite.rule;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Iterables;
 import org.apache.calcite.plan.RelOptRule;
@@ -47,6 +48,7 @@ import org.apache.calcite.tools.RelBuilder;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.druid.common.config.NullHandling;
 import org.apache.druid.error.DruidException;
+import org.apache.druid.java.util.common.StringUtils;
 import org.apache.druid.query.LookupDataSource;
 import org.apache.druid.sql.calcite.planner.PlannerContext;
 import org.apache.druid.sql.calcite.rel.DruidJoinQueryRel;
@@ -84,7 +86,7 @@ public class DruidJoinRule extends RelOptRule
   {
     return new DruidJoinRule(plannerContext);
   }
-  
+
   @Override
   public boolean matches(RelOptRuleCall call)
   {
@@ -125,6 +127,7 @@ public class DruidJoinRule extends RelOptRule
         join.getLeft().getRowType(),
         rexBuilder
     );
+    plannerContext.setPlanningError(conditionAnalysis.errorStr);
     final boolean isLeftDirectAccessPossible = enableLeftScanDirect && (left 
instanceof DruidQueryRel);
 
     if (!plannerContext.getJoinAlgorithm().requiresSubquery()
@@ -247,6 +250,7 @@ public class DruidJoinRule extends RelOptRule
   )
   {
     ConditionAnalysis conditionAnalysis = analyzeCondition(condition, 
leftRowType, rexBuilder);
+    plannerContext.setPlanningError(conditionAnalysis.errorStr);
     // if the right side requires a subquery, then even lookup will be 
transformed to a QueryDataSource
     // thereby allowing join conditions on both k and v columns of the lookup
     if (right != null
@@ -275,7 +279,7 @@ public class DruidJoinRule extends RelOptRule
       // for an example.
       return conditionAnalysis.getUnsupportedOnSubConditions().isEmpty();
     }
-    
+
     return true;
   }
 
@@ -304,12 +308,15 @@ public class DruidJoinRule extends RelOptRule
 
     private final Set<RexInputRef> rightColumns;
 
+    public final String errorStr;
+
     ConditionAnalysis(
         int numLeftFields,
         List<RexEquality> equalitySubConditions,
         List<RexLiteral> literalSubConditions,
         List<RexNode> unsupportedOnSubConditions,
-        Set<RexInputRef> rightColumns
+        Set<RexInputRef> rightColumns,
+        String errorStr
     )
     {
       this.numLeftFields = numLeftFields;
@@ -317,6 +324,7 @@ public class DruidJoinRule extends RelOptRule
       this.literalSubConditions = literalSubConditions;
       this.unsupportedOnSubConditions = unsupportedOnSubConditions;
       this.rightColumns = rightColumns;
+      this.errorStr = errorStr;
     }
 
     public ConditionAnalysis pushThroughLeftProject(final Project leftProject)
@@ -340,7 +348,8 @@ public class DruidJoinRule extends RelOptRule
               .collect(Collectors.toList()),
           literalSubConditions,
           unsupportedOnSubConditions,
-          rightColumns
+          rightColumns,
+          null
       );
     }
 
@@ -369,7 +378,8 @@ public class DruidJoinRule extends RelOptRule
               .collect(Collectors.toList()),
           literalSubConditions,
           unsupportedOnSubConditions,
-          rightColumns
+          rightColumns,
+          null
       );
     }
 
@@ -429,7 +439,7 @@ public class DruidJoinRule extends RelOptRule
    * that can be extracted into post join filter.
    * {@code f(LeftRel) = RightColumn}, then return a {@link ConditionAnalysis}.
    */
-  public ConditionAnalysis analyzeCondition(
+  public static ConditionAnalysis analyzeCondition(
       final RexNode condition,
       final RelDataType leftRowType,
       final RexBuilder rexBuilder
@@ -441,6 +451,7 @@ public class DruidJoinRule extends RelOptRule
     final List<RexNode> unSupportedSubConditions = new ArrayList<>();
     final Set<RexInputRef> rightColumns = new HashSet<>();
     final int numLeftFields = leftRowType.getFieldCount();
+    final List<String> errors = new ArrayList<String>();
 
     for (RexNode subCondition : subConditions) {
       if (RexUtil.isLiteral(subCondition, true)) {
@@ -475,10 +486,12 @@ public class DruidJoinRule extends RelOptRule
         comparisonKind = SqlKind.EQUALS;
 
         if 
(!SqlTypeName.BOOLEAN_TYPES.contains(secondOperand.getType().getSqlTypeName())) 
{
-          plannerContext.setPlanningError(
-              "SQL requires a join with '%s' condition where the column is of 
the type %s, that is not supported",
-              subCondition.getKind(),
-              secondOperand.getType().getSqlTypeName()
+          errors.add(
+              StringUtils.format(
+                  "SQL requires a join with '%s' condition where the column is 
of the type %s, that is not supported",
+                  subCondition.getKind(),
+                  secondOperand.getType().getSqlTypeName()
+              )
           );
           unSupportedSubConditions.add(subCondition);
           continue;
@@ -492,9 +505,11 @@ public class DruidJoinRule extends RelOptRule
         comparisonKind = subCondition.getKind();
       } else {
         // If it's not EQUALS or a BOOLEAN input ref, it's not supported.
-        plannerContext.setPlanningError(
-            "SQL requires a join with '%s' condition that is not supported.",
-            subCondition.getKind()
+        errors.add(
+            StringUtils.format(
+                "SQL requires a join with '%s' condition that is not 
supported.",
+                subCondition.getKind()
+            )
         );
         unSupportedSubConditions.add(subCondition);
         continue;
@@ -509,17 +524,27 @@ public class DruidJoinRule extends RelOptRule
         rightColumns.add((RexInputRef) firstOperand);
       } else {
         // Cannot handle this condition.
-        plannerContext.setPlanningError("SQL is resulting in a join that has 
unsupported operand types.");
+        errors.add(
+            StringUtils.format(
+                "SQL is resulting in a join that has unsupported operand 
types."
+            )
+        );
         unSupportedSubConditions.add(subCondition);
       }
     }
-
+    final String errorStr;
+    if (errors.size() > 0) {
+      errorStr = Joiner.on('\n').join(errors);
+    } else {
+      errorStr = null;
+    }
     return new ConditionAnalysis(
         numLeftFields,
         equalitySubConditions,
         literalSubConditions,
         unSupportedSubConditions,
-        rightColumns
+        rightColumns,
+        errorStr
     );
   }
 
diff --git 
a/sql/src/main/java/org/apache/druid/sql/calcite/rule/logical/DruidJoinRule.java
 
b/sql/src/main/java/org/apache/druid/sql/calcite/rule/logical/DruidJoinRule.java
new file mode 100644
index 00000000000..ded383cbefb
--- /dev/null
+++ 
b/sql/src/main/java/org/apache/druid/sql/calcite/rule/logical/DruidJoinRule.java
@@ -0,0 +1,76 @@
+/*
+ * 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.sql.calcite.rule.logical;
+
+import org.apache.calcite.plan.RelTrait;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.convert.ConverterRule;
+import org.apache.calcite.rel.core.Join;
+import org.apache.druid.error.InvalidSqlInput;
+import org.apache.druid.sql.calcite.rel.logical.DruidJoin;
+import org.apache.druid.sql.calcite.rel.logical.DruidLogicalConvention;
+import org.apache.druid.sql.calcite.rule.DruidJoinRule.ConditionAnalysis;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class DruidJoinRule extends ConverterRule
+{
+
+  public DruidJoinRule(Class<? extends RelNode> clazz, RelTrait in, RelTrait 
out, String descriptionPrefix)
+  {
+    super(Config.INSTANCE.withConversion(clazz, in, out, descriptionPrefix));
+  }
+
+  @Override
+  public @Nullable RelNode convert(RelNode rel)
+  {
+    Join join = (Join) rel;
+    RelTraitSet newTrait = 
join.getTraitSet().replace(DruidLogicalConvention.instance());
+
+    ConditionAnalysis analysis = 
org.apache.druid.sql.calcite.rule.DruidJoinRule.analyzeCondition(
+        join.getCondition(),
+        join.getLeft().getRowType(),
+        join.getCluster().getRexBuilder()
+    );
+
+    if (analysis.errorStr != null) {
+      // reject the query in case the anaysis detected any issues
+      throw InvalidSqlInput.exception(analysis.errorStr);
+    }
+
+    return new DruidJoin(
+        join.getCluster(),
+        newTrait,
+        join.getHints(),
+        convert(
+            join.getLeft(),
+            DruidLogicalConvention.instance()
+        ),
+        convert(
+            join.getRight(),
+            DruidLogicalConvention.instance()
+        ),
+        join.getCondition(),
+        join.getVariablesSet(),
+        join.getJoinType()
+    );
+  }
+
+}
diff --git 
a/sql/src/main/java/org/apache/druid/sql/calcite/rule/logical/DruidLogicalRules.java
 
b/sql/src/main/java/org/apache/druid/sql/calcite/rule/logical/DruidLogicalRules.java
index 5fe939d3e7c..a7d1a2c8c68 100644
--- 
a/sql/src/main/java/org/apache/druid/sql/calcite/rule/logical/DruidLogicalRules.java
+++ 
b/sql/src/main/java/org/apache/druid/sql/calcite/rule/logical/DruidLogicalRules.java
@@ -25,6 +25,7 @@ import org.apache.calcite.plan.RelOptRule;
 import org.apache.calcite.rel.core.Window;
 import org.apache.calcite.rel.logical.LogicalAggregate;
 import org.apache.calcite.rel.logical.LogicalFilter;
+import org.apache.calcite.rel.logical.LogicalJoin;
 import org.apache.calcite.rel.logical.LogicalProject;
 import org.apache.calcite.rel.logical.LogicalSort;
 import org.apache.calcite.rel.logical.LogicalTableScan;
@@ -98,6 +99,12 @@ public class DruidLogicalRules
                 Convention.NONE,
                 DruidLogicalConvention.instance(),
                 DruidUnionRule.class.getSimpleName()
+            ),
+            new DruidJoinRule(
+                LogicalJoin.class,
+                Convention.NONE,
+                DruidLogicalConvention.instance(),
+                DruidJoinRule.class.getSimpleName()
             )
         )
     );
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/BaseCalciteQueryTest.java 
b/sql/src/test/java/org/apache/druid/sql/calcite/BaseCalciteQueryTest.java
index 8e8f3287e6e..d9569d31615 100644
--- a/sql/src/test/java/org/apache/druid/sql/calcite/BaseCalciteQueryTest.java
+++ b/sql/src/test/java/org/apache/druid/sql/calcite/BaseCalciteQueryTest.java
@@ -31,6 +31,8 @@ import org.apache.commons.text.StringEscapeUtils;
 import org.apache.druid.annotations.UsedByJUnitParamsRunner;
 import org.apache.druid.common.config.NullHandling;
 import org.apache.druid.error.DruidException;
+import org.apache.druid.error.DruidException.Category;
+import org.apache.druid.error.DruidException.Persona;
 import org.apache.druid.error.DruidExceptionMatcher;
 import org.apache.druid.guice.DruidInjectorBuilder;
 import org.apache.druid.hll.VersionOneHyperLogLogCollector;
@@ -126,6 +128,7 @@ import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
 
 import javax.annotation.Nullable;
+
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -770,18 +773,21 @@ public class BaseCalciteQueryTest extends CalciteTestBase
     catch (DruidException e) {
       MatcherAssert.assertThat(
           e,
-          new DruidExceptionMatcher(DruidException.Persona.ADMIN, 
DruidException.Category.INVALID_INPUT, "general")
-              .expectMessageIs(
-                  StringUtils.format(
-                      "Query could not be planned. A possible reason is [%s]",
-                      expectedError
-                  )
-              )
+          
buildUnplannableExceptionMatcher().expectMessageContains(expectedError)
       );
     }
     catch (Exception e) {
       log.error(e, "Expected DruidException for query: %s", sql);
-      Assert.fail(sql);
+      throw e;
+    }
+  }
+
+  private DruidExceptionMatcher buildUnplannableExceptionMatcher()
+  {
+    if (testBuilder().isDecoupledMode()) {
+      return new DruidExceptionMatcher(Persona.USER, Category.INVALID_INPUT, 
"invalidInput");
+    } else {
+      return new DruidExceptionMatcher(Persona.ADMIN, Category.INVALID_INPUT, 
"general");
     }
   }
 
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 5420be74e7f..ac59234576c 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
@@ -85,6 +85,8 @@ import org.apache.druid.segment.join.JoinType;
 import org.apache.druid.segment.virtual.ListFilteredVirtualColumn;
 import org.apache.druid.server.QueryLifecycle;
 import org.apache.druid.server.security.Access;
+import org.apache.druid.sql.calcite.DecoupledTestConfig.NativeQueryIgnore;
+import org.apache.druid.sql.calcite.NotYetSupported.Modes;
 import org.apache.druid.sql.calcite.expression.DruidExpression;
 import org.apache.druid.sql.calcite.filtration.Filtration;
 import org.apache.druid.sql.calcite.planner.PlannerConfig;
@@ -188,6 +190,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
   // to compute the query with limit 1.
   @SqlTestFrameworkConfig(minTopNThreshold = 1)
   @Test
+  @DecoupledTestConfig(nativeQueryIgnore = NativeQueryIgnore.EQUIV_PLAN)
   public void testExactTopNOnInnerJoinWithLimit()
   {
     Map<String, Object> context = new HashMap<>(QUERY_CONTEXT_DEFAULT);
@@ -236,6 +239,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
   }
 
   @Test
+  @NotYetSupported(Modes.STACK_OVERFLOW)
   public void testJoinOuterGroupByAndSubqueryHasLimit()
   {
     // Cannot vectorize JOIN operator.
@@ -323,6 +327,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @NotYetSupported(Modes.JOIN_TABLE_TABLE)
   public void testJoinOuterGroupByAndSubqueryNoLimit(Map<String, Object> 
queryContext)
   {
     // Fully removing the join allows this query to vectorize.
@@ -406,6 +411,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
   }
 
   @Test
+  @NotYetSupported(Modes.JOIN_TABLE_TABLE)
   public void testJoinWithLimitBeforeJoining()
   {
     // Cannot vectorize JOIN operator.
@@ -492,6 +498,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
   }
 
   @Test
+  @DecoupledTestConfig(nativeQueryIgnore = 
NativeQueryIgnore.JOIN_FILTER_LOCATIONS)
   public void testJoinOnTimeseriesWithFloorOnTime()
   {
     // Cannot vectorize JOIN operator.
@@ -546,6 +553,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
   }
 
   @Test
+  @DecoupledTestConfig(nativeQueryIgnore = 
NativeQueryIgnore.JOIN_FILTER_LOCATIONS)
   public void testJoinOnGroupByInsteadOfTimeseriesWithFloorOnTime()
   {
     // Cannot vectorize JOIN operator.
@@ -612,6 +620,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
   }
 
   @Test
+  @DecoupledTestConfig(nativeQueryIgnore = 
NativeQueryIgnore.JOIN_FILTER_LOCATIONS)
   public void 
testJoinOnGroupByInsteadOfTimeseriesWithFloorOnTimeWithNoAggregateMultipleValues()
   {
     // Cannot vectorize JOIN operator.
@@ -679,6 +688,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @NotYetSupported(Modes.CANNOT_JOIN_LOOKUP_NON_KEY)
   public void 
testFilterAndGroupByLookupUsingJoinOperatorWithValueFilterPushdownMatchesNothing(Map<String,
 Object> queryContext)
 
   {
@@ -760,6 +770,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @NotYetSupported(Modes.JOIN_CONDITION_NOT_PUSHED_CONDITION)
   public void testFilterAndGroupByLookupUsingJoinOperatorBackwards(Map<String, 
Object> queryContext)
   {
     // Like "testFilterAndGroupByLookupUsingJoinOperator", but with the table 
and lookup reversed.
@@ -815,6 +826,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @NotYetSupported(Modes.JOIN_CONDITION_NOT_PUSHED_CONDITION)
   public void 
testFilterAndGroupByLookupUsingJoinOperatorWithNotFilter(Map<String, Object> 
queryContext)
 
   {
@@ -857,6 +869,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
     );
   }
 
+  @NotYetSupported(Modes.JOIN_CONDITION_NOT_PUSHED_CONDITION)
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
   public void testJoinUnionTablesOnLookup(Map<String, Object> queryContext)
@@ -911,6 +924,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @NotYetSupported(Modes.CANNOT_JOIN_LOOKUP_NON_KEY)
   public void testFilterAndGroupByLookupUsingJoinOperator(Map<String, Object> 
queryContext)
   {
     // Cannot vectorize JOIN operator.
@@ -1120,6 +1134,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @NotYetSupported(Modes.JOIN_CONDITION_UNSUPORTED_OPERAND)
   public void 
testInnerJoinTableLookupLookupWithFilterWithOuterLimit(Map<String, Object> 
queryContext)
   {
     testQuery(
@@ -1163,6 +1178,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @NotYetSupported(Modes.JOIN_CONDITION_UNSUPORTED_OPERAND)
   public void testInnerJoinTableLookupLookupWithFilterWithoutLimit(Map<String, 
Object> queryContext)
   {
     testQuery(
@@ -1204,6 +1220,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @NotYetSupported(Modes.JOIN_CONDITION_UNSUPORTED_OPERAND)
   public void 
testInnerJoinTableLookupLookupWithFilterWithOuterLimitWithAllColumns(Map<String,
 Object> queryContext)
 
   {
@@ -1248,6 +1265,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @NotYetSupported(Modes.JOIN_CONDITION_UNSUPORTED_OPERAND)
   public void 
testInnerJoinTableLookupLookupWithFilterWithoutLimitWithAllColumns(Map<String, 
Object> queryContext)
   {
     testQuery(
@@ -1289,6 +1307,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @NotYetSupported(Modes.JOIN_CONDITION_UNSUPORTED_OPERAND)
   public void testManyManyInnerJoinOnManyManyLookup(Map<String, Object> 
queryContext)
   {
     testQuery(
@@ -1518,6 +1537,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @DecoupledTestConfig(nativeQueryIgnore = 
NativeQueryIgnore.FINALIZING_FIELD_ACCESS)
   public void testInnerJoinQueryOfLookup(Map<String, Object> queryContext)
   {
     // Cannot vectorize the subquery.
@@ -1597,6 +1617,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @DecoupledTestConfig(nativeQueryIgnore = 
NativeQueryIgnore.DEFINETLY_WORSE_PLAN)
   public void testInnerJoinQueryOfLookupRemovable(Map<String, Object> 
queryContext)
   {
     // Like "testInnerJoinQueryOfLookup", but the subquery is removable.
@@ -1635,6 +1656,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @DecoupledTestConfig(nativeQueryIgnore = NativeQueryIgnore.EQUIV_PLAN)
   public void testInnerJoinTwoLookupsToTableUsingNumericColumn(Map<String, 
Object> queryContext)
   {
     // Regression test for https://github.com/apache/druid/issues/9646.
@@ -1696,6 +1718,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @NotYetSupported(Modes.JOIN_TABLE_TABLE)
   public void 
testInnerJoinTwoLookupsToTableUsingNumericColumnInReverse(Map<String, Object> 
queryContext)
 
   {
@@ -1753,6 +1776,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @NotYetSupported(Modes.JOIN_TABLE_TABLE)
   public void testInnerJoinLookupTableTable(Map<String, Object> queryContext)
   {
     // Regression test for https://github.com/apache/druid/issues/9646.
@@ -1835,6 +1859,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @NotYetSupported(Modes.JOIN_TABLE_TABLE)
   public void testInnerJoinLookupTableTableChained(Map<String, Object> 
queryContext)
   {
     // Cannot vectorize JOIN operator.
@@ -1957,6 +1982,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
   }
 
   @Test
+  @DecoupledTestConfig(nativeQueryIgnore = 
NativeQueryIgnore.JOIN_FILTER_LOCATIONS)
   public void testCommaJoinLeftFunction()
   {
     testQuery(
@@ -1995,6 +2021,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
   // Hence, comma join will result in a cross join with filter on outermost
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @NotYetSupported(Modes.JOIN_CONDITION_UNSUPORTED_OPERAND)
   public void testCommaJoinTableLookupTableMismatchedTypes(Map<String, Object> 
queryContext)
   {
     // Regression test for https://github.com/apache/druid/issues/9646.
@@ -2061,6 +2088,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @NotYetSupported(Modes.JOIN_TABLE_TABLE)
   public void testJoinTableLookupTableMismatchedTypesWithoutComma(Map<String, 
Object> queryContext)
   {
     // Empty-dataset aggregation queries in MSQ return an empty row, rather 
than a single row as SQL requires.
@@ -2131,6 +2159,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @DecoupledTestConfig(nativeQueryIgnore = 
NativeQueryIgnore.JOIN_FILTER_LOCATIONS)
   public void testInnerJoinCastLeft(Map<String, Object> queryContext)
   {
     // foo.m1 is FLOAT, l.k is STRING.
@@ -2259,6 +2288,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @DecoupledTestConfig(nativeQueryIgnore = 
NativeQueryIgnore.JOIN_FILTER_LOCATIONS)
   public void testInnerJoinLeftFunction(Map<String, Object> queryContext)
   {
     testQuery(
@@ -2711,6 +2741,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @DecoupledTestConfig(nativeQueryIgnore = 
NativeQueryIgnore.JOIN_FILTER_LOCATIONS)
   public void testUsingSubqueryWithExtractionFns(Map<String, Object> 
queryContext)
   {
     // Cannot vectorize JOIN operator.
@@ -2771,6 +2802,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @NotYetSupported(Modes.JOIN_CONDITION_NOT_PUSHED_CONDITION)
   public void testInnerJoinWithIsNullFilter(Map<String, Object> queryContext)
   {
     testQuery(
@@ -2913,6 +2945,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @DecoupledTestConfig(nativeQueryIgnore = 
NativeQueryIgnore.JOIN_LEFT_DIRECT_ACCESS)
   public void 
testLeftJoinOnTwoInlineDataSourcesWithTimeFilter_withLeftDirectAccess(Map<String,
 Object> queryContext)
   {
     queryContext = withLeftDirectAccessEnabled(queryContext);
@@ -3024,6 +3057,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @DecoupledTestConfig(nativeQueryIgnore = 
NativeQueryIgnore.JOIN_LEFT_DIRECT_ACCESS)
   public void 
testLeftJoinOnTwoInlineDataSourcesWithOuterWhere_withLeftDirectAccess(Map<String,
 Object> queryContext)
   {
     queryContext = withLeftDirectAccessEnabled(queryContext);
@@ -3125,6 +3159,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @DecoupledTestConfig(nativeQueryIgnore = 
NativeQueryIgnore.JOIN_LEFT_DIRECT_ACCESS)
   public void 
testLeftJoinOnTwoInlineDataSources_withLeftDirectAccess(Map<String, Object> 
queryContext)
   {
     queryContext = withLeftDirectAccessEnabled(queryContext);
@@ -3226,6 +3261,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @DecoupledTestConfig(nativeQueryIgnore = 
NativeQueryIgnore.JOIN_LEFT_DIRECT_ACCESS)
   public void 
testInnerJoinOnTwoInlineDataSourcesWithOuterWhere_withLeftDirectAccess(Map<String,
 Object> queryContext)
   {
     queryContext = withLeftDirectAccessEnabled(queryContext);
@@ -3327,6 +3363,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @DecoupledTestConfig(nativeQueryIgnore = NativeQueryIgnore.EQUIV_PLAN)
   public void 
testGroupByOverGroupByOverInnerJoinOnTwoInlineDataSources(Map<String, Object> 
queryContext)
   {
     skipVectorize();
@@ -3412,6 +3449,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @DecoupledTestConfig(nativeQueryIgnore = 
NativeQueryIgnore.JOIN_LEFT_DIRECT_ACCESS)
   public void 
testInnerJoinOnTwoInlineDataSources_withLeftDirectAccess(Map<String, Object> 
queryContext)
   {
     queryContext = withLeftDirectAccessEnabled(queryContext);
@@ -3640,6 +3678,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @DecoupledTestConfig(nativeQueryIgnore = NativeQueryIgnore.EQUIV_PLAN)
   public void testLeftJoinSubqueryWithSelectorFilter(Map<String, Object> 
queryContext)
   {
     // Cannot vectorize due to 'concat' expression.
@@ -3693,6 +3732,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @NotYetSupported(Modes.JOIN_TABLE_TABLE)
   public void testLeftJoinWithNotNullFilter(Map<String, Object> queryContext)
   {
     testQuery(
@@ -3740,6 +3780,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @NotYetSupported(Modes.JOIN_TABLE_TABLE)
   public void testInnerJoin(Map<String, Object> queryContext)
   {
     testQuery(
@@ -3794,6 +3835,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @NotYetSupported(Modes.JOIN_TABLE_TABLE)
   public void testJoinWithExplicitIsNotDistinctFromCondition(Map<String, 
Object> queryContext)
   {
     // Like "testInnerJoin", but uses IS NOT DISTINCT FROM instead of equals.
@@ -3839,6 +3881,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @DecoupledTestConfig(nativeQueryIgnore = NativeQueryIgnore.EQUIV_PLAN)
   public void testInnerJoinSubqueryWithSelectorFilter(Map<String, Object> 
queryContext)
   {
     if (sortBasedJoin) {
@@ -3898,6 +3941,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
   }
 
   @Test
+  @NotYetSupported(Modes.JOIN_CONDITION_NOT_PUSHED_CONDITION)
   public void testSemiJoinWithOuterTimeExtractScan()
   {
     testQuery(
@@ -3946,6 +3990,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @NotYetSupported(Modes.JOIN_CONDITION_NOT_PUSHED_CONDITION)
   public void testTwoSemiJoinsSimultaneously(Map<String, Object> queryContext)
   {
     // Fully removing the join allows this query to vectorize.
@@ -4117,6 +4162,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @DecoupledTestConfig(nativeQueryIgnore = NativeQueryIgnore.EQUIV_PLAN)
   public void testSemiAndAntiJoinSimultaneouslyUsingExplicitJoins(Map<String, 
Object> queryContext)
   {
     cannotVectorize();
@@ -4184,6 +4230,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
   }
 
   @Test
+  @NotYetSupported(Modes.JOIN_CONDITION_NOT_PUSHED_CONDITION)
   public void testSemiJoinWithOuterTimeExtractAggregateWithOrderBy()
   {
     // Cannot vectorize due to virtual columns.
@@ -4278,6 +4325,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @NotYetSupported(Modes.UNION_WITH_COMPLEX_OPERAND)
   public void testUnionAllTwoQueriesLeftQueryIsJoin(Map<String, Object> 
queryContext)
   {
     // MSQ does not support UNION ALL.
@@ -4322,6 +4370,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @NotYetSupported(Modes.UNION_WITH_COMPLEX_OPERAND)
   public void testUnionAllTwoQueriesRightQueryIsJoin(Map<String, Object> 
queryContext)
   {
     // MSQ does not support UNION ALL.
@@ -4364,6 +4413,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
     );
   }
 
+  @NotYetSupported(Modes.UNION_WITH_COMPLEX_OPERAND)
   @Test
   public void testUnionAllTwoQueriesBothQueriesAreJoin()
   {
@@ -4636,6 +4686,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @NotYetSupported(Modes.JOIN_CONDITION_NOT_PUSHED_CONDITION)
   public void testJoinWithNonEquiCondition(Map<String, Object> queryContext)
   {
     // Native JOIN operator cannot handle the condition, so a SQL JOIN with 
greater-than is translated into a
@@ -4698,6 +4749,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @NotYetSupported(Modes.JOIN_CONDITION_UNSUPORTED_OPERAND)
   public void testJoinWithEquiAndNonEquiCondition(Map<String, Object> 
queryContext)
   {
     // Native JOIN operator cannot handle the condition, so a SQL JOIN with 
greater-than is translated into a
@@ -4743,6 +4795,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @NotYetSupported(Modes.JOIN_CONDITION_NOT_PUSHED_CONDITION)
   public void testUsingSubqueryAsPartOfAndFilter(Map<String, Object> 
queryContext)
   {
     // Fully removing the join allows this query to vectorize.
@@ -4902,6 +4955,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @NotYetSupported(Modes.JOIN_CONDITION_UNSUPORTED_OPERAND)
   public void testNestedGroupByOnInlineDataSourceWithFilter(Map<String, 
Object> queryContext)
   {
     // Cannot vectorize due to virtual columns.
@@ -5094,6 +5148,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @DecoupledTestConfig(nativeQueryIgnore = NativeQueryIgnore.EQUIV_PLAN)
   public void testTopNOnStringWithNonSortedOrUniqueDictionary(Map<String, 
Object> queryContext)
   {
     testQuery(
@@ -5134,6 +5189,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @DecoupledTestConfig(nativeQueryIgnore = NativeQueryIgnore.EQUIV_PLAN)
   public void 
testTopNOnStringWithNonSortedOrUniqueDictionaryOrderByDim(Map<String, Object> 
queryContext)
 
   {
@@ -5174,6 +5230,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @DecoupledTestConfig(nativeQueryIgnore = 
NativeQueryIgnore.SLIGHTLY_WORSE_PLAN)
   public void testVirtualColumnOnMVFilterJoinExpression(Map<String, Object> 
queryContext)
   {
     // Doesn't work in MSQ, although it's not really MSQ's fault. In MSQ, the 
second field (foo2.dim3) is returned as
@@ -5230,6 +5287,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @DecoupledTestConfig(nativeQueryIgnore = 
NativeQueryIgnore.DEFINETLY_WORSE_PLAN)
   public void testVirtualColumnOnMVFilterMultiJoinExpression(Map<String, 
Object> queryContext)
   {
     // Doesn't work in MSQ, although it's not really MSQ's fault. In MSQ, the 
second field (foo2.dim3) is returned as
@@ -5309,6 +5367,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @NotYetSupported(Modes.JOIN_CONDITION_NOT_PUSHED_CONDITION)
   public void 
testInnerJoinWithFilterPushdownAndManyFiltersEmptyResults(Map<String, Object> 
queryContext)
   {
     // create the query we expect
@@ -5416,6 +5475,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @NotYetSupported(Modes.JOIN_CONDITION_NOT_PUSHED_CONDITION)
   public void 
testInnerJoinWithFilterPushdownAndManyFiltersNonEmptyResults(Map<String, 
Object> queryContext)
   {
     // create the query we expect
@@ -5584,6 +5644,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @Test
   @Parameters(source = QueryContextForJoinProvider.class)
+  @NotYetSupported(Modes.SORT_REMOVE_TROUBLE)
   public void testRegressionFilteredAggregatorsSubqueryJoins(Map<String, 
Object> queryContext)
   {
     cannotVectorize();
@@ -5778,6 +5839,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
 
   @SqlTestFrameworkConfig(minTopNThreshold = 1)
   @Test
+  @NotYetSupported(Modes.JOIN_TABLE_TABLE)
   public void testJoinWithAliasAndOrderByNoGroupBy()
   {
     Map<String, Object> context = new HashMap<>(QUERY_CONTEXT_DEFAULT);
@@ -5796,8 +5858,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
                         new QueryDataSource(
                             newScanQueryBuilder()
                                 .dataSource(CalciteTests.DATASOURCE3)
-                                .intervals(querySegmentSpec(Intervals.of(
-                                    
"-146136543-09-08T08:23:32.096Z/146140482-04-24T15:36:27.903Z")))
+                                
.intervals(querySegmentSpec(Filtration.eternity()))
                                 .columns("dim2")
                                 .context(context)
                                 .build()
@@ -5970,6 +6031,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
   }
 
   @Test
+  @DecoupledTestConfig(nativeQueryIgnore = 
NativeQueryIgnore.JOIN_FILTER_LOCATIONS)
   public void testJoinWithInputRefCondition()
   {
     cannotVectorize();
@@ -6088,6 +6150,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
   }
 
   @Test
+  @NotYetSupported(Modes.CORRELATE_CONVERSION)
   public void testJoinsWithUnnestOnLeft()
   {
     // Segment map function of MSQ needs some work
@@ -6143,6 +6206,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
   }
 
   @Test
+  @NotYetSupported(Modes.CORRELATE_CONVERSION)
   public void testJoinsWithUnnestOverFilteredDSOnLeft()
   {
     // Segment map function of MSQ needs some work
@@ -6201,6 +6265,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
   }
 
   @Test
+  @NotYetSupported(Modes.CORRELATE_CONVERSION)
   public void testJoinsWithUnnestOverJoin()
   {
     // Segment map function of MSQ needs some work
@@ -6287,6 +6352,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
   }
 
   @Test
+  @NotYetSupported(Modes.CORRELATE_CONVERSION)
   public void testSelfJoinsWithUnnestOnLeftAndRight()
   {
     // Segment map function of MSQ needs some work
@@ -6356,6 +6422,7 @@ public class CalciteJoinQueryTest extends 
BaseCalciteQueryTest
   }
 
   @Test
+  @NotYetSupported(Modes.JOIN_CONDITION_NOT_PUSHED_CONDITION)
   public void testJoinsOverUnnestOverFilterDSOverJoin()
   {
     // Segment map function of MSQ needs some work
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java 
b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java
index d513387cfe9..0a3cd0c7a54 100644
--- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java
+++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java
@@ -2817,7 +2817,6 @@ public class CalciteQueryTest extends BaseCalciteQueryTest
     );
   }
 
-  @NotYetSupported(Modes.PLAN_MISMATCH)
   @Test
   public void testGroupByWithSelectAndOrderByProjections()
   {
@@ -2902,7 +2901,6 @@ public class CalciteQueryTest extends BaseCalciteQueryTest
     );
   }
 
-  @NotYetSupported(Modes.PLAN_MISMATCH)
   @Test
   public void testTopNWithSelectAndOrderByProjections()
   {
@@ -4868,7 +4866,6 @@ public class CalciteQueryTest extends BaseCalciteQueryTest
     );
   }
 
-  @NotYetSupported(Modes.PLAN_MISMATCH)
   @Test
   public void testGroupByWithSortOnPostAggregationDefault()
   {
@@ -4900,7 +4897,6 @@ public class CalciteQueryTest extends BaseCalciteQueryTest
     );
   }
 
-  @NotYetSupported(Modes.PLAN_MISMATCH)
   @Test
   public void testGroupByWithSortOnPostAggregationNoTopNConfig()
   {
@@ -4944,7 +4940,6 @@ public class CalciteQueryTest extends BaseCalciteQueryTest
     );
   }
 
-  @NotYetSupported(Modes.PLAN_MISMATCH)
   @Test
   public void testGroupByWithSortOnPostAggregationNoTopNContext()
   {
@@ -5784,7 +5779,6 @@ public class CalciteQueryTest extends BaseCalciteQueryTest
     );
   }
 
-  @NotYetSupported(Modes.ERROR_HANDLING)
   @Test
   public void testUnplannableJoinQueriesInNonSQLCompatibleMode()
   {
@@ -6931,7 +6925,7 @@ public class CalciteQueryTest extends BaseCalciteQueryTest
     );
   }
 
-  @NotYetSupported(Modes.PLAN_MISMATCH)
+  @DecoupledTestConfig(nativeQueryIgnore = NativeQueryIgnore.AGG_COL_EXCHANGE)
   @Test
   public void testExactCountDistinctWithGroupingAndOtherAggregators()
   {
@@ -6986,7 +6980,7 @@ public class CalciteQueryTest extends BaseCalciteQueryTest
     );
   }
 
-  @NotYetSupported(Modes.MISSING_JOIN_CONVERSION)
+  @DecoupledTestConfig(nativeQueryIgnore = NativeQueryIgnore.AGG_COL_EXCHANGE)
   @Test
   public void 
testMultipleExactCountDistinctWithGroupingAndOtherAggregatorsUsingJoin()
   {
@@ -10515,7 +10509,7 @@ public class CalciteQueryTest extends 
BaseCalciteQueryTest
     );
   }
 
-  @NotYetSupported(Modes.PLAN_MISMATCH)
+  @DecoupledTestConfig(nativeQueryIgnore = NativeQueryIgnore.IMPROVED_PLAN)
   @Test
   public void testGroupByTimeFloorAndDimOnGroupByTimeFloorAndDim()
   {
@@ -12149,7 +12143,6 @@ public class CalciteQueryTest extends 
BaseCalciteQueryTest
     );
   }
 
-  @NotYetSupported(Modes.MISSING_JOIN_CONVERSION)
   @Test
   public void testRequireTimeConditionPositive()
   {
@@ -12178,7 +12171,11 @@ public class CalciteQueryTest extends 
BaseCalciteQueryTest
             new Object[]{3L, timestamp("2001-01-01")}
         )
     );
+  }
 
+  @Test
+  public void testRequireTimeConditionPositive2()
+  {
     // nested GROUP BY only requires time condition for inner most query
     testQuery(
         PLANNER_CONFIG_REQUIRE_TIME_CONDITION,
@@ -12221,7 +12218,13 @@ public class CalciteQueryTest extends 
BaseCalciteQueryTest
             new Object[]{6L, 4L}
         )
     );
+  }
 
+  // __time >= x remains in the join condition
+  @NotYetSupported(Modes.JOIN_CONDITION_NOT_PUSHED_CONDITION)
+  @Test
+  public void testRequireTimeConditionPositive3()
+  {
     // Cannot vectorize next test due to extraction dimension spec.
     cannotVectorize();
 
@@ -12353,7 +12356,6 @@ public class CalciteQueryTest extends 
BaseCalciteQueryTest
     );
   }
 
-  @NotYetSupported(Modes.MISSING_JOIN_CONVERSION2)
   @Test
   public void testRequireTimeConditionSemiJoinNegative()
   {
@@ -14648,7 +14650,6 @@ public class CalciteQueryTest extends 
BaseCalciteQueryTest
     );
   }
 
-  @NotYetSupported(Modes.MISSING_JOIN_CONVERSION)
   @Test
   public void testOrderByAlongWithInternalScanQuery()
   {
@@ -14691,7 +14692,6 @@ public class CalciteQueryTest extends 
BaseCalciteQueryTest
     );
   }
 
-  @NotYetSupported(Modes.MISSING_JOIN_CONVERSION)
   @Test
   public void testOrderByAlongWithInternalScanQueryNoDistinct()
   {
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledPlanningCalciteUnionQueryTest.java
 
b/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledPlanningCalciteJoinQueryTest.java
similarity index 81%
copy from 
sql/src/test/java/org/apache/druid/sql/calcite/DecoupledPlanningCalciteUnionQueryTest.java
copy to 
sql/src/test/java/org/apache/druid/sql/calcite/DecoupledPlanningCalciteJoinQueryTest.java
index e150ba52f30..9787f046be8 100644
--- 
a/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledPlanningCalciteUnionQueryTest.java
+++ 
b/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledPlanningCalciteJoinQueryTest.java
@@ -20,6 +20,7 @@
 package org.apache.druid.sql.calcite;
 
 import com.google.common.collect.ImmutableMap;
+import junitparams.Parameters;
 import org.apache.druid.query.QueryContexts;
 import org.apache.druid.server.security.AuthConfig;
 import org.apache.druid.sql.calcite.NotYetSupported.NotYetSupportedProcessor;
@@ -27,8 +28,13 @@ import org.apache.druid.sql.calcite.planner.PlannerConfig;
 import org.apache.druid.sql.calcite.util.SqlTestFramework;
 import 
org.apache.druid.sql.calcite.util.SqlTestFramework.PlannerComponentSupplier;
 import org.junit.Rule;
+import org.junit.Test;
 
-public class DecoupledPlanningCalciteUnionQueryTest extends 
CalciteUnionQueryTest
+import java.util.Map;
+
+import static org.junit.Assert.assertNotNull;
+
+public class DecoupledPlanningCalciteJoinQueryTest extends CalciteJoinQueryTest
 {
 
   @Rule(order = 0)
@@ -59,7 +65,7 @@ public class DecoupledPlanningCalciteUnionQueryTest extends 
CalciteUnionQueryTes
         .cannotVectorize(cannotVectorize)
         .skipVectorize(skipVectorize);
 
-    DecoupledTestConfig decTestConfig = 
queryFrameworkRule.getDescription().getAnnotation(DecoupledTestConfig.class);
+    DecoupledTestConfig decTestConfig = 
queryFrameworkRule.getAnnotation(DecoupledTestConfig.class);
 
     if (decTestConfig != null && 
decTestConfig.nativeQueryIgnore().isPresent()) {
       builder.verifyNativeQueries(x -> false);
@@ -67,4 +73,13 @@ public class DecoupledPlanningCalciteUnionQueryTest extends 
CalciteUnionQueryTes
 
     return builder;
   }
+
+  @Test
+  @Parameters(source = QueryContextForJoinProvider.class)
+  @DecoupledTestConfig
+  public void ensureDecoupledTestConfigAnnotationWorks(Map<String, Object> 
queryContext)
+  {
+    assertNotNull(queryFrameworkRule.getAnnotation(DecoupledTestConfig.class));
+    assertNotNull(queryContext);
+  }
 }
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledPlanningCalciteQueryTest.java
 
b/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledPlanningCalciteQueryTest.java
index 6dbfe4277bf..cf7d47ee084 100644
--- 
a/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledPlanningCalciteQueryTest.java
+++ 
b/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledPlanningCalciteQueryTest.java
@@ -59,7 +59,7 @@ public class DecoupledPlanningCalciteQueryTest extends 
CalciteQueryTest
         .cannotVectorize(cannotVectorize)
         .skipVectorize(skipVectorize);
 
-    DecoupledTestConfig decTestConfig = 
queryFrameworkRule.getDescription().getAnnotation(DecoupledTestConfig.class);
+    DecoupledTestConfig decTestConfig = 
queryFrameworkRule.getAnnotation(DecoupledTestConfig.class);
 
     if (decTestConfig != null && 
decTestConfig.nativeQueryIgnore().isPresent()) {
       builder.verifyNativeQueries(x -> false);
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledPlanningCalciteUnionQueryTest.java
 
b/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledPlanningCalciteUnionQueryTest.java
index e150ba52f30..1e8c3d0b37d 100644
--- 
a/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledPlanningCalciteUnionQueryTest.java
+++ 
b/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledPlanningCalciteUnionQueryTest.java
@@ -59,7 +59,7 @@ public class DecoupledPlanningCalciteUnionQueryTest extends 
CalciteUnionQueryTes
         .cannotVectorize(cannotVectorize)
         .skipVectorize(skipVectorize);
 
-    DecoupledTestConfig decTestConfig = 
queryFrameworkRule.getDescription().getAnnotation(DecoupledTestConfig.class);
+    DecoupledTestConfig decTestConfig = 
queryFrameworkRule.getAnnotation(DecoupledTestConfig.class);
 
     if (decTestConfig != null && 
decTestConfig.nativeQueryIgnore().isPresent()) {
       builder.verifyNativeQueries(x -> false);
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledTestConfig.java 
b/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledTestConfig.java
index bc1bb9362f2..511db82b76b 100644
--- a/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledTestConfig.java
+++ b/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledTestConfig.java
@@ -20,6 +20,10 @@
 package org.apache.druid.sql.calcite;
 
 import org.apache.calcite.rel.rules.CoreRules;
+import org.apache.druid.query.QueryContexts;
+import 
org.apache.druid.query.aggregation.post.FinalizingFieldAccessPostAggregator;
+import org.apache.druid.query.scan.ScanQuery;
+import org.apache.druid.query.timeseries.TimeseriesQuery;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
@@ -63,7 +67,41 @@ public @interface DecoupledTestConfig
     /**
      * Worse plan; may loose vectorization; but no extra queries
      */
-    SLIGHTLY_WORSE_PLAN;
+    SLIGHTLY_WORSE_PLAN,
+    /**
+     * {@link TimeseriesQuery} to {@link ScanQuery} change.
+     *
+     * Not yet sure if this is improvement; or some issue
+     */
+    TS_TO_SCAN,
+    /**
+     * GroupBy doesn't sorted?!
+     */
+    GBY_DOESNT_SORT,
+    /**
+     * Equvivalent plan.
+     *
+     * Renamed variable
+     */
+    EQUIV_PLAN,
+    /**
+     * {@link QueryContexts#SQL_JOIN_LEFT_SCAN_DIRECT} not supported.
+     */
+    JOIN_LEFT_DIRECT_ACCESS,
+    /**
+     * Different filter layout.
+     *
+     * Filter is pushed below join to the left.
+     */
+    JOIN_FILTER_LOCATIONS,
+    /**
+     * New scans / etc.
+     */
+    DEFINETLY_WORSE_PLAN,
+    /**
+     * A new {@link FinalizingFieldAccessPostAggregator} appeared in the plan.
+     */
+    FINALIZING_FIELD_ACCESS;
 
     public boolean isPresent()
     {
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/NotYetSupported.java 
b/sql/src/test/java/org/apache/druid/sql/calcite/NotYetSupported.java
index 31ff6fd079d..4fe39234fad 100644
--- a/sql/src/test/java/org/apache/druid/sql/calcite/NotYetSupported.java
+++ b/sql/src/test/java/org/apache/druid/sql/calcite/NotYetSupported.java
@@ -20,18 +20,27 @@
 package org.apache.druid.sql.calcite;
 
 import com.google.common.base.Throwables;
+import junitparams.JUnitParamsRunner;
+import org.apache.commons.lang3.RegExUtils;
 import org.apache.druid.error.DruidException;
+import org.apache.druid.java.util.common.ISE;
 import org.junit.AssumptionViolatedException;
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
+import org.junit.runner.RunWith;
 import org.junit.runners.model.Statement;
 
+import java.lang.annotation.Annotation;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import java.lang.reflect.Method;
+import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import static org.junit.Assert.assertThrows;
 
@@ -68,7 +77,6 @@ public @interface NotYetSupported
 
   enum Modes
   {
-    PLAN_MISMATCH(AssertionError.class, "AssertionError: query #"),
     NOT_ENOUGH_RULES(DruidException.class, "not enough rules"),
     ERROR_HANDLING(AssertionError.class, "targetPersona: is <[A-Z]+> and 
category: is <[A-Z_]+> and errorCode: is"),
     EXPRESSION_NOT_GROUPED(DruidException.class, "Expression '[a-z]+' is not 
being grouped"),
@@ -83,12 +91,17 @@ public @interface NotYetSupported
     INCORRECT_SYNTAX(DruidException.class, "Incorrect syntax near the 
keyword"),
     // at least c7 is represented oddly in the parquet file
     T_ALLTYPES_ISSUES(AssertionError.class, 
"(t_alltype|allTypsUniq|fewRowsAllData).parquet.*Verifier.verify"),
-    RESULT_MISMATCH(AssertionError.class, 
"(assertResultsEquals|AssertionError: column content mismatch)"),
+    RESULT_MISMATCH(AssertionError.class, "(assertResulEquals|AssertionError: 
column content mismatch)"),
     UNSUPPORTED_NULL_ORDERING(DruidException.class, "(A|DE)SCENDING ordering 
with NULLS (LAST|FIRST)"),
-    MISSING_JOIN_CONVERSION(DruidException.class, "Missing conversions? 
(was|is) (Logical)?Join"),
-    MISSING_JOIN_CONVERSION2(AssertionError.class, "Missing conversions? 
(was|is) (Logical)?Join"),
     UNION_WITH_COMPLEX_OPERAND(DruidException.class, "Only Table and Values 
are supported as inputs for Union"),
-    UNION_MORE_STRICT_ROWTYPE_CHECK(DruidException.class, "Row signature 
mismatch in Union inputs");
+    UNION_MORE_STRICT_ROWTYPE_CHECK(DruidException.class, "Row signature 
mismatch in Union inputs"),
+    JOIN_CONDITION_NOT_PUSHED_CONDITION(DruidException.class, "SQL requires a 
join with '.*' condition"),
+    JOIN_CONDITION_UNSUPORTED_OPERAND(DruidException.class, "SQL .* 
unsupported operand type"),
+    JOIN_TABLE_TABLE(ISE.class, "Cannot handle subquery structure for 
dataSource: JoinDataSource"),
+    CORRELATE_CONVERSION(DruidException.class, "Missing conversion( is|s are) 
LogicalCorrelate"),
+    SORT_REMOVE_TROUBLE(DruidException.class, "Calcite assertion 
violated.*Sort\\.<init>"),
+    STACK_OVERFLOW(StackOverflowError.class, ""),
+    CANNOT_JOIN_LOOKUP_NON_KEY(RuntimeException.class, "Cannot join lookup 
with condition referring to non-key");
 
     public Class<? extends Throwable> throwableClass;
     public String regex;
@@ -116,7 +129,7 @@ public @interface NotYetSupported
     @Override
     public Statement apply(Statement base, Description description)
     {
-      NotYetSupported annotation = 
description.getAnnotation(NotYetSupported.class);
+      NotYetSupported annotation = getAnnotation(description, 
NotYetSupported.class);
 
       if (annotation == null) {
         return base;
@@ -159,5 +172,38 @@ public @interface NotYetSupported
         }
       };
     }
+
+    private static Method getMethodForName(Class<?> testClass, String 
realMethodName)
+    {
+      List<Method> matches = Stream.of(testClass.getMethods())
+          .filter(m -> realMethodName.equals(m.getName()))
+          .collect(Collectors.toList());
+      switch (matches.size()) {
+        case 0:
+          throw new IllegalArgumentException("Expected to find method...but 
there is none?");
+        case 1:
+          return matches.get(0);
+        default:
+          throw new IllegalArgumentException("method overrides are not 
supported");
+      }
+    }
+
+    public static <T extends Annotation> T getAnnotation(Description 
description, Class<T> annotationType)
+    {
+      T annotation = description.getAnnotation(annotationType);
+      if (annotation != null) {
+        return annotation;
+      }
+      Class<?> testClass = description.getTestClass();
+      RunWith runWith = testClass.getAnnotation(RunWith.class);
+      if (runWith == null || !runWith.value().equals(JUnitParamsRunner.class)) 
{
+        return null;
+      }
+      String mehodName = description.getMethodName();
+      String realMethodName = RegExUtils.replaceAll(mehodName, "\\(.*", "");
+
+      Method m = getMethodForName(testClass, realMethodName);
+      return m.getAnnotation(annotationType);
+    }
   }
 }
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/SqlTestFrameworkConfig.java 
b/sql/src/test/java/org/apache/druid/sql/calcite/SqlTestFrameworkConfig.java
index 3a169560b6e..cb66572c53b 100644
--- a/sql/src/test/java/org/apache/druid/sql/calcite/SqlTestFrameworkConfig.java
+++ b/sql/src/test/java/org/apache/druid/sql/calcite/SqlTestFrameworkConfig.java
@@ -20,6 +20,7 @@
 package org.apache.druid.sql.calcite;
 
 import org.apache.druid.query.topn.TopNQueryConfig;
+import org.apache.druid.sql.calcite.NotYetSupported.NotYetSupportedProcessor;
 import org.apache.druid.sql.calcite.util.CacheTestHelperModule.ResultCacheMode;
 import org.apache.druid.sql.calcite.util.SqlTestFramework;
 import 
org.apache.druid.sql.calcite.util.SqlTestFramework.QueryComponentSupplier;
@@ -27,6 +28,7 @@ import org.junit.rules.ExternalResource;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
+import java.lang.annotation.Annotation;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -119,9 +121,9 @@ public @interface SqlTestFrameworkConfig
       return getConfigurationInstance().framework;
     }
 
-    public Description getDescription()
+    public <T extends Annotation> T getAnnotation(Class<T> annotationType)
     {
-      return description;
+      return NotYetSupportedProcessor.getAnnotation(description, 
annotationType);
     }
 
     private ConfigurationInstance getConfigurationInstance()
@@ -133,12 +135,10 @@ public @interface SqlTestFrameworkConfig
     {
       return new ConfigurationInstance(config, testHost);
     }
-
   }
 
   class ConfigurationInstance
   {
-
     public SqlTestFramework framework;
 
     ConfigurationInstance(SqlTestFrameworkConfig config, 
QueryComponentSupplier testHost)
@@ -156,5 +156,4 @@ public @interface SqlTestFrameworkConfig
       framework.close();
     }
   }
-
 }
diff --git a/sql/src/test/java/org/apache/druid/sql/http/ResultFormatTest.java 
b/sql/src/test/java/org/apache/druid/sql/http/ResultFormatTest.java
index 9f9f8400ffd..37ee6de032a 100644
--- a/sql/src/test/java/org/apache/druid/sql/http/ResultFormatTest.java
+++ b/sql/src/test/java/org/apache/druid/sql/http/ResultFormatTest.java
@@ -21,25 +21,21 @@ package org.apache.druid.sql.http;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
-import junitparams.JUnitParamsRunner;
-import junitparams.Parameters;
-import org.apache.druid.annotations.UsedByJUnitParamsRunner;
 import org.apache.druid.jackson.DefaultObjectMapper;
 import org.apache.druid.java.util.common.StringUtils;
 import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
 
 import java.util.EnumSet;
 
-@RunWith(JUnitParamsRunner.class)
 public class ResultFormatTest
 {
-
   private final ObjectMapper jsonMapper = new DefaultObjectMapper();
 
-  @Test
-  @Parameters(source = ResultFormatTypeProvider.class)
+  @ParameterizedTest
+  @MethodSource("provideResultFormats")
   public void testSerde(ResultFormat target) throws JsonProcessingException
   {
     final String json = jsonMapper.writeValueAsString(target);
@@ -56,15 +52,11 @@ public class ResultFormatTest
     Assert.assertEquals(ResultFormat.OBJECTLINES, 
jsonMapper.readValue("\"oBjEcTlInEs\"", ResultFormat.class));
   }
 
-  public static class ResultFormatTypeProvider
+  public static Object[] provideResultFormats()
   {
-    @UsedByJUnitParamsRunner
-    public static Object[] provideResultFormats()
-    {
-      return EnumSet.allOf(ResultFormat.class)
-                    .stream()
-                    .map(format -> new Object[]{format})
-                    .toArray(Object[]::new);
-    }
+    return EnumSet.allOf(ResultFormat.class)
+        .stream()
+        .map(format -> new Object[] {format})
+        .toArray(Object[]::new);
   }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@druid.apache.org
For additional commands, e-mail: commits-h...@druid.apache.org


Reply via email to