[fix] Fix the query result error caused by the grouping sets statemen… (#11316)

* [fix] Fix the query result error caused by the grouping sets statement grouping as an expression
This commit is contained in:
luozenglin
2022-08-01 13:52:18 +08:00
committed by GitHub
parent 4f5e1601df
commit 1cf57a985d
13 changed files with 364 additions and 268 deletions

View File

@ -22,6 +22,7 @@ import org.apache.doris.catalog.Type;
import org.apache.doris.common.AnalysisException;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.BitSet;
@ -36,47 +37,76 @@ public class GroupingInfo {
public static final String GROUPING_PREFIX = "GROUPING_PREFIX_";
private VirtualSlotRef groupingIDSlot;
private TupleDescriptor virtualTuple;
private Set<VirtualSlotRef> groupingSlots;
private TupleDescriptor outputTupleDesc;
private ExprSubstitutionMap outputTupleSmap;
private List<SlotDescriptor> groupingSlotDescList;
private Set<VirtualSlotRef> virtualSlotRefs;
private List<BitSet> groupingIdList;
private GroupByClause.GroupingType groupingType;
private BitSet bitSetAll;
private List<Expr> preRepeatExprs;
public GroupingInfo(Analyzer analyzer, GroupByClause groupByClause) throws AnalysisException {
this.groupingType = groupByClause.getGroupingType();
groupingSlots = new LinkedHashSet<>();
virtualSlotRefs = new LinkedHashSet<>();
virtualTuple = analyzer.getDescTbl().createTupleDescriptor("VIRTUAL_TUPLE");
groupingIDSlot = new VirtualSlotRef(COL_GROUPING_ID, Type.BIGINT, virtualTuple, new ArrayList<>());
groupingIDSlot.analyze(analyzer);
groupingSlots.add(groupingIDSlot);
virtualSlotRefs.add(groupingIDSlot);
outputTupleDesc = analyzer.getDescTbl().createTupleDescriptor("repeat-tuple");
outputTupleSmap = new ExprSubstitutionMap();
groupingSlotDescList = Lists.newArrayList();
preRepeatExprs = Lists.newArrayList();
}
public Set<VirtualSlotRef> getGroupingSlots() {
return groupingSlots;
public Set<VirtualSlotRef> getVirtualSlotRefs() {
return virtualSlotRefs;
}
public TupleDescriptor getVirtualTuple() {
return virtualTuple;
}
public TupleDescriptor getOutputTupleDesc() {
return outputTupleDesc;
}
public ExprSubstitutionMap getOutputTupleSmap() {
return outputTupleSmap;
}
public List<SlotDescriptor> getGroupingSlotDescList() {
return groupingSlotDescList;
}
public List<BitSet> getGroupingIdList() {
return groupingIdList;
}
public List<Expr> getPreRepeatExprs() {
return preRepeatExprs;
}
public void substitutePreRepeatExprs(ExprSubstitutionMap smap, Analyzer analyzer) {
preRepeatExprs = Expr.substituteList(preRepeatExprs, smap, analyzer, true);
}
// generate virtual slots for grouping or grouping_id functions
public VirtualSlotRef addGroupingSlots(List<Expr> realSlots, Analyzer analyzer) throws AnalysisException {
String colName = realSlots.stream().map(expr -> expr.toSql()).collect(Collectors.joining(
"_"));
String colName = realSlots.stream().map(expr -> expr.toSql()).collect(Collectors.joining("_"));
colName = GROUPING_PREFIX + colName;
VirtualSlotRef virtualSlot = new VirtualSlotRef(colName, Type.BIGINT, virtualTuple, realSlots);
virtualSlot.analyze(analyzer);
if (groupingSlots.contains(virtualSlot)) {
for (VirtualSlotRef vs : groupingSlots) {
if (virtualSlotRefs.contains(virtualSlot)) {
for (VirtualSlotRef vs : virtualSlotRefs) {
if (vs.equals(virtualSlot)) {
return vs;
}
}
}
groupingSlots.add(virtualSlot);
virtualSlotRefs.add(virtualSlot);
return virtualSlot;
}
@ -124,13 +154,13 @@ public class GroupingInfo {
default:
Preconditions.checkState(false);
}
groupingExprs.addAll(groupingSlots);
groupingExprs.addAll(virtualSlotRefs);
}
// generate grouping function's value
public List<List<Long>> genGroupingList(ArrayList<Expr> groupingExprs) throws AnalysisException {
List<List<Long>> groupingList = new ArrayList<>();
for (SlotRef slot : groupingSlots) {
for (SlotRef slot : virtualSlotRefs) {
List<Long> glist = new ArrayList<>();
for (BitSet bitSet : groupingIdList) {
long l = 0L;
@ -150,7 +180,7 @@ public class GroupingInfo {
int slotSize = ((VirtualSlotRef) slot).getRealSlots().size();
for (int i = 0; i < slotSize; ++i) {
int j = groupingExprs.indexOf(((VirtualSlotRef) slot).getRealSlots().get(i));
if (j < 0 || j >= bitSet.size()) {
if (j < 0 || j >= bitSet.size()) {
throw new AnalysisException("Column " + ((VirtualSlotRef) slot).getRealColumnName()
+ " in GROUP_ID() does not exist in GROUP BY clause.");
}
@ -164,6 +194,68 @@ public class GroupingInfo {
return groupingList;
}
public void genOutputTupleDescAndSMap(Analyzer analyzer, ArrayList<Expr> groupingAndVirtualSlotExprs,
List<FunctionCallExpr> aggExprs) {
List<Expr> groupingExprs = Lists.newArrayList();
List<Expr> virtualSlotExprs = Lists.newArrayList();
for (Expr expr : groupingAndVirtualSlotExprs) {
if (expr instanceof VirtualSlotRef) {
virtualSlotExprs.add(expr);
} else {
groupingExprs.add(expr);
}
}
for (Expr expr : groupingExprs) {
SlotDescriptor slotDesc = addSlot(analyzer, expr);
slotDesc.setIsNullable(true);
groupingSlotDescList.add(slotDesc);
preRepeatExprs.add(expr);
// register equivalence between grouping slot and grouping expr;
// do this only when the grouping expr isn't a constant, otherwise
// it'll simply show up as a gratuitous HAVING predicate
// (which would actually be incorrect if the constant happens to be NULL)
if (!expr.isConstant()) {
analyzer.createAuxEquivPredicate(new SlotRef(slotDesc), expr.clone());
}
}
List<SlotRef> aggSlot = Lists.newArrayList();
aggExprs.forEach(expr -> aggSlot.addAll(getSlotRefChildren(expr)));
for (SlotRef slotRef : aggSlot) {
addSlot(analyzer, slotRef);
preRepeatExprs.add(slotRef);
}
for (Expr expr : virtualSlotExprs) {
addSlot(analyzer, expr);
}
}
private SlotDescriptor addSlot(Analyzer analyzer, Expr expr) {
SlotDescriptor slotDesc = analyzer.addSlotDescriptor(outputTupleDesc);
slotDesc.initFromExpr(expr);
slotDesc.setIsMaterialized(true);
if (expr instanceof SlotRef) {
slotDesc.setColumn(((SlotRef) expr).getColumn());
}
if (expr instanceof VirtualSlotRef) {
outputTupleSmap.put(expr.clone(), new VirtualSlotRef(slotDesc));
} else {
outputTupleSmap.put(expr.clone(), new SlotRef(slotDesc));
}
return slotDesc;
}
private List<SlotRef> getSlotRefChildren(Expr root) {
List<SlotRef> result = new ArrayList<>();
for (Expr child : root.getChildren()) {
if (child instanceof SlotRef) {
result.add((SlotRef) child);
} else {
result.addAll(getSlotRefChildren(child));
}
}
return result;
}
public void substituteGroupingFn(List<Expr> exprs, Analyzer analyzer) throws AnalysisException {
if (groupingType == GroupByClause.GroupingType.GROUP_BY) {
throw new AnalysisException("cannot use GROUPING functions without [grouping sets|rollup|cube] a"

View File

@ -1047,15 +1047,16 @@ public class SelectStmt extends QueryStmt {
List<TupleId> groupingByTupleIds = new ArrayList<>();
if (groupByClause != null) {
// must do it before copying for createAggInfo()
if (groupingInfo != null) {
groupingByTupleIds.add(groupingInfo.getVirtualTuple().getId());
}
groupByClause.genGroupingExprs();
if (groupingInfo != null) {
groupingInfo.buildRepeat(groupByClause.getGroupingExprs(), groupByClause.getGroupingSetList());
}
substituteOrdinalsAliases(groupByClause.getGroupingExprs(), "GROUP BY", analyzer);
if (groupingInfo != null) {
groupingInfo.genOutputTupleDescAndSMap(analyzer, groupByClause.getGroupingExprs(), aggExprs);
// must do it before copying for createAggInfo()
groupingByTupleIds.add(groupingInfo.getOutputTupleDesc().getId());
}
groupByClause.analyze(analyzer);
createAggInfo(groupByClause.getGroupingExprs(), aggExprs, analyzer);
} else {

View File

@ -29,6 +29,8 @@ import java.io.DataOutput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* It like a SlotRef except that it is not a real column exist in table.
@ -55,6 +57,10 @@ public class VirtualSlotRef extends SlotRef {
tupleDescriptor = other.tupleDescriptor;
}
public VirtualSlotRef(SlotDescriptor desc) {
super(desc);
}
public static VirtualSlotRef read(DataInput in) throws IOException {
VirtualSlotRef virtualSlotRef = new VirtualSlotRef(null, Type.BIGINT, null, new ArrayList<>());
virtualSlotRef.readFields(in);
@ -68,6 +74,10 @@ public class VirtualSlotRef extends SlotRef {
return getColumnName();
}
@Override
public void getTableIdToColumnNames(Map<Long, Set<String>> tableIdToColumnNames) {
}
@Override
public void write(DataOutput out) throws IOException {
super.write(out);

View File

@ -19,9 +19,8 @@ package org.apache.doris.planner;
import org.apache.doris.analysis.Analyzer;
import org.apache.doris.analysis.Expr;
import org.apache.doris.analysis.FunctionCallExpr;
import org.apache.doris.analysis.ExprSubstitutionMap;
import org.apache.doris.analysis.GroupByClause;
import org.apache.doris.analysis.GroupingFunctionCallExpr;
import org.apache.doris.analysis.GroupingInfo;
import org.apache.doris.analysis.SlotDescriptor;
import org.apache.doris.analysis.SlotId;
@ -68,17 +67,16 @@ public class RepeatNode extends PlanNode {
private GroupByClause groupByClause;
protected RepeatNode(PlanNodeId id, PlanNode input, GroupingInfo groupingInfo, GroupByClause groupByClause) {
super(id, input.getTupleIds(), "REPEAT_NODE", StatisticalType.REPEAT_NODE);
super(id, groupingInfo.getOutputTupleDesc().getId().asList(), "REPEAT_NODE", StatisticalType.REPEAT_NODE);
this.children.add(input);
this.groupingInfo = groupingInfo;
this.input = input;
this.groupByClause = groupByClause;
}
// only for unittest
protected RepeatNode(PlanNodeId id, PlanNode input, List<Set<SlotId>> repeatSlotIdList,
TupleDescriptor outputTupleDesc, List<List<Long>> groupingList) {
TupleDescriptor outputTupleDesc, List<List<Long>> groupingList) {
super(id, input.getTupleIds(), "REPEAT_NODE", StatisticalType.REPEAT_NODE);
this.children.add(input);
this.repeatSlotIdList = buildIdSetList(repeatSlotIdList);
@ -116,25 +114,18 @@ public class RepeatNode extends PlanNode {
@Override
public void init(Analyzer analyzer) throws UserException {
Preconditions.checkState(conjuncts.isEmpty());
groupByClause.substituteGroupingExprs(groupingInfo.getGroupingSlots(), input.getOutputSmap(),
analyzer);
for (Expr expr : groupByClause.getGroupingExprs()) {
if (expr instanceof SlotRef || (expr instanceof GroupingFunctionCallExpr)) {
continue;
}
// throw new AnalysisException("function or expr is not allowed in grouping sets clause.");
ExprSubstitutionMap childSmap = getCombinedChildSmap();
groupByClause.substituteGroupingExprs(groupingInfo.getVirtualSlotRefs(), childSmap, analyzer);
groupingInfo.substitutePreRepeatExprs(childSmap, analyzer);
outputSmap = groupingInfo.getOutputTupleSmap();
conjuncts = Expr.substituteList(conjuncts, outputSmap, analyzer, false);
outputTupleDesc = groupingInfo.getOutputTupleDesc();
List<TupleId> inputTupleIds = input.getOutputTupleIds();
if (inputTupleIds.size() == 1) {
// used for MaterializedViewSelector getTableIdToColumnNames
outputTupleDesc.setTable(analyzer.getTupleDesc(inputTupleIds.get(0)).getTable());
}
// build new BitSet List for tupleDesc
Set<SlotDescriptor> slotDescSet = new HashSet<>();
for (TupleId tupleId : input.getTupleIds()) {
TupleDescriptor tupleDescriptor = analyzer.getDescTbl().getTupleDesc(tupleId);
slotDescSet.addAll(tupleDescriptor.getSlots());
}
// build tupleDesc according to child's tupleDesc info
outputTupleDesc = groupingInfo.getVirtualTuple();
//set aggregate nullable
for (Expr slot : groupByClause.getGroupingExprs()) {
if (slot instanceof SlotRef && !(slot instanceof VirtualSlotRef)) {
@ -144,74 +135,42 @@ public class RepeatNode extends PlanNode {
outputTupleDesc.computeStatAndMemLayout();
List<Set<SlotId>> groupingIdList = new ArrayList<>();
List<Expr> exprList = groupByClause.getGroupingExprs();
Preconditions.checkState(exprList.size() >= 2);
allSlotId = new HashSet<>();
List<SlotDescriptor> groupingSlotDescList = groupingInfo.getGroupingSlotDescList();
for (BitSet bitSet : Collections.unmodifiableList(groupingInfo.getGroupingIdList())) {
Set<SlotId> slotIdSet = new HashSet<>();
for (SlotDescriptor slotDesc : slotDescSet) {
SlotId slotId = slotDesc.getId();
if (slotId == null) {
continue;
}
for (int i = 0; i < exprList.size(); i++) {
if (exprList.get(i) instanceof SlotRef) {
SlotRef slotRef = (SlotRef) (exprList.get(i));
if (bitSet.get(i) && slotRef.getSlotId() == slotId) {
slotIdSet.add(slotId);
break;
}
} else if (exprList.get(i) instanceof FunctionCallExpr) {
List<SlotRef> slotRefs = getSlotRefChildren(exprList.get(i));
for (SlotRef slotRef : slotRefs) {
if (bitSet.get(i) && slotRef.getSlotId() == slotId) {
slotIdSet.add(slotId);
break;
}
}
}
for (int i = 0; i < groupingSlotDescList.size(); i++) {
if (bitSet.get(i)) {
slotIdSet.add(groupingSlotDescList.get(i).getId());
}
}
groupingIdList.add(slotIdSet);
}
this.repeatSlotIdList = buildIdSetList(groupingIdList);
allSlotId = new HashSet<>();
for (Set<Integer> s : this.repeatSlotIdList) {
allSlotId.addAll(s);
}
this.groupingList = groupingInfo.genGroupingList(groupByClause.getGroupingExprs());
tupleIds.add(outputTupleDesc.getId());
for (TupleId id : tupleIds) {
analyzer.getTupleDesc(id).setIsMaterialized(true);
}
computeTupleStatAndMemLayout(analyzer);
computeStats(analyzer);
createDefaultSmap(analyzer);
}
private List<SlotRef> getSlotRefChildren(Expr root) {
List<SlotRef> result = new ArrayList<>();
for (Expr child : root.getChildren()) {
if (child instanceof SlotRef) {
result.add((SlotRef) child);
} else {
result.addAll(getSlotRefChildren(child));
}
}
return result;
}
@Override
protected void toThrift(TPlanNode msg) {
msg.node_type = TPlanNodeType.REPEAT_NODE;
msg.repeat_node = new TRepeatNode(outputTupleDesc.getId().asInt(), repeatSlotIdList, groupingList.get(0),
groupingList, allSlotId);
msg.repeat_node =
new TRepeatNode(outputTupleDesc.getId().asInt(), repeatSlotIdList, groupingList.get(0), groupingList,
allSlotId, Expr.treesToThrift(groupingInfo.getPreRepeatExprs()));
}
@Override
protected String debugString() {
return MoreObjects.toStringHelper(this).add("Repeat", repeatSlotIdList.size()).addValue(
super.debugString()).toString();
return MoreObjects.toStringHelper(this).add("Repeat", repeatSlotIdList.size()).addValue(super.debugString())
.toString();
}
@Override
@ -223,11 +182,12 @@ public class RepeatNode extends PlanNode {
output.append(detailPrefix + "repeat: repeat ");
output.append(repeatSlotIdList.size() - 1);
output.append(" lines ");
output.append(repeatSlotIdList);
output.append(repeatSlotIdList).append("\n");
output.append(detailPrefix).append("exprs: ").append(getExplainString(groupingInfo.getPreRepeatExprs()));
output.append("\n");
if (CollectionUtils.isNotEmpty(outputTupleDesc.getSlots())) {
output.append(detailPrefix + "generate: ");
output.append(outputTupleDesc.getSlots().stream().map(slot -> "`" + slot.getColumn().getName() + "`")
output.append(detailPrefix + "output slots: ");
output.append(outputTupleDesc.getSlots().stream().map(slot -> "`" + slot.getLabel() + "`")
.collect(Collectors.joining(", ")) + "\n");
}
return output.toString();

View File

@ -421,7 +421,7 @@ public class QueryPlanTest extends TestWithFeService {
public void testFunctionViewGroupingSet() throws Exception {
String queryStr = "select query_id, client_ip, concat from test.function_view group by rollup("
+ "query_id, client_ip, concat);";
assertSQLPlanOrErrorMsgContains(queryStr, "repeat: repeat 3 lines [[], [0], [0, 1], [0, 1, 2, 3]]");
assertSQLPlanOrErrorMsgContains(queryStr, "repeat: repeat 3 lines [[], [8], [8, 9], [8, 9, 10]]");
}
@Test

View File

@ -17,79 +17,64 @@
package org.apache.doris.planner;
import org.apache.doris.analysis.AccessTestUtil;
import org.apache.doris.analysis.Analyzer;
import org.apache.doris.analysis.DescriptorTable;
import org.apache.doris.analysis.SlotId;
import org.apache.doris.analysis.SlotRef;
import org.apache.doris.analysis.TableName;
import org.apache.doris.analysis.TupleDescriptor;
import org.apache.doris.analysis.TupleId;
import org.apache.doris.datasource.InternalDataSource;
import org.apache.doris.thrift.TExplainLevel;
import org.apache.doris.thrift.TPlanNode;
import org.apache.doris.thrift.TPlanNodeType;
import org.apache.doris.utframe.TestWithFeService;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
public class RepeatNodeTest extends TestWithFeService {
public class RepeatNodeTest {
private Analyzer analyzer;
private RepeatNode node;
private TupleDescriptor virtualTuple;
private List<Set<SlotId>> groupingIdList = new ArrayList<>();
private List<List<Long>> groupingList = new ArrayList<>();
@Before
public void setUp() throws Exception {
Analyzer analyzerBase = AccessTestUtil.fetchTableAnalyzer();
analyzer = new Analyzer(analyzerBase.getEnv(), analyzerBase.getContext());
String[] cols = {"k1", "k2", "k3"};
List<SlotRef> slots = new ArrayList<>();
for (String col : cols) {
SlotRef expr = new SlotRef(new TableName(InternalDataSource.INTERNAL_DS_NAME, "testdb", "t"), col);
slots.add(expr);
}
try {
Field f = analyzer.getClass().getDeclaredField("tupleByAlias");
f.setAccessible(true);
Multimap<String, TupleDescriptor> tupleByAlias = ArrayListMultimap.create();
TupleDescriptor td = new TupleDescriptor(new TupleId(0));
td.setTable(analyzerBase.getTableOrAnalysisException(new TableName(InternalDataSource.INTERNAL_DS_NAME, "testdb", "t")));
tupleByAlias.put("testdb.t", td);
f.set(analyzer, tupleByAlias);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
virtualTuple = analyzer.getDescTbl().createTupleDescriptor("VIRTUAL_TUPLE");
groupingList.add(Arrays.asList(0L, 7L, 3L, 5L, 1L, 6L, 2L, 4L));
groupingList.add(Arrays.asList(0L, 7L, 3L, 5L, 1L, 6L, 2L, 4L));
DescriptorTable descTable = new DescriptorTable();
TupleDescriptor tuple = descTable.createTupleDescriptor("DstTable");
node = new RepeatNode(new PlanNodeId(1),
new OlapScanNode(new PlanNodeId(0), tuple, "null"), groupingIdList, virtualTuple, groupingList);
@Override
protected void runBeforeAll() throws Exception {
createDatabase("testdb");
useDatabase("testdb");
createTable(" CREATE TABLE `testdb`.`mycost` (\n" + " `id` tinyint(4) NULL,\n" + " `name` varchar(20) NULL,\n"
+ " `date` date NULL,\n" + " `cost` bigint(20) SUM NULL\n" + ") ENGINE=OLAP\n"
+ "AGGREGATE KEY(`id`, `name`, `date`)\n" + "COMMENT 'OLAP'\n" + "PARTITION BY RANGE(`date`)\n"
+ "(PARTITION p2020 VALUES [('0000-01-01'), ('2021-01-01')),\n"
+ "PARTITION p2021 VALUES [('2021-01-01'), ('2022-01-01')),\n"
+ "PARTITION p2022 VALUES [('2022-01-01'), ('2023-01-01')))\n" + "DISTRIBUTED BY HASH(`id`) BUCKETS 8\n"
+ "PROPERTIES (\n" + "\"replication_allocation\" = \"tag.location.default: 1\",\n"
+ "\"in_memory\" = \"false\",\n" + "\"storage_format\" = \"V2\"\n" + ");");
createTable(
" CREATE TABLE `testdb`.`mypeople` (\n" + " `id` bigint(20) NULL,\n" + " `name` varchar(20) NULL,\n"
+ " `sex` varchar(10) NULL,\n" + " `age` int(11) NULL,\n" + " `phone` char(15) NULL,\n"
+ " `address` varchar(50) NULL\n" + ") ENGINE=OLAP\n" + "DUPLICATE KEY(`id`, `name`)\n"
+ "COMMENT 'OLAP'\n" + "DISTRIBUTED BY HASH(`id`) BUCKETS 8\n" + "PROPERTIES (\n"
+ "\"replication_allocation\" = \"tag.location.default: 1\",\n" + "\"in_memory\" = \"false\",\n"
+ "\"storage_format\" = \"V2\"\n" + ");");
}
@Test
public void testNormal() {
try {
TPlanNode msg = new TPlanNode();
node.toThrift(msg);
node.getNodeExplainString("", TExplainLevel.NORMAL);
node.debugString();
Assert.assertEquals(TPlanNodeType.REPEAT_NODE, msg.node_type);
} catch (Exception e) {
Assert.fail("throw exceptions");
}
public void testNormal() throws Exception {
String sql = "select id, name, sum(cost), grouping_id(id, name) from mycost group by cube(id, name);";
String explainString = getSQLPlanOrErrorMsg("explain " + sql);
Assertions.assertTrue(explainString.contains("exprs: `id`, `name`, `cost`"));
Assertions.assertTrue(explainString.contains(
"output slots: ``id``, ``name``, ``cost``, ``GROUPING_ID``, ``GROUPING_PREFIX_`id`_`name```"));
}
@Test
public void testExpr() throws Exception {
String sql1 = "select if(c.id > 0, 1, 0) as id_, p.name, sum(c.cost) from mycost c "
+ "join mypeople p on c.id = p.id group by grouping sets((id_, name),());";
String explainString1 = getSQLPlanOrErrorMsg("explain " + sql1);
System.out.println(explainString1);
Assertions.assertTrue(explainString1.contains(
"output slots: `if(`c`.`id` > 0, 1, 0)`, ``p`.`name``, ``c`.`cost``, ``GROUPING_ID``"));
String sql2 = "select (id + 1) id_, name, sum(cost) from mycost group by grouping sets((id_, name),());";
String explainString2 = getSQLPlanOrErrorMsg("explain " + sql2);
System.out.println(explainString2);
Assertions.assertTrue(explainString2.contains("exprs: (`id` + 1), `name`, `cost`"));
Assertions.assertTrue(
explainString2.contains(" output slots: `(`id` + 1)`, ``name``, ``cost``, ``GROUPING_ID``"));
String sql3 = "select 1 as id_, name, sum(cost) from mycost group by grouping sets((id_, name),());";
String explainString3 = getSQLPlanOrErrorMsg("explain " + sql3);
System.out.println(explainString3);
Assertions.assertTrue(explainString3.contains("exprs: 1, `name`, `cost`"));
Assertions.assertTrue(explainString3.contains("output slots: `1`, ``name``, ``cost``, ``GROUPING_ID``"));
}
}