diff --git a/br/pkg/lightning/backend/kv/sql2kv.go b/br/pkg/lightning/backend/kv/sql2kv.go index 489bb6e30d824..1d3ef8b1d2038 100644 --- a/br/pkg/lightning/backend/kv/sql2kv.go +++ b/br/pkg/lightning/backend/kv/sql2kv.go @@ -115,7 +115,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/pkg/ddl/ddl_api.go b/pkg/ddl/ddl_api.go index b2f4b78febda2..0cff70e6417da 100644 --- a/pkg/ddl/ddl_api.go +++ b/pkg/ddl/ddl_api.go @@ -5349,7 +5349,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{}{} } @@ -5415,7 +5416,7 @@ func checkModifyColumnWithGeneratedColumnsConstraint(allCols []*table.Column, ol 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/pkg/ddl/generated_column.go b/pkg/ddl/generated_column.go index 098e3609af9ce..13d508f02bd2e 100644 --- a/pkg/ddl/generated_column.go +++ b/pkg/ddl/generated_column.go @@ -252,7 +252,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/pkg/ddl/schematracker/dm_tracker.go b/pkg/ddl/schematracker/dm_tracker.go index e25b623210077..467f35ffcaad8 100644 --- a/pkg/ddl/schematracker/dm_tracker.go +++ b/pkg/ddl/schematracker/dm_tracker.go @@ -613,7 +613,7 @@ func (d SchemaTracker) renameColumn(_ sessionctx.Context, ident ast.Ident, spec 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/pkg/planner/core/logical_plan_builder.go b/pkg/planner/core/logical_plan_builder.go index 8c30012e63d15..7a67c92d73988 100644 --- a/pkg/planner/core/logical_plan_builder.go +++ b/pkg/planner/core/logical_plan_builder.go @@ -5358,7 +5358,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 @@ -6327,7 +6327,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/pkg/planner/core/planbuilder.go b/pkg/planner/core/planbuilder.go index b607dc62c6e47..8d3e6926965c4 100644 --- a/pkg/planner/core/planbuilder.go +++ b/pkg/planner/core/planbuilder.go @@ -3889,7 +3889,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/pkg/table/column.go b/pkg/table/column.go index 15be521014ca8..b7a3c95de27f9 100644 --- a/pkg/table/column.go +++ b/pkg/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/pkg/table/tables/tables.go b/pkg/table/tables/tables.go index 5af28951bb79a..d82cfdf195ff3 100644 --- a/pkg/table/tables/tables.go +++ b/pkg/table/tables/tables.go @@ -33,6 +33,7 @@ import ( "github.com/pingcap/tidb/pkg/kv" "github.com/pingcap/tidb/pkg/meta" "github.com/pingcap/tidb/pkg/meta/autoid" + "github.com/pingcap/tidb/pkg/parser/ast" "github.com/pingcap/tidb/pkg/parser/model" "github.com/pingcap/tidb/pkg/parser/mysql" "github.com/pingcap/tidb/pkg/sessionctx" @@ -138,15 +139,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 { @@ -177,6 +184,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, constraints []*table.Constraint) { t.tableID = tblInfo.ID