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

yangzhg pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 6a05879  [Feature][Step1] Support lateral view FE part (#6745)
6a05879 is described below

commit 6a058792af8d9120eb85633844fa6f178bbb7a05
Author: EmmyMiao87 <[email protected]>
AuthorDate: Wed Oct 13 11:37:12 2021 +0800

    [Feature][Step1] Support lateral view FE part (#6745)
    
    * [Feature] Support lateral view
    
    The syntax:
    ```
    select k1, e1 from test lateral view explode_split(k1, ",") tmp as e1;
    ```
    ```explode_split``` is a special function of doris,
    which is used to separate the string column according to the specified 
split string,
    and then convert the row to column.
    This is a conforming function of string separation + table function,
    and its behavior is equivalent to explode in hive ```explode(split(string, 
string))```
    
    The implement:
    A tablefunction operator is added to the implementation to handle the 
syntax of the lateral view separately.
    The query plan is following:
    ```
    MySQL [test]> explain select k1, e1 from test_explode lateral view 
explode_split (k2, ",") tmp as e1;
    
+---------------------------------------------------------------------------+
    | Explain String                                                            
|
    
+---------------------------------------------------------------------------+
    | PLAN FRAGMENT 0                                                           
|
    |  OUTPUT EXPRS:`k1` | `e1`                                                 
|
    |                                                                           
|
    |   RESULT SINK                                                             
|
    |                                                                           
|
    |   1:TABLE FUNCTION NODE                                                   
|
    |   |  table function: explode_split(`k2`, ',')                             
|
    |   |                                                                       
|
    |   0:OlapScanNode                                                          
|
    |      TABLE: test_explode                                                  
|
    
+---------------------------------------------------------------------------+
    ```
    
    * Add ut
    
    * Add multi table function node
    
    * Add session variables 'enable_lateral_view'
    
    * Fix ut
---
 fe/fe-core/src/main/cup/sql_parser.cup             |  43 ++++-
 .../org/apache/doris/analysis/BaseTableRef.java    |  15 +-
 .../main/java/org/apache/doris/analysis/Expr.java  |   9 +
 .../apache/doris/analysis/FunctionCallExpr.java    |  20 ++-
 .../org/apache/doris/analysis/InlineViewRef.java   |   5 +-
 .../org/apache/doris/analysis/LateralViewRef.java  | 127 +++++++++++++
 .../java/org/apache/doris/analysis/SelectStmt.java |   9 +
 .../java/org/apache/doris/analysis/TableRef.java   |  16 +-
 .../java/org/apache/doris/catalog/Catalog.java     |   4 +
 .../java/org/apache/doris/catalog/FunctionSet.java |  40 ++++-
 .../apache/doris/planner/DistributedPlanner.java   |  13 +-
 .../apache/doris/planner/SingleNodePlanner.java    |  22 ++-
 .../apache/doris/planner/TableFunctionNode.java    |  81 +++++++++
 .../java/org/apache/doris/qe/SessionVariable.java  |  13 ++
 fe/fe-core/src/main/jflex/sql_scanner.flex         |   1 +
 .../doris/planner/RuntimeFilterGeneratorTest.java  |   3 +-
 .../doris/planner/TableFunctionPlanTest.java       | 199 +++++++++++++++++++++
 .../org/apache/doris/utframe/UtFrameUtils.java     |   6 +-
 gensrc/thrift/PlanNodes.thrift                     |   7 +
 19 files changed, 612 insertions(+), 21 deletions(-)

diff --git a/fe/fe-core/src/main/cup/sql_parser.cup 
b/fe/fe-core/src/main/cup/sql_parser.cup
index 20e9c8e..be00fd2 100644
--- a/fe/fe-core/src/main/cup/sql_parser.cup
+++ b/fe/fe-core/src/main/cup/sql_parser.cup
@@ -236,7 +236,7 @@ parser code {:
 terminal String KW_ADD, KW_ADMIN, KW_AFTER, KW_AGGREGATE, KW_ALIAS, KW_ALL, 
KW_ALTER, KW_AND, KW_ANTI, KW_APPEND, KW_AS, KW_ASC, KW_AUTHORS, KW_ARRAY,
     KW_BACKEND, KW_BACKUP, KW_BETWEEN, KW_BEGIN, KW_BIGINT, KW_BINLOG, 
KW_BITMAP, KW_BITMAP_UNION, KW_BLOB, KW_BOOLEAN, KW_BROKER, KW_BACKENDS, KW_BY, 
KW_BUILTIN,
     KW_CANCEL, KW_CASE, KW_CAST, KW_CHAIN, KW_CHAR, KW_CHARSET, KW_CHECK, 
KW_CLUSTER, KW_CLUSTERS, KW_CLEAN,
-    KW_COLLATE, KW_COLLATION, KW_COLUMN, KW_COLON, KW_COLUMNS, KW_COMMENT, 
KW_COMMIT, KW_COMMITTED,
+    KW_COLLATE, KW_COLLATION, KW_COLUMN, KW_COLUMNS, KW_COMMENT, KW_COMMIT, 
KW_COMMITTED,
     KW_CONFIG, KW_CONNECTION, KW_CONNECTION_ID, KW_CONSISTENT, KW_CONVERT, 
KW_COUNT, KW_CREATE, KW_CROSS, KW_CUBE, KW_CURRENT, KW_CURRENT_USER,
     KW_DATA, KW_DATABASE, KW_DATABASES, KW_DATE, KW_DATETIME, KW_DAY, 
KW_DECIMAL, KW_DECOMMISSION, KW_DEFAULT, KW_DESC, KW_DESCRIBE,
     KW_DELETE, KW_UPDATE, KW_DISTINCT, KW_DISTINCTPC, KW_DISTINCTPCSA, 
KW_DISTRIBUTED, KW_DISTRIBUTION, KW_DYNAMIC, KW_BUCKETS, KW_DIV, KW_DOUBLE, 
KW_DROP, KW_DROPP, KW_DUPLICATE,
@@ -250,9 +250,9 @@ terminal String KW_ADD, KW_ADMIN, KW_AFTER, KW_AGGREGATE, 
KW_ALIAS, KW_ALL, KW_A
     KW_JOB, KW_JOIN,
     KW_KEY, KW_KEYS, KW_KILL,
     KW_LABEL, KW_LARGEINT, KW_LAST, KW_LEFT, KW_LESS, KW_LEVEL, KW_LIKE, 
KW_LIMIT, KW_LINK, KW_LIST, KW_LOAD,
-    KW_LOCAL, KW_LOCATION,
+    KW_LOCAL, KW_LOCATION, KW_LATERAL,
     KW_MAP, KW_MATERIALIZED, KW_MAX, KW_MAX_VALUE, KW_MERGE, KW_MIN, 
KW_MINUTE, KW_MINUS, KW_MIGRATE, KW_MIGRATIONS, KW_MODIFY, KW_MONTH,
-    KW_NAME, KW_NAMED_STRUCT, KW_NAMES, KW_NEGATIVE, KW_NO, KW_NOT, KW_NULL, 
KW_NULLS,
+    KW_NAME, KW_NAMES, KW_NEGATIVE, KW_NO, KW_NOT, KW_NULL, KW_NULLS,
     KW_OBSERVER, KW_OFFSET, KW_ON, KW_ONLY, KW_OPEN, KW_OR, KW_ORDER, 
KW_OUTER, KW_OUTFILE, KW_OVER,
     KW_PARAMETER, KW_PARTITION, KW_PARTITIONS, KW_PASSWORD, 
KW_LDAP_ADMIN_PASSWORD, KW_PATH, KW_PAUSE, KW_PIPE, KW_PRECEDING,
     KW_PLUGIN, KW_PLUGINS,
@@ -385,9 +385,11 @@ nonterminal AnalyticWindow.Boundary window_boundary;
 nonterminal SlotRef column_ref;
 nonterminal FunctionCallExpr column_subscript;
 nonterminal ArrayList<TableRef> table_ref_list, base_table_ref_list;
+nonterminal ArrayList<LateralViewRef> opt_lateral_view_ref_list, 
lateral_view_ref_list;
 nonterminal FromClause from_clause;
 nonterminal TableRef table_ref;
 nonterminal TableRef base_table_ref;
+nonterminal LateralViewRef lateral_view_ref;
 nonterminal WithClause opt_with_clause;
 nonterminal ArrayList<View> with_view_def_list;
 nonterminal View with_view_def;
@@ -3998,8 +4000,9 @@ base_table_ref_list ::=
 
 base_table_ref ::=
     table_name:name opt_partition_names:partitionNames opt_table_alias:alias 
opt_common_hints:commonHints
+    opt_lateral_view_ref_list:lateralViewRefList
     {:
-        RESULT = new TableRef(name, alias, partitionNames, commonHints);
+        RESULT = new TableRef(name, alias, partitionNames, commonHints, 
lateralViewRefList);
     :}
     ;
 
@@ -4075,6 +4078,38 @@ partition_names ::=
     :}
     ;
 
+opt_lateral_view_ref_list ::=
+    /* empty */
+    {:
+        RESULT = null;
+    :}
+    | lateral_view_ref_list:lateralViewRefList
+    {:
+        RESULT = lateralViewRefList;
+    :}
+    ;
+
+lateral_view_ref_list ::=
+    lateral_view_ref:lateralViewRef
+    {:
+        ArrayList<LateralViewRef> list = new ArrayList<LateralViewRef>();
+        list.add(lateralViewRef);
+        RESULT = list;
+    :}
+    |  lateral_view_ref:lateralViewRef lateral_view_ref_list:lateralViewRefList
+    {:
+        lateralViewRefList.add(lateralViewRef);
+        RESULT = lateralViewRefList;
+    :}
+    ;
+
+lateral_view_ref ::=
+    KW_LATERAL KW_VIEW function_call_expr:fnExpr ident:viewName KW_AS 
ident:columnName
+    {:
+        RESULT = new LateralViewRef(fnExpr, viewName, columnName);
+    :}
+    ;
+
 join_operator ::=
   opt_inner KW_JOIN
   {: RESULT = JoinOperator.INNER_JOIN; :}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/analysis/BaseTableRef.java 
b/fe/fe-core/src/main/java/org/apache/doris/analysis/BaseTableRef.java
index f46e60a..f39db3a 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/BaseTableRef.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/BaseTableRef.java
@@ -18,7 +18,7 @@
 package org.apache.doris.analysis;
 
 import org.apache.doris.catalog.Table;
-import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.UserException;
 
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
@@ -63,7 +63,7 @@ public class BaseTableRef extends TableRef {
      * Register this table ref and then analyze the Join clause.
      */
     @Override
-    public void analyze(Analyzer analyzer) throws AnalysisException {
+    public void analyze(Analyzer analyzer) throws UserException {
         name = analyzer.getFqTableName(name);
         name.analyze(analyzer);
         desc = analyzer.registerTableRef(this);
@@ -71,6 +71,17 @@ public class BaseTableRef extends TableRef {
         analyzeJoin(analyzer);
         analyzeSortHints();
         analyzeHints();
+        analyzeLateralViewRef(analyzer);
+    }
+
+    private void analyzeLateralViewRef(Analyzer analyzer) throws UserException 
{
+        if (lateralViewRefs == null) {
+            return;
+        }
+        for (LateralViewRef lateralViewRef : lateralViewRefs) {
+            lateralViewRef.setRelatedTable(table);
+            lateralViewRef.analyze(analyzer);
+        }
     }
 }
 
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/Expr.java 
b/fe/fe-core/src/main/java/org/apache/doris/analysis/Expr.java
index a7098a9..d27000c 100755
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/Expr.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/Expr.java
@@ -1575,6 +1575,15 @@ abstract public class Expr extends TreeNode<Expr> 
implements ParseNode, Cloneabl
         return f;
     }
 
+    protected Function getTableFunction(String name, Type[] argTypes,
+                                        Function.CompareMode mode) {
+        FunctionName fnName = new FunctionName(name);
+        Function searchDesc = new Function(fnName, Arrays.asList(argTypes), 
Type.INVALID, false,
+                VectorizedUtil.isVectorized());
+        Function f = Catalog.getCurrentCatalog().getTableFunction(searchDesc, 
mode);
+        return f;
+    }
+
     /**
      * Pushes negation to the individual operands of a predicate
      * tree rooted at 'root'.
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/analysis/FunctionCallExpr.java 
b/fe/fe-core/src/main/java/org/apache/doris/analysis/FunctionCallExpr.java
index c93efd8..d10bc32 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/FunctionCallExpr.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/FunctionCallExpr.java
@@ -18,8 +18,8 @@
 package org.apache.doris.analysis;
 
 import org.apache.doris.catalog.AggregateFunction;
-import org.apache.doris.catalog.ArrayType;
 import org.apache.doris.catalog.AliasFunction;
+import org.apache.doris.catalog.ArrayType;
 import org.apache.doris.catalog.Catalog;
 import org.apache.doris.catalog.Database;
 import org.apache.doris.catalog.Function;
@@ -52,9 +52,9 @@ import org.apache.logging.log4j.Logger;
 import java.io.DataInput;
 import java.io.DataOutput;
 import java.io.IOException;
+import java.text.StringCharacterIterator;
 import java.util.Arrays;
 import java.util.List;
-import java.text.StringCharacterIterator;
 // TODO: for aggregations, we need to unify the code paths for builtins and 
UDAs.
 public class FunctionCallExpr extends Expr {
     private static final Logger LOG = 
LogManager.getLogger(FunctionCallExpr.class);
@@ -64,6 +64,8 @@ public class FunctionCallExpr extends Expr {
 
     // check analytic function
     private boolean isAnalyticFnCall = false;
+    // check table function
+    private boolean isTableFnCall = false;
 
     // Indicates whether this is a merge aggregation function that should use 
the merge
     // instead of the update symbol. This flag also affects the behavior of
@@ -87,6 +89,10 @@ public class FunctionCallExpr extends Expr {
         isAnalyticFnCall = v;
     }
 
+    public void setTableFnCall(boolean tableFnCall) {
+        isTableFnCall = tableFnCall;
+    }
+
     public Function getFn() {
         return fn;
     }
@@ -675,6 +681,16 @@ public class FunctionCallExpr extends Expr {
             fn = getBuiltinFunction(analyzer, fnName.getFunction(), new 
Type[]{compatibleType},
                     Function.CompareMode.IS_NONSTRICT_SUPERTYPE_OF);
         } else {
+            // now first find table function in table function sets
+            if (isTableFnCall) {
+                Type[] childTypes = collectChildReturnTypes();
+                fn = getTableFunction(fnName.getFunction(), childTypes,
+                        Function.CompareMode.IS_NONSTRICT_SUPERTYPE_OF);
+                if (fn == null) {
+                    throw new AnalysisException("Doris only support 
`explode_split(varchar, varchar)` table function");
+                }
+                return;
+            }
             // now first find function in built-in functions
             if (Strings.isNullOrEmpty(fnName.getDb())) {
                 Type[] childTypes = collectChildReturnTypes();
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/analysis/InlineViewRef.java 
b/fe/fe-core/src/main/java/org/apache/doris/analysis/InlineViewRef.java
index 807949e..cd8b045 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/InlineViewRef.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/InlineViewRef.java
@@ -266,8 +266,9 @@ public class InlineViewRef extends TableRef {
             }
 
             columnSet.add(colAlias);
-            columnList.add(new Column(colAlias, 
selectItemExpr.getType().getPrimitiveType(),
-                    selectItemExpr.isNullable()));
+            columnList.add(new Column(colAlias, selectItemExpr.getType(),
+                    false, null, selectItemExpr.isNullable(),
+                    null, ""));
         }
         InlineView inlineView = (view != null) ? new InlineView(view, 
columnList) : new InlineView(getExplicitAlias(), columnList);
 
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/analysis/LateralViewRef.java 
b/fe/fe-core/src/main/java/org/apache/doris/analysis/LateralViewRef.java
new file mode 100644
index 0000000..e6576ec
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/LateralViewRef.java
@@ -0,0 +1,127 @@
+// 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 org.apache.doris.catalog.Column;
+import org.apache.doris.catalog.FunctionSet;
+import org.apache.doris.catalog.InlineView;
+import org.apache.doris.catalog.OlapTable;
+import org.apache.doris.catalog.Table;
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.UserException;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+/**
+ * lateralView: LATERAL VIEW udtf(expression) tableAlias AS columnAlias (',' 
columnAlias)
+ * fromClause: FROM baseTable (lateralView)
+ */
+public class LateralViewRef extends TableRef {
+
+    private Expr expr;
+    private String viewName;
+    private String columnName;
+    private Table relatedTable;
+
+    // after analyzed
+    private FunctionCallExpr fnExpr;
+    private Column originColumn;
+    private SlotRef originSlotRef;
+    private InlineView view;
+    private SlotRef explodeSlotRef;
+
+    public LateralViewRef(Expr expr, String viewName, String columnName) {
+        super(null, viewName);
+        this.expr = expr;
+        this.viewName = viewName;
+        this.columnName = columnName;
+    }
+
+    public void setRelatedTable(Table relatedTable) {
+        this.relatedTable = relatedTable;
+    }
+
+    public FunctionCallExpr getFnExpr() {
+        return fnExpr;
+    }
+
+    @Override
+    public void analyze(Analyzer analyzer) throws UserException {
+        if (!analyzer.getContext().getSessionVariable().isEnableLateralView()) 
{
+            throw new AnalysisException("The session variables 
`enable_lateral_view` is false");
+        }
+
+        if (isAnalyzed) {
+            return;
+        }
+        Preconditions.checkNotNull(relatedTable);
+        // analyze table
+        if (!(relatedTable instanceof OlapTable)) {
+            throw new AnalysisException("Only doris table could be exploded");
+        }
+        // analyze function and slot
+        if (!(expr instanceof FunctionCallExpr)) {
+            throw new AnalysisException("Only support function call expr in 
lateral view");
+        }
+        fnExpr = (FunctionCallExpr) expr;
+        fnExpr.setTableFnCall(true);
+        fnExpr.analyze(analyzer);
+        if 
(!fnExpr.getFnName().getFunction().equals(FunctionSet.EXPLODE_SPLIT)) {
+            throw new AnalysisException("Only support explode function in 
lateral view");
+        }
+        if (!(fnExpr.getChild(0) instanceof SlotRef)) {
+            throw new AnalysisException("Explode column must be varchar 
column");
+        }
+        if (!(fnExpr.getChild(1) instanceof StringLiteral)) {
+            throw new AnalysisException("Split separator of explode must be a 
string const");
+        }
+        originSlotRef = ((SlotRef) fnExpr.getChild(0));
+        originColumn = originSlotRef.getColumn();
+        if (originColumn == null) {
+            throw new AnalysisException("The explode column must be a real 
column in table");
+        }
+        // analyze lateral view
+        desc = analyzer.registerTableRef(this);
+        explodeSlotRef = new SlotRef(new TableName(null, viewName), 
columnName);
+        explodeSlotRef.analyze(analyzer);
+        isAnalyzed = true;  // true now that we have assigned desc
+    }
+
+    @Override
+    public TupleDescriptor createTupleDescriptor(Analyzer analyzer) throws 
AnalysisException {
+        // Create a fake catalog table for the lateral view
+        List<Column> columnList = Lists.newArrayList();
+        columnList.add(new Column(columnName, originColumn.getType(),
+                false, null, originColumn.isAllowNull(),
+                null, ""));
+        view = new InlineView(viewName, columnList);
+
+        // Create the non-materialized tuple and set the fake table in it.
+        TupleDescriptor result = analyzer.getDescTbl().createTupleDescriptor();
+        result.setTable(view);
+        return result;
+    }
+
+    public void materializeRequiredSlots() {
+        originSlotRef.getDesc().setIsMaterialized(true);
+        explodeSlotRef.getDesc().setIsMaterialized(true);
+    }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/SelectStmt.java 
b/fe/fe-core/src/main/java/org/apache/doris/analysis/SelectStmt.java
index 0f2167e..1d19f79 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/SelectStmt.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/SelectStmt.java
@@ -692,6 +692,15 @@ public class SelectStmt extends QueryStmt {
             materializeSlots(analyzer, havingConjuncts);
             aggInfo.materializeRequiredSlots(analyzer, baseTblSmap);
         }
+
+        // materialized all lateral view column and origin column
+        for (TableRef tableRef : fromClause_.getTableRefs()) {
+            if (tableRef.lateralViewRefs != null) {
+                for (LateralViewRef lateralViewRef : tableRef.lateralViewRefs) 
{
+                    lateralViewRef.materializeRequiredSlots();
+                }
+            }
+        }
     }
 
     protected void reorderTable(Analyzer analyzer) throws AnalysisException {
diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/TableRef.java 
b/fe/fe-core/src/main/java/org/apache/doris/analysis/TableRef.java
index f18d574..256d0f3 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/analysis/TableRef.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/TableRef.java
@@ -17,7 +17,6 @@
 
 package org.apache.doris.analysis;
 
-import org.apache.commons.collections.CollectionUtils;
 import org.apache.doris.catalog.Catalog;
 import org.apache.doris.catalog.Table;
 import org.apache.doris.common.AnalysisException;
@@ -34,6 +33,7 @@ import com.google.common.base.Preconditions;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 
+import org.apache.commons.collections.CollectionUtils;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
@@ -94,6 +94,7 @@ public class TableRef implements ParseNode, Writable {
     private ArrayList<String> joinHints;
     private ArrayList<String> sortHints;
     private ArrayList<String> commonHints; //The Hints is set by user
+    protected ArrayList<LateralViewRef> lateralViewRefs;
     private boolean isForcePreAggOpened;
     // ///////////////////////////////////////
     // BEGIN: Members that need to be reset()
@@ -141,19 +142,21 @@ public class TableRef implements ParseNode, Writable {
     }
 
     public TableRef(TableName name, String alias, PartitionNames 
partitionNames) {
-        this(name, alias, partitionNames, null);
+        this(name, alias, partitionNames, null, null);
     }
 
-    public TableRef(TableName name, String alias, PartitionNames 
partitionNames, ArrayList<String> commonHints) {
+    public TableRef(TableName name, String alias, PartitionNames 
partitionNames, ArrayList<String> commonHints,
+                    ArrayList<LateralViewRef> lateralViewRefs) {
         this.name = name;
         if (alias != null) {
-            aliases_ = new String[] { alias };
+            aliases_ = new String[]{alias};
             hasExplicitAlias_ = true;
         } else {
             hasExplicitAlias_ = false;
         }
         this.partitionNames = partitionNames;
         this.commonHints = commonHints;
+        this.lateralViewRefs = lateralViewRefs;
         isAnalyzed = false;
     }
     // Only used to clone
@@ -182,6 +185,7 @@ public class TableRef implements ParseNode, Writable {
         allMaterializedTupleIds_ = 
Lists.newArrayList(other.allMaterializedTupleIds_);
         correlatedTupleIds_ = Lists.newArrayList(other.correlatedTupleIds_);
         desc = other.desc;
+        lateralViewRefs = other.lateralViewRefs;
     }
 
     public PartitionNames getPartitionNames() {
@@ -329,6 +333,10 @@ public class TableRef implements ParseNode, Writable {
         return sortColumn;
     }
 
+    public ArrayList<LateralViewRef> getLateralViewRefs() {
+        return lateralViewRefs;
+    }
+
     protected void analyzeSortHints() throws AnalysisException {
         if (sortHints == null) {
             return;
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/Catalog.java 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/Catalog.java
index f846b01..e806fea 100755
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/Catalog.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/Catalog.java
@@ -5888,6 +5888,10 @@ public class Catalog {
         return functionSet.getBulitinFunctions();
     }
 
+    public Function getTableFunction(Function desc, Function.CompareMode mode) 
{
+        return  functionSet.getFunction(desc, mode, true);
+    }
+
     public boolean isNullResultWithOneNullParamFunction(String funcName) {
         return functionSet.isNullResultWithOneNullParamFunctions(funcName);
     }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionSet.java 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionSet.java
index d318193..379c567 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionSet.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionSet.java
@@ -17,7 +17,6 @@
 
 package org.apache.doris.catalog;
 
-import com.google.common.base.Preconditions;
 import org.apache.doris.analysis.ArithmeticExpr;
 import org.apache.doris.analysis.BinaryPredicate;
 import org.apache.doris.analysis.CastExpr;
@@ -27,6 +26,7 @@ import org.apache.doris.analysis.IsNullPredicate;
 import org.apache.doris.analysis.LikePredicate;
 import org.apache.doris.builtins.ScalarBuiltins;
 
+import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
@@ -51,6 +51,7 @@ public class 
FunctionSet<min_initIN9doris_udf12DecimalV2ValEEEvPNS2_15FunctionCo
     // FunctionResolutionOrder.
     private final HashMap<String, List<Function>> functions;
     private final HashMap<String, List<Function>> vectorizedFunctions;
+    private final HashMap<String, List<Function>> tableFunctions;
     // For most build-in functions, it will return NullLiteral when params 
contain NullLiteral.
     // But a few functions need to handle NullLiteral differently, such as 
"if". It need to add
     // an attribute to LiteralExpr to mark null and check the attribute to 
decide whether to
@@ -66,6 +67,7 @@ public class 
FunctionSet<min_initIN9doris_udf12DecimalV2ValEEEvPNS2_15FunctionCo
     public FunctionSet() {
         functions = Maps.newHashMap();
         vectorizedFunctions = Maps.newHashMap();
+        tableFunctions = Maps.newHashMap();
     }
 
     public void init() {
@@ -82,6 +84,9 @@ public class 
FunctionSet<min_initIN9doris_udf12DecimalV2ValEEEvPNS2_15FunctionCo
         LikePredicate.initBuiltins(this);
         InPredicate.initBuiltins(this);
         AliasFunction.initBuiltins(this);
+
+        // init table function
+        initTableFunction();
     }
 
     public void buildNullResultWithOneNullParamFunction(Set<String> funcNames) 
{
@@ -977,7 +982,18 @@ public class 
FunctionSet<min_initIN9doris_udf12DecimalV2ValEEEvPNS2_15FunctionCo
                     .build();
 
     public Function getFunction(Function desc, Function.CompareMode mode) {
-        List<Function> fns = desc.isVectorized() ? 
vectorizedFunctions.get(desc.functionName()) : 
functions.get(desc.functionName());
+        return getFunction(desc, mode, false);
+    }
+
+    public Function getFunction(Function desc, Function.CompareMode mode, 
boolean isTableFunction) {
+        List<Function> fns;
+        if (isTableFunction) {
+            fns = tableFunctions.get(desc.functionName());
+        } else if (desc.isVectorized()) {
+            fns = vectorizedFunctions.get(desc.functionName());
+        } else {
+            fns = functions.get(desc.functionName());
+        }
         if (fns == null) {
             return null;
         }
@@ -1032,6 +1048,10 @@ public class 
FunctionSet<min_initIN9doris_udf12DecimalV2ValEEEvPNS2_15FunctionCo
         final String functionName = desc.getFunctionName().getFunction();
         final Type[] descArgTypes = desc.getArgs();
         final Type[] candicateArgTypes = candicate.getArgs();
+        if (!(descArgTypes[0] instanceof ScalarType)
+                || !(candicateArgTypes[0] instanceof ScalarType)) {
+            return false;
+        }
         if (functionName.equalsIgnoreCase("hex")
                 || functionName.equalsIgnoreCase("greast")
                 || functionName.equalsIgnoreCase("least")
@@ -1989,4 +2009,20 @@ public class 
FunctionSet<min_initIN9doris_udf12DecimalV2ValEEEvPNS2_15FunctionCo
         }
         return builtinFunctions;
     }
+
+
+    public static final String EXPLODE_SPLIT = "explode_split";
+
+    private void initTableFunction() {
+        // init explode_split function
+        ArrayList<Type> argsType = Lists.newArrayList();
+        argsType.add(Type.VARCHAR);
+        argsType.add(Type.VARCHAR);
+        Function explodeSplit = ScalarFunction.createBuiltin(
+                EXPLODE_SPLIT, Type.VARCHAR, 
Function.NullableMode.DEPEND_ON_ARGUMENT, argsType, false,
+                "", "", "", true);
+        List<Function> explodeSplits = Lists.newArrayList();
+        explodeSplits.add(explodeSplit);
+        tableFunctions.put(EXPLODE_SPLIT, explodeSplits);
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/planner/DistributedPlanner.java 
b/fe/fe-core/src/main/java/org/apache/doris/planner/DistributedPlanner.java
index ddf578c..a77a3ce 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/DistributedPlanner.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/DistributedPlanner.java
@@ -200,6 +200,8 @@ public class DistributedPlanner {
         if (root instanceof ScanNode) {
             result = createScanFragment(root);
             fragments.add(result);
+        } else if (root instanceof TableFunctionNode) {
+            result = createTableFunctionFragment(root, childFragments.get(0));
         } else if (root instanceof HashJoinNode) {
             Preconditions.checkState(childFragments.size() == 2);
             result = createHashJoinFragment((HashJoinNode) root, 
childFragments.get(1),
@@ -209,7 +211,7 @@ public class DistributedPlanner {
                     childFragments.get(0));
         } else if (root instanceof SelectNode) {
             result = createSelectNodeFragment((SelectNode) root, 
childFragments);
-        }  else if (root instanceof SetOperationNode) {
+        } else if (root instanceof SetOperationNode) {
             result = createSetOperationNodeFragment((SetOperationNode) root, 
childFragments, fragments);
         } else if (root instanceof MergeNode) {
             result = createMergeNodeFragment((MergeNode) root, childFragments, 
fragments);
@@ -288,12 +290,19 @@ public class DistributedPlanner {
         }
     }
 
+    private PlanFragment createTableFunctionFragment(PlanNode node, 
PlanFragment childFragment) {
+        Preconditions.checkState(node instanceof TableFunctionNode);
+        node.setChild(0, childFragment.getPlanRoot());
+        childFragment.addPlanRoot(node);
+        return childFragment;
+    }
+
     /**
      * When broadcastCost and partitionCost are equal, there is no uniform 
standard for which join implementation is better.
      * Some scenarios are suitable for broadcast join, and some scenarios are 
suitable for shuffle join.
      * Therefore, we add a SessionVariable to help users choose a better join 
implementation.
      */
-    private boolean isBroadcastCostSmaller(long broadcastCost, long 
partitionCost)  {
+    private boolean isBroadcastCostSmaller(long broadcastCost, long 
partitionCost) {
         String joinMethod = 
ConnectContext.get().getSessionVariable().getPreferJoinMethod();
         if (joinMethod.equalsIgnoreCase("broadcast")) {
             return broadcastCost <= partitionCost;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/planner/SingleNodePlanner.java 
b/fe/fe-core/src/main/java/org/apache/doris/planner/SingleNodePlanner.java
index f10e676..3d0533b 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/SingleNodePlanner.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/SingleNodePlanner.java
@@ -35,6 +35,7 @@ import org.apache.doris.analysis.InPredicate;
 import org.apache.doris.analysis.InlineViewRef;
 import org.apache.doris.analysis.IsNullPredicate;
 import org.apache.doris.analysis.JoinOperator;
+import org.apache.doris.analysis.LateralViewRef;
 import org.apache.doris.analysis.LiteralExpr;
 import org.apache.doris.analysis.NullLiteral;
 import org.apache.doris.analysis.QueryStmt;
@@ -1859,7 +1860,12 @@ public class SingleNodePlanner {
     private PlanNode createTableRefNode(Analyzer analyzer, TableRef tblRef, 
SelectStmt selectStmt)
             throws UserException, AnalysisException {
         if (tblRef instanceof BaseTableRef) {
-            return createScanNode(analyzer, tblRef, selectStmt);
+            PlanNode scanNode = createScanNode(analyzer, tblRef, selectStmt);
+            List<LateralViewRef> lateralViewRefs = tblRef.getLateralViewRefs();
+            if (lateralViewRefs != null && lateralViewRefs.size() != 0) {
+                return createTableFunctionNode(analyzer, scanNode, 
lateralViewRefs);
+            }
+            return scanNode;
         }
         if (tblRef instanceof InlineViewRef) {
             return createInlineViewPlan(analyzer, (InlineViewRef) tblRef);
@@ -1867,6 +1873,20 @@ public class SingleNodePlanner {
         throw new UserException("unknown TableRef node");
     }
 
+    private PlanNode createTableFunctionNode(Analyzer analyzer, PlanNode 
inputNode,
+                                                      List<LateralViewRef> 
lateralViewRefs)
+            throws UserException {
+        Preconditions.checkNotNull(lateralViewRefs);
+        Preconditions.checkState(lateralViewRefs.size() > 0);
+        for (LateralViewRef lateralViewRef: lateralViewRefs) {
+            TableFunctionNode tableFunctionNode = new 
TableFunctionNode(ctx_.getNextNodeId(), inputNode,
+                    lateralViewRef);
+            tableFunctionNode.init(analyzer);
+            inputNode = tableFunctionNode;
+        }
+        return inputNode;
+    }
+
     /**
      * Create a plan tree corresponding to 'setOperands' for the given 
SetOperationStmt.
      * The individual operands' plan trees are attached to a single 
SetOperationNode.
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/planner/TableFunctionNode.java 
b/fe/fe-core/src/main/java/org/apache/doris/planner/TableFunctionNode.java
new file mode 100644
index 0000000..4601f8a
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/TableFunctionNode.java
@@ -0,0 +1,81 @@
+// 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.planner;
+
+import org.apache.doris.analysis.Analyzer;
+import org.apache.doris.analysis.FunctionCallExpr;
+import org.apache.doris.analysis.LateralViewRef;
+import org.apache.doris.common.UserException;
+import org.apache.doris.thrift.TExplainLevel;
+import org.apache.doris.thrift.TPlanNode;
+import org.apache.doris.thrift.TPlanNodeType;
+import org.apache.doris.thrift.TTableFunctionNode;
+
+public class TableFunctionNode extends PlanNode {
+
+    private LateralViewRef lateralViewRef;
+
+    private FunctionCallExpr fnCallExpr;
+
+    protected TableFunctionNode(PlanNodeId id, PlanNode inputNode, 
LateralViewRef lateralViewRef) {
+        super(id, "TABLE FUNCTION NODE");
+        tupleIds.addAll(inputNode.getTupleIds());
+        tblRefIds.addAll(inputNode.getTupleIds());
+        tupleIds.add(lateralViewRef.getDesc().getId());
+        tblRefIds.add(lateralViewRef.getDesc().getId());
+        children.add(inputNode);
+        this.lateralViewRef = lateralViewRef;
+    }
+
+    @Override
+    public void init(Analyzer analyzer) throws UserException {
+        super.init(analyzer);
+        fnCallExpr = lateralViewRef.getFnExpr();
+        computeStats(analyzer);
+    }
+
+    @Override
+    protected void computeStats(Analyzer analyzer) {
+        super.computeStats(analyzer);
+        // TODO the cardinality = child cardinality * cardinality of list 
column
+        cardinality = children.get(0).cardinality;
+    }
+
+    @Override
+    public String getNodeExplainString(String prefix, TExplainLevel 
detailLevel) {
+        StringBuilder output = new StringBuilder();
+        output.append(prefix + "table function: ").append(fnCallExpr.toSql() + 
"\n");
+        if (detailLevel == TExplainLevel.BRIEF) {
+            return output.toString();
+        }
+
+        if (!conjuncts.isEmpty()) {
+            output.append(prefix).append("PREDICATES: ").append(
+                    getExplainString(conjuncts)).append("\n");
+        }
+        output.append(prefix).append(String.format("cardinality=%s", 
cardinality)).append("\n");
+        return output.toString();
+    }
+
+    @Override
+    protected void toThrift(TPlanNode msg) {
+        msg.node_type = TPlanNodeType.TABLE_FUNCTION_NODE;
+        msg.table_function_node = new TTableFunctionNode();
+        msg.table_function_node.setFnCallExpr(fnCallExpr.treeToThrift());
+    }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java 
b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
index 4e9e0e9..13a5cf7 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
@@ -160,6 +160,8 @@ public class SessionVariable implements Serializable, 
Writable {
     
     public static final String ENABLE_PARALLEL_OUTFILE = 
"enable_parallel_outfile";
 
+    public static final String ENABLE_LATERAL_VIEW = "enable_lateral_view";
+
     // session origin value
     public Map<Field, String> sessionOriginValue = new HashMap<Field, 
String>();
     // check stmt is or not [select /*+ SET_VAR(...)*/ ...]
@@ -379,6 +381,9 @@ public class SessionVariable implements Serializable, 
Writable {
     @VariableMgr.VarAttr(name = CPU_RESOURCE_LIMIT)
     public int cpuResourceLimit = -1;
 
+    @VariableMgr.VarAttr(name = ENABLE_LATERAL_VIEW, needForward = true)
+    public boolean enableLateralView = false;
+
     public long getMaxExecMemByte() {
         return maxExecMemByte;
     }
@@ -787,6 +792,14 @@ public class SessionVariable implements Serializable, 
Writable {
         return enableParallelOutfile;
     }
 
+    public boolean isEnableLateralView() {
+        return enableLateralView;
+    }
+
+    public void setEnableLateralView(boolean enableLateralView) {
+        this.enableLateralView = enableLateralView;
+    }
+
     // Serialize to thrift object
     // used for rest api
     public TQueryOptions toThrift() {
diff --git a/fe/fe-core/src/main/jflex/sql_scanner.flex 
b/fe/fe-core/src/main/jflex/sql_scanner.flex
index 42cb39c..f52f3a6 100644
--- a/fe/fe-core/src/main/jflex/sql_scanner.flex
+++ b/fe/fe-core/src/main/jflex/sql_scanner.flex
@@ -247,6 +247,7 @@ import org.apache.doris.qe.SqlModeHelper;
         keywordMap.put("label", new Integer(SqlParserSymbols.KW_LABEL));
         keywordMap.put("largeint", new Integer(SqlParserSymbols.KW_LARGEINT));
         keywordMap.put("last", new Integer(SqlParserSymbols.KW_LAST));
+        keywordMap.put("lateral", new Integer(SqlParserSymbols.KW_LATERAL));
         keywordMap.put("left", new Integer(SqlParserSymbols.KW_LEFT));
         keywordMap.put("less", new Integer(SqlParserSymbols.KW_LESS));
         keywordMap.put("level", new Integer(SqlParserSymbols.KW_LEVEL));
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/planner/RuntimeFilterGeneratorTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/planner/RuntimeFilterGeneratorTest.java
index 09fe44a..9c9816c 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/planner/RuntimeFilterGeneratorTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/planner/RuntimeFilterGeneratorTest.java
@@ -17,6 +17,7 @@ import org.apache.doris.catalog.Column;
 import org.apache.doris.catalog.PrimitiveType;
 import org.apache.doris.catalog.Table;
 import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.UserException;
 import org.apache.doris.common.jmockit.Deencapsulation;
 import org.apache.doris.qe.ConnectContext;
 import org.apache.doris.thrift.TPartitionType;
@@ -43,7 +44,7 @@ public class RuntimeFilterGeneratorTest {
     private ConnectContext connectContext;
 
     @Before
-    public void setUp() throws AnalysisException {
+    public void setUp() throws UserException {
         Catalog catalog = Deencapsulation.newInstance(Catalog.class);
         analyzer = new Analyzer(catalog, connectContext);
         new Expectations() {
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/planner/TableFunctionPlanTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/planner/TableFunctionPlanTest.java
new file mode 100644
index 0000000..1d59d15
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/planner/TableFunctionPlanTest.java
@@ -0,0 +1,199 @@
+// 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.planner;
+
+import org.apache.doris.analysis.CreateDbStmt;
+import org.apache.doris.analysis.CreateTableStmt;
+import org.apache.doris.catalog.Catalog;
+import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.utframe.UtFrameUtils;
+
+import org.apache.commons.io.FileUtils;
+
+import java.io.File;
+import java.util.UUID;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class TableFunctionPlanTest {
+    private static String runningDir = "fe/mocked/TableFunctionPlanTest/" + 
UUID.randomUUID().toString() + "/";
+    private static ConnectContext ctx;
+
+    @After
+    public void tearDown() throws Exception {
+        FileUtils.deleteDirectory(new File(runningDir));
+    }
+
+    @BeforeClass
+    public static void setUp() throws Exception {
+        UtFrameUtils.createDorisCluster(runningDir);
+        ctx = UtFrameUtils.createDefaultCtx();
+        ctx.getSessionVariable().setEnableLateralView(true);
+        String createDbStmtStr = "create database db1;";
+        CreateDbStmt createDbStmt = (CreateDbStmt) 
UtFrameUtils.parseAndAnalyzeStmt(createDbStmtStr, ctx);
+        Catalog.getCurrentCatalog().createDb(createDbStmt);
+        // 3. create table tbl1
+        String createTblStmtStr = "create table db1.tbl1(k1 int, k2 varchar) "
+                + "DUPLICATE KEY(k1) distributed by hash(k1) buckets 3 
properties('replication_num' = '1');";
+        CreateTableStmt createTableStmt = (CreateTableStmt) 
UtFrameUtils.parseAndAnalyzeStmt(createTblStmtStr, ctx);
+        Catalog.getCurrentCatalog().createTable(createTableStmt);
+    }
+
+    // test planner
+    /* Case1 normal table function
+      select k1, e1 from table lateral view explode_split(k2, ",") tmp as e1;
+     */
+    @Test
+    public void normalTableFunction() throws Exception {
+        String sql = "desc verbose select k1, e1 from db1.tbl1 lateral view 
explode_split(k2, \",\") tmp as e1;";
+        String explainString = UtFrameUtils.getSQLPlanOrErrorMsg(ctx, sql, 
true);
+        Assert.assertTrue(explainString.contains("1:TABLE FUNCTION NODE"));
+        Assert.assertTrue(explainString.contains("table function: 
explode_split(`k2`, ',')"));
+        Assert.assertTrue(explainString.contains("tuple ids: 0 1"));
+        Assert.assertTrue(explainString.contains("TupleDescriptor{id=1, 
tbl=tmp, byteSize=32, materialized=true}"));
+        Assert.assertTrue(explainString.contains("SlotDescriptor{id=1, col=e1, 
type=VARCHAR(*)}"));
+    }
+
+    /* Case2 without output explode column
+      select k1 from table lateral view explode_split(k2, ",") tmp as e1;
+     */
+    @Test
+    public void withoutOutputExplodeColumn() throws Exception {
+        String sql = "desc verbose select k1 from db1.tbl1 lateral view 
explode_split(k2, \",\") tmp as e1;";
+        String explainString = UtFrameUtils.getSQLPlanOrErrorMsg(ctx, sql, 
true);
+        Assert.assertTrue(explainString.contains("OUTPUT EXPRS:`k1`"));
+        Assert.assertTrue(explainString.contains("1:TABLE FUNCTION NODE"));
+        Assert.assertTrue(explainString.contains("table function: 
explode_split(`k2`, ',')"));
+        Assert.assertTrue(explainString.contains("tuple ids: 0 1"));
+        Assert.assertTrue(explainString.contains("TupleDescriptor{id=1, 
tbl=tmp, byteSize=32, materialized=true}"));
+        Assert.assertTrue(explainString.contains("SlotDescriptor{id=1, col=e1, 
type=VARCHAR(*)}"));
+    }
+
+    /* Case3 group by explode column
+      select k1, e1, count(*) from table lateral view explode_split(k2, ",") 
tmp as e1 group by k1 e1;
+     */
+    @Test
+    public void groupByExplodeColumn() throws Exception {
+        String sql = "desc verbose select k1, e1, count(*) from db1.tbl1 
lateral view explode_split(k2, \",\") tmp as e1 "
+                + "group by k1, e1;";
+        String explainString = UtFrameUtils.getSQLPlanOrErrorMsg(ctx, sql, 
true);
+        // group by node with k1, e1
+        Assert.assertTrue(explainString.contains("2:AGGREGATE (update 
finalize)"));
+        Assert.assertTrue(explainString.contains("group by: `k1`, `e1`"));
+        // table function node
+        Assert.assertTrue(explainString.contains("1:TABLE FUNCTION NODE"));
+        Assert.assertTrue(explainString.contains("table function: 
explode_split(`k2`, ',')"));
+        Assert.assertTrue(explainString.contains("tuple ids: 0 1"));
+        Assert.assertTrue(explainString.contains("TupleDescriptor{id=1, 
tbl=tmp, byteSize=32, materialized=true}"));
+        Assert.assertTrue(explainString.contains("SlotDescriptor{id=1, col=e1, 
type=VARCHAR(*)}"));
+        // group by tuple
+        Assert.assertTrue(explainString.contains("TupleDescriptor{id=2, 
tbl=null, byteSize=32, materialized=true}"));
+    }
+
+    /* Case4 where explode column
+      select k1, e1 from table lateral view explode_split(k2, ",") tmp as e1 
where e1 = "1";
+     */
+    @Test
+    public void whereExplodeColumn() throws Exception {
+        String sql = "desc verbose select k1, e1 from db1.tbl1 lateral view 
explode_split(k2, \",\") tmp as e1 "
+                + "where e1='1'; ";
+        String explainString = UtFrameUtils.getSQLPlanOrErrorMsg(ctx, sql, 
true);
+        Assert.assertTrue(explainString.contains("1:TABLE FUNCTION NODE"));
+        Assert.assertTrue(explainString.contains("table function: 
explode_split(`k2`, ',')"));
+        Assert.assertTrue(explainString.contains("PREDICATES: `e1` = '1'"));
+        Assert.assertTrue(explainString.contains("tuple ids: 0 1"));
+        Assert.assertTrue(explainString.contains("TupleDescriptor{id=1, 
tbl=tmp, byteSize=32, materialized=true}"));
+        Assert.assertTrue(explainString.contains("SlotDescriptor{id=1, col=e1, 
type=VARCHAR(*)}"));
+    }
+
+    /* Case5 where normal column
+      select k1, e1 from table lateral view explode_split(k2, ",") tmp as e1 
where k1 = 1;
+     */
+    @Test
+    public void whereNormalColumn() throws Exception {
+        String sql = "desc verbose select k1, e1 from db1.tbl1 lateral view 
explode_split(k2, \",\") tmp as e1 "
+                + "where k1=1; ";
+        String explainString = UtFrameUtils.getSQLPlanOrErrorMsg(ctx, sql, 
true);
+        Assert.assertTrue(explainString.contains("1:TABLE FUNCTION NODE"));
+        Assert.assertTrue(explainString.contains("table function: 
explode_split(`k2`, ',')"));
+        Assert.assertTrue(explainString.contains("tuple ids: 0 1"));
+        Assert.assertTrue(explainString.contains("TupleDescriptor{id=1, 
tbl=tmp, byteSize=32, materialized=true}"));
+        Assert.assertTrue(explainString.contains("SlotDescriptor{id=1, col=e1, 
type=VARCHAR(*)}"));
+        Assert.assertTrue(explainString.contains("0:OlapScanNode"));
+        Assert.assertTrue(explainString.contains("PREDICATES: `k1` = 1"));
+    }
+
+    /* Case6 multi lateral view
+      select k1, e1, e2 from table lateral view explode_split(k2, ",") tmp1 as 
e1
+                                   lateral view explode_split(k2, ",") tmp2 as 
e2;
+     */
+    @Test
+    public void testMultiLateralView() throws Exception {
+        String sql = "desc verbose select k1, e1, e2 from db1.tbl1 lateral 
view explode_split(k2, \",\") tmp1 as e1"
+                + " lateral view explode_split(k2, \",\") tmp2 as e2;";
+        String explainString = UtFrameUtils.getSQLPlanOrErrorMsg(ctx, sql, 
true);
+        Assert.assertTrue(explainString.contains("2:TABLE FUNCTION NODE"));
+        Assert.assertTrue(explainString.contains("table function: 
explode_split(`k2`, ',')"));
+        Assert.assertTrue(explainString.contains("tuple ids: 0 1 2"));
+        Assert.assertTrue(explainString.contains("1:TABLE FUNCTION NODE"));
+        Assert.assertTrue(explainString.contains("table function: 
explode_split(`k2`, ',')"));
+        Assert.assertTrue(explainString.contains("tuple ids: 0 1"));
+        // lateral view 2 tuple
+        Assert.assertTrue(explainString.contains("TupleDescriptor{id=1, 
tbl=tmp2, byteSize=32, materialized=true}"));
+        Assert.assertTrue(explainString.contains("SlotDescriptor{id=1, col=e2, 
type=VARCHAR(*)}"));
+        // lateral view 1 tuple
+        Assert.assertTrue(explainString.contains("TupleDescriptor{id=2, 
tbl=tmp1, byteSize=32, materialized=true}"));
+        Assert.assertTrue(explainString.contains("SlotDescriptor{id=2, col=e1, 
type=VARCHAR(*)}"));
+    }
+
+    // test explode_split function
+    // k1 int ,k2 string
+    /* Case1 error param
+      select k1, e1 from table lateral view explode_split(k2) tmp as e1;
+      select k1, e1 from table lateral view explode_split(k1) tmp as e1;
+      select k1, e1 from table lateral view explode_split(k2, k2) tmp as e1;
+     */
+    @Test
+    public void errorParam() throws Exception {
+        String sql = "explain select k1, e1 from db1.tbl1 lateral view 
explode_split(k2) tmp as e1;";
+        String explainString = UtFrameUtils.getSQLPlanOrErrorMsg(ctx, sql);
+        Assert.assertTrue(explainString.contains("Doris only support 
`explode_split(varchar, varchar)` table function"));
+
+        sql = "explain select k1, e1 from db1.tbl1 lateral view 
explode_split(k1) tmp as e1;";
+        explainString = UtFrameUtils.getSQLPlanOrErrorMsg(ctx, sql);
+        Assert.assertTrue(explainString.contains("Doris only support 
`explode_split(varchar, varchar)` table function"));
+
+        sql = "explain select k1, e1 from db1.tbl1 lateral view 
explode_split(k1, k2) tmp as e1;";
+        explainString = UtFrameUtils.getSQLPlanOrErrorMsg(ctx, sql);
+        Assert.assertTrue(explainString.contains("Split separator of explode 
must be a string const"));
+    }
+
+    /* Case2 table function in where stmt
+      select k1 from table where explode_split(k2, ",") = "1";
+     */
+    @Test
+    public void tableFunctionInWhere() throws Exception {
+        String sql = "explain select k1 from db1.tbl1 where explode_split(k2, 
\",\");";
+        String explainString = UtFrameUtils.getSQLPlanOrErrorMsg(ctx, sql);
+        Assert.assertTrue(
+                explainString.contains("No matching function with signature: 
explode_split(varchar(-1), varchar(-1))."));
+    }
+}
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/utframe/UtFrameUtils.java 
b/fe/fe-core/src/test/java/org/apache/doris/utframe/UtFrameUtils.java
index a7033ce..518e210 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/utframe/UtFrameUtils.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/utframe/UtFrameUtils.java
@@ -244,12 +244,16 @@ public class UtFrameUtils {
     }
 
     public static String getSQLPlanOrErrorMsg(ConnectContext ctx, String 
queryStr) throws Exception {
+        return getSQLPlanOrErrorMsg(ctx, queryStr, false);
+    }
+
+    public static String getSQLPlanOrErrorMsg(ConnectContext ctx, String 
queryStr, boolean isVerbose) throws Exception {
         ctx.getState().reset();
         StmtExecutor stmtExecutor = new StmtExecutor(ctx, queryStr);
         stmtExecutor.execute();
         if (ctx.getState().getStateType() != QueryState.MysqlStateType.ERR) {
             Planner planner = stmtExecutor.planner();
-            return planner.getExplainString(planner.getFragments(), new 
ExplainOptions(false, false));
+            return planner.getExplainString(planner.getFragments(), new 
ExplainOptions(isVerbose, false));
         } else {
             return ctx.getState().getErrorMessage();
         }
diff --git a/gensrc/thrift/PlanNodes.thrift b/gensrc/thrift/PlanNodes.thrift
index 0a90997..c76ed33 100644
--- a/gensrc/thrift/PlanNodes.thrift
+++ b/gensrc/thrift/PlanNodes.thrift
@@ -51,6 +51,7 @@ enum TPlanNodeType {
   INTERSECT_NODE,
   EXCEPT_NODE,
   ODBC_SCAN_NODE,
+  TABLE_FUNCTION_NODE,
 }
 
 // phases of an execution node
@@ -652,6 +653,10 @@ struct TOlapRewriteNode {
     3: required Types.TTupleId output_tuple_id
 }
 
+struct TTableFunctionNode {
+    1: required Exprs.TExpr fnCallExpr
+}
+
 // This contains all of the information computed by the plan as part of the 
resource
 // profile that is needed by the backend to execute.
 struct TBackendResourceProfile {
@@ -772,6 +777,8 @@ struct TPlanNode {
   36: optional list<TRuntimeFilterDesc> runtime_filters
 
   40: optional Exprs.TExpr vconjunct
+
+  41: optional TTableFunctionNode table_function_node
 }
 
 // A flattened representation of a tree of PlanNodes, obtained by depth-first

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

Reply via email to