From e03ef6099cbde896f6fce172030499901c10a3ff Mon Sep 17 00:00:00 2001 From: tiancaiamao Date: Tue, 11 Dec 2018 13:52:57 +0800 Subject: [PATCH] ddl: support alter table .. truncate partition (#8624) --- .travis.yml | 1 - ddl/db_test.go | 56 +++++++++++++++++++++++++++++++++++++++++++ ddl/ddl.go | 3 +++ ddl/ddl_api.go | 39 ++++++++++++++++++++++++++++++ ddl/ddl_worker.go | 4 +++- ddl/delete_range.go | 2 +- ddl/partition.go | 58 +++++++++++++++++++++++++++++++++++++++++++++ go.mod | 2 +- go.sum | 4 ++-- 9 files changed, 163 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index d81c12ae3b407..c473d1d6443dc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,4 +17,3 @@ before_install: script: - make dev - ./hack/check-tidy.sh - - ./hack/check_parser_replace.sh diff --git a/ddl/db_test.go b/ddl/db_test.go index 0461845e68f67..41617c4824f73 100644 --- a/ddl/db_test.go +++ b/ddl/db_test.go @@ -3056,6 +3056,62 @@ func (s *testDBSuite) TestAlterTableDropPartition(c *C) { s.testErrorCode(c, sql4, tmysql.ErrDropPartitionNonExistent) } +func (s *testDBSuite) TestAlterTableTruncatePartition(c *C) { + s.tk = testkit.NewTestKit(c, s.store) + s.tk.MustExec("use test") + s.tk.MustExec("drop table if exists employees") + s.tk.MustExec("set @@tidb_enable_table_partition = 1") + s.tk.MustExec(`create table employees ( + id int not null, + hired int not null + ) partition by range( hired ) ( + partition p1 values less than (1991), + partition p2 values less than (1996), + partition p3 values less than (2001) + )`) + s.tk.MustExec("insert into employees values (1, 1990)") + s.tk.MustExec("insert into employees values (2, 1995)") + s.tk.MustExec("insert into employees values (3, 2000)") + result := s.tk.MustQuery("select * from employees order by id") + result.Check(testkit.Rows(`1 1990`, `2 1995`, `3 2000`)) + + s.testErrorCode(c, "alter table employees truncate partition xxx", tmysql.ErrUnknownPartition) + + ctx := s.tk.Se.(sessionctx.Context) + is := domain.GetDomain(ctx).InfoSchema() + oldTblInfo, err := is.TableByName(model.NewCIStr("test"), model.NewCIStr("employees")) + c.Assert(err, IsNil) + oldPID := oldTblInfo.Meta().Partition.Definitions[0].ID + + s.tk.MustExec("alter table employees truncate partition p1") + result = s.tk.MustQuery("select * from employees order by id") + result.Check(testkit.Rows(`2 1995`, `3 2000`)) + + partitionPrefix := tablecodec.EncodeTablePrefix(oldPID) + hasOldPartitionData := checkPartitionDelRangeDone(c, s, partitionPrefix) + c.Assert(hasOldPartitionData, IsFalse) + + is = domain.GetDomain(ctx).InfoSchema() + oldTblInfo, err = is.TableByName(model.NewCIStr("test"), model.NewCIStr("employees")) + c.Assert(err, IsNil) + newPID := oldTblInfo.Meta().Partition.Definitions[0].ID + c.Assert(oldPID != newPID, IsTrue) + + s.tk.MustExec("alter table employees truncate partition p3") + result = s.tk.MustQuery("select * from employees") + result.Check(testkit.Rows(`2 1995`)) + + s.tk.MustExec("insert into employees values (1, 1984)") + result = s.tk.MustQuery("select * from employees order by id") + result.Check(testkit.Rows(`1 1984`, `2 1995`)) + s.tk.MustExec("insert into employees values (3, 2000)") + result = s.tk.MustQuery("select * from employees order by id") + result.Check(testkit.Rows(`1 1984`, `2 1995`, `3 2000`)) + + s.tk.MustExec(`create table non_partition (id int)`) + s.testErrorCode(c, "alter table non_partition truncate partition p0", tmysql.ErrPartitionMgmtOnNonpartitioned) +} + func (s *testDBSuite) TestAddPartitionTooManyPartitions(c *C) { s.tk = testkit.NewTestKit(c, s.store) s.tk.MustExec("use test") diff --git a/ddl/ddl.go b/ddl/ddl.go index fe5af0570b979..36a3e6bc80aa7 100644 --- a/ddl/ddl.go +++ b/ddl/ddl.go @@ -83,6 +83,7 @@ var ( errWaitReorgTimeout = terror.ClassDDL.New(codeWaitReorgTimeout, "wait for reorganization timeout") errInvalidStoreVer = terror.ClassDDL.New(codeInvalidStoreVer, "invalid storage current version") + errUnknownPartition = terror.ClassDDL.New(codeUnknownPartition, mysql.MySQLErrName[mysql.ErrUnknownPartition]) // We don't support dropping column with index covered now. errCantDropColWithIndex = terror.ClassDDL.New(codeCantDropColWithIndex, "can't drop column with index") errUnsupportedAddColumn = terror.ClassDDL.New(codeUnsupportedAddColumn, "unsupported add column") @@ -636,6 +637,7 @@ const ( codeWrongExprInPartitionFunc = terror.ErrCode(mysql.ErrWrongExprInPartitionFunc) codeWarnDataTruncated = terror.ErrCode(mysql.WarnDataTruncated) codeCoalesceOnlyOnHashPartition = terror.ErrCode(mysql.ErrCoalesceOnlyOnHashPartition) + codeUnknownPartition = terror.ErrCode(mysql.ErrUnknownPartition) ) func init() { @@ -685,6 +687,7 @@ func init() { codeWrongExprInPartitionFunc: mysql.ErrWrongExprInPartitionFunc, codeWarnDataTruncated: mysql.WarnDataTruncated, codeCoalesceOnlyOnHashPartition: mysql.ErrCoalesceOnlyOnHashPartition, + codeUnknownPartition: mysql.ErrUnknownPartition, } terror.ErrClassToMySQLCodes[terror.ClassDDL] = ddlMySQLErrCodes } diff --git a/ddl/ddl_api.go b/ddl/ddl_api.go index efe645de0e343..9953a39f3e9b6 100644 --- a/ddl/ddl_api.go +++ b/ddl/ddl_api.go @@ -1154,6 +1154,8 @@ func (d *ddl) AlterTable(ctx sessionctx.Context, ident ast.Ident, specs []*ast.A err = d.DropIndex(ctx, ident, model.NewCIStr(spec.Name)) case ast.AlterTableDropPartition: err = d.DropTablePartition(ctx, ident, spec) + case ast.AlterTableTruncatePartition: + err = d.TruncateTablePartition(ctx, ident, spec) case ast.AlterTableAddConstraint: constr := spec.Constraint switch spec.Constraint.Tp { @@ -1470,6 +1472,43 @@ func (d *ddl) CoalescePartitions(ctx sessionctx.Context, ident ast.Ident, spec * return errors.Trace(err) } +func (d *ddl) TruncateTablePartition(ctx sessionctx.Context, ident ast.Ident, spec *ast.AlterTableSpec) error { + is := d.infoHandle.Get() + schema, ok := is.SchemaByName(ident.Schema) + if !ok { + return errors.Trace(infoschema.ErrDatabaseNotExists.GenWithStackByArgs(schema)) + } + t, err := is.TableByName(ident.Schema, ident.Name) + if err != nil { + return errors.Trace(infoschema.ErrTableNotExists.GenWithStackByArgs(ident.Schema, ident.Name)) + } + meta := t.Meta() + if meta.GetPartitionInfo() == nil { + return errors.Trace(ErrPartitionMgmtOnNonpartitioned) + } + + var pid int64 + pid, err = findPartitionByName(meta, spec.Name) + if err != nil { + return errors.Trace(err) + } + + job := &model.Job{ + SchemaID: schema.ID, + TableID: meta.ID, + Type: model.ActionTruncateTablePartition, + BinlogInfo: &model.HistoryInfo{}, + Args: []interface{}{pid}, + } + + err = d.doDDLJob(ctx, job) + if err != nil { + return errors.Trace(err) + } + err = d.callHookOnChanged(err) + return errors.Trace(err) +} + func (d *ddl) DropTablePartition(ctx sessionctx.Context, ident ast.Ident, spec *ast.AlterTableSpec) error { is := d.infoHandle.Get() schema, ok := is.SchemaByName(ident.Schema) diff --git a/ddl/ddl_worker.go b/ddl/ddl_worker.go index 2888cad98fe17..b1d1553030e53 100644 --- a/ddl/ddl_worker.go +++ b/ddl/ddl_worker.go @@ -285,7 +285,7 @@ func (w *worker) finishDDLJob(t *meta.Meta, job *model.Job) (err error) { // After rolling back an AddIndex operation, we need to use delete-range to delete the half-done index data. err = w.deleteRange(job) - case model.ActionDropSchema, model.ActionDropTable, model.ActionTruncateTable, model.ActionDropIndex, model.ActionDropTablePartition: + case model.ActionDropSchema, model.ActionDropTable, model.ActionTruncateTable, model.ActionDropIndex, model.ActionDropTablePartition, model.ActionTruncateTablePartition: err = w.deleteRange(job) } if err != nil { @@ -472,6 +472,8 @@ func (w *worker) runDDLJob(d *ddlCtx, t *meta.Meta, job *model.Job) (ver int64, ver, err = onDropTable(t, job) case model.ActionDropTablePartition: ver, err = onDropTablePartition(t, job) + case model.ActionTruncateTablePartition: + ver, err = onTruncateTablePartition(t, job) case model.ActionAddColumn: ver, err = onAddColumn(d, t, job) case model.ActionDropColumn: diff --git a/ddl/delete_range.go b/ddl/delete_range.go index 6a4397c734b83..dfee35efe0ca6 100644 --- a/ddl/delete_range.go +++ b/ddl/delete_range.go @@ -243,7 +243,7 @@ func insertJobIntoDeleteRangeTable(ctx sessionctx.Context, job *model.Job) error startKey = tablecodec.EncodeTablePrefix(tableID) endKey := tablecodec.EncodeTablePrefix(tableID + 1) return doInsert(s, job.ID, tableID, startKey, endKey, now) - case model.ActionDropTablePartition: + case model.ActionDropTablePartition, model.ActionTruncateTablePartition: var physicalTableID int64 if err := job.DecodeArgs(&physicalTableID); err != nil { return errors.Trace(err) diff --git a/ddl/partition.go b/ddl/partition.go index 2323363c5873b..a5db931b9f5ab 100644 --- a/ddl/partition.go +++ b/ddl/partition.go @@ -318,6 +318,18 @@ func checkDropTablePartition(meta *model.TableInfo, partName string) error { return errors.Trace(ErrDropPartitionNonExistent.GenWithStackByArgs(partName)) } +func findPartitionByName(meta *model.TableInfo, parName string) (int64, error) { + // TODO: MySQL behavior for hash partition is weird, "create table .. partition by hash partition 4", + // it use p0, p1, p2, p3 as partition names automatically. + parName = strings.ToLower(parName) + for _, def := range meta.Partition.Definitions { + if strings.EqualFold(def.Name.L, parName) { + return def.ID, nil + } + } + return -1, errors.Trace(errUnknownPartition.GenWithStackByArgs(parName, meta.Name.O)) +} + // removePartitionInfo each ddl job deletes a partition. func removePartitionInfo(tblInfo *model.TableInfo, partName string) int64 { oldDefs := tblInfo.Partition.Definitions @@ -365,6 +377,52 @@ func onDropTablePartition(t *meta.Meta, job *model.Job) (ver int64, _ error) { return ver, nil } +// onDropTablePartition truncates old partition meta. +func onTruncateTablePartition(t *meta.Meta, job *model.Job) (int64, error) { + var ver int64 + var oldID int64 + if err := job.DecodeArgs(&oldID); err != nil { + job.State = model.JobStateCancelled + return ver, errors.Trace(err) + } + tblInfo, err := getTableInfo(t, job, job.SchemaID) + if err != nil { + return ver, errors.Trace(err) + } + pi := tblInfo.GetPartitionInfo() + if pi == nil { + return ver, errors.Trace(ErrPartitionMgmtOnNonpartitioned) + } + + var find bool + for i := 0; i < len(pi.Definitions); i++ { + def := &pi.Definitions[i] + if def.ID == oldID { + pid, err1 := t.GenGlobalID() + if err != nil { + return ver, errors.Trace(err1) + } + def.ID = pid + find = true + break + } + } + if !find { + return ver, errUnknownPartition.GenWithStackByArgs("drop?", tblInfo.Name.O) + } + + ver, err = updateVersionAndTableInfo(t, job, tblInfo, true) + if err != nil { + return ver, errors.Trace(err) + } + + // Finish this job. + job.FinishTableJob(model.JobStateDone, model.StateNone, ver, tblInfo) + // A background job will be created to delete old partition data. + job.Args = []interface{}{oldID} + return ver, nil +} + func checkAddPartitionTooManyPartitions(piDefs uint64) error { if piDefs > uint64(PartitionCountLimit) { return ErrTooManyPartitions diff --git a/go.mod b/go.mod index c1ed756e0b4b8..08135b2bc61e1 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,7 @@ require ( github.com/pingcap/errors v0.11.0 github.com/pingcap/goleveldb v0.0.0-20171020122428-b9ff6c35079e github.com/pingcap/kvproto v0.0.0-20181105061835-1b5d69cd1d26 - github.com/pingcap/parser v0.0.0-20181210061630-27e9d3e251d4 + github.com/pingcap/parser v0.0.0-20181211024540-4e6d047fcaae github.com/pingcap/pd v2.1.0-rc.4+incompatible github.com/pingcap/tidb-tools v0.0.0-20181112132202-4860a0d5de03 github.com/pingcap/tipb v0.0.0-20181012112600-11e33c750323 diff --git a/go.sum b/go.sum index 6a8c51c3ac0a9..be57fc9899053 100644 --- a/go.sum +++ b/go.sum @@ -107,8 +107,8 @@ github.com/pingcap/goleveldb v0.0.0-20171020122428-b9ff6c35079e h1:P73/4dPCL96rG github.com/pingcap/goleveldb v0.0.0-20171020122428-b9ff6c35079e/go.mod h1:O17XtbryoCJhkKGbT62+L2OlrniwqiGLSqrmdHCMzZw= github.com/pingcap/kvproto v0.0.0-20181105061835-1b5d69cd1d26 h1:JK4VLNYbSn36QSbCnqALi2ySXdH0DfcMssT/zmLf4Ls= github.com/pingcap/kvproto v0.0.0-20181105061835-1b5d69cd1d26/go.mod h1:0gwbe1F2iBIjuQ9AH0DbQhL+Dpr5GofU8fgYyXk+ykk= -github.com/pingcap/parser v0.0.0-20181210061630-27e9d3e251d4 h1:2rCHDk4h8VZw0fiC2CFJffOlXU3iMuz1kOt5wTMCemY= -github.com/pingcap/parser v0.0.0-20181210061630-27e9d3e251d4/go.mod h1:1FNvfp9+J0wvc4kl8eGNh7Rqrxveg15jJoWo/a0uHwA= +github.com/pingcap/parser v0.0.0-20181211024540-4e6d047fcaae h1:RD98+89F/yakFLnztEL4Pi9f+RkOAm2vgRJcB1p6tTw= +github.com/pingcap/parser v0.0.0-20181211024540-4e6d047fcaae/go.mod h1:1FNvfp9+J0wvc4kl8eGNh7Rqrxveg15jJoWo/a0uHwA= github.com/pingcap/pd v2.1.0-rc.4+incompatible h1:/buwGk04aHO5odk/+O8ZOXGs4qkUjYTJ2UpCJXna8NE= github.com/pingcap/pd v2.1.0-rc.4+incompatible/go.mod h1:nD3+EoYes4+aNNODO99ES59V83MZSI+dFbhyr667a0E= github.com/pingcap/tidb-tools v0.0.0-20181112132202-4860a0d5de03 h1:xVuo5U+l6XAWHsb+xhkZ8zz3jerIwDfCHAO6kR2Kaog=