diff --git a/fe/src/main/java/org/apache/doris/analysis/InsertStmt.java b/fe/src/main/java/org/apache/doris/analysis/InsertStmt.java index 70f94f3d3601da..296aeecbf50efd 100644 --- a/fe/src/main/java/org/apache/doris/analysis/InsertStmt.java +++ b/fe/src/main/java/org/apache/doris/analysis/InsertStmt.java @@ -33,6 +33,7 @@ import org.apache.doris.common.DdlException; import org.apache.doris.common.ErrorCode; import org.apache.doris.common.ErrorReport; +import org.apache.doris.common.Pair; import org.apache.doris.common.UserException; import org.apache.doris.metric.MetricRepo; import org.apache.doris.mysql.privilege.PrivPredicate; @@ -427,25 +428,46 @@ private void analyzeSubquery(Analyzer analyzer) throws UserException { /* * When doing schema change, there may be some shadow columns. we should add - * them to the end of targetColumns. And use 'origColIdxsForShadowCols' to save + * them to the end of targetColumns. And use 'origColIdxsForExtendCols' to save * the index of column in 'targetColumns' which the shadow column related to. * eg: origin targetColumns: (A,B,C), shadow column: __doris_shadow_B after * processing, targetColumns: (A, B, C, __doris_shadow_B), and - * origColIdxsForShadowCols has 1 element: "1", which is the index of column B + * origColIdxsForExtendCols has 1 element: "1", which is the index of column B * in targetColumns. * * Rule A: If the column which the shadow column related to is not mentioned, * then do not add the shadow column to targetColumns. They will be filled by * null or default value when loading. + * + * When table have materialized view, there may be some materialized view columns. + * we should add them to the end of targetColumns. + * eg: origin targetColumns: (A,B,C), shadow column: mv_bitmap_union_C + * after processing, targetColumns: (A, B, C, mv_bitmap_union_C), and + * origColIdx2MVColumn has 1 element: "2, mv_bitmap_union_C" + * will be used in as a mapping from queryStmt.getResultExprs() to targetColumns define expr */ - List origColIdxsForShadowCols = Lists.newArrayList(); + List> origColIdxsForExtendCols = Lists.newArrayList(); for (Column column : targetTable.getFullSchema()) { if (column.isNameWithPrefix(SchemaChangeHandler.SHADOW_NAME_PRFIX)) { String origName = Column.removeNamePrefix(column.getName()); for (int i = 0; i < targetColumns.size(); i++) { if (targetColumns.get(i).nameEquals(origName, false)) { // Rule A - origColIdxsForShadowCols.add(i); + origColIdxsForExtendCols.add(new Pair<>(i, null)); + targetColumns.add(column); + break; + } + } + } + if (column.isNameWithPrefix(CreateMaterializedViewStmt.MATERIALIZED_VIEW_NAME_PRFIX)) { + SlotRef refColumn = column.getRefColumn(); + if (refColumn == null) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_BAD_FIELD_ERROR, column.getName(), targetTable.getName()); + } + String origName = refColumn.getColumnName(); + for (int originColumnIdx = 0; originColumnIdx < targetColumns.size(); originColumnIdx++) { + if (targetColumns.get(originColumnIdx).nameEquals(origName, false)) { + origColIdxsForExtendCols.add(new Pair<>(originColumnIdx, column)); targetColumns.add(column); break; } @@ -472,7 +494,7 @@ private void analyzeSubquery(Analyzer analyzer) throws UserException { // INSERT INTO VALUES(...) List> rows = selectStmt.getValueList().getRows(); for (int rowIdx = 0; rowIdx < rows.size(); ++rowIdx) { - analyzeRow(analyzer, targetColumns, rows, rowIdx, origColIdxsForShadowCols); + analyzeRow(analyzer, targetColumns, rows, rowIdx, origColIdxsForExtendCols); } // clear these 2 structures, rebuild them using VALUES exprs @@ -487,7 +509,7 @@ private void analyzeSubquery(Analyzer analyzer) throws UserException { // INSERT INTO SELECT 1,2,3 ... List> rows = Lists.newArrayList(); rows.add(selectStmt.getResultExprs()); - analyzeRow(analyzer, targetColumns, rows, 0, origColIdxsForShadowCols); + analyzeRow(analyzer, targetColumns, rows, 0, origColIdxsForExtendCols); // rows may be changed in analyzeRow(), so rebuild the result exprs selectStmt.getResultExprs().clear(); for (Expr expr : rows.get(0)) { @@ -497,12 +519,22 @@ private void analyzeSubquery(Analyzer analyzer) throws UserException { isStreaming = true; } else { // INSERT INTO SELECT ... FROM tbl - if (!origColIdxsForShadowCols.isEmpty()) { + if (!origColIdxsForExtendCols.isEmpty()) { // extend the result expr by duplicating the related exprs - for (Integer idx : origColIdxsForShadowCols) { - queryStmt.getResultExprs().add(queryStmt.getResultExprs().get(idx)); + for (Pair entry : origColIdxsForExtendCols) { + if (entry.second == null) { + queryStmt.getResultExprs().add(queryStmt.getResultExprs().get(entry.first)); + } else { + //substitute define expr slot with select statement result expr + ExprSubstitutionMap smap = new ExprSubstitutionMap(); + smap.getLhs().add(entry.second.getRefColumn()); + smap.getRhs().add(queryStmt.getResultExprs().get(entry.first)); + Expr e = Expr.substituteList(Lists.newArrayList(entry.second.getDefineExpr()), smap, analyzer, false).get(0); + queryStmt.getResultExprs().add(e); + } } } + // check compatibility for (int i = 0; i < targetColumns.size(); ++i) { Column column = targetColumns.get(i); @@ -518,17 +550,26 @@ private void analyzeSubquery(Analyzer analyzer) throws UserException { } } - // expand baseTblResultExprs and colLabels in QueryStmt - if (!origColIdxsForShadowCols.isEmpty()) { + // expand colLabels in QueryStmt + if (!origColIdxsForExtendCols.isEmpty()) { if (queryStmt.getResultExprs().size() != queryStmt.getBaseTblResultExprs().size()) { - for (Integer idx : origColIdxsForShadowCols) { - queryStmt.getBaseTblResultExprs().add(queryStmt.getBaseTblResultExprs().get(idx)); + for (Pair entry : origColIdxsForExtendCols) { + if (entry.second == null) { + queryStmt.getBaseTblResultExprs().add(queryStmt.getBaseTblResultExprs().get(entry.first)); + } else { + //substitute define expr slot with select statement result expr + ExprSubstitutionMap smap = new ExprSubstitutionMap(); + smap.getLhs().add(entry.second.getRefColumn()); + smap.getRhs().add(queryStmt.getResultExprs().get(entry.first)); + Expr e = Expr.substituteList(Lists.newArrayList(entry.second.getDefineExpr()), smap, analyzer, false).get(0); + queryStmt.getBaseTblResultExprs().add(e); + } } } if (queryStmt.getResultExprs().size() != queryStmt.getColLabels().size()) { - for (Integer idx : origColIdxsForShadowCols) { - queryStmt.getColLabels().add(queryStmt.getColLabels().get(idx)); + for (Pair entry : origColIdxsForExtendCols) { + queryStmt.getColLabels().add(queryStmt.getColLabels().get(entry.first)); } } } @@ -547,16 +588,16 @@ private void analyzeSubquery(Analyzer analyzer) throws UserException { } private void analyzeRow(Analyzer analyzer, List targetColumns, List> rows, - int rowIdx, List origColIdxsForShadowCols) throws AnalysisException { + int rowIdx, List> origColIdxsForExtendCols) throws AnalysisException { // 1. check number of fields if equal with first row // targetColumns contains some shadow columns, which is added by system, // so we should minus this - if (rows.get(rowIdx).size() != targetColumns.size() - origColIdxsForShadowCols.size()) { + if (rows.get(rowIdx).size() != targetColumns.size() - origColIdxsForExtendCols.size()) { throw new AnalysisException("Column count doesn't match value count at row " + (rowIdx + 1)); } ArrayList row = rows.get(rowIdx); - if (!origColIdxsForShadowCols.isEmpty()) { + if (!origColIdxsForExtendCols.isEmpty()) { /** * we should extends the row for shadow columns. * eg: @@ -566,14 +607,20 @@ private void analyzeRow(Analyzer analyzer, List targetColumns, List extentedRow = Lists.newArrayList(); extentedRow.addAll(row); - for (Integer idx : origColIdxsForShadowCols) { - extentedRow.add(extentedRow.get(idx)); + for (Pair entry : origColIdxsForExtendCols) { + if (entry == null) { + extentedRow.add(extentedRow.get(entry.first)); + } else { + ExprSubstitutionMap smap = new ExprSubstitutionMap(); + smap.getLhs().add(entry.second.getRefColumn()); + smap.getRhs().add(extentedRow.get(entry.first)); + extentedRow.add(Expr.substituteList(Lists.newArrayList(entry.second.getDefineExpr()), smap, analyzer, false).get(0)); + } } row = extentedRow; rows.set(rowIdx, row); } - // check the compatibility of expr in row and column in targetColumns for (int i = 0; i < row.size(); ++i) { Expr expr = row.get(i); diff --git a/fe/src/main/java/org/apache/doris/catalog/Column.java b/fe/src/main/java/org/apache/doris/catalog/Column.java index 53b65c7e34aa48..634757939a695a 100644 --- a/fe/src/main/java/org/apache/doris/catalog/Column.java +++ b/fe/src/main/java/org/apache/doris/catalog/Column.java @@ -17,8 +17,10 @@ package org.apache.doris.catalog; +import com.google.common.base.Preconditions; import org.apache.doris.alter.SchemaChangeHandler; import org.apache.doris.analysis.Expr; +import org.apache.doris.analysis.SlotRef; import org.apache.doris.common.CaseSensibility; import org.apache.doris.common.DdlException; import org.apache.doris.common.FeMetaVersion; @@ -37,6 +39,8 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; /** * This class represents the column-related metadata. @@ -334,6 +338,17 @@ public void setDefineExpr(Expr expr) { defineExpr = expr; } + public SlotRef getRefColumn() { + List slots = new ArrayList<>(); + if (defineExpr == null) { + return null; + } else { + defineExpr.collect(SlotRef.class, slots); + Preconditions.checkArgument(slots.size() == 1); + return (SlotRef) slots.get(0); + } + } + public String toSql() { StringBuilder sb = new StringBuilder(); sb.append("`").append(name).append("` "); diff --git a/fe/src/main/java/org/apache/doris/load/Load.java b/fe/src/main/java/org/apache/doris/load/Load.java index 7816e1004ad964..683f5a9c9921d4 100644 --- a/fe/src/main/java/org/apache/doris/load/Load.java +++ b/fe/src/main/java/org/apache/doris/load/Load.java @@ -21,6 +21,7 @@ import org.apache.doris.analysis.Analyzer; import org.apache.doris.analysis.BinaryPredicate; import org.apache.doris.analysis.CancelLoadStmt; +import org.apache.doris.analysis.CastExpr; import org.apache.doris.analysis.ColumnSeparator; import org.apache.doris.analysis.DataDescription; import org.apache.doris.analysis.DeleteStmt; @@ -1054,7 +1055,20 @@ public static void initColumns(Table tbl, List columnExprs, slotDescByName.put(realColName, slotDesc); } } - LOG.debug("slotDescByName: {}, exprsByName: {}", slotDescByName, exprsByName); + /* + * The extension column of the materialized view is added to the expression evaluation of load + * To avoid nested expressions. eg : column(a, tmp_c, c = expr(tmp_c)) , + * __doris_materialized_view_bitmap_union_c need be analyzed after exprsByName + * So the columns of the materialized view are stored separately here + */ + Map mvDefineExpr = Maps.newHashMap(); + for (Column column : tbl.getFullSchema()) { + if (column.getDefineExpr() != null) { + mvDefineExpr.put(column.getName(), column.getDefineExpr()); + } + } + + LOG.debug("slotDescByName: {}, exprsByName: {}, mvDefineExpr: {}", slotDescByName, exprsByName, mvDefineExpr); // analyze all exprs for (Map.Entry entry : exprsByName.entrySet()) { @@ -1083,6 +1097,30 @@ public static void initColumns(Table tbl, List columnExprs, } exprsByName.put(entry.getKey(), expr); } + + for (Map.Entry entry : mvDefineExpr.entrySet()) { + ExprSubstitutionMap smap = new ExprSubstitutionMap(); + List slots = Lists.newArrayList(); + entry.getValue().collect(SlotRef.class, slots); + for (SlotRef slot : slots) { + if (slotDescByName.get(slot.getColumnName()) != null) { + smap.getLhs().add(slot); + smap.getRhs().add(new CastExpr(tbl.getColumn(slot.getColumnName()).getType(), + new SlotRef(slotDescByName.get(slot.getColumnName())))); + } else if (exprsByName.get(slot.getColumnName()) != null) { + smap.getLhs().add(slot); + smap.getRhs().add(new CastExpr(tbl.getColumn(slot.getColumnName()).getType(), + exprsByName.get(slot.getColumnName()))); + } else { + throw new UserException("unknown reference column, column=" + entry.getKey() + + ", reference=" + slot.getColumnName()); + } + } + Expr expr = entry.getValue().clone(smap); + expr.analyze(analyzer); + + exprsByName.put(entry.getKey(), expr); + } LOG.debug("after init column, exprMap: {}", exprsByName); } diff --git a/fe/src/test/java/org/apache/doris/analysis/InsertStmtTest.java b/fe/src/test/java/org/apache/doris/analysis/InsertStmtTest.java new file mode 100644 index 00000000000000..bcc9d87b63d901 --- /dev/null +++ b/fe/src/test/java/org/apache/doris/analysis/InsertStmtTest.java @@ -0,0 +1,284 @@ +// 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.doris.analysis; + +import mockit.Tested; +import org.apache.doris.catalog.AggregateType; +import org.apache.doris.catalog.Catalog; +import org.apache.doris.catalog.Column; +import org.apache.doris.catalog.Function; +import org.apache.doris.catalog.KeysType; +import org.apache.doris.catalog.OlapTable; +import org.apache.doris.catalog.PrimitiveType; +import org.apache.doris.catalog.ScalarType; +import org.apache.doris.catalog.Table; +import org.apache.doris.catalog.Type; +import org.apache.doris.clone.TabletScheduler; +import org.apache.doris.common.AnalysisException; +import org.apache.doris.common.Config; +import org.apache.doris.common.UserException; +import org.apache.doris.common.jmockit.Deencapsulation; +import org.apache.doris.common.util.SqlParserUtils; +import org.apache.doris.planner.StreamLoadScanNode; +import org.apache.doris.planner.StreamLoadScanNodeTest; +import org.apache.doris.qe.ConnectContext; + +import com.google.common.collect.Lists; + +import org.apache.doris.thrift.TStreamLoadPutRequest; +import org.apache.doris.utframe.DorisAssert; +import org.apache.doris.utframe.UtFrameUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.StringReader; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import mockit.Expectations; +import mockit.Injectable; +import mockit.Mocked; + +public class InsertStmtTest { + private static String runningDir = "fe/mocked/DemoTest/" + UUID.randomUUID().toString() + "/"; + private static DorisAssert dorisAssert; + + @AfterClass + public static void tearDown() throws Exception { + UtFrameUtils.cleanDorisFeDir(runningDir); + } + + @BeforeClass + public static void setUp() throws Exception { + UtFrameUtils.createMinDorisCluster(runningDir); + String createTblStmtStr = "create table db.tbl(kk1 int, kk2 varchar(32), kk3 int, kk4 int) " + + "AGGREGATE KEY(kk1, kk2,kk3,kk4) distributed by hash(kk1) buckets 3 properties('replication_num' = '1');"; + dorisAssert = new DorisAssert(); + dorisAssert.withDatabase("db").useDatabase("db"); + dorisAssert.withTable(createTblStmtStr); + + ConnectContext ctx = UtFrameUtils.createDefaultCtx(); + } + + List getBaseSchema() { + List columns = Lists.newArrayList(); + + Column k1 = new Column("k1", PrimitiveType.BIGINT); + k1.setIsKey(true); + k1.setIsAllowNull(false); + columns.add(k1); + + Column k2 = new Column("k2", ScalarType.createVarchar(25)); + k2.setIsKey(true); + k2.setIsAllowNull(true); + columns.add(k2); + + Column v1 = new Column("v1", PrimitiveType.BIGINT); + v1.setIsKey(false); + v1.setIsAllowNull(true); + v1.setAggregationType(AggregateType.SUM, false); + + columns.add(v1); + + Column v2 = new Column("v2", ScalarType.createVarchar(25)); + v2.setIsKey(false); + v2.setAggregationType(AggregateType.REPLACE, false); + v2.setIsAllowNull(false); + columns.add(v2); + + return columns; + } + + List getFullSchema() throws Exception { + List columns = Lists.newArrayList(); + + Column k1 = new Column("k1", PrimitiveType.BIGINT); + k1.setIsKey(true); + k1.setIsAllowNull(false); + columns.add(k1); + + Column k2 = new Column("k2", ScalarType.createVarchar(25)); + k2.setIsKey(true); + k2.setIsAllowNull(true); + columns.add(k2); + + Column v1 = new Column("v1", PrimitiveType.BIGINT); + v1.setIsKey(false); + v1.setIsAllowNull(true); + v1.setAggregationType(AggregateType.SUM, false); + + columns.add(v1); + + Column v2 = new Column("v2", ScalarType.createVarchar(25)); + v2.setIsKey(false); + v2.setAggregationType(AggregateType.REPLACE, false); + v2.setIsAllowNull(false); + columns.add(v2); + + Column v3 = new Column("__doris_materialized_view_bitmap_k1", PrimitiveType.BITMAP); + v3.setIsKey(false); + v3.setAggregationType(AggregateType.BITMAP_UNION, false); + v3.setIsAllowNull(false); + ArrayList params = new ArrayList<>(); + + SlotRef slotRef = new SlotRef(null , "k1"); + slotRef.setType(Type.BIGINT); + params.add(slotRef.uncheckedCastTo(Type.VARCHAR)); + + Expr defineExpr = new FunctionCallExpr("to_bitmap", params); + v3.setDefineExpr(defineExpr); + columns.add(v3); + + Column v4 = new Column("__doris_materialized_view_hll_k2", PrimitiveType.HLL); + v4.setIsKey(false); + v4.setAggregationType(AggregateType.HLL_UNION, false); + v4.setIsAllowNull(false); + params = new ArrayList<>(); + params.add(new SlotRef(null, "k2")); + defineExpr = new FunctionCallExpr("hll_hash", params); + v4.setDefineExpr(defineExpr); + columns.add(v4); + + return columns; + } + + + @Injectable InsertTarget target; + @Injectable InsertSource source; + @Injectable Table targetTable; + + @Test + public void testNormal() throws Exception { + ConnectContext ctx = UtFrameUtils.createDefaultCtx(); + String sql = "values(1,'a',2,'b')"; + + SqlScanner input = new SqlScanner(new StringReader(sql), ctx.getSessionVariable().getSqlMode()); + SqlParser parser = new SqlParser(input); + Analyzer analyzer = new Analyzer(ctx.getCatalog(), ctx); + StatementBase statementBase = null; + try { + statementBase = SqlParserUtils.getFirstStmt(parser); + } catch (AnalysisException e) { + String errorMessage = parser.getErrorMsg(sql); + System.err.println("parse failed: " + errorMessage); + if (errorMessage == null) { + throw e; + } else { + throw new AnalysisException(errorMessage, e); + } + } + statementBase.analyze(analyzer); + + QueryStmt queryStmt = (QueryStmt) statementBase; + + new Expectations() {{ + targetTable.getBaseSchema(); result = getBaseSchema(); + targetTable.getFullSchema(); result = getFullSchema(); + }}; + + + InsertStmt stmt = new InsertStmt(target, "label", null, source, new ArrayList<>()); + stmt.setTargetTable(targetTable); + stmt.setQueryStmt(queryStmt); + + Deencapsulation.invoke(stmt, "analyzeSubquery", analyzer); + System.out.println(stmt.getQueryStmt()); + + QueryStmt queryStmtSubstitue = stmt.getQueryStmt(); + Assert.assertEquals(6, queryStmtSubstitue.getResultExprs().size()); + + Assert.assertTrue(queryStmtSubstitue.getResultExprs().get(4) instanceof FunctionCallExpr); + FunctionCallExpr expr4 = (FunctionCallExpr) queryStmtSubstitue.getResultExprs().get(4); + Assert.assertTrue(expr4.getFnName().getFunction().equals("to_bitmap")); + List slots = Lists.newArrayList(); + expr4.collect(IntLiteral.class, slots); + Assert.assertEquals(1, slots.size()); + Assert.assertEquals(queryStmtSubstitue.getResultExprs().get(0), slots.get(0)); + + Assert.assertTrue(queryStmtSubstitue.getResultExprs().get(5) instanceof FunctionCallExpr); + FunctionCallExpr expr5 = (FunctionCallExpr) queryStmtSubstitue.getResultExprs().get(5); + Assert.assertTrue(expr5.getFnName().getFunction().equals("hll_hash")); + slots = Lists.newArrayList(); + expr5.collect(StringLiteral.class, slots); + Assert.assertEquals(1, slots.size()); + Assert.assertEquals(queryStmtSubstitue.getResultExprs().get(1), slots.get(0)); + } + + @Test + public void testInsertSelect() throws Exception { + ConnectContext ctx = UtFrameUtils.createDefaultCtx(); + String sql = "select kk1, kk2, kk3, kk4 from db.tbl"; + + SqlScanner input = new SqlScanner(new StringReader(sql), ctx.getSessionVariable().getSqlMode()); + SqlParser parser = new SqlParser(input); + Analyzer analyzer = new Analyzer(ctx.getCatalog(), ctx); + StatementBase statementBase = null; + try { + statementBase = SqlParserUtils.getFirstStmt(parser); + } catch (AnalysisException e) { + String errorMessage = parser.getErrorMsg(sql); + System.err.println("parse failed: " + errorMessage); + if (errorMessage == null) { + throw e; + } else { + throw new AnalysisException(errorMessage, e); + } + } + statementBase.analyze(analyzer); + + QueryStmt queryStmt = (QueryStmt) statementBase; + + new Expectations() {{ + targetTable.getBaseSchema(); result = getBaseSchema(); + targetTable.getFullSchema(); result = getFullSchema(); + }}; + + + InsertStmt stmt = new InsertStmt(target, "label", null, source, new ArrayList<>()); + stmt.setTargetTable(targetTable); + stmt.setQueryStmt(queryStmt); + + Deencapsulation.invoke(stmt, "analyzeSubquery", analyzer); + System.out.println(stmt.getQueryStmt()); + + QueryStmt queryStmtSubstitue = stmt.getQueryStmt(); + Assert.assertEquals(6, queryStmtSubstitue.getResultExprs().size()); + + Assert.assertTrue(queryStmtSubstitue.getResultExprs().get(4) instanceof FunctionCallExpr); + FunctionCallExpr expr4 = (FunctionCallExpr) queryStmtSubstitue.getResultExprs().get(4); + Assert.assertTrue(expr4.getFnName().getFunction().equals("to_bitmap")); + List slots = Lists.newArrayList(); + expr4.collect(SlotRef.class, slots); + Assert.assertEquals(1, slots.size()); + Assert.assertEquals(queryStmtSubstitue.getResultExprs().get(0), slots.get(0)); + + Assert.assertTrue(queryStmtSubstitue.getResultExprs().get(5) instanceof FunctionCallExpr); + FunctionCallExpr expr5 = (FunctionCallExpr) queryStmtSubstitue.getResultExprs().get(5); + Assert.assertTrue(expr5.getFnName().getFunction().equals("hll_hash")); + slots = Lists.newArrayList(); + expr5.collect(SlotRef.class, slots); + Assert.assertEquals(1, slots.size()); + Assert.assertEquals(queryStmtSubstitue.getResultExprs().get(1), slots.get(0)); + } +}