This is an automated email from the ASF dual-hosted git repository.
xiangfu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git
The following commit(s) were added to refs/heads/master by this push:
new 86a6b78935 Validate ArrayToMv function presence only in leaf stage
(#11454)
86a6b78935 is described below
commit 86a6b78935e98e6f6e9999d4a9b61b406f15a1c9
Author: Xiang Fu <[email protected]>
AuthorDate: Thu Aug 31 04:40:32 2023 -0700
Validate ArrayToMv function presence only in leaf stage (#11454)
---
.../planner/physical/PinotDispatchPlanner.java | 18 ++-
.../validation/ArrayToMvValidationVisitor.java | 179 +++++++++++++++++++++
.../pinot/query/QueryEnvironmentTestBase.java | 146 +++++++++++------
.../query/queries/ResourceBasedQueryPlansTest.java | 2 +
.../test/resources/queries/BasicQueryPlans.json | 16 +-
.../resources/queries/ValidationErrorPlan.json | 16 ++
6 files changed, 324 insertions(+), 53 deletions(-)
diff --git
a/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/physical/PinotDispatchPlanner.java
b/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/physical/PinotDispatchPlanner.java
index d1618f2fc0..253e01fa92 100644
---
a/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/physical/PinotDispatchPlanner.java
+++
b/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/physical/PinotDispatchPlanner.java
@@ -29,6 +29,7 @@ import org.apache.pinot.query.planner.PlanFragment;
import org.apache.pinot.query.planner.SubPlan;
import
org.apache.pinot.query.planner.physical.colocated.GreedyShuffleRewriteVisitor;
import org.apache.pinot.query.planner.plannode.PlanNode;
+import org.apache.pinot.query.planner.validation.ArrayToMvValidationVisitor;
import org.apache.pinot.query.routing.WorkerManager;
@@ -69,11 +70,26 @@ public class PinotDispatchPlanner {
rootNode.visit(MailboxAssignmentVisitor.INSTANCE, context);
// 5. Run physical optimizations
runPhysicalOptimizers(rootNode, context, _tableCache);
- // 6. convert it into query plan.
+ // 6. Run validations
+ runValidations(rootFragment, context);
+ // 7. convert it into query plan.
// TODO: refactor this to be a pluggable interface.
return finalizeDispatchableSubPlan(rootFragment, context);
}
+ /**
+ * Run validations on the plan. Since there is only one validator right now,
don't try to over-engineer it.
+ */
+ private void runValidations(PlanFragment planFragment,
DispatchablePlanContext context) {
+ PlanNode rootPlanNode = planFragment.getFragmentRoot();
+ boolean isIntermediateStage =
+
context.getDispatchablePlanMetadataMap().get(rootPlanNode.getPlanFragmentId()).getScannedTables().isEmpty();
+ rootPlanNode.visit(ArrayToMvValidationVisitor.INSTANCE,
isIntermediateStage);
+ for (PlanFragment child : planFragment.getChildren()) {
+ runValidations(child, context);
+ }
+ }
+
// TODO: Switch to Worker SPI to avoid multiple-places where workers are
assigned.
private void runPhysicalOptimizers(PlanNode subPlanRoot,
DispatchablePlanContext dispatchablePlanContext,
TableCache tableCache) {
diff --git
a/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/validation/ArrayToMvValidationVisitor.java
b/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/validation/ArrayToMvValidationVisitor.java
new file mode 100644
index 0000000000..c54a07632b
--- /dev/null
+++
b/pinot-query-planner/src/main/java/org/apache/pinot/query/planner/validation/ArrayToMvValidationVisitor.java
@@ -0,0 +1,179 @@
+/**
+ * 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.pinot.query.planner.validation;
+
+import java.util.List;
+import javax.annotation.Nullable;
+import org.apache.pinot.query.planner.logical.RexExpression;
+import org.apache.pinot.query.planner.plannode.AggregateNode;
+import org.apache.pinot.query.planner.plannode.ExchangeNode;
+import org.apache.pinot.query.planner.plannode.FilterNode;
+import org.apache.pinot.query.planner.plannode.JoinNode;
+import org.apache.pinot.query.planner.plannode.MailboxReceiveNode;
+import org.apache.pinot.query.planner.plannode.MailboxSendNode;
+import org.apache.pinot.query.planner.plannode.PlanNodeVisitor;
+import org.apache.pinot.query.planner.plannode.ProjectNode;
+import org.apache.pinot.query.planner.plannode.SetOpNode;
+import org.apache.pinot.query.planner.plannode.SortNode;
+import org.apache.pinot.query.planner.plannode.TableScanNode;
+import org.apache.pinot.query.planner.plannode.ValueNode;
+import org.apache.pinot.query.planner.plannode.WindowNode;
+
+
+/**
+ * This class is used to validate the arrayToMv usage.
+ * Only leaf nodes are allowed to use arrayToMv function.
+ */
+public class ArrayToMvValidationVisitor implements PlanNodeVisitor<Void,
Boolean> {
+ public static final ArrayToMvValidationVisitor INSTANCE = new
ArrayToMvValidationVisitor();
+
+ @Override
+ public Void visitFilter(FilterNode node, Boolean isIntermediateStage) {
+ if (isIntermediateStage && containsArrayToMv(node.getCondition())) {
+ throw new UnsupportedOperationException("Function 'ArrayToMv' is not
supported in FILTER Intermediate Stage");
+ }
+ node.getInputs().forEach(e -> e.visit(this, isIntermediateStage));
+ return null;
+ }
+
+ @Override
+ public Void visitJoin(JoinNode node, Boolean isIntermediateStage) {
+ if (containsArrayToMv(node.getJoinClauses())) {
+ throw new UnsupportedOperationException("Function 'ArrayToMv' is not
supported in JOIN Intermediate Stage");
+ }
+ node.getInputs().forEach(e -> e.visit(this, isIntermediateStage));
+ return null;
+ }
+
+ @Override
+ public Void visitMailboxReceive(MailboxReceiveNode node, Boolean
isIntermediateStage) {
+ node.getInputs().forEach(e -> e.visit(this, isIntermediateStage));
+ return null;
+ }
+
+ @Override
+ public Void visitMailboxSend(MailboxSendNode node, Boolean
isIntermediateStage) {
+ node.getInputs().forEach(e -> e.visit(this, isIntermediateStage));
+ return null;
+ }
+
+ @Override
+ public Void visitAggregate(AggregateNode node, Boolean isIntermediateStage) {
+ if (isIntermediateStage && containsArrayToMv(node.getGroupSet())) {
+ throw new UnsupportedOperationException(
+ "Function 'ArrayToMv' is not supported in AGGREGATE Intermediate
Stage");
+ }
+ if (isIntermediateStage && containsArrayToMv(node.getAggCalls())) {
+ throw new UnsupportedOperationException(
+ "Function 'ArrayToMv' is not supported in AGGREGATE Intermediate
Stage");
+ }
+ if (isIntermediateStage) {
+ node.getInputs().forEach(e -> e.visit(this, true));
+ }
+ // No need to traverse underlying ProjectNode in leaf stage
+ return null;
+ }
+
+ @Override
+ public Void visitProject(ProjectNode node, Boolean isIntermediateStage) {
+ // V1 project node contains arrayToMv function is not supported as it will
be transferred using toString.
+ if (containsArrayToMv(node.getProjects())) {
+ throw new UnsupportedOperationException(
+ "Function 'ArrayToMv' is not supported in PROJECT " +
(isIntermediateStage ? "Intermediate Stage"
+ : "Leaf Stage"));
+ }
+ node.getInputs().forEach(e -> e.visit(this, isIntermediateStage));
+ return null;
+ }
+
+ @Override
+ public Void visitSort(SortNode node, Boolean isIntermediateStage) {
+ if (isIntermediateStage && containsArrayToMv(node.getCollationKeys())) {
+ throw new UnsupportedOperationException("Function 'ArrayToMv' is not
supported in SORT Intermediate Stage");
+ }
+ node.getInputs().forEach(e -> e.visit(this, isIntermediateStage));
+ return null;
+ }
+
+ @Override
+ public Void visitTableScan(TableScanNode node, Boolean isIntermediateStage) {
+ return null;
+ }
+
+ @Override
+ public Void visitValue(ValueNode node, Boolean isIntermediateStage) {
+ return null;
+ }
+
+ @Override
+ public Void visitWindow(WindowNode node, Boolean isIntermediateStage) {
+ if (isIntermediateStage && containsArrayToMv(node.getGroupSet())) {
+ throw new UnsupportedOperationException("Function 'ArrayToMv' is not
supported in WINDOW Intermediate Stage");
+ }
+ if (isIntermediateStage && containsArrayToMv(node.getAggCalls())) {
+ throw new UnsupportedOperationException("Function 'ArrayToMv' is not
supported in WINDOW Intermediate Stage");
+ }
+ if (isIntermediateStage && containsArrayToMv(node.getOrderSet())) {
+ throw new UnsupportedOperationException("Function 'ArrayToMv' is not
supported in WINDOW Intermediate Stage");
+ }
+ node.getInputs().forEach(e -> e.visit(this, isIntermediateStage));
+ return null;
+ }
+
+ @Override
+ public Void visitSetOp(SetOpNode setOpNode, Boolean isIntermediateStage) {
+ setOpNode.getInputs().forEach(e -> e.visit(this, isIntermediateStage));
+ return null;
+ }
+
+ @Override
+ public Void visitExchange(ExchangeNode exchangeNode, Boolean
isIntermediateStage) {
+ exchangeNode.getInputs().forEach(input -> input.visit(this,
isIntermediateStage));
+ return null;
+ }
+
+ private boolean containsArrayToMv(@Nullable RexExpression expression) {
+ if (expression == null) {
+ return false;
+ }
+ if (expression instanceof RexExpression.FunctionCall) {
+ RexExpression.FunctionCall functionCall = (RexExpression.FunctionCall)
expression;
+ String functionName = functionCall.getFunctionName();
+ if (functionName.equalsIgnoreCase("ARRAY_TO_MV") || functionName
+ .equalsIgnoreCase("ARRAYTOMV")) {
+ return true;
+ }
+ // Process operands
+ return containsArrayToMv(functionCall.getFunctionOperands());
+ }
+ return false;
+ }
+
+ private boolean containsArrayToMv(@Nullable List<RexExpression> conditions) {
+ if (conditions == null) {
+ return false;
+ }
+ for (RexExpression condition : conditions) {
+ if (containsArrayToMv(condition)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git
a/pinot-query-planner/src/test/java/org/apache/pinot/query/QueryEnvironmentTestBase.java
b/pinot-query-planner/src/test/java/org/apache/pinot/query/QueryEnvironmentTestBase.java
index 59aa44553d..dfed800ec1 100644
---
a/pinot-query-planner/src/test/java/org/apache/pinot/query/QueryEnvironmentTestBase.java
+++
b/pinot-query-planner/src/test/java/org/apache/pinot/query/QueryEnvironmentTestBase.java
@@ -51,14 +51,24 @@ public class QueryEnvironmentTestBase {
protected static final Random RANDOM_REQUEST_ID_GEN = new Random();
public static final Map<String, List<String>> SERVER1_SEGMENTS =
ImmutableMap.of("a_REALTIME", ImmutableList.of("a1", "a2"),
"b_REALTIME", ImmutableList.of("b1"), "c_OFFLINE",
- ImmutableList.of("c1"), "d_OFFLINE", ImmutableList.of("d1"));
+ ImmutableList.of("c1"), "d_OFFLINE", ImmutableList.of("d1"),
"e_OFFLINE", ImmutableList.of("e1"));
public static final Map<String, List<String>> SERVER2_SEGMENTS =
ImmutableMap.of("a_REALTIME", ImmutableList.of("a3"), "c_OFFLINE",
ImmutableList.of("c2", "c3"),
- "d_REALTIME", ImmutableList.of("d2"), "d_OFFLINE",
ImmutableList.of("d3"));
+ "d_REALTIME", ImmutableList.of("d2"), "d_OFFLINE",
ImmutableList.of("d3"), "e_REALTIME",
+ ImmutableList.of("e2"), "e_OFFLINE", ImmutableList.of("e3"));
public static final Map<String, Schema> TABLE_SCHEMAS = new HashMap<>();
- private static final Schema.SchemaBuilder SCHEMA_BUILDER;
+
static {
- SCHEMA_BUILDER = new Schema.SchemaBuilder()
+ TABLE_SCHEMAS.put("a_REALTIME", getSchemaBuilder("a").build());
+ TABLE_SCHEMAS.put("b_REALTIME", getSchemaBuilder("b").build());
+ TABLE_SCHEMAS.put("c_OFFLINE", getSchemaBuilder("c").build());
+ TABLE_SCHEMAS.put("d", getSchemaBuilder("d").build());
+ TABLE_SCHEMAS.put("e", getSchemaBuilder("e")
+ .addMultiValueDimension("mcol1", FieldSpec.DataType.STRING).build());
+ }
+
+ static Schema.SchemaBuilder getSchemaBuilder(String schemaName) {
+ return new Schema.SchemaBuilder()
.addSingleValueDimension("col1", FieldSpec.DataType.STRING, "")
.addSingleValueDimension("col2", FieldSpec.DataType.STRING, "")
.addSingleValueDimension("col5", FieldSpec.DataType.BOOLEAN, false)
@@ -66,11 +76,7 @@ public class QueryEnvironmentTestBase {
.addMetric("col3", FieldSpec.DataType.INT, 0)
.addMetric("col4", FieldSpec.DataType.BIG_DECIMAL, 0)
.addMetric("col6", FieldSpec.DataType.INT, 0)
- .setSchemaName("defaultSchemaName");
- TABLE_SCHEMAS.put("a_REALTIME", SCHEMA_BUILDER.setSchemaName("a").build());
- TABLE_SCHEMAS.put("b_REALTIME", SCHEMA_BUILDER.setSchemaName("b").build());
- TABLE_SCHEMAS.put("c_OFFLINE", SCHEMA_BUILDER.setSchemaName("c").build());
- TABLE_SCHEMAS.put("d", SCHEMA_BUILDER.setSchemaName("d").build());
+ .setSchemaName(schemaName);
}
protected QueryEnvironment _queryEnvironment;
@@ -83,7 +89,7 @@ public class QueryEnvironmentTestBase {
@DataProvider(name = "testQueryDataProvider")
protected Object[][] provideQueries() {
- return new Object[][] {
+ return new Object[][]{
new Object[]{"SELECT * FROM a UNION SELECT * FROM b"},
new Object[]{"SELECT * FROM a UNION ALL SELECT * FROM b"},
new Object[]{"SELECT * FROM a INTERSECT SELECT * FROM b"},
@@ -96,8 +102,10 @@ public class QueryEnvironmentTestBase {
new Object[]{"SELECT * FROM a JOIN b ON a.col1 = b.col2 WHERE a.col3
>= 0"},
new Object[]{"SELECT * FROM a JOIN b ON a.col1 = b.col2 WHERE a.col3
>= 0 AND a.col3 > b.col3"},
new Object[]{"SELECT * FROM a JOIN b on a.col1 = b.col1 AND a.col2 =
b.col2"},
- new Object[]{"SELECT a.col1, a.ts, b.col3 FROM a JOIN b ON a.col1 =
b.col2 "
- + " WHERE a.col3 >= 0 AND a.col2 = 'a' AND b.col3 < 0"},
+ new Object[]{
+ "SELECT a.col1, a.ts, b.col3 FROM a JOIN b ON a.col1 = b.col2 "
+ + " WHERE a.col3 >= 0 AND a.col2 = 'a' AND b.col3 < 0"
+ },
new Object[]{"SELECT a.col1, a.col3 + a.ts FROM a WHERE a.col3 >= 0
AND a.col2 = 'a'"},
new Object[]{"SELECT SUM(a.col3), COUNT(*) FROM a WHERE a.col3 >= 0
AND a.col2 = 'a'"},
new Object[]{"SELECT AVG(a.col3), SUM(a.col3), COUNT(a.col3) FROM a"},
@@ -108,24 +116,36 @@ public class QueryEnvironmentTestBase {
new Object[]{"SELECT a.col1, KURTOSIS(a.col2), SKEWNESS(a.col3) FROM a
GROUP BY a.col1"},
new Object[]{"SELECT COUNT(a.col3), AVG(a.col3), SUM(a.col3),
MIN(a.col3), MAX(a.col3) FROM a"},
new Object[]{"SELECT DISTINCTCOUNT(a.col3), COUNT(a.col4), COUNT(*),
COUNT(DISTINCT a.col1) FROM a"},
- new Object[]{"SELECT a.col2, DISTINCTCOUNT(a.col3), COUNT(a.col4),
COUNT(*), COUNT(DISTINCT a.col1) FROM a "
- + "GROUP BY a.col2 ORDER BY a.col2"},
+ new Object[]{
+ "SELECT a.col2, DISTINCTCOUNT(a.col3), COUNT(a.col4), COUNT(*),
COUNT(DISTINCT a.col1) FROM a "
+ + "GROUP BY a.col2 ORDER BY a.col2"
+ },
new Object[]{"SELECT a.col1, SKEWNESS(a.col3), KURTOSIS(a.col3),
DISTINCTCOUNT(a.col1) FROM a GROUP BY a.col1"},
new Object[]{"SELECT a.col1, SUM(a.col3) FROM a WHERE a.col3 >= 0 AND
a.col2 = 'a' GROUP BY a.col1"},
new Object[]{"SELECT a.col1, COUNT(*) FROM a WHERE a.col3 >= 0 AND
a.col2 = 'a' GROUP BY a.col1"},
- new Object[]{"SELECT a.col2, a.col1, SUM(a.col3) FROM a WHERE a.col3
>= 0 AND a.col1 = 'a' "
- + " GROUP BY a.col1, a.col2"},
- new Object[]{"SELECT a.col1, AVG(b.col3) FROM a JOIN b ON a.col1 =
b.col2 "
- + " WHERE a.col3 >= 0 AND a.col2 = 'a' AND b.col3 < 0 GROUP BY
a.col1"},
- new Object[]{"SELECT a.col1, COUNT(*), SUM(a.col3) FROM a WHERE a.col3
>= 0 AND a.col2 = 'a' GROUP BY a.col1 "
- + "HAVING COUNT(*) > 10 AND MAX(a.col3) >= 0 AND MIN(a.col3) < 20
AND SUM(a.col3) <= 10 "
- + "AND AVG(a.col3) = 5"},
+ new Object[]{
+ "SELECT a.col2, a.col1, SUM(a.col3) FROM a WHERE a.col3 >= 0 AND
a.col1 = 'a' "
+ + " GROUP BY a.col1, a.col2"
+ },
+ new Object[]{
+ "SELECT a.col1, AVG(b.col3) FROM a JOIN b ON a.col1 = b.col2 "
+ + " WHERE a.col3 >= 0 AND a.col2 = 'a' AND b.col3 < 0 GROUP BY
a.col1"
+ },
+ new Object[]{
+ "SELECT a.col1, COUNT(*), SUM(a.col3) FROM a WHERE a.col3 >= 0 AND
a.col2 = 'a' GROUP BY a.col1 "
+ + "HAVING COUNT(*) > 10 AND MAX(a.col3) >= 0 AND MIN(a.col3) <
20 AND SUM(a.col3) <= 10 "
+ + "AND AVG(a.col3) = 5"
+ },
new Object[]{"SELECT dateTrunc('DAY', ts) FROM a LIMIT 10"},
new Object[]{"SELECT dateTrunc('DAY', a.ts + b.ts) FROM a JOIN b on
a.col1 = b.col1 AND a.col2 = b.col2"},
- new Object[]{"SELECT a.col2, a.col3 FROM a JOIN b ON a.col1 = b.col1 "
- + " WHERE a.col3 >= 0 GROUP BY a.col2, a.col3"},
- new Object[]{"SELECT a.col1, b.col2 FROM a JOIN b ON a.col1 = b.col1
WHERE a.col2 IN ('foo', 'bar') AND"
- + " b.col2 NOT IN ('alice', 'charlie')"},
+ new Object[]{
+ "SELECT a.col2, a.col3 FROM a JOIN b ON a.col1 = b.col1 "
+ + " WHERE a.col3 >= 0 GROUP BY a.col2, a.col3"
+ },
+ new Object[]{
+ "SELECT a.col1, b.col2 FROM a JOIN b ON a.col1 = b.col1 WHERE
a.col2 IN ('foo', 'bar') AND"
+ + " b.col2 NOT IN ('alice', 'charlie')"
+ },
new Object[]{"SELECT COUNT(*) OVER() FROM a"},
new Object[]{"SELECT 42, COUNT(*) OVER() FROM a"},
new Object[]{"SELECT a.col1, SUM(a.col3) OVER () FROM a"},
@@ -133,38 +153,62 @@ public class QueryEnvironmentTestBase {
new Object[]{"SELECT a.col1, SUM(a.col3) OVER (PARTITION BY a.col2
ORDER BY a.col2) FROM a"},
new Object[]{"SELECT a.col1, AVG(a.col3) OVER (), SUM(a.col3) OVER ()
FROM a"},
new Object[]{"SELECT a.col1, SUM(a.col3) OVER () FROM a WHERE a.col3
>= 0"},
- new Object[]{"SELECT a.col1, SUM(a.col3) OVER (PARTITION BY a.col2),
MIN(a.col3) OVER (PARTITION BY a.col2) "
- + "FROM a"},
+ new Object[]{
+ "SELECT a.col1, SUM(a.col3) OVER (PARTITION BY a.col2),
MIN(a.col3) OVER (PARTITION BY a.col2) "
+ + "FROM a"
+ },
new Object[]{"SELECT a.col1, SUM(a.col3) OVER (PARTITION BY a.col2,
a.col1) FROM a"},
- new Object[]{"SELECT a.col1, SUM(a.col3) OVER (ORDER BY a.col2,
a.col1), MIN(a.col3) OVER (ORDER BY a.col2, "
- + "a.col1) FROM a"},
+ new Object[]{
+ "SELECT a.col1, SUM(a.col3) OVER (ORDER BY a.col2, a.col1),
MIN(a.col3) OVER (ORDER BY a.col2, "
+ + "a.col1) FROM a"
+ },
new Object[]{"SELECT a.col1, ROW_NUMBER() OVER(PARTITION BY a.col2
ORDER BY a.col3) FROM a"},
new Object[]{"SELECT RANK() OVER(PARTITION BY a.col2 ORDER BY a.col2)
FROM a"},
- new Object[]{"SELECT col1, total, rank FROM (SELECT a.col1 as col1,
count(*) as total, "
- + "RANK() OVER(ORDER BY count(*) DESC) AS rank FROM a GROUP BY
a.col1) WHERE rank < 5"},
+ new Object[]{
+ "SELECT col1, total, rank FROM (SELECT a.col1 as col1, count(*) as
total, "
+ + "RANK() OVER(ORDER BY count(*) DESC) AS rank FROM a GROUP BY
a.col1) WHERE rank < 5"
+ },
new Object[]{"SELECT RANK() OVER(PARTITION BY a.col2 ORDER BY a.col1)
FROM a"},
new Object[]{"SELECT DENSE_RANK() OVER(ORDER BY a.col1) FROM a"},
new Object[]{"SELECT a.col1, SUM(a.col3) OVER (ORDER BY a.col2),
MIN(a.col3) OVER (ORDER BY a.col2) FROM a"},
- new Object[]{"SELECT /*+
aggOptions(is_partitioned_by_group_by_keys='true') */ a.col3, a.col1,
SUM(b.col3) "
- + "FROM a JOIN b ON a.col3 = b.col3 GROUP BY a.col3, a.col1"},
- new Object[]{"SELECT /*+
aggOptions(is_skip_leaf_stage_group_by='true') */ a.col2, COUNT(*),
SUM(a.col3), "
- + "SUM(a.col1) FROM a WHERE a.col3 >= 0 AND a.col2 = 'a' GROUP BY
a.col2 HAVING COUNT(*) > 10 "
- + "AND MAX(a.col3) >= 0 AND MIN(a.col3) < 20 AND SUM(a.col3) <= 10
AND AVG(a.col3) = 5"},
- new Object[]{"SELECT /*+
aggOptions(is_skip_leaf_stage_group_by='true') */ a.col1, SUM(a.col3) FROM a "
- + "WHERE a.col3 >= 0 AND a.col2 = 'a' GROUP BY a.col1"},
- new Object[]{"SELECT /*+
aggOptions(is_skip_leaf_stage_group_by='true') */ a.col1, COUNT(*) FROM a "
- + "WHERE a.col3 >= 0 AND a.col2 = 'a' GROUP BY a.col1"},
- new Object[]{"SELECT /*+
aggOptions(is_skip_leaf_stage_group_by='true') */ a.col2, a.col1, SUM(a.col3)
FROM a "
- + "WHERE a.col3 >= 0 AND a.col1 = 'a' GROUP BY a.col1, a.col2"},
- new Object[]{"SELECT /*+
aggOptions(is_skip_leaf_stage_group_by='true') */ a.col1, AVG(b.col3) FROM a
JOIN b "
- + "ON a.col1 = b.col2 WHERE a.col3 >= 0 AND a.col2 = 'a' AND
b.col3 < 0 GROUP BY a.col1"},
- new Object[]{"SELECT /*+
aggOptions(is_skip_leaf_stage_group_by='true') */ a.col1 as v1, a.col1 as v2, "
- + "AVG(a.col3) FROM a GROUP BY v1, v2"},
- new Object[]{"SELECT /*+
aggOptions(is_skip_leaf_stage_group_by='true') */ a.col2, COUNT(*),
SUM(a.col3), "
- + "SUM(a.col1) FROM a WHERE a.col3 >= 0 AND a.col2 = 'a' GROUP BY
a.col2 HAVING COUNT(*) > 10 "
- + "AND MAX(a.col3) >= 0 AND MIN(a.col3) < 20 AND SUM(a.col3) <= 10
AND AVG(a.col3) = 5"},
- new Object[]{"SELECT /*+
aggOptions(is_skip_leaf_stage_group_by='true') */ a.col2, a.col3 FROM a JOIN b "
- + "ON a.col1 = b.col1 WHERE a.col3 >= 0 GROUP BY a.col2, a.col3"},
+ new Object[]{
+ "SELECT /*+ aggOptions(is_partitioned_by_group_by_keys='true') */
a.col3, a.col1, SUM(b.col3) "
+ + "FROM a JOIN b ON a.col3 = b.col3 GROUP BY a.col3, a.col1"
+ },
+ new Object[]{
+ "SELECT /*+ aggOptions(is_skip_leaf_stage_group_by='true') */
a.col2, COUNT(*), SUM(a.col3), "
+ + "SUM(a.col1) FROM a WHERE a.col3 >= 0 AND a.col2 = 'a' GROUP
BY a.col2 HAVING COUNT(*) > 10 "
+ + "AND MAX(a.col3) >= 0 AND MIN(a.col3) < 20 AND SUM(a.col3)
<= 10 AND AVG(a.col3) = 5"
+ },
+ new Object[]{
+ "SELECT /*+ aggOptions(is_skip_leaf_stage_group_by='true') */
a.col1, SUM(a.col3) FROM a "
+ + "WHERE a.col3 >= 0 AND a.col2 = 'a' GROUP BY a.col1"
+ },
+ new Object[]{
+ "SELECT /*+ aggOptions(is_skip_leaf_stage_group_by='true') */
a.col1, COUNT(*) FROM a "
+ + "WHERE a.col3 >= 0 AND a.col2 = 'a' GROUP BY a.col1"
+ },
+ new Object[]{
+ "SELECT /*+ aggOptions(is_skip_leaf_stage_group_by='true') */
a.col2, a.col1, SUM(a.col3) FROM a "
+ + "WHERE a.col3 >= 0 AND a.col1 = 'a' GROUP BY a.col1, a.col2"
+ },
+ new Object[]{
+ "SELECT /*+ aggOptions(is_skip_leaf_stage_group_by='true') */
a.col1, AVG(b.col3) FROM a JOIN b "
+ + "ON a.col1 = b.col2 WHERE a.col3 >= 0 AND a.col2 = 'a' AND
b.col3 < 0 GROUP BY a.col1"
+ },
+ new Object[]{
+ "SELECT /*+ aggOptions(is_skip_leaf_stage_group_by='true') */
a.col1 as v1, a.col1 as v2, "
+ + "AVG(a.col3) FROM a GROUP BY v1, v2"
+ },
+ new Object[]{
+ "SELECT /*+ aggOptions(is_skip_leaf_stage_group_by='true') */
a.col2, COUNT(*), SUM(a.col3), "
+ + "SUM(a.col1) FROM a WHERE a.col3 >= 0 AND a.col2 = 'a' GROUP
BY a.col2 HAVING COUNT(*) > 10 "
+ + "AND MAX(a.col3) >= 0 AND MIN(a.col3) < 20 AND SUM(a.col3)
<= 10 AND AVG(a.col3) = 5"
+ },
+ new Object[]{
+ "SELECT /*+ aggOptions(is_skip_leaf_stage_group_by='true') */
a.col2, a.col3 FROM a JOIN b "
+ + "ON a.col1 = b.col1 WHERE a.col3 >= 0 GROUP BY a.col2,
a.col3"
+ },
};
}
diff --git
a/pinot-query-planner/src/test/java/org/apache/pinot/query/queries/ResourceBasedQueryPlansTest.java
b/pinot-query-planner/src/test/java/org/apache/pinot/query/queries/ResourceBasedQueryPlansTest.java
index 3da8ce8edf..c545bd2e29 100644
---
a/pinot-query-planner/src/test/java/org/apache/pinot/query/queries/ResourceBasedQueryPlansTest.java
+++
b/pinot-query-planner/src/test/java/org/apache/pinot/query/queries/ResourceBasedQueryPlansTest.java
@@ -66,6 +66,8 @@ public class ResourceBasedQueryPlansTest extends
QueryEnvironmentTestBase {
try {
long requestId = RANDOM_REQUEST_ID_GEN.nextLong();
_queryEnvironment.explainQuery(query, requestId);
+ String queryWithoutExplainPlan = query.replace("EXPLAIN PLAN FOR ", "");
+ _queryEnvironment.planQuery(queryWithoutExplainPlan);
Assert.fail("Query compilation should have failed with exception message
pattern: " + expectedException);
} catch (Exception e) {
if (expectedException == null) {
diff --git
a/pinot-query-planner/src/test/resources/queries/BasicQueryPlans.json
b/pinot-query-planner/src/test/resources/queries/BasicQueryPlans.json
index 562fbfb364..46eb14d55b 100644
--- a/pinot-query-planner/src/test/resources/queries/BasicQueryPlans.json
+++ b/pinot-query-planner/src/test/resources/queries/BasicQueryPlans.json
@@ -59,7 +59,7 @@
]
},
{
- "description": "",
+ "description": "case when with cast",
"sql": "EXPLAIN PLAN FOR SELECT SUM(CASE WHEN col3 > 10 THEN 1 WHEN
col3 > 20 THEN 2 WHEN col3 > 30 THEN 3 WHEN col3 > 40 THEN 4 WHEN col3 > 50
THEN '5' ELSE 0 END) FROM a",
"output": [
"Execution Plan",
@@ -71,6 +71,20 @@
"\n LogicalTableScan(table=[[a]])",
"\n"
]
+ },
+ {
+ "description": "Valid arrayToMV 2",
+ "sql": "EXPLAIN PLAN FOR SELECT SUM(col3) as sumCol3, arrayToMv(mcol1)
FROM e GROUP BY arrayToMv(e.mcol1)",
+ "output": [
+ "Execution Plan",
+ "\nLogicalProject(sumCol3=[$1], EXPR$1=[$0])",
+ "\n LogicalAggregate(group=[{0}], agg#0=[$SUM0($1)])",
+ "\n PinotLogicalExchange(distribution=[hash[0]])",
+ "\n LogicalAggregate(group=[{0}], agg#0=[$SUM0($1)])",
+ "\n LogicalProject(EXPR$1=[ARRAYTOMV($6)], col3=[$2])",
+ "\n LogicalTableScan(table=[[e]])",
+ "\n"
+ ]
}
]
}
diff --git
a/pinot-query-planner/src/test/resources/queries/ValidationErrorPlan.json
b/pinot-query-planner/src/test/resources/queries/ValidationErrorPlan.json
new file mode 100644
index 0000000000..2d4edda388
--- /dev/null
+++ b/pinot-query-planner/src/test/resources/queries/ValidationErrorPlan.json
@@ -0,0 +1,16 @@
+{
+ "validation_tests": {
+ "queries": [
+ {
+ "description": "arrayToMV validation error 1",
+ "sql": "EXPLAIN PLAN FOR SELECT sum(sumCol3), arrayToMv(mcol1) FROM
(SELECT SUM(a.col3) as sumCol3, e.mcol1, a.col2 FROM a JOIN e on a.col1=e.col1
GROUP BY e.mcol1, a.col2) GROUP BY arrayToMv(mcol1)",
+ "expectedException": "Error composing query plan for.*"
+ },
+ {
+ "description": "arrayToMV validation error 2",
+ "sql": "EXPLAIN PLAN FOR SELECT SUM(a.col3) as sumCol3,
arrayToMv(e.mcol1), a.col2 FROM a JOIN e on a.col1=e.col1 GROUP BY
arrayToMv(e.mcol1), a.col2",
+ "expectedException": "Error composing query plan for.*"
+ }
+ ]
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]