diff --git a/br/pkg/lightning/backend/kv/sql2kv.go b/br/pkg/lightning/backend/kv/sql2kv.go index 5f4050b0444b2..3a5fa8163beab 100644 --- a/br/pkg/lightning/backend/kv/sql2kv.go +++ b/br/pkg/lightning/backend/kv/sql2kv.go @@ -114,7 +114,7 @@ func CollectGeneratedColumns(se *Session, meta *model.TableInfo, cols []*table.C var genCols []GeneratedCol for i, col := range cols { if col.GeneratedExpr != nil { - expr, err := expression.RewriteAstExpr(se, col.GeneratedExpr, schema, names, true) + expr, err := expression.RewriteAstExpr(se, col.GeneratedExpr.Internal(), schema, names, true) if err != nil { return nil, err } diff --git a/ddl/ddl_api.go b/ddl/ddl_api.go index dc871d4586815..95243532c2fb2 100644 --- a/ddl/ddl_api.go +++ b/ddl/ddl_api.go @@ -4874,7 +4874,8 @@ func processColumnOptions(ctx sessionctx.Context, col *table.Column, options []* col.GeneratedExprString = sb.String() col.GeneratedStored = opt.Stored col.Dependences = make(map[string]struct{}) - col.GeneratedExpr = opt.Expr + // Only used by checkModifyGeneratedColumn, there is no need to set a ctor for it. + col.GeneratedExpr = table.NewClonableExprNode(nil, opt.Expr) for _, colName := range FindColumnNamesInExpr(opt.Expr) { col.Dependences[colName.Name.L] = struct{}{} } @@ -5441,7 +5442,7 @@ func (d *ddl) RenameColumn(ctx sessionctx.Context, ident ast.Ident, spec *ast.Al if col.GeneratedExpr == nil { continue } - dependedColNames := FindColumnNamesInExpr(col.GeneratedExpr) + dependedColNames := FindColumnNamesInExpr(col.GeneratedExpr.Internal()) for _, name := range dependedColNames { if name.Name.L == oldColName.L { if col.Hidden { diff --git a/ddl/generated_column.go b/ddl/generated_column.go index 0e877a3e63eb3..0c4fa935959b7 100644 --- a/ddl/generated_column.go +++ b/ddl/generated_column.go @@ -253,7 +253,7 @@ func checkModifyGeneratedColumn(sctx sessionctx.Context, schemaName model.CIStr, if newCol.IsGenerated() { // rule 3. - if err := checkIllegalFn4Generated(newCol.Name.L, typeColumn, newCol.GeneratedExpr); err != nil { + if err := checkIllegalFn4Generated(newCol.Name.L, typeColumn, newCol.GeneratedExpr.Internal()); err != nil { return errors.Trace(err) } diff --git a/ddl/schematracker/dm_tracker.go b/ddl/schematracker/dm_tracker.go index 7bac171beab9f..85dc5aa49abcb 100644 --- a/ddl/schematracker/dm_tracker.go +++ b/ddl/schematracker/dm_tracker.go @@ -609,7 +609,7 @@ func (d SchemaTracker) renameColumn(ctx sessionctx.Context, ident ast.Ident, spe if col.GeneratedExpr == nil { continue } - dependedColNames := ddl.FindColumnNamesInExpr(col.GeneratedExpr) + dependedColNames := ddl.FindColumnNamesInExpr(col.GeneratedExpr.Internal()) for _, name := range dependedColNames { if name.Name.L == oldColName.L { if col.Hidden { diff --git a/planner/core/logical_plan_builder.go b/planner/core/logical_plan_builder.go index 0140375675105..93cb275c58f8e 100644 --- a/planner/core/logical_plan_builder.go +++ b/planner/core/logical_plan_builder.go @@ -4913,7 +4913,7 @@ func (b *PlanBuilder) buildDataSource(ctx context.Context, tn *ast.TableName, as var err error originVal := b.allowBuildCastArray b.allowBuildCastArray = true - expr, _, err = b.rewrite(ctx, columns[i].GeneratedExpr, ds, nil, true) + expr, _, err = b.rewrite(ctx, columns[i].GeneratedExpr.Clone(), ds, nil, true) b.allowBuildCastArray = originVal if err != nil { return nil, err @@ -5882,7 +5882,7 @@ func (b *PlanBuilder) buildUpdateLists(ctx context.Context, tableList []*ast.Tab } virtualAssignments = append(virtualAssignments, &ast.Assignment{ Column: &ast.ColumnName{Schema: tn.Schema, Table: tn.Name, Name: colInfo.Name}, - Expr: tableVal.Cols()[i].GeneratedExpr, + Expr: tableVal.Cols()[i].GeneratedExpr.Clone(), }) } } diff --git a/planner/core/planbuilder.go b/planner/core/planbuilder.go index 0334249d48810..803c424fdf5ea 100644 --- a/planner/core/planbuilder.go +++ b/planner/core/planbuilder.go @@ -3751,7 +3751,7 @@ func (b *PlanBuilder) resolveGeneratedColumns(ctx context.Context, columns []*ta originalVal := b.allowBuildCastArray b.allowBuildCastArray = true - expr, _, err := b.rewrite(ctx, column.GeneratedExpr, mockPlan, nil, true) + expr, _, err := b.rewrite(ctx, column.GeneratedExpr.Clone(), mockPlan, nil, true) b.allowBuildCastArray = originalVal if err != nil { return igc, err diff --git a/table/column.go b/table/column.go index 3155585faa7c9..624af420ae3be 100644 --- a/table/column.go +++ b/table/column.go @@ -46,11 +46,39 @@ import ( type Column struct { *model.ColumnInfo // If this column is a generated column, the expression will be stored here. - GeneratedExpr ast.ExprNode + GeneratedExpr *ClonableExprNode // If this column has default expr value, this expression will be stored here. DefaultExpr ast.ExprNode } +// ClonableExprNode is a wrapper for ast.ExprNode. +type ClonableExprNode struct { + ctor func() ast.ExprNode + internal ast.ExprNode +} + +// NewClonableExprNode creates a ClonableExprNode. +func NewClonableExprNode(ctor func() ast.ExprNode, internal ast.ExprNode) *ClonableExprNode { + return &ClonableExprNode{ + ctor: ctor, + internal: internal, + } +} + +// Clone makes a "copy" of internal ast.ExprNode by reconstructing it. +func (n *ClonableExprNode) Clone() ast.ExprNode { + if n.ctor == nil { + return n.internal + } + return n.ctor() +} + +// Internal returns the reference of the internal ast.ExprNode. +// Note: only use this method when you are sure that the internal ast.ExprNode is not modified concurrently. +func (n *ClonableExprNode) Internal() ast.ExprNode { + return n.internal +} + // String implements fmt.Stringer interface. func (c *Column) String() string { ans := []string{c.Name.O, types.TypeToStr(c.GetType(), c.GetCharset())} diff --git a/table/tables/tables.go b/table/tables/tables.go index 98af2ae53a35a..c760a5607b745 100644 --- a/table/tables/tables.go +++ b/table/tables/tables.go @@ -33,6 +33,7 @@ import ( "github.com/pingcap/tidb/kv" "github.com/pingcap/tidb/meta" "github.com/pingcap/tidb/meta/autoid" + "github.com/pingcap/tidb/parser/ast" "github.com/pingcap/tidb/parser/model" "github.com/pingcap/tidb/parser/mysql" "github.com/pingcap/tidb/sessionctx" @@ -131,15 +132,21 @@ func TableFromMeta(allocs autoid.Allocators, tblInfo *model.TableInfo) (table.Ta col := table.ToColumn(colInfo) if col.IsGenerated() { - expr, err := generatedexpr.ParseExpression(colInfo.GeneratedExprString) + genStr := colInfo.GeneratedExprString + expr, err := buildGeneratedExpr(tblInfo, genStr) if err != nil { return nil, err } - expr, err = generatedexpr.SimpleResolveName(expr, tblInfo) - if err != nil { - return nil, err - } - col.GeneratedExpr = expr + col.GeneratedExpr = table.NewClonableExprNode(func() ast.ExprNode { + newExpr, err1 := buildGeneratedExpr(tblInfo, genStr) + if err1 != nil { + logutil.BgLogger().Warn("unexpected parse generated string error", + zap.String("generatedStr", genStr), + zap.Error(err1)) + return expr + } + return newExpr + }, expr) } // default value is expr. if col.DefaultIsExpr { @@ -166,6 +173,18 @@ func TableFromMeta(allocs autoid.Allocators, tblInfo *model.TableInfo) (table.Ta return newPartitionedTable(&t, tblInfo) } +func buildGeneratedExpr(tblInfo *model.TableInfo, genExpr string) (ast.ExprNode, error) { + expr, err := generatedexpr.ParseExpression(genExpr) + if err != nil { + return nil, err + } + expr, err = generatedexpr.SimpleResolveName(expr, tblInfo) + if err != nil { + return nil, err + } + return expr, nil +} + // initTableCommon initializes a TableCommon struct. func initTableCommon(t *TableCommon, tblInfo *model.TableInfo, physicalTableID int64, cols []*table.Column, allocs autoid.Allocators) { t.tableID = tblInfo.ID