diff --git ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/rules/HivePointLookupOptimizerRule.java ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/rules/HivePointLookupOptimizerRule.java index eff9a312aa..7bf3269caf 100644 --- ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/rules/HivePointLookupOptimizerRule.java +++ ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/rules/HivePointLookupOptimizerRule.java @@ -19,6 +19,8 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -50,6 +52,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.base.Function; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.LinkedHashMultimap; @@ -57,9 +60,9 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; import com.google.common.collect.Sets; - public abstract class HivePointLookupOptimizerRule extends RelOptRule { /** @@ -74,6 +77,7 @@ public FilterCondition (int minNumORClauses) { super(operand(Filter.class, any()), minNumORClauses); } + @Override public void onMatch(RelOptRuleCall call) { final Filter filter = call.rel(0); final RexBuilder rexBuilder = filter.getCluster().getRexBuilder(); @@ -93,12 +97,13 @@ public void onMatch(RelOptRuleCall call) { * to generate an IN clause (which is more efficient). If the OR operator contains * AND operator children, the optimization might generate an IN clause that uses * structs. - */ + */ public static class JoinCondition extends HivePointLookupOptimizerRule { public JoinCondition (int minNumORClauses) { super(operand(Join.class, any()), minNumORClauses); } - + + @Override public void onMatch(RelOptRuleCall call) { final Join join = call.rel(0); final RexBuilder rexBuilder = join.getCluster().getRexBuilder(); @@ -132,7 +137,7 @@ protected HivePointLookupOptimizerRule( public void analyzeCondition(RelOptRuleCall call, RexBuilder rexBuilder, - AbstractRelNode node, + AbstractRelNode node, RexNode condition) { // 1. We try to transform possible candidates @@ -173,29 +178,31 @@ public void analyzeCondition(RelOptRuleCall call, @Override public RexNode visitCall(RexCall call) { RexNode node; switch (call.getKind()) { - case AND: - ImmutableList operands = RexUtil.flattenAnd(((RexCall) call).getOperands()); - List newOperands = new ArrayList(); - for (RexNode operand: operands) { - RexNode newOperand; - if (operand.getKind() == SqlKind.OR) { - try { - newOperand = transformIntoInClauseCondition(rexBuilder, - nodeOp.getRowType(), operand, minNumORClauses); - if (newOperand == null) { - newOperand = operand; - } - } catch (SemanticException e) { - LOG.error("Exception in HivePointLookupOptimizerRule", e); - return call; + // FIXME: I don't think there is a need for this right now...calcite have already done the flattening/etc + // removing this case clause will not miss the OR below AND + case AND: + ImmutableList operands = RexUtil.flattenAnd(call.getOperands()); + List newOperands = new ArrayList(); + for (RexNode operand : operands) { + RexNode newOperand; + if (operand.getKind() == SqlKind.OR) { + try { + newOperand = transformIntoInClauseCondition(rexBuilder, + nodeOp.getRowType(), operand, minNumORClauses); + if (newOperand == null) { + newOperand = operand; } - } else { - newOperand = operand; + } catch (SemanticException e) { + LOG.error("Exception in HivePointLookupOptimizerRule", e); + return call; } - newOperands.add(newOperand); + } else { + newOperand = operand; } - node = RexUtil.composeConjunction(rexBuilder, newOperands, false); - break; + newOperands.add(newOperand); + } + node = RexUtil.composeConjunction(rexBuilder, newOperands, false); + break; case OR: try { node = transformIntoInClauseCondition(rexBuilder, @@ -214,13 +221,102 @@ public void analyzeCondition(RelOptRuleCall call, return node; } - private static RexNode transformIntoInClauseCondition(RexBuilder rexBuilder, RelDataType inputSchema, - RexNode condition, int minNumORClauses) throws SemanticException { + static class Constraint { + + private RexLiteral literal; + private RexInputRef inputRef; + + public Constraint(RexInputRef inputRef, RexLiteral literal) { + this.literal = literal; + this.inputRef = inputRef; + } + + public static Constraint of(RexNode n) { + if (!(n instanceof RexCall)) { + return null; + } + RexCall call = (RexCall) n; + if (call.getOperator().getKind() != SqlKind.EQUALS) { + return null; + } + RexNode opA = call.operands.get(0); + RexNode opB = call.operands.get(1); + if (opA instanceof RexLiteral && opB instanceof RexInputRef) { + RexLiteral rexLiteral = (RexLiteral) opA; + RexInputRef rexInputRef = (RexInputRef) opB; + return new Constraint(rexInputRef, rexLiteral); + } + if (opA instanceof RexInputRef && opB instanceof RexLiteral) { + RexLiteral rexLiteral = (RexLiteral) opB; + RexInputRef rexInputRef = (RexInputRef) opA; + return new Constraint(rexInputRef, rexLiteral); + } + return null; + } + + public RexInputRef getKey() { + return inputRef; + } + + } + + static class MX { + + public static final Function> KEY_FUNCTION = new Function>() { + + @Override + public Set apply(MX a) { + if (a.key == null) { + return Collections.EMPTY_SET; + } + return a.key; + } + }; + private Map constraints = new HashMap<>(); + private RexNode originalRexNode; + private Set key; + + public MX(RexNode rexNode) { + originalRexNode = rexNode; + + final List conjunctions = RelOptUtil.conjunctions(rexNode); + + for (RexNode n : conjunctions) { + + Constraint c = Constraint.of(n); + if (c == null) { + // interpretation failed; make this node opaque + return; + } + constraints.put(c.getKey(), c); + } + if (constraints.size() != conjunctions.size()) { + LOG.info("unexpected situation; giving up on this branch"); + return; + } + key = constraints.keySet(); + } + + public List getValuesInOrder(List columns) throws SemanticException { + List ret = new ArrayList<>(); + for (RexInputRef rexInputRef : columns) { + Constraint constraint = constraints.get(rexInputRef); + if(constraint== null) { + throw new SemanticException("tried to get data for non-existent column"); + } + ret.add(constraint.literal); + } + return ret; + } + } + + private static RexNode transformIntoInClauseCondition2(RexBuilder rexBuilder, RelDataType inputSchema, + RexNode condition, int minNumORClauses) throws SemanticException { assert condition.getKind() == SqlKind.OR; // 1. We extract the information necessary to create the predicate for the new // filter - ListMultimap columnConstantsMap = ArrayListMultimap.create(); + ListMultimap columnConstantsMap = ArrayListMultimap.create(); ImmutableList operands = RexUtil.flattenOr(((RexCall) condition).getOperands()); if (operands.size() < minNumORClauses) { // We bail out @@ -228,29 +324,29 @@ private static RexNode transformIntoInClauseCondition(RexBuilder rexBuilder, Rel } for (int i = 0; i < operands.size(); i++) { final List conjunctions = RelOptUtil.conjunctions(operands.get(i)); - for (RexNode conjunction: conjunctions) { + for (RexNode conjunction : conjunctions) { // 1.1. If it is not a RexCall, we bail out if (!(conjunction instanceof RexCall)) { return null; } // 1.2. We extract the information that we need RexCall conjCall = (RexCall) conjunction; - if(conjCall.getOperator().getKind() == SqlKind.EQUALS) { + if (conjCall.getOperator().getKind() == SqlKind.EQUALS) { if (conjCall.operands.get(0) instanceof RexInputRef && - conjCall.operands.get(1) instanceof RexLiteral) { + conjCall.operands.get(1) instanceof RexLiteral) { RexInputRef ref = (RexInputRef) conjCall.operands.get(0); RexLiteral literal = (RexLiteral) conjCall.operands.get(1); columnConstantsMap.put(ref, literal); - if (columnConstantsMap.get(ref).size() != i+1) { + if (columnConstantsMap.get(ref).size() != i + 1) { // If we have not added to this column before, we bail out return null; } } else if (conjCall.operands.get(1) instanceof RexInputRef && - conjCall.operands.get(0) instanceof RexLiteral) { + conjCall.operands.get(0) instanceof RexLiteral) { RexInputRef ref = (RexInputRef) conjCall.operands.get(1); RexLiteral literal = (RexLiteral) conjCall.operands.get(0); columnConstantsMap.put(ref, literal); - if (columnConstantsMap.get(ref).size() != i+1) { + if (columnConstantsMap.get(ref).size() != i + 1) { // If we have not added to this column before, we bail out return null; } @@ -316,6 +412,133 @@ private static RexNode transformIntoInClauseCondition(RexBuilder rexBuilder, Rel return rexBuilder.makeCall(HiveIn.INSTANCE, newOperands); } + private RexNode transformIntoInClauseCondition(RexBuilder rexBuilder, RelDataType inputSchema, + RexNode condition, int minNumORClauses) throws SemanticException { + assert condition.getKind() == SqlKind.OR; + + // 1. We extract the information necessary to create the predicate for the new + // filter + ListMultimap columnConstantsMap = ArrayListMultimap.create(); + ImmutableList operands = RexUtil.flattenOr(((RexCall) condition).getOperands()); + // FIXME: this needs rethinking + if (operands.size() < minNumORClauses) { + // We bail out + return null; + } + List allNodes = new ArrayList<>(); + List processedNodes = new ArrayList<>(); + for (int i = 0; i < operands.size(); i++) { + MX m = new MX(operands.get(i)); + allNodes.add(m); + } + + Multimap, MX> a = Multimaps.index(allNodes, MX.KEY_FUNCTION); + + for (Entry, Collection> sa : a.asMap().entrySet()) { + // skip opaque + if (sa.getKey() == null || sa.getKey().size()==0 ) { + continue; + } + // not enough equalities should not be handled + if (sa.getValue().size() < 2 || sa.getValue().size() < minNumORClauses) { + continue; + } + + allNodes.add(new MX(buildInFor(sa.getKey(), sa.getValue()))); + processedNodes.addAll(sa.getValue()); + } + + if (processedNodes.isEmpty()) { + return null; + } + allNodes.removeAll(processedNodes); + + List ops = new ArrayList<>(); + for (MX mx : allNodes) { + ops.add(mx.originalRexNode); + } + if(ops.size()==1) { + return ops.get(0); + } else { + return rexBuilder.makeCall(SqlStdOperatorTable.OR, ops); + } + + } + + private RexNode buildInFor(Set set, Collection value) throws SemanticException { + + List columns = new ArrayList(); + columns.addAll(set); + Listoperands = new ArrayList<>(); + + operands.add(makeOrBreak(columns)); + for (MX node : value) { + List values = node.getValuesInOrder(columns); + operands.add(makeOrBreak(values)); + } + + return rexBuilder.makeCall(HiveIn.INSTANCE, operands); + + // return null; + /* + List names = new ArrayList(); + ImmutableList.Builder paramsTypes = ImmutableList.builder(); + List structReturnType = new ArrayList(); + ImmutableList.Builder newOperandsTypes = ImmutableList.builder(); + for (int i = 0; i < operands.size(); i++) { + List constantFields = new ArrayList(operands.size()); + + for (RexInputRef ref : columnConstantsMap.keySet()) { + // If any of the elements was not referenced by every operand, we bail out + if (columnConstantsMap.get(ref).size() <= i) { + return null; + } + RexLiteral columnConstant = columnConstantsMap.get(ref).get(i); + if (i == 0) { + columns.add(ref); + names.add(inputSchema.getFieldNames().get(ref.getIndex())); + paramsTypes.add(ref.getType()); + structReturnType.add(TypeConverter.convert(ref.getType())); + } + constantFields.add(columnConstant); + } + + if (i == 0) { + RexNode columnsRefs; + if (columns.size() == 1) { + columnsRefs = columns.get(0); + } else { + // Create STRUCT clause + columnsRefs = rexBuilder.makeCall(SqlStdOperatorTable.ROW, columns); + } + newOperands.add(columnsRefs); + newOperandsTypes.add(columnsRefs.getType()); + } + RexNode values; + if (constantFields.size() == 1) { + values = constantFields.get(0); + } else { + // Create STRUCT clause + values = rexBuilder.makeCall(SqlStdOperatorTable.ROW, constantFields); + } + newOperands.add(values); + newOperandsTypes.add(values.getType()); + } + + // 4. Create and return IN clause + return rexBuilder.makeCall(HiveIn.INSTANCE, newOperands); + */ + } + + private RexNode makeOrBreak(List columns) { + // Create STRUCT clause + if (columns.size() == 1) { + return columns.get(0); + } else { + return rexBuilder.makeCall(SqlStdOperatorTable.ROW, columns); + } + } + } /** @@ -337,7 +560,7 @@ private static RexNode transformIntoInClauseCondition(RexBuilder rexBuilder, Rel switch (call.getKind()) { case AND: // IN clauses need to be combined by keeping only common elements - operands = Lists.newArrayList(RexUtil.flattenAnd(((RexCall) call).getOperands())); + operands = Lists.newArrayList(RexUtil.flattenAnd(call.getOperands())); for (int i = 0; i < operands.size(); i++) { RexNode operand = operands.get(i); if (operand.getKind() == SqlKind.IN) { @@ -374,7 +597,7 @@ private static RexNode transformIntoInClauseCondition(RexBuilder rexBuilder, Rel break; case OR: // IN clauses need to be combined by keeping all elements - operands = Lists.newArrayList(RexUtil.flattenOr(((RexCall) call).getOperands())); + operands = Lists.newArrayList(RexUtil.flattenOr(call.getOperands())); for (int i = 0; i < operands.size(); i++) { RexNode operand = operands.get(i); if (operand.getKind() == SqlKind.IN) { diff --git ql/src/test/org/apache/hadoop/hive/ql/optimizer/calcite/rules/TestHivePointLookupOptimizerRule.java ql/src/test/org/apache/hadoop/hive/ql/optimizer/calcite/rules/TestHivePointLookupOptimizerRule.java new file mode 100644 index 0000000000..14e0761db5 --- /dev/null +++ ql/src/test/org/apache/hadoop/hive/ql/optimizer/calcite/rules/TestHivePointLookupOptimizerRule.java @@ -0,0 +1,174 @@ +/* + * 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.hadoop.hive.ql.optimizer.calcite.rules; + +import static org.junit.Assert.assertEquals; + +import org.apache.calcite.jdbc.JavaTypeFactoryImpl; +import org.apache.calcite.plan.RelOptCluster; +import org.apache.calcite.plan.RelOptSchema; +import org.apache.calcite.plan.hep.HepPlanner; +import org.apache.calcite.plan.hep.HepProgramBuilder; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rex.RexBuilder; +import org.apache.calcite.rex.RexNode; +import org.apache.calcite.sql.fun.SqlStdOperatorTable; +import org.apache.calcite.tools.RelBuilder; +import org.apache.hadoop.hive.ql.metadata.Table; +import org.apache.hadoop.hive.ql.optimizer.calcite.HiveRelFactories; +import org.apache.hadoop.hive.ql.optimizer.calcite.RelOptHiveTable; +import org.apache.hadoop.hive.ql.optimizer.calcite.reloperators.HiveFilter; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class TestHivePointLookupOptimizerRule { + + @Mock + private RelOptSchema schemaMock; + @Mock + RelOptHiveTable tableMock; + @Mock + Table hiveTableMDMock; + + private HepPlanner planner; + private RelBuilder builder; + + @SuppressWarnings("unused") + private static class MyRecord { + public int f1; + public int f2; + } + + @Before + public void before() { + HepProgramBuilder programBuilder = new HepProgramBuilder(); + programBuilder.addRuleInstance(new HivePointLookupOptimizerRule.FilterCondition(2)); + + planner = new HepPlanner(programBuilder.build()); + + JavaTypeFactoryImpl typeFactory = new JavaTypeFactoryImpl(); + RexBuilder rexBuilder = new RexBuilder(typeFactory); + final RelOptCluster optCluster = RelOptCluster.create(planner, rexBuilder); + RelDataType rowTypeMock = typeFactory.createStructType(MyRecord.class); + Mockito.doReturn(rowTypeMock).when(tableMock).getRowType(); + Mockito.doReturn(tableMock).when(schemaMock).getTableForMember(Matchers.any()); + Mockito.doReturn(hiveTableMDMock).when(tableMock).getHiveTableMD(); + + builder = HiveRelFactories.HIVE_BUILDER.create(optCluster, schemaMock); + + } + + public RexNode or(RexNode... args) { + return builder.call(SqlStdOperatorTable.OR, args); + } + + public RexNode and(RexNode... args) { + return builder.call(SqlStdOperatorTable.AND, args); + } + + public RexNode eq(String field, int value) { + return builder.call(SqlStdOperatorTable.EQUALS, + builder.field(field), builder.literal(value)); + } + + @Test + public void testSimpleCase() { + + // @formatter:off + final RelNode basePlan = builder + .scan("t") + .filter( + and( + or( + eq("f1",1), + eq("f1",2) + ), + or( + eq("f2",3), + eq("f2",4) + ) + ) + ) + .build(); + // @formatter:on + + planner.setRoot(basePlan); + RelNode optimizedRelNode = planner.findBestExp(); + + HiveFilter filter = (HiveFilter) optimizedRelNode; + RexNode condition = filter.getCondition(); + assertEquals("AND(IN($0, 1, 2), IN($1, 3, 4))", condition.toString()); + } + + @Test + public void testSimpleStructCase() { + + // @formatter:off + final RelNode basePlan = builder + .scan("t") + .filter( + or( + and( eq("f1",1),eq("f2",1)), + and( eq("f1",2),eq("f2",2)) + ) + ) + .build(); + // @formatter:on + + planner.setRoot(basePlan); + RelNode optimizedRelNode = planner.findBestExp(); + + HiveFilter filter = (HiveFilter) optimizedRelNode; + RexNode condition = filter.getCondition(); + assertEquals("IN(ROW($0, $1), ROW(1, 1), ROW(2, 2))", condition.toString()); + } + + /** Despite the fact that f2=99 is there...the extraction should happen */ + @Test + public void testObscuredSimple() { + + // @formatter:off + final RelNode basePlan = builder + .scan("t") + .filter( + or( + eq("f2",99), + eq("f1",1), + eq("f1",2) + ) + ) + .build(); + // @formatter:on + + planner.setRoot(basePlan); + RelNode optimizedRelNode = planner.findBestExp(); + + HiveFilter filter = (HiveFilter) optimizedRelNode; + RexNode condition = filter.getCondition(); + System.out.println(condition); + assertEquals("X", condition.toString()); + } +} diff --git ql/src/test/results/clientpositive/perf/tez/query15.q.out ql/src/test/results/clientpositive/perf/tez/query15.q.out index e1eca99d95..3c7ae664b1 100644 --- ql/src/test/results/clientpositive/perf/tez/query15.q.out +++ ql/src/test/results/clientpositive/perf/tez/query15.q.out @@ -71,7 +71,7 @@ Stage-0 Select Operator [SEL_23] (rows=348467716 width=135) Output:["_col4","_col7"] Filter Operator [FIL_22] (rows=348467716 width=135) - predicate:((_col3 = 'CA') or (_col3 = 'GA') or (_col3 = 'WA') or (_col7 > 500) or (substr(_col4, 1, 5)) IN ('85669', '86197', '88274', '83405', '86475', '85392', '85460', '80348', '81792')) + predicate:((_col3) IN ('CA', 'WA', 'GA') or (_col7 > 500) or (substr(_col4, 1, 5)) IN ('85669', '86197', '88274', '83405', '86475', '85392', '85460', '80348', '81792')) Merge Join Operator [MERGEJOIN_77] (rows=348467716 width=135) Conds:RS_19._col0=RS_20._col1(Inner),Output:["_col3","_col4","_col7"] <-Reducer 2 [SIMPLE_EDGE] diff --git ql/src/test/results/clientpositive/perf/tez/query47.q.out ql/src/test/results/clientpositive/perf/tez/query47.q.out index d034ea9433..779982484f 100644 --- ql/src/test/results/clientpositive/perf/tez/query47.q.out +++ ql/src/test/results/clientpositive/perf/tez/query47.q.out @@ -199,10 +199,10 @@ Stage-0 <-Map 12 [SIMPLE_EDGE] vectorized SHUFFLE [RS_282] PartitionCols:_col0 - Select Operator [SEL_281] (rows=73048 width=1119) + Select Operator [SEL_281] (rows=73049 width=1119) Output:["_col0","_col1","_col2"] - Filter Operator [FIL_280] (rows=73048 width=1119) - predicate:(((d_year = 2000) or ((d_year = 1999) and (d_moy = 12)) or ((d_year = 2001) and (d_moy = 1))) and d_date_sk is not null) + Filter Operator [FIL_280] (rows=73049 width=1119) + predicate:(((struct(d_year,d_moy)) IN (const struct(1999,12), const struct(2001,1)) or (d_year = 2000)) and d_date_sk is not null) TableScan [TS_73] (rows=73049 width=1119) default@date_dim,date_dim,Tbl:COMPLETE,Col:NONE,Output:["d_date_sk","d_year","d_moy"] <-Map 1 [SIMPLE_EDGE] vectorized @@ -222,7 +222,7 @@ Stage-0 SHUFFLE [RS_285] Group By Operator [GBY_284] (rows=1 width=12) Output:["_col0","_col1","_col2"],aggregations:["min(_col0)","max(_col0)","bloom_filter(_col0, expectedEntries=1000000)"] - Select Operator [SEL_283] (rows=73048 width=1119) + Select Operator [SEL_283] (rows=73049 width=1119) Output:["_col0"] Please refer to the previous Select Operator [SEL_281] <-Reducer 15 [BROADCAST_EDGE] vectorized diff --git ql/src/test/results/clientpositive/perf/tez/query57.q.out ql/src/test/results/clientpositive/perf/tez/query57.q.out index 42cbbdc2a4..91929b19e0 100644 --- ql/src/test/results/clientpositive/perf/tez/query57.q.out +++ ql/src/test/results/clientpositive/perf/tez/query57.q.out @@ -193,10 +193,10 @@ Stage-0 <-Map 12 [SIMPLE_EDGE] vectorized SHUFFLE [RS_282] PartitionCols:_col0 - Select Operator [SEL_281] (rows=73048 width=1119) + Select Operator [SEL_281] (rows=73049 width=1119) Output:["_col0","_col1","_col2"] - Filter Operator [FIL_280] (rows=73048 width=1119) - predicate:(((d_year = 2000) or ((d_year = 1999) and (d_moy = 12)) or ((d_year = 2001) and (d_moy = 1))) and d_date_sk is not null) + Filter Operator [FIL_280] (rows=73049 width=1119) + predicate:(((struct(d_year,d_moy)) IN (const struct(1999,12), const struct(2001,1)) or (d_year = 2000)) and d_date_sk is not null) TableScan [TS_73] (rows=73049 width=1119) default@date_dim,date_dim,Tbl:COMPLETE,Col:NONE,Output:["d_date_sk","d_year","d_moy"] <-Map 1 [SIMPLE_EDGE] vectorized @@ -216,7 +216,7 @@ Stage-0 SHUFFLE [RS_285] Group By Operator [GBY_284] (rows=1 width=12) Output:["_col0","_col1","_col2"],aggregations:["min(_col0)","max(_col0)","bloom_filter(_col0, expectedEntries=1000000)"] - Select Operator [SEL_283] (rows=73048 width=1119) + Select Operator [SEL_283] (rows=73049 width=1119) Output:["_col0"] Please refer to the previous Select Operator [SEL_281] <-Reducer 15 [BROADCAST_EDGE] vectorized