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

kgyrtkirk 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 c1ef38b0526 Minor fixes and enhancements in UnionQuery handling 
(#17483)
c1ef38b0526 is described below

commit c1ef38b0526554bceedd78001aed8e9fb86ef126
Author: Zoltan Haindrich <[email protected]>
AuthorDate: Thu Nov 28 10:05:12 2024 +0100

    Minor fixes and enhancements in UnionQuery handling (#17483)
    
    * plan consistently with either UnionDataSource or UnionQuery for decoupled 
mode
    * expose errors
    * move decoupled related setting from PlannerConfig to QueryContexts
---
 .../druid/msq/test/CalciteUnionQueryMSQTest.java   |   3 +-
 .../java/org/apache/druid/query/QueryContext.java  |   9 +
 .../java/org/apache/druid/query/QueryContexts.java |   5 +
 .../org/apache/druid/query/UnionDataSource.java    |   5 +
 .../druid/query/timeseries/TimeseriesQuery.java    |   3 +-
 .../apache/druid/query/union/UnionQueryLogic.java  |   1 +
 .../org/apache/druid/query/QueryContextTest.java   |  22 +++
 .../apache/druid/query/UnionDataSourceTest.java    |  15 +-
 sql/pom.xml                                        |   9 +-
 .../druid/sql/calcite/planner/PlannerConfig.java   |   8 +-
 .../druid/sql/calcite/planner/PlannerContext.java  |   4 +
 .../druid/sql/calcite/planner/PlannerFactory.java  |   3 +-
 .../druid/sql/calcite/planner/QueryHandler.java    |   3 +-
 .../planner/querygen/DruidQueryGenerator.java      | 148 ++++++++++++----
 .../planner/querygen/SourceDescProducer.java       |   7 +
 .../apache/druid/sql/calcite/rel/DruidQuery.java   |  19 ++-
 .../druid/sql/calcite/rel/logical/DruidUnion.java  |  93 ++++------
 .../sql/calcite/CalciteNestedDataQueryTest.java    |   4 +-
 .../apache/druid/sql/calcite/CalciteQueryTest.java |   2 -
 .../druid/sql/calcite/CalciteUnionQueryTest.java   | 190 ++++++++++++++++-----
 .../druid/sql/calcite/DecoupledExtension.java      |   2 +-
 .../apache/druid/sql/calcite/NotYetSupported.java  |   3 -
 .../apache/druid/sql/calcite/QueryTestBuilder.java |   4 +-
 23 files changed, 388 insertions(+), 174 deletions(-)

diff --git 
a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteUnionQueryMSQTest.java
 
b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteUnionQueryMSQTest.java
index f0686498786..a470615fd0d 100644
--- 
a/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteUnionQueryMSQTest.java
+++ 
b/extensions-core/multi-stage-query/src/test/java/org/apache/druid/msq/test/CalciteUnionQueryMSQTest.java
@@ -62,13 +62,12 @@ public class CalciteUnionQueryMSQTest extends 
CalciteUnionQueryTest
    */
   @Test
   @Override
-  public void testUnionIsUnplannable()
+  public void testUnionDifferentColumnOrder()
   {
     assertQueryIsUnplannable(
         "SELECT dim2, dim1, m1 FROM foo2 UNION SELECT dim1, dim2, m1 FROM foo",
         "SQL requires union between two tables and column names queried for 
each table are different Left: [dim2, dim1, m1], Right: [dim1, dim2, m1]."
     );
-
   }
 
   @Disabled("Ignored till MSQ can plan UNION ALL with any operand")
diff --git a/processing/src/main/java/org/apache/druid/query/QueryContext.java 
b/processing/src/main/java/org/apache/druid/query/QueryContext.java
index d44e0153f0b..1a79f524d5c 100644
--- a/processing/src/main/java/org/apache/druid/query/QueryContext.java
+++ b/processing/src/main/java/org/apache/druid/query/QueryContext.java
@@ -661,4 +661,13 @@ public class QueryContext
            "context=" + context +
            '}';
   }
+
+  public boolean isDecoupledMode()
+  {
+    String value = getString(
+        QueryContexts.CTX_NATIVE_QUERY_SQL_PLANNING_MODE,
+        QueryContexts.NATIVE_QUERY_SQL_PLANNING_MODE_COUPLED
+    );
+    return 
QueryContexts.NATIVE_QUERY_SQL_PLANNING_MODE_DECOUPLED.equals(value);
+  }
 }
diff --git a/processing/src/main/java/org/apache/druid/query/QueryContexts.java 
b/processing/src/main/java/org/apache/druid/query/QueryContexts.java
index 759a9b49437..950dea52e90 100644
--- a/processing/src/main/java/org/apache/druid/query/QueryContexts.java
+++ b/processing/src/main/java/org/apache/druid/query/QueryContexts.java
@@ -29,6 +29,7 @@ import org.apache.druid.java.util.common.Numbers;
 import org.apache.druid.java.util.common.StringUtils;
 
 import javax.annotation.Nullable;
+
 import java.math.BigDecimal;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -105,6 +106,10 @@ public class QueryContexts
   // SQL statement resource specific keys
   public static final String CTX_EXECUTION_MODE = "executionMode";
 
+  public static final String CTX_NATIVE_QUERY_SQL_PLANNING_MODE = 
"plannerStrategy";
+  public static final String NATIVE_QUERY_SQL_PLANNING_MODE_COUPLED = 
"COUPLED";
+  public static final String NATIVE_QUERY_SQL_PLANNING_MODE_DECOUPLED = 
"DECOUPLED";
+
   // Defaults
   public static final boolean DEFAULT_BY_SEGMENT = false;
   public static final boolean DEFAULT_POPULATE_CACHE = true;
diff --git 
a/processing/src/main/java/org/apache/druid/query/UnionDataSource.java 
b/processing/src/main/java/org/apache/druid/query/UnionDataSource.java
index 27a0113d76f..0384ecbc885 100644
--- a/processing/src/main/java/org/apache/druid/query/UnionDataSource.java
+++ b/processing/src/main/java/org/apache/druid/query/UnionDataSource.java
@@ -198,4 +198,9 @@ public class UnionDataSource implements DataSource
            "dataSources=" + dataSources +
            '}';
   }
+
+  public static boolean isCompatibleDataSource(DataSource dataSource)
+  {
+    return (dataSource instanceof TableDataSource || dataSource instanceof 
InlineDataSource);
+  }
 }
diff --git 
a/processing/src/main/java/org/apache/druid/query/timeseries/TimeseriesQuery.java
 
b/processing/src/main/java/org/apache/druid/query/timeseries/TimeseriesQuery.java
index b5e135968a8..b2165cd5b70 100644
--- 
a/processing/src/main/java/org/apache/druid/query/timeseries/TimeseriesQuery.java
+++ 
b/processing/src/main/java/org/apache/druid/query/timeseries/TimeseriesQuery.java
@@ -185,14 +185,13 @@ public class TimeseriesQuery extends 
BaseQuery<Result<TimeseriesResultValue>>
   @Override
   public RowSignature getResultRowSignature(Finalization finalization)
   {
-    final Finalization finalization1 = finalization;
     final RowSignature.Builder builder = RowSignature.builder();
     builder.addTimeColumn();
     String timestampResultField = getTimestampResultField();
     if (StringUtils.isNotEmpty(timestampResultField)) {
       builder.add(timestampResultField, ColumnType.LONG);
     }
-    builder.addAggregators(aggregatorSpecs, finalization1);
+    builder.addAggregators(aggregatorSpecs, finalization);
     builder.addPostAggregators(postAggregatorSpecs);
     return builder.build();
   }
diff --git 
a/processing/src/main/java/org/apache/druid/query/union/UnionQueryLogic.java 
b/processing/src/main/java/org/apache/druid/query/union/UnionQueryLogic.java
index 42f031487a2..750e369054f 100644
--- a/processing/src/main/java/org/apache/druid/query/union/UnionQueryLogic.java
+++ b/processing/src/main/java/org/apache/druid/query/union/UnionQueryLogic.java
@@ -96,6 +96,7 @@ public class UnionQueryLogic implements QueryLogic
         Sequence run = runner.run(queryPlus.withQuery(q), responseContext);
         seqs.add(run);
       }
+
       return Sequences.concat(seqs);
     }
   }
diff --git 
a/processing/src/test/java/org/apache/druid/query/QueryContextTest.java 
b/processing/src/test/java/org/apache/druid/query/QueryContextTest.java
index d0d6a512627..0b32c391cc8 100644
--- a/processing/src/test/java/org/apache/druid/query/QueryContextTest.java
+++ b/processing/src/test/java/org/apache/druid/query/QueryContextTest.java
@@ -372,6 +372,28 @@ public class QueryContextTest
     assertTrue(QueryContext.of(ImmutableMap.of(QueryContexts.ENABLE_DEBUG, 
true)).isDebug());
   }
 
+  @Test
+  public void testIsDecoupled()
+  {
+    assertFalse(QueryContext.empty().isDecoupledMode());
+    assertTrue(
+        QueryContext.of(
+            ImmutableMap.of(
+                QueryContexts.CTX_NATIVE_QUERY_SQL_PLANNING_MODE,
+                QueryContexts.NATIVE_QUERY_SQL_PLANNING_MODE_DECOUPLED
+            )
+        ).isDecoupledMode()
+    );
+    assertFalse(
+        QueryContext.of(
+            ImmutableMap.of(
+                QueryContexts.CTX_NATIVE_QUERY_SQL_PLANNING_MODE,
+                "garbage"
+            )
+        ).isDecoupledMode()
+    );
+  }
+
   // This test is a bit silly. It is retained because another test uses the
   // LegacyContextQuery test.
   @Test
diff --git 
a/processing/src/test/java/org/apache/druid/query/UnionDataSourceTest.java 
b/processing/src/test/java/org/apache/druid/query/UnionDataSourceTest.java
index 12522df08df..864a7442926 100644
--- a/processing/src/test/java/org/apache/druid/query/UnionDataSourceTest.java
+++ b/processing/src/test/java/org/apache/druid/query/UnionDataSourceTest.java
@@ -24,6 +24,8 @@ import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import nl.jqno.equalsverifier.EqualsVerifier;
 import org.apache.druid.segment.TestHelper;
+import org.apache.druid.segment.column.RowSignature;
+import org.apache.druid.segment.join.NoopDataSource;
 import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
@@ -62,6 +64,17 @@ public class UnionDataSourceTest
     new UnionDataSource(Collections.emptyList());
   }
 
+  @Test
+  public void test_isCompatible()
+  {
+    TableDataSource tableDataSource = new TableDataSource("foo");
+    InlineDataSource inlineDataSource = 
InlineDataSource.fromIterable(Collections.emptyList(), RowSignature.empty());
+
+    Assert.assertTrue(UnionDataSource.isCompatibleDataSource(tableDataSource));
+    
Assert.assertTrue(UnionDataSource.isCompatibleDataSource(inlineDataSource));
+    Assert.assertFalse(UnionDataSource.isCompatibleDataSource(new 
NoopDataSource()));
+  }
+
   @Test
   public void test_getTableNames()
   {
@@ -131,7 +144,7 @@ public class UnionDataSourceTest
     //noinspection unchecked
     Assert.assertEquals(
         new UnionDataSource(newDataSources),
-        unionDataSource.withChildren((List) newDataSources)
+        unionDataSource.withChildren(newDataSources)
     );
   }
 
diff --git a/sql/pom.xml b/sql/pom.xml
index 20bf288d90d..1c68ad0c0ed 100644
--- a/sql/pom.xml
+++ b/sql/pom.xml
@@ -165,6 +165,10 @@
       <groupId>org.apache.calcite.avatica</groupId>
       <artifactId>avatica-metrics</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-lang3</artifactId>
+    </dependency>
     <dependency>
       <groupId>org.checkerframework</groupId>
       <artifactId>checker-qual</artifactId>
@@ -239,11 +243,6 @@
       <version>1.3.0</version>
       <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/PlannerConfig.java 
b/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerConfig.java
index 6e2e6685a08..7fb10041734 100644
--- a/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerConfig.java
+++ b/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerConfig.java
@@ -38,9 +38,7 @@ public class PlannerConfig
   public static final String CTX_KEY_USE_NATIVE_QUERY_EXPLAIN = 
"useNativeQueryExplain";
   public static final String CTX_KEY_FORCE_EXPRESSION_VIRTUAL_COLUMNS = 
"forceExpressionVirtualColumns";
   public static final String CTX_MAX_NUMERIC_IN_FILTERS = 
"maxNumericInFilters";
-  public static final String CTX_NATIVE_QUERY_SQL_PLANNING_MODE = 
"plannerStrategy";
   public static final int NUM_FILTER_NOT_USED = -1;
-
   @JsonProperty
   private int maxTopNLimit = 100_000;
 
@@ -75,9 +73,7 @@ public class PlannerConfig
   private int maxNumericInFilters = NUM_FILTER_NOT_USED;
 
   @JsonProperty
-  private String nativeQuerySqlPlanningMode = 
NATIVE_QUERY_SQL_PLANNING_MODE_COUPLED; // can be COUPLED or DECOUPLED
-  public static final String NATIVE_QUERY_SQL_PLANNING_MODE_COUPLED = 
"COUPLED";
-  public static final String NATIVE_QUERY_SQL_PLANNING_MODE_DECOUPLED = 
"DECOUPLED";
+  private String nativeQuerySqlPlanningMode = 
QueryContexts.NATIVE_QUERY_SQL_PLANNING_MODE_COUPLED; // can be COUPLED or 
DECOUPLED
 
   private boolean serializeComplexValues = true;
 
@@ -383,7 +379,7 @@ public class PlannerConfig
           maxNumericInFilters);
       nativeQuerySqlPlanningMode = QueryContexts.parseString(
           queryContext,
-          CTX_NATIVE_QUERY_SQL_PLANNING_MODE,
+          QueryContexts.CTX_NATIVE_QUERY_SQL_PLANNING_MODE,
           nativeQuerySqlPlanningMode
       );
       return this;
diff --git 
a/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerContext.java 
b/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerContext.java
index cd2e9401954..c02cb3cd8bd 100644
--- a/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerContext.java
+++ b/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerContext.java
@@ -29,6 +29,7 @@ import org.apache.calcite.avatica.remote.TypedValue;
 import org.apache.calcite.linq4j.QueryProvider;
 import org.apache.calcite.schema.SchemaPlus;
 import org.apache.druid.common.config.NullHandling;
+import org.apache.druid.error.InvalidSqlInput;
 import org.apache.druid.java.util.common.DateTimes;
 import org.apache.druid.java.util.common.ISE;
 import org.apache.druid.java.util.common.Numbers;
@@ -508,6 +509,9 @@ public class PlannerContext
    */
   public void setPlanningError(String formatText, Object... arguments)
   {
+    if (queryContext().isDecoupledMode()) {
+      throw InvalidSqlInput.exception(formatText, arguments);
+    }
     planningError = StringUtils.nonStrictFormat(formatText, arguments);
   }
 
diff --git 
a/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerFactory.java 
b/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerFactory.java
index 514952972d3..c21f6408b52 100644
--- a/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerFactory.java
+++ b/sql/src/main/java/org/apache/druid/sql/calcite/planner/PlannerFactory.java
@@ -39,6 +39,7 @@ import org.apache.calcite.tools.FrameworkConfig;
 import org.apache.calcite.tools.Frameworks;
 import org.apache.druid.guice.annotations.Json;
 import org.apache.druid.math.expr.ExprMacroTable;
+import org.apache.druid.query.QueryContexts;
 import org.apache.druid.segment.join.JoinableFactoryWrapper;
 import org.apache.druid.server.security.Access;
 import org.apache.druid.server.security.AuthConfig;
@@ -196,7 +197,7 @@ public class PlannerFactory extends PlannerToolbox
           }
         });
 
-    if (PlannerConfig.NATIVE_QUERY_SQL_PLANNING_MODE_DECOUPLED
+    if (QueryContexts.NATIVE_QUERY_SQL_PLANNING_MODE_DECOUPLED
         .equals(plannerConfig().getNativeQuerySqlPlanningMode())
     ) {
       frameworkConfigBuilder.costFactory(new DruidVolcanoCost.Factory());
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 2fca51e8dc0..67cc7f2f44c 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
@@ -62,6 +62,7 @@ import org.apache.druid.java.util.common.guava.BaseSequence;
 import org.apache.druid.java.util.common.guava.Sequences;
 import org.apache.druid.java.util.emitter.EmittingLogger;
 import org.apache.druid.query.Query;
+import org.apache.druid.query.QueryContexts;
 import org.apache.druid.server.QueryResponse;
 import org.apache.druid.server.security.Action;
 import org.apache.druid.server.security.Resource;
@@ -540,7 +541,7 @@ public abstract class QueryHandler extends 
SqlStatementHandler.BaseStatementHand
 
     if (plannerContext.getPlannerConfig()
                       .getNativeQuerySqlPlanningMode()
-                      
.equals(PlannerConfig.NATIVE_QUERY_SQL_PLANNING_MODE_DECOUPLED)
+                      
.equals(QueryContexts.NATIVE_QUERY_SQL_PLANNING_MODE_DECOUPLED)
     ) {
       RelNode newRoot = parameterized;
       newRoot = planner.transform(
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 511065b2c4f..280d2d85cd5 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
@@ -27,10 +27,13 @@ import org.apache.calcite.rel.core.Project;
 import org.apache.calcite.rel.core.Sort;
 import org.apache.calcite.rel.core.Window;
 import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexNode;
 import org.apache.druid.error.DruidException;
 import org.apache.druid.query.DataSource;
 import org.apache.druid.query.FilteredDataSource;
 import org.apache.druid.query.QueryDataSource;
+import org.apache.druid.query.UnionDataSource;
 import org.apache.druid.query.filter.DimFilter;
 import org.apache.druid.sql.calcite.filtration.Filtration;
 import org.apache.druid.sql.calcite.planner.PlannerContext;
@@ -43,6 +46,7 @@ import 
org.apache.druid.sql.calcite.rel.logical.DruidAggregate;
 import org.apache.druid.sql.calcite.rel.logical.DruidJoin;
 import org.apache.druid.sql.calcite.rel.logical.DruidLogicalNode;
 import org.apache.druid.sql.calcite.rel.logical.DruidSort;
+import org.apache.druid.sql.calcite.rel.logical.DruidUnion;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -202,31 +206,22 @@ public class DruidQueryGenerator
     SourceDesc unwrapSourceDesc();
   }
 
-  enum JoinSupportTweaks
+  private static class VertexTweaks
   {
-    NONE,
-    LEFT,
-    RIGHT;
+    public final JoinPosition joinType;
+    public final boolean isParentUnion;
 
-    static JoinSupportTweaks analyze(DruidNodeStack stack)
+    public VertexTweaks(JoinPosition joinType, boolean isParentUnion)
     {
-      if (stack.size() < 2) {
-        return NONE;
-      }
-      DruidLogicalNode possibleJoin = stack.parentNode();
-      if (!(possibleJoin instanceof DruidJoin)) {
-        return NONE;
-      }
-      if (stack.peekOperandIndex() == 0) {
-        return LEFT;
-      } else {
-        return RIGHT;
-      }
+      this.joinType = joinType;
+      this.isParentUnion = isParentUnion;
     }
 
-    boolean finalizeSubQuery()
+    static VertexTweaks analyze(DruidNodeStack stack)
     {
-      return this == NONE;
+      JoinPosition joinType = JoinPosition.analyze(stack);
+      boolean isParentUnion = stack.size() > 2 && stack.parentNode() 
instanceof DruidUnion;
+      return new VertexTweaks(joinType, isParentUnion);
     }
 
     boolean forceSubQuery(SourceDesc sourceDesc)
@@ -234,12 +229,43 @@ public class DruidQueryGenerator
       if (sourceDesc.dataSource.isGlobal()) {
         return false;
       }
-      return this == RIGHT;
+      return joinType == JoinPosition.RIGHT;
     }
 
     boolean filteredDatasourceAllowed()
     {
-      return this == NONE;
+      return joinType == JoinPosition.NONE;
+    }
+
+    boolean finalizeSubQuery()
+    {
+      return joinType == JoinPosition.NONE;
+    }
+
+    boolean mayUnwrapWithRename()
+    {
+      return !isParentUnion;
+    }
+
+    enum JoinPosition
+    {
+      NONE, LEFT, RIGHT;
+
+      public static JoinPosition analyze(DruidNodeStack stack)
+      {
+        if (stack.size() < 2) {
+          return NONE;
+        }
+        DruidLogicalNode possibleJoin = stack.parentNode();
+        if (!(possibleJoin instanceof DruidJoin)) {
+          return NONE;
+        }
+        if (stack.peekOperandIndex() == 0) {
+          return LEFT;
+        } else {
+          return RIGHT;
+        }
+      }
     }
   }
 
@@ -259,22 +285,22 @@ public class DruidQueryGenerator
 
     Vertex createVertex(DruidNodeStack stack, PartialDruidQuery 
partialDruidQuery, List<Vertex> inputs)
     {
-      JoinSupportTweaks jst = JoinSupportTweaks.analyze(stack);
-      return new PDQVertex(partialDruidQuery, inputs, jst);
+      VertexTweaks tweaks = VertexTweaks.analyze(stack);
+      return new PDQVertex(partialDruidQuery, inputs, tweaks);
     }
 
     public class PDQVertex implements Vertex
     {
       final PartialDruidQuery partialDruidQuery;
       final List<Vertex> inputs;
-      final JoinSupportTweaks jst;
+      final VertexTweaks tweaks;
       private SourceDesc source;
 
-      public PDQVertex(PartialDruidQuery partialDruidQuery, List<Vertex> 
inputs, JoinSupportTweaks jst)
+      public PDQVertex(PartialDruidQuery partialDruidQuery, List<Vertex> 
inputs, VertexTweaks tweaks)
       {
         this.partialDruidQuery = partialDruidQuery;
         this.inputs = inputs;
-        this.jst = jst;
+        this.tweaks = tweaks;
       }
 
       @Override
@@ -286,7 +312,7 @@ public class DruidQueryGenerator
             source.rowSignature,
             plannerContext,
             rexBuilder,
-            !(topLevel) && jst.finalizeSubQuery()
+            !(topLevel) && tweaks.finalizeSubQuery()
         );
       }
 
@@ -304,9 +330,10 @@ public class DruidQueryGenerator
       private SourceDesc realGetSource()
       {
         List<SourceDesc> sourceDescs = new ArrayList<>();
+        boolean mayUnwrap = mayUnwrapInputs();
         for (Vertex inputVertex : inputs) {
           final SourceDesc desc;
-          if (inputVertex.canUnwrapSourceDesc()) {
+          if (mayUnwrap && inputVertex.canUnwrapSourceDesc()) {
             desc = inputVertex.unwrapSourceDesc();
           } else {
             DruidQuery inputQuery = inputVertex.buildQuery(false);
@@ -325,6 +352,20 @@ public class DruidQueryGenerator
         throw DruidException.defensive("Unable to create SourceDesc for 
Operator [%s]", scan);
       }
 
+      private boolean mayUnwrapInputs()
+      {
+        if (!(partialDruidQuery.getScan() instanceof DruidUnion)) {
+          return true;
+        }
+        boolean mayUnwrap = true;
+        for (Vertex vertex : inputs) {
+          if (!vertex.canUnwrapSourceDesc()) {
+            mayUnwrap = false;
+          }
+        }
+        return mayUnwrap;
+      }
+
       /**
        * Extends the the current partial query with the new parent if possible.
        */
@@ -424,22 +465,65 @@ public class DruidQueryGenerator
       @Override
       public boolean canUnwrapSourceDesc()
       {
-        if (jst.forceSubQuery(getSource())) {
+        if (tweaks.forceSubQuery(getSource())) {
           return false;
         }
         if (partialDruidQuery.stage() == Stage.SCAN) {
           return true;
         }
-        if (jst.filteredDatasourceAllowed() && partialDruidQuery.stage() == 
PartialDruidQuery.Stage.WHERE_FILTER) {
+        if (tweaks.filteredDatasourceAllowed() && partialDruidQuery.stage() == 
PartialDruidQuery.Stage.WHERE_FILTER) {
           return true;
         }
         if (partialDruidQuery.stage() == 
PartialDruidQuery.Stage.SELECT_PROJECT &&
-            (jst.filteredDatasourceAllowed() || 
partialDruidQuery.getWhereFilter() == null) &&
-            partialDruidQuery.getSelectProject().isMapping()) {
+            (tweaks.filteredDatasourceAllowed() || 
partialDruidQuery.getWhereFilter() == null) &&
+            mayDiscardSelectProject()) {
           return true;
         }
         return false;
       }
+
+      private boolean mayDiscardSelectProject()
+      {
+        if (!partialDruidQuery.getSelectProject().isMapping()) {
+          return false;
+        }
+        if (!tweaks.isParentUnion) {
+          return true;
+        }
+        SourceDesc src = getSource();
+        List<String> inputFieldNames = src.rowSignature.getColumnNames();
+        List<String> outputFieldNames = 
partialDruidQuery.getRowType().getFieldNames();
+
+        if (!isNameConsistentMapping(partialDruidQuery.getSelectProject(), 
inputFieldNames, outputFieldNames)) {
+          return false;
+        }
+
+        boolean isAssociative = 
UnionDataSource.isCompatibleDataSource(src.dataSource);
+
+        if (!isAssociative) {
+          if (!outputFieldNames.equals(inputFieldNames.subList(0, 
outputFieldNames.size()))) {
+            return false;
+          }
+        }
+        return true;
+      }
+
+      private boolean isNameConsistentMapping(
+          Project selectProject,
+          List<String> inputFieldNames,
+          List<String> outputFieldNames)
+      {
+        List<RexNode> projects = selectProject.getProjects();
+        for (int i = 0; i < projects.size(); i++) {
+          RexInputRef p = (RexInputRef) projects.get(i);
+          String inputName = inputFieldNames.get(p.getIndex());
+          String outputName = outputFieldNames.get(i);
+          if (!inputName.equals(outputName)) {
+            return false;
+          }
+        }
+        return true;
+      }
     }
   }
 
diff --git 
a/sql/src/main/java/org/apache/druid/sql/calcite/planner/querygen/SourceDescProducer.java
 
b/sql/src/main/java/org/apache/druid/sql/calcite/planner/querygen/SourceDescProducer.java
index 5e2fa2dc4d0..7816e5db383 100644
--- 
a/sql/src/main/java/org/apache/druid/sql/calcite/planner/querygen/SourceDescProducer.java
+++ 
b/sql/src/main/java/org/apache/druid/sql/calcite/planner/querygen/SourceDescProducer.java
@@ -19,6 +19,7 @@
 
 package org.apache.druid.sql.calcite.planner.querygen;
 
+import org.apache.commons.lang3.builder.ToStringBuilder;
 import org.apache.druid.query.DataSource;
 import org.apache.druid.segment.column.RowSignature;
 import org.apache.druid.sql.calcite.planner.PlannerContext;
@@ -55,6 +56,12 @@ public interface SourceDescProducer
       this.rowSignature = rowSignature;
       this.virtualColumnRegistry = virtualColumnRegistry;
     }
+
+    @Override
+    public String toString()
+    {
+      return ToStringBuilder.reflectionToString(this);
+    }
   }
 
   SourceDesc getSourceDesc(PlannerContext plannerContext, List<SourceDesc> 
sources);
diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/rel/DruidQuery.java 
b/sql/src/main/java/org/apache/druid/sql/calcite/rel/DruidQuery.java
index 4470ebde361..5d6fc2d2899 100644
--- a/sql/src/main/java/org/apache/druid/sql/calcite/rel/DruidQuery.java
+++ b/sql/src/main/java/org/apache/druid/sql/calcite/rel/DruidQuery.java
@@ -1654,21 +1654,24 @@ public class DruidQuery
                   : Order.ASCENDING
               )
       ).collect(Collectors.toList());
+
     } else {
       orderByColumns = Collections.emptyList();
     }
 
     if (!plannerContext.featureAvailable(EngineFeature.SCAN_ORDER_BY_NON_TIME) 
&& !orderByColumns.isEmpty()) {
       if (orderByColumns.size() > 1
-          || orderByColumns.stream()
-                           .anyMatch(orderBy -> 
!orderBy.getColumnName().equals(ColumnHolder.TIME_COLUMN_NAME))) {
-        // We cannot handle this ordering, but we encounter this ordering as 
part of the exploration of the volcano
-        // planner, which means that the query that we are looking right now 
might only be doing this as one of the
-        // potential branches of exploration rather than being a semantic 
requirement of the query itself.  So, it is
-        // not safe to send an error message telling the end-user exactly what 
is happening, instead we need to set the
-        // planning error and hope.
-        setPlanningErrorOrderByNonTimeIsUnsupported();
+          || orderByColumns.stream().anyMatch(orderBy -> 
!orderBy.getColumnName().equals(ColumnHolder.TIME_COLUMN_NAME))) {
+        if (!plannerContext.queryContext().isDecoupledMode()) {
+          // We cannot handle this ordering, but we encounter this ordering as 
part of the exploration of the volcano
+          // planner, which means that the query that we are looking right now 
might only be doing this as one of the
+          // potential branches of exploration rather than being a semantic 
requirement of the query itself.  So, it is
+          // not safe to send an error message telling the end-user exactly 
what is happening, instead we need to set the
+          // planning error and hope.
+          setPlanningErrorOrderByNonTimeIsUnsupported();
+        }
         return null;
+
       }
     }
 
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 ec50e3b5025..2de100483dd 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
@@ -29,16 +29,16 @@ import org.apache.calcite.rel.core.Union;
 import org.apache.calcite.rel.hint.RelHint;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.druid.error.DruidException;
+import org.apache.druid.error.InvalidSqlInput;
 import org.apache.druid.query.DataSource;
-import org.apache.druid.query.InlineDataSource;
 import org.apache.druid.query.Query;
 import org.apache.druid.query.QueryDataSource;
-import org.apache.druid.query.TableDataSource;
 import org.apache.druid.query.UnionDataSource;
 import org.apache.druid.query.union.UnionQuery;
 import org.apache.druid.segment.column.RowSignature;
 import org.apache.druid.sql.calcite.planner.PlannerContext;
 import org.apache.druid.sql.calcite.planner.querygen.SourceDescProducer;
+import org.apache.druid.sql.calcite.table.RowSignatures;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -53,6 +53,9 @@ public class DruidUnion extends Union implements 
DruidLogicalNode, SourceDescPro
       boolean all)
   {
     super(cluster, traits, hints, inputs, all);
+    if (!all) {
+      throw InvalidSqlInput.exception("SQL requires 'UNION' but only 'UNION 
ALL' is supported.");
+    }
   }
 
   @Override
@@ -70,79 +73,47 @@ public class DruidUnion extends Union implements 
DruidLogicalNode, SourceDescPro
   @Override
   public SourceDesc getSourceDesc(PlannerContext plannerContext, 
List<SourceDesc> sources)
   {
-    if (mayUseUnionDataSource(sources)) {
-      List<DataSource> dataSources = new ArrayList<>();
-      RowSignature signature = null;
-      for (SourceDesc sourceDesc : sources) {
-        checkDataSourceSupported(sourceDesc.dataSource);
-        dataSources.add(sourceDesc.dataSource);
-        if (signature == null) {
-          signature = sourceDesc.rowSignature;
-        } else {
-          if (!signature.equals(sourceDesc.rowSignature)) {
-            throw DruidException.defensive(
-                "Row signature mismatch in Union inputs [%s] and [%s]",
-                signature,
-                sourceDesc.rowSignature
-            );
-          }
-        }
-      }
-      return new SourceDesc(new UnionDataSource(dataSources), signature);
-    }
-    if (mayUseUnionQuery(sources)) {
-      RowSignature signature = null;
-      List<Query<?>> queries = new ArrayList<>();
-      for (SourceDesc sourceDesc : sources) {
-        QueryDataSource qds = (QueryDataSource) sourceDesc.dataSource;
-        queries.add(qds.getQuery());
-        if (signature == null) {
-          signature = sourceDesc.rowSignature;
-        } else {
-          if (!signature.equals(sourceDesc.rowSignature)) {
-            throw DruidException.defensive(
-                "Row signature mismatch in Union inputs [%s] and [%s]",
-                signature,
-                sourceDesc.rowSignature
-            );
-          }
-        }
-      }
-      return new SourceDesc(new QueryDataSource(new UnionQuery(queries)), 
signature);
-    }
+    RowSignature signature = RowSignatures.fromRelDataType(
+        sources.get(0).rowSignature.getColumnNames(),
+        getRowType()
+    );
+    UnionDataSource unionDataSource = buildUnionDataSource(sources);
+    if (unionDataSource != null) {
+      return new SourceDesc(unionDataSource, signature);
 
-    throw DruidException.defensive("Union with input [%s] is not supported. 
This should not happen.", sources);
+    }
+    // all other cases are handled via UnionQuery
+    UnionQuery unionQuery = makeUnionQuery(sources);
+    return new SourceDesc(new QueryDataSource(unionQuery), signature);
   }
 
-  private boolean mayUseUnionQuery(List<SourceDesc> sources)
+  private UnionDataSource buildUnionDataSource(List<SourceDesc> sources)
   {
+    List<DataSource> dataSources = new ArrayList<>();
     for (SourceDesc sourceDesc : sources) {
       DataSource dataSource = sourceDesc.dataSource;
-      if (dataSource instanceof QueryDataSource) {
-        continue;
+      if (!UnionDataSource.isCompatibleDataSource(dataSource)) {
+        return null;
       }
-      return false;
+      dataSources.add(dataSource);
     }
-    return true;
+    return new UnionDataSource(dataSources);
   }
 
-  private boolean mayUseUnionDataSource(List<SourceDesc> sources)
+  private UnionQuery makeUnionQuery(List<SourceDesc> sources)
   {
+    List<Query<?>> queries = new ArrayList<>();
     for (SourceDesc sourceDesc : sources) {
       DataSource dataSource = sourceDesc.dataSource;
-      if (dataSource instanceof TableDataSource || dataSource instanceof 
InlineDataSource) {
-        continue;
+      if (dataSource instanceof QueryDataSource) {
+        queries.add(((QueryDataSource) dataSource).getQuery());
+      } else {
+        throw DruidException.defensive(
+            "Expected that all inputs are QueryDataSource-s! Encountered 
something else [%s].",
+            dataSource
+        );
       }
-      return false;
-    }
-    return true;
-  }
-
-  private void checkDataSourceSupported(DataSource dataSource)
-  {
-    if (dataSource instanceof TableDataSource || dataSource instanceof 
InlineDataSource) {
-      return;
     }
-    throw DruidException.defensive("Only Table and Values are supported as 
inputs for Union [%s]", dataSource);
+    return new UnionQuery(queries);
   }
 }
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteNestedDataQueryTest.java
 
b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteNestedDataQueryTest.java
index 328e9cbdd5b..9f33756e7b5 100644
--- 
a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteNestedDataQueryTest.java
+++ 
b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteNestedDataQueryTest.java
@@ -75,7 +75,6 @@ import 
org.apache.druid.segment.virtual.NestedFieldVirtualColumn;
 import 
org.apache.druid.segment.writeout.OffHeapMemorySegmentWriteOutMediumFactory;
 import org.apache.druid.server.SpecificSegmentsQuerySegmentWalker;
 import 
org.apache.druid.sql.calcite.CalciteNestedDataQueryTest.NestedComponentSupplier;
-import org.apache.druid.sql.calcite.NotYetSupported.Modes;
 import org.apache.druid.sql.calcite.filtration.Filtration;
 import 
org.apache.druid.sql.calcite.util.SqlTestFramework.StandardComponentSupplier;
 import org.apache.druid.sql.calcite.util.TestDataBuilder;
@@ -7373,13 +7372,12 @@ public class CalciteNestedDataQueryTest extends 
BaseCalciteQueryTest
     );
   }
 
-  @NotYetSupported(Modes.ERROR_CANNOT_TRANSLATE_COUNT_DISTINCT)
   @Test
   public void testApproxCountDistinctOnUnsupportedComplexColumn()
   {
     assertQueryIsUnplannable(
         "SELECT COUNT(DISTINCT nester) FROM druid.nested",
-        "Query could not be planned. A possible reason is [Using 
APPROX_COUNT_DISTINCT() or enabling "
+        "Using APPROX_COUNT_DISTINCT() or enabling "
         + "approximation with COUNT(DISTINCT) is not supported for column type 
[COMPLEX<json>]. "
         + "You can disable approximation by setting 
[useApproximateCountDistinct: false] in the query context."
     );
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 492625020a6..f2a85b4a4a9 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
@@ -6414,7 +6414,6 @@ public class CalciteQueryTest extends BaseCalciteQueryTest
     );
   }
 
-  @NotYetSupported(Modes.ERROR_HANDLING)
   @Test
   public void testUnplannableScanOrderByNonTime()
   {
@@ -6531,7 +6530,6 @@ public class CalciteQueryTest extends BaseCalciteQueryTest
     );
   }
 
-  @NotYetSupported(Modes.ERROR_HANDLING)
   @Test
   public void testUnplannableExactCountDistinctOnSketch()
   {
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteUnionQueryTest.java 
b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteUnionQueryTest.java
index e172470279c..c3b91458227 100644
--- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteUnionQueryTest.java
+++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteUnionQueryTest.java
@@ -20,6 +20,7 @@
 package org.apache.druid.sql.calcite;
 
 import com.google.common.collect.ImmutableList;
+import org.apache.druid.common.config.NullHandling;
 import org.apache.druid.error.DruidException;
 import org.apache.druid.java.util.common.granularity.Granularities;
 import org.apache.druid.query.TableDataSource;
@@ -28,7 +29,6 @@ import 
org.apache.druid.query.aggregation.CountAggregatorFactory;
 import org.apache.druid.query.aggregation.DoubleSumAggregatorFactory;
 import org.apache.druid.query.dimension.DefaultDimensionSpec;
 import org.apache.druid.query.groupby.GroupByQuery;
-import org.apache.druid.sql.calcite.NotYetSupported.Modes;
 import org.apache.druid.sql.calcite.filtration.Filtration;
 import org.apache.druid.sql.calcite.util.CalciteTests;
 import org.junit.Assert;
@@ -142,7 +142,6 @@ public class CalciteUnionQueryTest extends 
BaseCalciteQueryTest
     }
   }
 
-  @NotYetSupported(Modes.UNION_MORE_STRICT_ROWTYPE_CHECK)
   @Test
   public void testUnionAllTablesColumnTypeMismatchFloatLong()
   {
@@ -189,66 +188,157 @@ public class CalciteUnionQueryTest extends 
BaseCalciteQueryTest
     );
   }
 
-  @NotYetSupported(Modes.ERROR_HANDLING)
   @Test
-  public void testUnionAllTablesColumnTypeMismatchStringLong()
+  public void testUnionFlipFlop()
   {
-    // "dim3" has a different type in foo and foo2 (string vs long), which 
requires a casting subquery, so this
-    // query cannot be planned.
+    String sql = "SELECT\n"
+        + "dim1,dim2,count(1) \n"
+        + "FROM (SELECT dim1 as dim1, dim2 as dim2 FROM foo UNION ALL SELECT 
dim2 as dim1, dim1 as dim2 FROM foo)\n"
+        + "WHERE dim1 = 'def' OR dim2 = 'def'\n"
+        + "GROUP BY 1, 2";
+    if (testBuilder().isDecoupledMode()) {
+      cannotVectorize();
+      testBuilder()
+          .sql(sql)
+          .expectedResults(
+              ImmutableList.of(
+                  new Object[] {"abc", "def", 1L},
+                  new Object[] {"def", "abc", 1L}
+              )
+          )
+          .run();
+    } else {
+      assertQueryIsUnplannable(
+          sql,
+          "names queried for each table are different"
+      );
+    }
+  }
 
-    assertQueryIsUnplannable(
-        "SELECT\n"
+
+  @Test
+  public void testUnionAllTablesColumnTypeMismatchStringLong()
+  {
+    String sql = "SELECT\n"
         + "dim3, dim2, SUM(m1), COUNT(*)\n"
         + "FROM (SELECT dim3, dim2, m1 FROM foo2 UNION ALL SELECT dim3, dim2, 
m1 FROM foo)\n"
         + "WHERE dim2 = 'a' OR dim2 = 'en'\n"
-        + "GROUP BY 1, 2",
-        "SQL requires union between inputs that are not simple table scans and 
involve a " +
-        "filter or aliasing. Or column types of tables being unioned are not 
of same type."
-    );
+        + "GROUP BY 1, 2";
+    if (testBuilder().isDecoupledMode()) {
+      cannotVectorize();
+      testBuilder()
+          .sql(sql)
+          .expectedResults(
+              ImmutableList.of(
+                  new Object[] {"", "a", 4.0D, 1L},
+                  new Object[] {"11", "en", 1.0D, 1L},
+                  new Object[] {"a", "a", 1.0D, 1L},
+                  new Object[] {"b", "a", 1.0D, 1L}
+              )
+          )
+          .run();
+    } else {
+      // "dim3" has a different type in foo and foo2 (string vs long), which 
requires a casting subquery, so this
+      // query cannot be planned.
+      assertQueryIsUnplannable(
+          sql,
+          "SQL requires union between inputs that are not simple table scans 
and involve a " +
+          "filter or aliasing. Or column types of tables being unioned are not 
of same type."
+      );
+    }
   }
 
-  @NotYetSupported(Modes.ERROR_HANDLING)
   @Test
   public void testUnionAllTablesWhenMappingIsRequired()
   {
-    // Cannot plan this UNION ALL operation, because the column swap would 
require generating a subquery.
-
-    assertQueryIsUnplannable(
-        "SELECT\n"
+    String sql = "SELECT\n"
         + "c, COUNT(*)\n"
         + "FROM (SELECT dim1 AS c, m1 FROM foo UNION ALL SELECT dim2 AS c, m1 
FROM numfoo)\n"
         + "WHERE c = 'a' OR c = 'def'\n"
-        + "GROUP BY 1",
-        "SQL requires union between two tables " +
-        "and column names queried for each table are different Left: [dim1], 
Right: [dim2]."
-    );
+        + "GROUP BY 1";
+    if (testBuilder().isDecoupledMode()) {
+      cannotVectorize();
+      testBuilder()
+          .sql(sql)
+          .expectedResults(
+              ImmutableList.of(
+                  new Object[] {"a", 2L},
+                  new Object[] {"def", 1L}
+              )
+          )
+          .run();
+    } else {
+      // Cannot plan this UNION ALL operation, because the column swap would 
require generating a subquery.
+      assertQueryIsUnplannable(
+          sql,
+          "SQL requires union between two tables " +
+          "and column names queried for each table are different Left: [dim1], 
Right: [dim2]."
+      );
+    }
   }
 
-  @NotYetSupported(Modes.ERROR_HANDLING)
   @Test
-  public void testUnionIsUnplannable()
+  public void testUnionDifferentColumnOrder()
   {
-    // Cannot plan this UNION operation
-    assertQueryIsUnplannable(
-        "SELECT dim2, dim1, m1 FROM foo2 UNION SELECT dim1, dim2, m1 FROM foo",
-        "SQL requires 'UNION' but only 'UNION ALL' is supported."
-    );
+    String sql = "SELECT dim2, dim1, m1 FROM foo2 UNION SELECT dim1, dim2, m1 
FROM foo";
+    if (testBuilder().isDecoupledMode()) {
+      // UnionToDistinctRule
+      testBuilder()
+          .sql(sql)
+          .expectedResults(ImmutableList.of(new Object[] {"def", 2L}))
+          .expectedResults(
+              ResultMatchMode.RELAX_NULLS,
+              ImmutableList.of(
+                  new Object[] {null, "10.1", 2.0F},
+                  // these 2 results invert order because in compatible mode 
`null` becomes "" and thus they change order
+                  NullHandling.sqlCompatible() ? new Object[] {null, "abc", 
6.0F} : new Object[] {"", "2", 3.0F},
+                  NullHandling.sqlCompatible() ? new Object[] {"", "2", 3.0F} 
: new Object[] {null, "abc", 6.0F},
+                  new Object[] {"a", "", 1.0F},
+                  new Object[] {"a", "1", 4.0F},
+                  new Object[] {"abc", "def", 5.0F},
+                  new Object[] {"en", "druid", 1.0F},
+                  new Object[] {"he", "\u05D3\u05E8\u05D5\u05D0\u05D9\u05D3", 
1.0F},
+                  new Object[] {"ru", "\u0434\u0440\u0443\u0438\u0434", 1.0F}
+              )
+
+          )
+          .run();
+    } else {
+      // Cannot plan this UNION operation
+      assertQueryIsUnplannable(
+          sql,
+          "SQL requires 'UNION' but only 'UNION ALL' is supported."
+      );
+    }
   }
 
-  @NotYetSupported(Modes.ERROR_HANDLING)
   @Test
   public void testUnionAllTablesWhenCastAndMappingIsRequired()
   {
-    // Cannot plan this UNION ALL operation, because the column swap would 
require generating a subquery.
-    assertQueryIsUnplannable(
-        "SELECT\n"
+    String sql = "SELECT\n"
         + "c, COUNT(*)\n"
         + "FROM (SELECT dim1 AS c, m1 FROM foo UNION ALL SELECT cnt AS c, m1 
FROM numfoo)\n"
-        + "WHERE c = 'a' OR c = 'def'\n"
-        + "GROUP BY 1",
-        "SQL requires union between inputs that are not simple table scans and 
involve " +
-        "a filter or aliasing. Or column types of tables being unioned are not 
of same type."
-    );
+        + "WHERE c = '1' OR c = 'def'\n"
+        + "GROUP BY 1";
+    if (testBuilder().isDecoupledMode()) {
+      cannotVectorize();
+      testBuilder()
+          .sql(sql)
+          .expectedResults(
+              ImmutableList.of(
+                  new Object[]{"1", 7L},
+                  new Object[]{"def", 1L}
+              )
+          )
+          .run();
+    } else {
+      // Cannot plan this UNION ALL operation, because the column swap would 
require generating a subquery.
+      assertQueryIsUnplannable(
+          sql,
+          "SQL requires union between inputs that are not simple table scans 
and involve " +
+          "a filter or aliasing. Or column types of tables being unioned are 
not of same type."
+      );
+    }
   }
 
   @Test
@@ -335,19 +425,31 @@ public class CalciteUnionQueryTest extends 
BaseCalciteQueryTest
     );
   }
 
-  @NotYetSupported(Modes.ERROR_HANDLING)
   @Test
   public void testUnionAllSameTableTwiceWithDifferentMapping()
   {
-    // Cannot plan this UNION ALL operation, because the column swap would 
require generating a subquery.
-    assertQueryIsUnplannable(
-        "SELECT\n"
+    String sql = "SELECT\n"
         + "dim1, dim2, SUM(m1), COUNT(*)\n"
         + "FROM (SELECT dim1, dim2, m1 FROM foo UNION ALL SELECT dim2, dim1, 
m1 FROM foo)\n"
         + "WHERE dim2 = 'a' OR dim2 = 'def'\n"
-        + "GROUP BY 1, 2",
-        "SQL requires union between two tables and column names queried for 
each table are different Left: [dim1, dim2, m1], Right: [dim2, dim1, m1]."
-    );
+        + "GROUP BY 1, 2";
+    if (testBuilder().isDecoupledMode()) {
+      testBuilder()
+          .sql(sql)
+          .expectedResults(
+              ImmutableList.of(
+                  new Object[] {"", "a", 2.0D, 2L},
+                  new Object[] {"1", "a", 8.0D, 2L}
+              )
+          )
+          .run();
+    } else {
+      // Cannot plan this UNION ALL operation, because the column swap would 
require generating a subquery.
+      assertQueryIsUnplannable(
+          sql,
+          "SQL requires union between two tables and column names queried for 
each table are different Left: [dim1, dim2, m1], Right: [dim2, dim1, m1]."
+      );
+    }
   }
 
   @Test
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledExtension.java 
b/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledExtension.java
index c4ba1f77a67..be9e388fb39 100644
--- a/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledExtension.java
+++ b/sql/src/test/java/org/apache/druid/sql/calcite/DecoupledExtension.java
@@ -56,7 +56,7 @@ public class DecoupledExtension implements BeforeEachCallback
 
   private static final ImmutableMap<String, Object> CONTEXT_OVERRIDES = 
ImmutableMap.<String, Object>builder()
       .putAll(BaseCalciteQueryTest.QUERY_CONTEXT_DEFAULT)
-      .put(PlannerConfig.CTX_NATIVE_QUERY_SQL_PLANNING_MODE, 
PlannerConfig.NATIVE_QUERY_SQL_PLANNING_MODE_DECOUPLED)
+      .put(QueryContexts.CTX_NATIVE_QUERY_SQL_PLANNING_MODE, 
QueryContexts.NATIVE_QUERY_SQL_PLANNING_MODE_DECOUPLED)
       .put(QueryContexts.ENABLE_DEBUG, true)
       .build();
 
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 f456bae91f7..2f4199d429a 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
@@ -78,7 +78,6 @@ public @interface NotYetSupported
   {
     // @formatter:off
     DISTINCT_AGGREGATE_NOT_SUPPORTED(DruidException.class, "DISTINCT is not 
supported"),
-    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"),
     NULLS_FIRST_LAST(DruidException.class, "NULLS (FIRST|LAST)"),
     BIGINT_TO_DATE(DruidException.class, "BIGINT to type (DATE|TIME)"),
@@ -89,11 +88,9 @@ public @interface NotYetSupported
     RESULT_MISMATCH(AssertionError.class, "(assertResulEquals|AssertionError: 
column content mismatch)"),
     LONG_CASTING(AssertionError.class, "expected: java.lang.Long"),
     UNSUPPORTED_NULL_ORDERING(DruidException.class, "(A|DE)SCENDING ordering 
with NULLS (LAST|FIRST)"),
-    UNION_MORE_STRICT_ROWTYPE_CHECK(DruidException.class, "Row signature 
mismatch in Union inputs"),
     SORT_REMOVE_TROUBLE(DruidException.class, "Calcite assertion 
violated.*Sort\\.<init>"),
     SORT_REMOVE_CONSTANT_KEYS_CONFLICT(DruidException.class, "not enough 
rules"),
     REQUIRE_TIME_CONDITION(CannotBuildQueryException.class, 
"requireTimeCondition is enabled"),
-    ERROR_CANNOT_TRANSLATE_COUNT_DISTINCT(AssertionError.class, "Cannot 
translate aggregator.COUNT.DISTINCT"),
     UNNEST_INLINED(Exception.class, "Missing conversion is Uncollect"),
     UNNEST_RESULT_MISMATCH(AssertionError.class, "(Result count 
mismatch|column content mismatch)");
     // @formatter:on
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/QueryTestBuilder.java 
b/sql/src/test/java/org/apache/druid/sql/calcite/QueryTestBuilder.java
index 0c3ee685732..f53b2f37b1f 100644
--- a/sql/src/test/java/org/apache/druid/sql/calcite/QueryTestBuilder.java
+++ b/sql/src/test/java/org/apache/druid/sql/calcite/QueryTestBuilder.java
@@ -311,8 +311,8 @@ public class QueryTestBuilder
 
   public boolean isDecoupledMode()
   {
-    String mode = (String) 
queryContext.getOrDefault(PlannerConfig.CTX_NATIVE_QUERY_SQL_PLANNING_MODE, "");
-    return 
PlannerConfig.NATIVE_QUERY_SQL_PLANNING_MODE_DECOUPLED.equalsIgnoreCase(mode);
+    String mode = (String) 
queryContext.getOrDefault(QueryContexts.CTX_NATIVE_QUERY_SQL_PLANNING_MODE, "");
+    return 
QueryContexts.NATIVE_QUERY_SQL_PLANNING_MODE_DECOUPLED.equalsIgnoreCase(mode);
   }
 
 }


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

Reply via email to