diff --git a/ddl/ddl_api.go b/ddl/ddl_api.go index 6715fd1a3b666..1a974e4b84c1a 100644 --- a/ddl/ddl_api.go +++ b/ddl/ddl_api.go @@ -2732,6 +2732,8 @@ func (d *ddl) AlterTable(ctx context.Context, sctx sessionctx.Context, ident ast err = d.AlterTableAttributes(sctx, ident, spec) case ast.AlterTablePartitionAttributes: err = d.AlterTablePartitionAttributes(sctx, ident, spec) + case ast.AlterTablePartitionOptions: + err = d.AlterTablePartitionOptions(sctx, ident, spec) default: // Nothing to do now. } @@ -6324,6 +6326,81 @@ func (d *ddl) AlterTablePartitionAttributes(ctx sessionctx.Context, ident ast.Id return errors.Trace(err) } +func (d *ddl) AlterTablePartitionOptions(ctx sessionctx.Context, ident ast.Ident, spec *ast.AlterTableSpec) (err error) { + schema, tb, err := d.getSchemaAndTableByIdent(ctx, ident) + if err != nil { + return errors.Trace(err) + } + + meta := tb.Meta() + if meta.Partition == nil { + return errors.Trace(ErrPartitionMgmtOnNonpartitioned) + } + + partitionID, err := tables.FindPartitionByName(meta, spec.PartitionNames[0].L) + if err != nil { + return errors.Trace(err) + } + var policyRefInfo *model.PolicyRefInfo + var placementSettings *model.PlacementSettings + if spec.Options != nil { + for _, op := range spec.Options { + switch op.Tp { + case ast.TableOptionPlacementPolicy: + policyRefInfo = &model.PolicyRefInfo{ + Name: model.NewCIStr(op.StrValue), + } + case ast.TableOptionPlacementPrimaryRegion, ast.TableOptionPlacementRegions, + ast.TableOptionPlacementFollowerCount, ast.TableOptionPlacementVoterCount, + ast.TableOptionPlacementLearnerCount, ast.TableOptionPlacementSchedule, + ast.TableOptionPlacementConstraints, ast.TableOptionPlacementLeaderConstraints, + ast.TableOptionPlacementLearnerConstraints, ast.TableOptionPlacementFollowerConstraints, + ast.TableOptionPlacementVoterConstraints: + if placementSettings == nil { + placementSettings = &model.PlacementSettings{} + } + err = SetDirectPlacementOpt(placementSettings, ast.PlacementOptionType(op.Tp), op.StrValue, op.UintValue) + if err != nil { + return err + } + default: + return errors.Trace(errors.New("unknown partition option")) + } + } + } + + // Can not use both a placement policy and direct assignment. If you specify both in a CREATE TABLE or ALTER TABLE an error will be returned. + if placementSettings != nil && policyRefInfo != nil { + return errors.Trace(ErrPlacementPolicyWithDirectOption.GenWithStackByArgs(policyRefInfo.Name)) + } + if placementSettings != nil { + // check the direct placement option compatibility. + if err := checkPolicyValidation(placementSettings); err != nil { + return errors.Trace(err) + } + } + if policyRefInfo != nil { + policy, ok := ctx.GetInfoSchema().(infoschema.InfoSchema).PolicyByName(policyRefInfo.Name) + if !ok { + return errors.Trace(infoschema.ErrPlacementPolicyNotExists.GenWithStackByArgs(policyRefInfo.Name)) + } + policyRefInfo.ID = policy.ID + } + + job := &model.Job{ + SchemaID: schema.ID, + TableID: meta.ID, + SchemaName: schema.Name.L, + Type: model.ActionAlterTablePartitionPolicy, + BinlogInfo: &model.HistoryInfo{}, + Args: []interface{}{partitionID, policyRefInfo, placementSettings}, + } + + err = d.doDDLJob(ctx, job) + err = d.callHookOnChanged(err) + return errors.Trace(err) +} + func buildPolicyInfo(name model.CIStr, options []*ast.PlacementOption) (*model.PolicyInfo, error) { policyInfo := &model.PolicyInfo{PlacementSettings: &model.PlacementSettings{}} policyInfo.Name = name diff --git a/ddl/ddl_worker.go b/ddl/ddl_worker.go index a7c253b5ddbdf..004b09397374e 100644 --- a/ddl/ddl_worker.go +++ b/ddl/ddl_worker.go @@ -832,6 +832,8 @@ func (w *worker) runDDLJob(d *ddlCtx, t *meta.Meta, job *model.Job) (ver int64, ver, err = onDropPlacementPolicy(d, t, job) case model.ActionAlterPlacementPolicy: ver, err = onAlterPlacementPolicy(d, t, job) + case model.ActionAlterTablePartitionPolicy: + ver, err = onAlterTablePartitionOptions(t, job) default: // Invalid job, cancel it. job.State = model.JobStateCancelled diff --git a/ddl/placement_policy_test.go b/ddl/placement_policy_test.go index 0a7f52fcbc75c..48465d32a986e 100644 --- a/ddl/placement_policy_test.go +++ b/ddl/placement_policy_test.go @@ -495,3 +495,108 @@ func (s *testDBSuite6) TestPolicyCacheAndPolicyDependencyCache(c *C) { c.Assert(dependencies, NotNil) c.Assert(len(dependencies), Equals, 0) } + +func (s *testDBSuite6) TestAlterTablePartitionWithPlacementPolicy(c *C) { + tk := testkit.NewTestKit(c, s.store) + defer func() { + tk.MustExec("drop table if exists t1") + tk.MustExec("drop placement policy if exists x") + }() + tk.MustExec("use test") + tk.MustExec("drop table if exists t1") + // Direct placement option: special constraints may be incompatible with common constraint. + tk.MustExec("create table t1 (c int) PARTITION BY RANGE (c) " + + "(PARTITION p0 VALUES LESS THAN (6)," + + "PARTITION p1 VALUES LESS THAN (11)," + + "PARTITION p2 VALUES LESS THAN (16)," + + "PARTITION p3 VALUES LESS THAN (21));") + + tk.MustExec("alter table t1 partition p0 " + + "PRIMARY_REGION=\"cn-east-1\" " + + "REGIONS=\"cn-east-1, cn-east-2\" " + + "FOLLOWERS=2 ") + + tbl := testGetTableByName(c, tk.Se, "test", "t1") + c.Assert(tbl, NotNil) + ptDef := testGetPartitionDefinitionsByName(c, tk.Se, "test", "t1", "p0") + c.Assert(ptDef.PlacementPolicyRef.Name.L, Equals, "") + c.Assert(ptDef.DirectPlacementOpts, NotNil) + + checkFunc := func(policySetting *model.PlacementSettings) { + c.Assert(policySetting.PrimaryRegion, Equals, "cn-east-1") + c.Assert(policySetting.Regions, Equals, "cn-east-1, cn-east-2") + c.Assert(policySetting.Followers, Equals, uint64(2)) + c.Assert(policySetting.FollowerConstraints, Equals, "") + c.Assert(policySetting.Voters, Equals, uint64(0)) + c.Assert(policySetting.VoterConstraints, Equals, "") + c.Assert(policySetting.Learners, Equals, uint64(0)) + c.Assert(policySetting.LearnerConstraints, Equals, "") + c.Assert(policySetting.Constraints, Equals, "") + c.Assert(policySetting.Schedule, Equals, "") + } + checkFunc(ptDef.DirectPlacementOpts) + + //Direct placement option and placement policy can't co-exist. + _, err := tk.Exec("alter table t1 partition p0 " + + "PRIMARY_REGION=\"cn-east-1\" " + + "REGIONS=\"cn-east-1, cn-east-2\" " + + "FOLLOWERS=2 " + + "PLACEMENT POLICY=\"x\"") + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "[ddl:8240]Placement policy 'x' can't co-exist with direct placement options") + + // Only placement policy should check the policy existence. + tk.MustGetErrCode("alter table t1 partition p0 "+ + "PLACEMENT POLICY=\"x\"", mysql.ErrPlacementPolicyNotExists) + tk.MustExec("create placement policy x " + + "FOLLOWERS=2 ") + tk.MustExec("alter table t1 partition p0 " + + "PLACEMENT POLICY=\"x\"") + + ptDef = testGetPartitionDefinitionsByName(c, tk.Se, "test", "t1", "p0") + c.Assert(ptDef, NotNil) + c.Assert(ptDef.PlacementPolicyRef, NotNil) + c.Assert(ptDef.PlacementPolicyRef.Name.L, Equals, "x") + c.Assert(ptDef.PlacementPolicyRef.ID != 0, Equals, true) + + tk.MustExec("alter table t1 partition p0 " + + "PRIMARY_REGION=\"cn-east-1\" " + + "REGIONS=\"cn-east-1, cn-east-2\" " + + "FOLLOWERS=2 ") + + ptDef = testGetPartitionDefinitionsByName(c, tk.Se, "test", "t1", "p0") + c.Assert(ptDef, NotNil) + c.Assert(ptDef.DirectPlacementOpts, NotNil) + + checkFunc = func(policySetting *model.PlacementSettings) { + c.Assert(policySetting.PrimaryRegion, Equals, "cn-east-1") + c.Assert(policySetting.Regions, Equals, "cn-east-1, cn-east-2") + c.Assert(policySetting.Followers, Equals, uint64(2)) + c.Assert(policySetting.FollowerConstraints, Equals, "") + c.Assert(policySetting.Voters, Equals, uint64(0)) + c.Assert(policySetting.VoterConstraints, Equals, "") + c.Assert(policySetting.Learners, Equals, uint64(0)) + c.Assert(policySetting.LearnerConstraints, Equals, "") + c.Assert(policySetting.Constraints, Equals, "") + c.Assert(policySetting.Schedule, Equals, "") + } + checkFunc(ptDef.DirectPlacementOpts) +} + +func testGetPartitionDefinitionsByName(c *C, ctx sessionctx.Context, db string, table string, ptName string) model.PartitionDefinition { + dom := domain.GetDomain(ctx) + // Make sure the table schema is the new schema. + err := dom.Reload() + c.Assert(err, IsNil) + tbl, err := dom.InfoSchema().TableByName(model.NewCIStr(db), model.NewCIStr(table)) + c.Assert(err, IsNil) + c.Assert(tbl, NotNil) + var ptDef model.PartitionDefinition + for _, def := range tbl.Meta().Partition.Definitions { + if ptName == def.Name.L { + ptDef = def + break + } + } + return ptDef +} diff --git a/ddl/table.go b/ddl/table.go index 507ca5b404451..b5fc58ad968f3 100644 --- a/ddl/table.go +++ b/ddl/table.go @@ -1292,6 +1292,45 @@ func onAlterTablePartitionAttributes(t *meta.Meta, job *model.Job) (ver int64, e return ver, nil } +func onAlterTablePartitionOptions(t *meta.Meta, job *model.Job) (ver int64, err error) { + var partitionID int64 + policyRefInfo := &model.PolicyRefInfo{} + placementSettings := &model.PlacementSettings{} + err = job.DecodeArgs(&partitionID, policyRefInfo, placementSettings) + if err != nil { + job.State = model.JobStateCancelled + return 0, errors.Trace(err) + } + tblInfo, err := getTableInfoAndCancelFaultJob(t, job, job.SchemaID) + if err != nil { + return 0, err + } + + ptInfo := tblInfo.GetPartitionInfo() + isFound := false + definitions := ptInfo.Definitions + for i := range definitions { + if partitionID == definitions[i].ID { + definitions[i].DirectPlacementOpts = placementSettings + definitions[i].PlacementPolicyRef = policyRefInfo + isFound = true + break + } + } + if !isFound { + job.State = model.JobStateCancelled + return 0, errors.Trace(table.ErrUnknownPartition.GenWithStackByArgs("drop?", tblInfo.Name.O)) + } + + ver, err = updateVersionAndTableInfo(t, job, tblInfo, true) + if err != nil { + return ver, errors.Trace(err) + } + job.FinishTableJob(model.JobStateDone, model.StatePublic, ver, tblInfo) + + return ver, nil +} + func getOldLabelRules(tblInfo *model.TableInfo, oldSchemaName, oldTableName string) (string, []string, []string, map[string]*label.Rule, error) { tableRuleID := fmt.Sprintf(label.TableIDFormat, label.IDPrefix, oldSchemaName, oldTableName) oldRuleIDs := []string{tableRuleID}