Skip to content

Commit

Permalink
*: fix a bug causes indexed virtual generated column return wrong val…
Browse files Browse the repository at this point in the history
…ue and refine admin check table (#18408) (#19062)

* save

Signed-off-by: wjhuang2016 <[email protected]>

* done

Signed-off-by: wjhuang2016 <[email protected]>

* 1

Signed-off-by: wjhuang2016 <[email protected]>

* 1

Signed-off-by: wjhuang2016 <[email protected]>

Co-authored-by: ti-srebot <[email protected]>
  • Loading branch information
wjhuang2016 and ti-srebot authored Aug 11, 2020
1 parent 2d72f3c commit 1c8feb3
Show file tree
Hide file tree
Showing 20 changed files with 157 additions and 263 deletions.
2 changes: 1 addition & 1 deletion ddl/db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3674,7 +3674,7 @@ func (s *testDBSuite5) TestAddIndexForGeneratedColumn(c *C) {
s.tk.MustExec("insert into t values()")
s.tk.MustExec("ALTER TABLE t ADD COLUMN y1 year as (y + 2)")
_, err := s.tk.Exec("ALTER TABLE t ADD INDEX idx_y(y1)")
c.Assert(err.Error(), Equals, "[ddl:8202]Cannot decode index value, because cannot convert datum from unsigned bigint to type year.")
c.Assert(err, IsNil)

t := s.testGetTable(c, "t")
for _, idx := range t.Indices() {
Expand Down
17 changes: 5 additions & 12 deletions ddl/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -1160,19 +1160,12 @@ func makeupDecodeColMap(sessCtx sessionctx.Context, t table.Table, indexInfo *mo
indexedCols[i] = cols[v.Offset]
}

var containsVirtualCol bool
decodeColMap, err := decoder.BuildFullDecodeColMap(indexedCols, t, func(genCol *table.Column) (expression.Expression, error) {
containsVirtualCol = true
return expression.ParseSimpleExprCastWithTableInfo(sessCtx, genCol.GeneratedExprString, t.Meta(), &genCol.FieldType)
})
if err != nil {
return nil, err
}
dbName := model.NewCIStr(sessCtx.GetSessionVars().CurrentDB)
exprCols, _ := expression.ColumnInfos2ColumnsAndNames(sessCtx, dbName, t.Meta().Name, t.Meta().Columns, t.Meta())
mockSchema := expression.NewSchema(exprCols...)

decodeColMap := decoder.BuildFullDecodeColMap(t, mockSchema)

if containsVirtualCol {
decoder.SubstituteGenColsInDecodeColMap(decodeColMap)
decoder.RemoveUnusedVirtualCols(decodeColMap, indexedCols)
}
return decodeColMap, nil
}

Expand Down
2 changes: 1 addition & 1 deletion executor/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ func buildIndexLookUpChecker(b *executorBuilder, readerPlan *plannercore.Physica
tps = append(tps, &col.FieldType)
}
tps = append(tps, types.NewFieldType(mysql.TypeLonglong))
readerExec.checkIndexValue = &checkIndexValue{genExprs: is.GenExprs, idxColTps: tps}
readerExec.checkIndexValue = &checkIndexValue{idxColTps: tps}

colNames := make([]string, 0, len(is.Columns))
for _, col := range is.Columns {
Expand Down
18 changes: 1 addition & 17 deletions executor/distsql.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,6 @@ type IndexLookUpExecutor struct {
type checkIndexValue struct {
idxColTps []*types.FieldType
idxTblCols []*table.Column
genExprs map[model.TableColumnID]expression.Expression
}

// Open implements the Executor Open interface.
Expand Down Expand Up @@ -831,7 +830,6 @@ func (w *tableWorker) compareData(ctx context.Context, task *lookupTableTask, ta
break
}

tblReaderExec := tableReader.(*TableReaderExecutor)
iter := chunk.NewIterator4Chunk(chk)
for row := iter.Begin(); row != iter.End(); row = iter.Next() {
handle := row.GetInt64(w.handleIdx)
Expand All @@ -843,21 +841,7 @@ func (w *tableWorker) compareData(ctx context.Context, task *lookupTableTask, ta
idxRow := task.idxRows.GetRow(offset)
vals = vals[:0]
for i, col := range w.idxTblCols {
if col.IsGenerated() && !col.GeneratedStored {
expr := w.genExprs[model.TableColumnID{TableID: tblInfo.ID, ColumnID: col.ID}]
// Eval the column value
val, err := expr.Eval(row)
if err != nil {
return errors.Trace(err)
}
val, err = table.CastValue(tblReaderExec.ctx, val, col.ColumnInfo, false, false)
if err != nil {
return errors.Trace(err)
}
vals = append(vals, val)
} else {
vals = append(vals, row.GetDatum(i, &col.FieldType))
}
vals = append(vals, row.GetDatum(i, &col.FieldType))
}
vals = tables.TruncateIndexValuesIfNeeded(tblInfo, w.idxLookup.index, vals)
for i, val := range vals {
Expand Down
6 changes: 2 additions & 4 deletions executor/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -763,23 +763,21 @@ func (e *CheckTableExec) Next(ctx context.Context, req *chunk.Chunk) error {

func (e *CheckTableExec) checkTableRecord(idxOffset int) error {
idxInfo := e.indexInfos[idxOffset]
// TODO: Fix me later, can not use genExprs in indexLookUpReader, because the schema of expression is different.
genExprs := e.srcs[idxOffset].genExprs
txn, err := e.ctx.Txn(true)
if err != nil {
return err
}
if e.table.Meta().GetPartitionInfo() == nil {
idx := tables.NewIndex(e.table.Meta().ID, e.table.Meta(), idxInfo)
return admin.CheckRecordAndIndex(e.ctx, txn, e.table, idx, genExprs)
return admin.CheckRecordAndIndex(e.ctx, txn, e.table, idx)
}

info := e.table.Meta().GetPartitionInfo()
for _, def := range info.Definitions {
pid := def.ID
partition := e.table.(table.PartitionedTable).GetPartition(pid)
idx := tables.NewIndex(def.ID, e.table.Meta(), idxInfo)
if err := admin.CheckRecordAndIndex(e.ctx, txn, partition, idx, genExprs); err != nil {
if err := admin.CheckRecordAndIndex(e.ctx, txn, partition, idx); err != nil {
return errors.Trace(err)
}
}
Expand Down
2 changes: 1 addition & 1 deletion expression/builtin_compare_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func (s *testEvaluatorSuite) TestCompareFunctionWithRefine(c *C) {
{"-123456789123456789123456789.12345 < a", "1"},
{"'aaaa'=a", "eq(0, a)"},
}
cols, names := ColumnInfos2ColumnsAndNames(s.ctx, model.NewCIStr(""), tblInfo.Name, tblInfo.Columns)
cols, names := ColumnInfos2ColumnsAndNames(s.ctx, model.NewCIStr(""), tblInfo.Name, tblInfo.Columns, tblInfo)
schema := NewSchema(cols...)
for _, t := range tests {
f, err := ParseSimpleExprsWithNames(s.ctx, t.exprStr, schema, names)
Expand Down
36 changes: 34 additions & 2 deletions expression/expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/pingcap/tidb/types"
"github.com/pingcap/tidb/types/json"
"github.com/pingcap/tidb/util/chunk"
"github.com/pingcap/tidb/util/generatedexpr"
"github.com/pingcap/tidb/util/logutil"
"github.com/pingcap/tipb/go-tipb"
"go.uber.org/zap"
Expand All @@ -49,6 +50,9 @@ const (
// EvalAstExpr evaluates ast expression directly.
var EvalAstExpr func(sctx sessionctx.Context, expr ast.ExprNode) (types.Datum, error)

// RewriteAstExpr rewrite ast expression directly.
var RewriteAstExpr func(sctx sessionctx.Context, expr ast.ExprNode, schema *Schema, names types.NameSlice) (Expression, error)

// VecExpr contains all vectorized evaluation methods.
type VecExpr interface {
// Vectorized returns if this expression supports vectorized evaluation.
Expand Down Expand Up @@ -713,7 +717,7 @@ func EvaluateExprWithNull(ctx sessionctx.Context, schema *Schema, expr Expressio

// TableInfo2SchemaAndNames converts the TableInfo to the schema and name slice.
func TableInfo2SchemaAndNames(ctx sessionctx.Context, dbName model.CIStr, tbl *model.TableInfo) (*Schema, []*types.FieldName) {
cols, names := ColumnInfos2ColumnsAndNames(ctx, dbName, tbl.Name, tbl.Columns)
cols, names := ColumnInfos2ColumnsAndNames(ctx, dbName, tbl.Name, tbl.Columns, tbl)
keys := make([]KeyInfo, 0, len(tbl.Indices)+1)
for _, idx := range tbl.Indices {
if !idx.Unique || idx.State != model.StatePublic {
Expand Down Expand Up @@ -756,7 +760,7 @@ func TableInfo2SchemaAndNames(ctx sessionctx.Context, dbName model.CIStr, tbl *m
}

// ColumnInfos2ColumnsAndNames converts the ColumnInfo to the *Column and NameSlice.
func ColumnInfos2ColumnsAndNames(ctx sessionctx.Context, dbName, tblName model.CIStr, colInfos []*model.ColumnInfo) ([]*Column, types.NameSlice) {
func ColumnInfos2ColumnsAndNames(ctx sessionctx.Context, dbName, tblName model.CIStr, colInfos []*model.ColumnInfo, tblInfo *model.TableInfo) ([]*Column, types.NameSlice) {
columns := make([]*Column, 0, len(colInfos))
names := make([]*types.FieldName, 0, len(colInfos))
for i, col := range colInfos {
Expand All @@ -780,6 +784,34 @@ func ColumnInfos2ColumnsAndNames(ctx sessionctx.Context, dbName, tblName model.C
}
columns = append(columns, newCol)
}
// Resolve virtual generated column.
mockSchema := NewSchema(columns...)
for i, col := range colInfos {
if col.State != model.StatePublic {
continue
}
if col.IsGenerated() && !col.GeneratedStored {
expr, err := generatedexpr.ParseExpression(col.GeneratedExprString)
if err != nil {
terror.Log(err)
}
expr, err = generatedexpr.SimpleResolveName(expr, tblInfo)
if err != nil {
terror.Log(err)
}
e, err := RewriteAstExpr(ctx, expr, mockSchema, names)
if err != nil {
terror.Log(err)
}
if e != nil {
columns[i].VirtualExpr = e.Clone()
}
columns[i].VirtualExpr, err = columns[i].VirtualExpr.ResolveIndices(mockSchema)
if err != nil {
terror.Log(err)
}
}
}
return columns, names
}

Expand Down
12 changes: 12 additions & 0 deletions expression/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6558,6 +6558,18 @@ func (s *testIntegrationSuite) TestIssue17727(c *C) {
tk.MustQuery("select @@last_plan_from_cache;").Check(testkit.Rows("1"))
}

func (s *testIntegrationSerialSuite) TestIssue17989(c *C) {
tk := testkit.NewTestKit(c, s.store)
tk.MustExec("use test")
tk.MustExec("drop table if exists t")
tk.MustExec("create table t(a int, b tinyint as(a+1), c int as(b+1));")
tk.MustExec("set sql_mode='';")
tk.MustExec("insert into t(a) values(2000);")
tk.MustExec("create index idx on t(c);")
tk.MustQuery("select c from t;").Check(testkit.Rows("128"))
tk.MustExec("admin check table t")
}

func (s *testIntegrationSerialSuite) TestIssue18702(c *C) {
collate.SetNewCollationEnabledForTest(true)
defer collate.SetNewCollationEnabledForTest(false)
Expand Down
2 changes: 1 addition & 1 deletion expression/simple_rewriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func ParseSimpleExprCastWithTableInfo(ctx sessionctx.Context, exprStr string, ta
// RewriteSimpleExprWithTableInfo rewrites simple ast.ExprNode to expression.Expression.
func RewriteSimpleExprWithTableInfo(ctx sessionctx.Context, tbl *model.TableInfo, expr ast.ExprNode) (Expression, error) {
dbName := model.NewCIStr(ctx.GetSessionVars().CurrentDB)
columns, names := ColumnInfos2ColumnsAndNames(ctx, dbName, tbl.Name, tbl.Columns)
columns, names := ColumnInfos2ColumnsAndNames(ctx, dbName, tbl.Name, tbl.Columns, tbl)
rewriter := &simpleRewriter{ctx: ctx, schema: NewSchema(columns...), names: names}
expr.Accept(rewriter)
if rewriter.err != nil {
Expand Down
17 changes: 15 additions & 2 deletions planner/core/expression_rewriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,30 @@ func evalAstExpr(sctx sessionctx.Context, expr ast.ExprNode) (types.Datum, error
if val, ok := expr.(*driver.ValueExpr); ok {
return val.Datum, nil
}
NewExpr, err := rewriteAstExpr(sctx, expr, nil, nil)
if err != nil {
return types.Datum{}, err
}
return NewExpr.Eval(chunk.Row{})
}

// rewriteAstExpr rewrite ast expression directly.
func rewriteAstExpr(sctx sessionctx.Context, expr ast.ExprNode, schema *expression.Schema, names types.NameSlice) (expression.Expression, error) {
var is infoschema.InfoSchema
if sctx.GetSessionVars().TxnCtx.InfoSchema != nil {
is = sctx.GetSessionVars().TxnCtx.InfoSchema.(infoschema.InfoSchema)
}
b := NewPlanBuilder(sctx, is, &hint.BlockHintProcessor{})
fakePlan := LogicalTableDual{}.Init(sctx, 0)
if schema != nil {
fakePlan.schema = schema
fakePlan.names = names
}
newExpr, _, err := b.rewrite(context.TODO(), expr, fakePlan, nil, true)
if err != nil {
return types.Datum{}, err
return nil, err
}
return newExpr.Eval(chunk.Row{})
return newExpr, nil
}

func (b *PlanBuilder) rewriteInsertOnDuplicateUpdate(ctx context.Context, exprNode ast.ExprNode, mockPlan LogicalPlan, insertPlan *Insert) (expression.Expression, error) {
Expand Down
1 change: 1 addition & 0 deletions planner/core/optimizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ var DefaultDisabledLogicalRulesList *atomic.Value

func init() {
expression.EvalAstExpr = evalAstExpr
expression.RewriteAstExpr = rewriteAstExpr
DefaultDisabledLogicalRulesList = new(atomic.Value)
DefaultDisabledLogicalRulesList.Store(set.NewStringSet())
}
2 changes: 1 addition & 1 deletion planner/core/partition_pruning_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ func prepareTestCtx(c *C, createTable string, partitionExpr string) *testCtx {
sctx := mock.NewContext()
tblInfo, err := ddl.BuildTableInfoFromAST(stmt.(*ast.CreateTableStmt))
c.Assert(err, IsNil)
columns, names := expression.ColumnInfos2ColumnsAndNames(sctx, model.NewCIStr("t"), tblInfo.Name, tblInfo.Columns)
columns, names := expression.ColumnInfos2ColumnsAndNames(sctx, model.NewCIStr("t"), tblInfo.Name, tblInfo.Columns, tblInfo)
schema := expression.NewSchema(columns...)

col, fn, err := makePartitionByFnCol(sctx, columns, names, partitionExpr)
Expand Down
72 changes: 22 additions & 50 deletions planner/core/planbuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -994,7 +994,6 @@ func (b *PlanBuilder) getGenExprs(ctx context.Context, dbName model.CIStr, tbl t
if err != nil {
return nil, errors.Trace(err)
}
expr = expression.BuildCastFunction(b.ctx, expr, colExpr.GetType())
found := false
for _, column := range idx.Columns {
if strings.EqualFold(col.Name.L, column.Name.L) {
Expand Down Expand Up @@ -1031,70 +1030,24 @@ func FindColumnInfoByID(colInfos []*model.ColumnInfo, id int64) *model.ColumnInf
}

func (b *PlanBuilder) buildPhysicalIndexLookUpReader(ctx context.Context, dbName model.CIStr, tbl table.Table, idx *model.IndexInfo) (Plan, error) {
// Get generated columns.
var genCols []*expression.Column
pkOffset := -1
tblInfo := tbl.Meta()
colsMap := set.NewInt64Set()
schema := expression.NewSchema(make([]*expression.Column, 0, len(idx.Columns))...)
idxReaderCols := make([]*model.ColumnInfo, 0, len(idx.Columns))
tblReaderCols := make([]*model.ColumnInfo, 0, len(tbl.Cols()))
fullExprCols, fullColNames := expression.TableInfo2SchemaAndNames(b.ctx, dbName, tblInfo)
genExprsMap, err := b.getGenExprs(ctx, dbName, tbl, idx, fullExprCols, fullColNames)
if err != nil {
return nil, err
}
fullExprCols, _ := expression.TableInfo2SchemaAndNames(b.ctx, dbName, tblInfo)
for _, idxCol := range idx.Columns {
for i, col := range tblInfo.Columns {
if idxCol.Name.L == col.Name.L {
idxReaderCols = append(idxReaderCols, col)
tblReaderCols = append(tblReaderCols, col)
schema.Append(fullExprCols.Columns[i])
colsMap.Insert(col.ID)
if mysql.HasPriKeyFlag(col.Flag) {
pkOffset = len(tblReaderCols) - 1
}
genColumnID := model.TableColumnID{TableID: tblInfo.ID, ColumnID: col.ID}
if expr, ok := genExprsMap[genColumnID]; ok {
cols := expression.ExtractColumns(expr)
genCols = append(genCols, cols...)
}
}
}
}
idxCols, idxColLens := expression.IndexInfo2PrefixCols(tblReaderCols, schema.Columns, idx)
fullIdxCols, _ := expression.IndexInfo2Cols(tblReaderCols, schema.Columns, idx)
// Add generated columns to tblSchema and tblReaderCols.
tblSchema := schema.Clone()
for _, col := range genCols {
if !colsMap.Exist(col.ID) {
info := FindColumnInfoByID(tblInfo.Columns, col.ID)
if info != nil {
tblReaderCols = append(tblReaderCols, info)
tblSchema.Append(col)
colsMap.Insert(col.ID)
if mysql.HasPriKeyFlag(col.RetType.Flag) {
pkOffset = len(tblReaderCols) - 1
}
}
}
}
for k, expr := range genExprsMap {
genExprsMap[k], err = expr.ResolveIndices(tblSchema)
if err != nil {
return nil, err
}
}
if !tbl.Meta().PKIsHandle || pkOffset == -1 {
tblReaderCols = append(tblReaderCols, model.NewExtraHandleColInfo())
handleCol := &expression.Column{
RetType: types.NewFieldType(mysql.TypeLonglong),
UniqueID: b.ctx.GetSessionVars().AllocPlanColumnID(),
ID: model.ExtraHandleID,
}
tblSchema.Append(handleCol)
pkOffset = len(tblReaderCols) - 1
}

is := PhysicalIndexScan{
Table: tblInfo,
Expand All @@ -1106,13 +1059,29 @@ func (b *PlanBuilder) buildPhysicalIndexLookUpReader(ctx context.Context, dbName
IdxColLens: idxColLens,
dataSourceSchema: schema,
Ranges: ranger.FullRange(),
GenExprs: genExprsMap,
}.Init(b.ctx, b.getSelectOffset())
// There is no alternative plan choices, so just use pseudo stats to avoid panic.
is.stats = &property.StatsInfo{HistColl: &(statistics.PseudoTable(tblInfo)).HistColl}
// It's double read case.
ts := PhysicalTableScan{Columns: tblReaderCols, Table: is.Table, TableAsName: &tblInfo.Name}.Init(b.ctx, b.getSelectOffset())
ts.SetSchema(tblSchema.Clone())
ts.SetSchema(schema.Clone())
ts.Columns = ExpandVirtualColumn(ts.Columns, ts.schema, ts.Table.Columns)
for offset, col := range ts.Columns {
if mysql.HasPriKeyFlag(col.Flag) {
pkOffset = offset
}
}
if !tbl.Meta().PKIsHandle || pkOffset == -1 {
ts.Columns = append(ts.Columns, model.NewExtraHandleColInfo())
handleCol := &expression.Column{
RetType: types.NewFieldType(mysql.TypeLonglong),
UniqueID: b.ctx.GetSessionVars().AllocPlanColumnID(),
ID: model.ExtraHandleID,
}
ts.schema.Append(handleCol)
pkOffset = len(ts.Columns) - 1
}

if tbl.Meta().GetPartitionInfo() != nil {
pid := tbl.(table.PhysicalTable).GetPhysicalID()
is.physicalTableID = pid
Expand All @@ -1128,6 +1097,9 @@ func (b *PlanBuilder) buildPhysicalIndexLookUpReader(ctx context.Context, dbName
ts.HandleIdx = pkOffset
is.initSchema(idx, fullIdxCols, true)
rootT := finishCopTask(b.ctx, cop).(*rootTask)
if err := rootT.p.ResolveIndices(); err != nil {
return nil, err
}
return rootT.p, nil
}

Expand Down
Loading

0 comments on commit 1c8feb3

Please sign in to comment.