morrySnow commented on code in PR #35284:
URL: https://github.com/apache/doris/pull/35284#discussion_r1613074910


##########
fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4:
##########
@@ -115,6 +115,7 @@ AT: 'AT';
 AUTHORS: 'AUTHORS';
 AUTO: 'AUTO';
 AUTO_INCREMENT: 'AUTO_INCREMENT';
+ALWAYS: 'ALWAYS';

Review Comment:
   let it be non-reserved keyword



##########
fe/fe-core/src/main/java/org/apache/doris/analysis/CreateTableStmt.java:
##########
@@ -711,4 +717,100 @@ public String toString() {
     public boolean needAuditEncryption() {
         return !engineName.equals("olap");
     }
+
+    private void checkGeneratedColumn(List<ColumnDef> columnDefs, Analyzer 
analyzer) throws AnalysisException {
+        Map<String, ColumnDef> nameToColumnDef = Maps.newHashMap();
+        for (ColumnDef columnDef : columnDefs) {
+            if (!columnDef.getGeneratedColumnInfo().isPresent()) {
+                nameToColumnDef.put(columnDef.getName(), columnDef);
+            }
+        }
+        List<GeneratedColumnUtil.ExprAndname> exprAndnames = 
Lists.newArrayList();
+        for (ColumnDef columnDef : columnDefs) {
+            if (!columnDef.getGeneratedColumnInfo().isPresent()) {
+                continue;
+            }
+            // 写一个函数把expr中的SlotRef设置上类型,并且都设置为analyzed了

Review Comment:
   use english



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindSink.java:
##########
@@ -276,8 +277,13 @@ private static Map<String, NamedExpression> 
getColumnToOutput(
         Map<String, NamedExpression> columnToOutput = 
Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
         NereidsParser expressionParser = new NereidsParser();
 
+        List<Column> generatedColumns = Lists.newArrayList();
         // generate slots not mentioned in sql, mv slots and shaded slots.
         for (Column column : boundSink.getTargetTable().getFullSchema()) {
+            if (column.getGeneratedColumnInfo() != null) {

Review Comment:
   test row store format: 
https://doris.apache.org/zh-CN/docs/query/high-concurrent-point-query?_highlight=%E8%A1%8C&_highlight=%E5%AD%98#%E8%A1%8C%E5%AD%98



##########
fe/fe-core/src/main/java/org/apache/doris/analysis/CreateTableStmt.java:
##########
@@ -711,4 +717,118 @@ public String toString() {
     public boolean needAuditEncryption() {
         return !engineName.equals("olap");
     }
+
+    private void generatedColumnCheck(Analyzer analyzer) throws 
AnalysisException {
+        genreatedColumnCommonCheck();
+        Map<String, ColumnDef> nameToColumnDef = Maps.newHashMap();
+        for (ColumnDef columnDef : columnDefs) {
+            if (!columnDef.getGeneratedColumnInfo().isPresent()) {
+                nameToColumnDef.put(columnDef.getName(), columnDef);
+            }
+        }
+        List<GeneratedColumnUtil.ExprAndname> exprAndnames = 
Lists.newArrayList();

Review Comment:
   mysql: Generated column can refer only to generated columns defined prior to 
it.
   ```sql
    create table tg2 (id int, c1 int as (c1), c2 int);
   ```



##########
fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4:
##########
@@ -272,6 +273,7 @@ FRONTENDS: 'FRONTENDS';
 FULL: 'FULL';
 FUNCTION: 'FUNCTION';
 FUNCTIONS: 'FUNCTIONS';
+GENERATED: 'GENERATED';
 GENERIC: 'GENERIC';
 GLOBAL: 'GLOBAL';
 GRANT: 'GRANT';

Review Comment:
   let it be non-reserved keyword



##########
fe/fe-core/src/main/java/org/apache/doris/analysis/CreateTableStmt.java:
##########
@@ -711,4 +717,118 @@ public String toString() {
     public boolean needAuditEncryption() {
         return !engineName.equals("olap");
     }
+
+    private void generatedColumnCheck(Analyzer analyzer) throws 
AnalysisException {
+        genreatedColumnEngineCheck(columnDefs);
+        Map<String, ColumnDef> nameToColumnDef = Maps.newHashMap();
+        for (ColumnDef columnDef : columnDefs) {
+            if (!columnDef.getGeneratedColumnInfo().isPresent()) {
+                nameToColumnDef.put(columnDef.getName(), columnDef);
+            }
+        }
+        List<GeneratedColumnUtil.ExprAndname> exprAndnames = 
Lists.newArrayList();
+        for (ColumnDef columnDef : columnDefs) {
+            if (!columnDef.getGeneratedColumnInfo().isPresent()) {
+                continue;
+            }
+            // 写一个函数把expr中的SlotRef设置上类型,并且都设置为analyzed了
+            SlotRefRewriteRule slotRefRewriteRule = new 
SlotRefRewriteRule(nameToColumnDef);
+            ExprRewriter rewriter = new ExprRewriter(slotRefRewriteRule);
+            GeneratedColumnInfo generatedColumnInfo = 
columnDef.getGeneratedColumnInfo().get();
+            Expr expr = rewriter.rewrite(generatedColumnInfo.getExpr(), 
analyzer);
+            expr.foreach(e -> {
+                if (e instanceof LambdaFunctionCallExpr) {
+                    throw new AnalysisException("Generated column does not 
support lambda.");
+                } else if (e instanceof Subquery) {
+                    throw new AnalysisException("Generated column does not 
support subquery.");
+                }
+            });
+            expr.analyze(analyzer);
+            expr.foreach(e -> {
+                if (e instanceof VariableExpr) {
+                    throw new AnalysisException("Generated column expression 
cannot contain variable.");
+                } else if (e instanceof SlotRef && 
nameToColumnDef.containsKey(((SlotRef) e).getColumnName())) {
+                    ColumnDef refColumnDef = nameToColumnDef.get(((SlotRef) 
e).getColumnName());
+                    if (refColumnDef.getAutoIncInitValue() != -1) {
+                        throw new AnalysisException("Generated column '" + 
columnDef.getName()
+                                + "' cannot refer to auto-increment column.");
+                    }
+                } else if (e instanceof FunctionCallExpr && 
!checkFunctionInGeneratedColumn((FunctionCallExpr) e)) {
+                    throw new 
org.apache.doris.nereids.exceptions.AnalysisException(
+                            "Expression of generated column '"
+                                    + columnDef.getName() + "' contains a 
disallowed function");
+                } else if (e instanceof GroupingFunctionCallExpr) {
+                    throw new 
org.apache.doris.nereids.exceptions.AnalysisException(
+                            "Expression of generated column '"
+                                    + columnDef.getName() + "' contains a 
disallowed function");
+                }
+            });
+            if (!Type.canCastTo(expr.getType(), columnDef.getType())) {
+                throw new 
org.apache.doris.nereids.exceptions.AnalysisException("can not cast from origin 
type "
+                        + expr.getType() + " to target type=" + 
columnDef.getType());
+            }
+            nameToColumnDef.put(columnDef.getName(), columnDef);
+            exprAndnames.add(new GeneratedColumnUtil.ExprAndname(expr, 
columnDef.getName()));
+        }
+        GeneratedColumnUtil.rewriteColumns(exprAndnames);
+        for (GeneratedColumnUtil.ExprAndname exprAndname : exprAndnames) {
+            if (nameToColumnDef.containsKey(exprAndname.getName())) {
+                ColumnDef columnDef = 
nameToColumnDef.get(exprAndname.getName());
+                Optional<GeneratedColumnInfo> info = 
columnDef.getGeneratedColumnInfo();
+                info.ifPresent(columnInfo -> 
columnInfo.setExpandExprForLoad(exprAndname.getExpr()));
+            }
+        }
+    }
+
+    private boolean checkFunctionInGeneratedColumn(FunctionCallExpr funcExpr) {
+        if (!funcExpr.isScalarFunction()) {
+            return false;
+        }
+        if (funcExpr.fn.isUdf()) {
+            return false;
+        }
+        return true;
+    }
+
+    public static final class SlotRefRewriteRule implements ExprRewriteRule {
+        private static Map<String, ColumnDef> nameToColumnDefMap = new 
HashMap<>();
+
+        public SlotRefRewriteRule(Map<String, ColumnDef> columnMap) {
+            this.nameToColumnDefMap = columnMap;
+        }
+
+        @Override
+        public Expr apply(Expr expr, Analyzer analyzer, 
ExprRewriter.ClauseType clauseType) throws AnalysisException {
+            if (!(expr instanceof SlotRef)) {
+                return expr;
+            }
+            SlotRef slotRef = (SlotRef) expr;
+            if (!nameToColumnDefMap.containsKey(slotRef.getColumnName())) {
+                throw new AnalysisException("Unknown column '" + 
slotRef.getColumnName()
+                        + "' in 'generated column function'");
+            }
+            ColumnDef columnDef = 
nameToColumnDefMap.get(slotRef.getColumnName());
+            slotRef.setType(columnDef.getType());
+            slotRef.setAnalyzed(true);
+            return slotRef;
+        }
+    }
+
+    private void genreatedColumnEngineCheck(List<ColumnDef> columns) {
+        boolean hasGeneratedCol = false;
+        for (ColumnDef column : columns) {
+            if (column.getGeneratedColumnInfo().isPresent()) {
+                hasGeneratedCol = true;
+                break;
+            }
+        }
+        if (hasGeneratedCol && !engineName.equalsIgnoreCase("olap")) {
+            throw new org.apache.doris.nereids.exceptions.AnalysisException(

Review Comment:
   do not throw nereids' exception in analysis package



##########
fe/fe-core/src/main/java/org/apache/doris/analysis/CreateTableStmt.java:
##########
@@ -711,4 +717,118 @@ public String toString() {
     public boolean needAuditEncryption() {
         return !engineName.equals("olap");
     }
+
+    private void generatedColumnCheck(Analyzer analyzer) throws 
AnalysisException {
+        genreatedColumnCommonCheck();
+        Map<String, ColumnDef> nameToColumnDef = Maps.newHashMap();
+        for (ColumnDef columnDef : columnDefs) {
+            if (!columnDef.getGeneratedColumnInfo().isPresent()) {
+                nameToColumnDef.put(columnDef.getName(), columnDef);
+            }
+        }
+        List<GeneratedColumnUtil.ExprAndname> exprAndnames = 
Lists.newArrayList();
+        for (ColumnDef columnDef : columnDefs) {
+            if (!columnDef.getGeneratedColumnInfo().isPresent()) {
+                continue;
+            }
+            // 写一个函数把expr中的SlotRef设置上类型,并且都设置为analyzed了
+            SlotRefRewriteRule slotRefRewriteRule = new 
SlotRefRewriteRule(nameToColumnDef);
+            ExprRewriter rewriter = new ExprRewriter(slotRefRewriteRule);
+            GeneratedColumnInfo generatedColumnInfo = 
columnDef.getGeneratedColumnInfo().get();
+            Expr expr = rewriter.rewrite(generatedColumnInfo.getExpr(), 
analyzer);
+            expr.foreach(e -> {
+                if (e instanceof LambdaFunctionCallExpr) {
+                    throw new AnalysisException("Generated column does not 
support lambda.");
+                } else if (e instanceof Subquery) {
+                    throw new AnalysisException("Generated column does not 
support subquery.");
+                }
+            });
+            expr.analyze(analyzer);
+            expr.foreach(e -> {
+                if (e instanceof VariableExpr) {
+                    throw new AnalysisException("Generated column expression 
cannot contain variable.");
+                } else if (e instanceof SlotRef && 
nameToColumnDef.containsKey(((SlotRef) e).getColumnName())) {
+                    ColumnDef refColumnDef = nameToColumnDef.get(((SlotRef) 
e).getColumnName());
+                    if (refColumnDef.getAutoIncInitValue() != -1) {
+                        throw new AnalysisException("Generated column '" + 
columnDef.getName()
+                                + "' cannot refer to auto-increment column.");
+                    }
+                } else if (e instanceof FunctionCallExpr && 
!checkFunctionInGeneratedColumn((FunctionCallExpr) e)) {
+                    throw new 
org.apache.doris.nereids.exceptions.AnalysisException(
+                            "Expression of generated column '"
+                                    + columnDef.getName() + "' contains a 
disallowed function");

Review Comment:
   print function name?



##########
fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4:
##########
@@ -588,8 +588,14 @@ columnDef
            | CURRENT_DATE | defaultTimestamp=CURRENT_TIMESTAMP (LEFT_PAREN 
defaultValuePrecision=number RIGHT_PAREN)?))?
         (ON UPDATE CURRENT_TIMESTAMP (LEFT_PAREN onUpdateValuePrecision=number 
RIGHT_PAREN)?)?
         (COMMENT comment=STRING_LITERAL)?
+    | generatedColumn
     ;
-    
+
+generatedColumn

Review Comment:
   put them into columnDef
   ```
         colName=identifier type=dataType
           KEY?
           (aggType=aggTypeDef)?
           (GENERATED ALWAYS)? AS LEFT_PAREN expression RIGHT_PAREN
           ((NOT)? NULL)?
   ```



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/GeneratedColumnInfo.java:
##########
@@ -0,0 +1,96 @@
+// 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.nereids.trees.plans.commands;
+
+import org.apache.doris.analysis.Expr;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.persist.gson.GsonPostProcessable;
+import org.apache.doris.persist.gson.GsonUtils;
+
+import com.google.gson.annotations.SerializedName;
+import jline.internal.Nullable;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**GeneratedColumnInfo*/
+public class GeneratedColumnInfo implements Writable, GsonPostProcessable {
+    /**GeneratedColumnType*/
+    public enum GeneratedColumnType {
+        VIRTUAL,
+        STORED
+    }
+
+    @SerializedName(value = "type")
+    private final GeneratedColumnType type;
+    @SerializedName(value = "generatedExprSql")

Review Comment:
   use abbreviations to reduce the size of serialized data



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/GeneratedColumnInfo.java:
##########
@@ -0,0 +1,96 @@
+// 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.nereids.trees.plans.commands;
+
+import org.apache.doris.analysis.Expr;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.persist.gson.GsonPostProcessable;
+import org.apache.doris.persist.gson.GsonUtils;
+
+import com.google.gson.annotations.SerializedName;
+import jline.internal.Nullable;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**GeneratedColumnInfo*/
+public class GeneratedColumnInfo implements Writable, GsonPostProcessable {

Review Comment:
   move to catalog package



##########
fe/fe-core/src/main/java/org/apache/doris/analysis/CreateTableStmt.java:
##########
@@ -711,4 +717,118 @@ public String toString() {
     public boolean needAuditEncryption() {
         return !engineName.equals("olap");
     }
+
+    private void generatedColumnCheck(Analyzer analyzer) throws 
AnalysisException {
+        genreatedColumnCommonCheck();
+        Map<String, ColumnDef> nameToColumnDef = Maps.newHashMap();
+        for (ColumnDef columnDef : columnDefs) {
+            if (!columnDef.getGeneratedColumnInfo().isPresent()) {
+                nameToColumnDef.put(columnDef.getName(), columnDef);
+            }
+        }
+        List<GeneratedColumnUtil.ExprAndname> exprAndnames = 
Lists.newArrayList();
+        for (ColumnDef columnDef : columnDefs) {
+            if (!columnDef.getGeneratedColumnInfo().isPresent()) {
+                continue;
+            }
+            // 写一个函数把expr中的SlotRef设置上类型,并且都设置为analyzed了
+            SlotRefRewriteRule slotRefRewriteRule = new 
SlotRefRewriteRule(nameToColumnDef);
+            ExprRewriter rewriter = new ExprRewriter(slotRefRewriteRule);
+            GeneratedColumnInfo generatedColumnInfo = 
columnDef.getGeneratedColumnInfo().get();
+            Expr expr = rewriter.rewrite(generatedColumnInfo.getExpr(), 
analyzer);
+            expr.foreach(e -> {
+                if (e instanceof LambdaFunctionCallExpr) {
+                    throw new AnalysisException("Generated column does not 
support lambda.");
+                } else if (e instanceof Subquery) {
+                    throw new AnalysisException("Generated column does not 
support subquery.");
+                }
+            });
+            expr.analyze(analyzer);
+            expr.foreach(e -> {
+                if (e instanceof VariableExpr) {
+                    throw new AnalysisException("Generated column expression 
cannot contain variable.");
+                } else if (e instanceof SlotRef && 
nameToColumnDef.containsKey(((SlotRef) e).getColumnName())) {
+                    ColumnDef refColumnDef = nameToColumnDef.get(((SlotRef) 
e).getColumnName());
+                    if (refColumnDef.getAutoIncInitValue() != -1) {
+                        throw new AnalysisException("Generated column '" + 
columnDef.getName()
+                                + "' cannot refer to auto-increment column.");
+                    }
+                } else if (e instanceof FunctionCallExpr && 
!checkFunctionInGeneratedColumn((FunctionCallExpr) e)) {
+                    throw new 
org.apache.doris.nereids.exceptions.AnalysisException(

Review Comment:
   use analysis.AnalysisException



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/GeneratedColumnInfo.java:
##########
@@ -0,0 +1,96 @@
+// 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.nereids.trees.plans.commands;
+
+import org.apache.doris.analysis.Expr;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.persist.gson.GsonPostProcessable;
+import org.apache.doris.persist.gson.GsonUtils;
+
+import com.google.gson.annotations.SerializedName;
+import jline.internal.Nullable;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**GeneratedColumnInfo*/
+public class GeneratedColumnInfo implements Writable, GsonPostProcessable {
+    /**GeneratedColumnType*/
+    public enum GeneratedColumnType {
+        VIRTUAL,
+        STORED
+    }
+
+    @SerializedName(value = "type")
+    private final GeneratedColumnType type;
+    @SerializedName(value = "generatedExprSql")
+    private final String exprSql;
+    @SerializedName(value = "generatedExpr")
+    private Expr expr;
+    @SerializedName(value = "expandExprForLoad")
+    private Expr expandExprForLoad;
+
+    /** constructor */
+    public GeneratedColumnInfo(@Nullable String exprSql, @Nullable Expr expr) {
+        if (exprSql != null) {
+            this.exprSql = exprSql;
+        } else {
+            this.exprSql = expr.toSql();
+        }
+        this.expr = expr;
+        this.type = GeneratedColumnType.STORED;
+    }
+
+    @Override
+    public void write(DataOutput out) throws IOException {
+        String json = GsonUtils.GSON.toJson(this);
+        Text.writeString(out, json);
+    }

Review Comment:
   write and read is not use anymore?



##########
fe/fe-core/src/main/java/org/apache/doris/analysis/CreateTableStmt.java:
##########
@@ -711,4 +717,118 @@ public String toString() {
     public boolean needAuditEncryption() {
         return !engineName.equals("olap");
     }
+
+    private void generatedColumnCheck(Analyzer analyzer) throws 
AnalysisException {
+        genreatedColumnCommonCheck();
+        Map<String, ColumnDef> nameToColumnDef = Maps.newHashMap();
+        for (ColumnDef columnDef : columnDefs) {
+            if (!columnDef.getGeneratedColumnInfo().isPresent()) {
+                nameToColumnDef.put(columnDef.getName(), columnDef);
+            }
+        }
+        List<GeneratedColumnUtil.ExprAndname> exprAndnames = 
Lists.newArrayList();
+        for (ColumnDef columnDef : columnDefs) {
+            if (!columnDef.getGeneratedColumnInfo().isPresent()) {
+                continue;
+            }
+            // 写一个函数把expr中的SlotRef设置上类型,并且都设置为analyzed了
+            SlotRefRewriteRule slotRefRewriteRule = new 
SlotRefRewriteRule(nameToColumnDef);
+            ExprRewriter rewriter = new ExprRewriter(slotRefRewriteRule);
+            GeneratedColumnInfo generatedColumnInfo = 
columnDef.getGeneratedColumnInfo().get();
+            Expr expr = rewriter.rewrite(generatedColumnInfo.getExpr(), 
analyzer);
+            expr.foreach(e -> {
+                if (e instanceof LambdaFunctionCallExpr) {
+                    throw new AnalysisException("Generated column does not 
support lambda.");
+                } else if (e instanceof Subquery) {
+                    throw new AnalysisException("Generated column does not 
support subquery.");
+                }
+            });

Review Comment:
   i think some other expression could raise exception, such as `between` that 
BE do not support it. so we need add all type expression to ensure we could 
process them correctly all throw appropriate exception



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/GeneratedColumnInfo.java:
##########
@@ -0,0 +1,96 @@
+// 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.nereids.trees.plans.commands;
+
+import org.apache.doris.analysis.Expr;
+import org.apache.doris.common.io.Text;
+import org.apache.doris.common.io.Writable;
+import org.apache.doris.persist.gson.GsonPostProcessable;
+import org.apache.doris.persist.gson.GsonUtils;
+
+import com.google.gson.annotations.SerializedName;
+import jline.internal.Nullable;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**GeneratedColumnInfo*/
+public class GeneratedColumnInfo implements Writable, GsonPostProcessable {
+    /**GeneratedColumnType*/
+    public enum GeneratedColumnType {
+        VIRTUAL,
+        STORED
+    }
+
+    @SerializedName(value = "type")
+    private final GeneratedColumnType type;
+    @SerializedName(value = "generatedExprSql")
+    private final String exprSql;
+    @SerializedName(value = "generatedExpr")
+    private Expr expr;
+    @SerializedName(value = "expandExprForLoad")
+    private Expr expandExprForLoad;

Review Comment:
   add comment to explain what different between these two variable



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java:
##########
@@ -3544,4 +3549,22 @@ public LogicalPlan 
visitCreateTableLike(CreateTableLikeContext ctx) {
                 rollupNames, withAllRollUp);
         return new CreateTableLikeCommand(info);
     }
+
+    @Override
+    public ColumnDefinition visitGeneratedColumn(GeneratedColumnContext ctx) {
+        String colName = ctx.colName.getText();
+        DataType colType = ctx.type instanceof PrimitiveDataTypeContext
+                ? visitPrimitiveDataType(((PrimitiveDataTypeContext) ctx.type))
+                : ctx.type instanceof ComplexDataTypeContext
+                        ? visitComplexDataType((ComplexDataTypeContext) 
ctx.type)
+                        : visitAggStateDataType((AggStateDataTypeContext) 
ctx.type);

Review Comment:
   DataType colType = typedVisit(ctx.type);



##########
fe/fe-core/src/main/java/org/apache/doris/analysis/CreateTableStmt.java:
##########
@@ -711,4 +717,118 @@ public String toString() {
     public boolean needAuditEncryption() {
         return !engineName.equals("olap");
     }
+
+    private void generatedColumnCheck(Analyzer analyzer) throws 
AnalysisException {
+        genreatedColumnCommonCheck();
+        Map<String, ColumnDef> nameToColumnDef = Maps.newHashMap();
+        for (ColumnDef columnDef : columnDefs) {
+            if (!columnDef.getGeneratedColumnInfo().isPresent()) {
+                nameToColumnDef.put(columnDef.getName(), columnDef);
+            }
+        }
+        List<GeneratedColumnUtil.ExprAndname> exprAndnames = 
Lists.newArrayList();
+        for (ColumnDef columnDef : columnDefs) {
+            if (!columnDef.getGeneratedColumnInfo().isPresent()) {
+                continue;
+            }
+            // 写一个函数把expr中的SlotRef设置上类型,并且都设置为analyzed了
+            SlotRefRewriteRule slotRefRewriteRule = new 
SlotRefRewriteRule(nameToColumnDef);
+            ExprRewriter rewriter = new ExprRewriter(slotRefRewriteRule);
+            GeneratedColumnInfo generatedColumnInfo = 
columnDef.getGeneratedColumnInfo().get();
+            Expr expr = rewriter.rewrite(generatedColumnInfo.getExpr(), 
analyzer);
+            expr.foreach(e -> {
+                if (e instanceof LambdaFunctionCallExpr) {
+                    throw new AnalysisException("Generated column does not 
support lambda.");
+                } else if (e instanceof Subquery) {
+                    throw new AnalysisException("Generated column does not 
support subquery.");
+                }
+            });
+            expr.analyze(analyzer);
+            expr.foreach(e -> {
+                if (e instanceof VariableExpr) {
+                    throw new AnalysisException("Generated column expression 
cannot contain variable.");
+                } else if (e instanceof SlotRef && 
nameToColumnDef.containsKey(((SlotRef) e).getColumnName())) {
+                    ColumnDef refColumnDef = nameToColumnDef.get(((SlotRef) 
e).getColumnName());
+                    if (refColumnDef.getAutoIncInitValue() != -1) {
+                        throw new AnalysisException("Generated column '" + 
columnDef.getName()
+                                + "' cannot refer to auto-increment column.");
+                    }
+                } else if (e instanceof FunctionCallExpr && 
!checkFunctionInGeneratedColumn((FunctionCallExpr) e)) {
+                    throw new 
org.apache.doris.nereids.exceptions.AnalysisException(
+                            "Expression of generated column '"
+                                    + columnDef.getName() + "' contains a 
disallowed function");
+                } else if (e instanceof GroupingFunctionCallExpr) {

Review Comment:
   why check GroupingFunctionCallExpr here? GroupingFunctionCallExpr is extends 
from FunctionCallExpr, so we could check in `checkFunctionInGeneratedColumn`?



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindSink.java:
##########
@@ -381,6 +387,20 @@ private static Map<String, NamedExpression> 
getColumnToOutput(
                 }
             }
         }
+        for (Column column : generatedColumns) {

Review Comment:
   why cannot process in upper for loop?



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/InsertUtils.java:
##########
@@ -383,6 +395,9 @@ public static TableIf getTargetTable(Plan plan, 
ConnectContext ctx) {
 
     private static NamedExpression generateDefaultExpression(Column column) {
         try {
+            if (column.getGeneratedColumnInfo() != null) {
+                return new DefaultValueSlot();

Review Comment:
   u could not return DefaultValueSlot() here, must replace by valid 
expression. u can try 
   ```sql
   create table t(id int, c1 int as(c2), c2 int) 
properties('replication_num'='1');
   insert into t values(1, default, 2), (2, default, 3);
   ```



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindSink.java:
##########
@@ -276,8 +277,13 @@ private static Map<String, NamedExpression> 
getColumnToOutput(
         Map<String, NamedExpression> columnToOutput = 
Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
         NereidsParser expressionParser = new NereidsParser();
 
+        List<Column> generatedColumns = Lists.newArrayList();
         // generate slots not mentioned in sql, mv slots and shaded slots.
         for (Column column : boundSink.getTargetTable().getFullSchema()) {
+            if (column.getGeneratedColumnInfo() != null) {
+                generatedColumns.add(column);
+                continue;
+            }
             if (column.isMaterializedViewColumn()) {

Review Comment:
   test create sync marterialized view on generated column: 
https://doris.apache.org/docs/query/view-materialized-view/materialized-view



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/ColumnDefinition.java:
##########
@@ -64,6 +65,7 @@ public class ColumnDefinition {
     private boolean aggTypeImplicit = false;
     private long autoIncInitValue = -1;
     private int clusterKeyId = -1;
+    private Optional<GeneratedColumnInfo> generatedColumnInfo = 
Optional.empty();

Review Comment:
   nit: it is better to use two diff structure in catalog and Nereids for 
easily remove legacy planner



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org
For additional commands, e-mail: commits-h...@doris.apache.org

Reply via email to