From 5c1e065c182598bd5815c0f2a9cf46df4905dfd3 Mon Sep 17 00:00:00 2001 From: Ti Chi Robot Date: Mon, 27 Nov 2023 12:22:14 +0800 Subject: [PATCH] ddl: fix issue of alter last partition failed when partition column is datetime (#48815) (#48847) close pingcap/tidb#48814 --- ddl/db_partition_test.go | 148 +++++++++++++++++++++++++++++++++++++++ ddl/partition.go | 39 ++++++++--- 2 files changed, 179 insertions(+), 8 deletions(-) diff --git a/ddl/db_partition_test.go b/ddl/db_partition_test.go index 5ef673142e7c4..ecaf93f59ebc9 100644 --- a/ddl/db_partition_test.go +++ b/ddl/db_partition_test.go @@ -5365,4 +5365,152 @@ func TestRangeExchangeValidate(t *testing.T) { // TODO: add test not in first partition (both last without maxvalue and also not last with/without maxvalue) } +func TestAlterLastIntervalPartition(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec(`use test`) + tk.MustExec(`create table t (id int, create_time datetime) + partition by range columns (create_time) + interval (1 day) + first partition less than ('2023-01-01') + last partition less than ('2023-01-03');`) + ctx := tk.Session() + tbl, err := domain.GetDomain(ctx).InfoSchema().TableByName(model.NewCIStr("test"), model.NewCIStr("t")) + require.NoError(t, err) + pd := tbl.Meta().Partition.Definitions + require.Equal(t, 3, len(pd)) + require.Equal(t, "'2023-01-01 00:00:00'", pd[0].LessThan[0]) + require.Equal(t, "'2023-01-02 00:00:00'", pd[1].LessThan[0]) + require.Equal(t, "'2023-01-03 00:00:00'", pd[2].LessThan[0]) + tk.MustExec("alter table t last partition less than ('2024-01-04')") + tk.MustExec("alter table t last partition less than ('2025-01-01 00:00:00')") + tbl, err = domain.GetDomain(ctx).InfoSchema().TableByName(model.NewCIStr("test"), model.NewCIStr("t")) + require.NoError(t, err) + pd = tbl.Meta().Partition.Definitions + require.Equal(t, 732, len(pd)) + require.Equal(t, "'2023-01-01 00:00:00'", pd[0].LessThan[0]) + require.Equal(t, "'2023-01-02 00:00:00'", pd[1].LessThan[0]) + require.Equal(t, "'2023-01-03 00:00:00'", pd[2].LessThan[0]) + require.Equal(t, "'2024-12-31 00:00:00'", pd[730].LessThan[0]) + require.Equal(t, "'2025-01-01 00:00:00'", pd[731].LessThan[0]) + + // Test for interval 2 days. + tk.MustExec(`create table t2 (id int, create_time datetime) + partition by range columns (create_time) + interval (2 day) + first partition less than ('2023-01-01') + last partition less than ('2023-01-05');`) + tbl, err = domain.GetDomain(ctx).InfoSchema().TableByName(model.NewCIStr("test"), model.NewCIStr("t2")) + require.NoError(t, err) + pd = tbl.Meta().Partition.Definitions + require.Equal(t, 3, len(pd)) + require.Equal(t, "'2023-01-01 00:00:00'", pd[0].LessThan[0]) + require.Equal(t, "'2023-01-03 00:00:00'", pd[1].LessThan[0]) + require.Equal(t, "'2023-01-05 00:00:00'", pd[2].LessThan[0]) + tk.MustExec("alter table t2 last partition less than ('2023-01-09')") + tk.MustExec("alter table t2 last partition less than ('2023-01-11 00:00:00')") + tbl, err = domain.GetDomain(ctx).InfoSchema().TableByName(model.NewCIStr("test"), model.NewCIStr("t2")) + require.NoError(t, err) + pd = tbl.Meta().Partition.Definitions + require.Equal(t, 6, len(pd)) + require.Equal(t, "'2023-01-01 00:00:00'", pd[0].LessThan[0]) + require.Equal(t, "'2023-01-03 00:00:00'", pd[1].LessThan[0]) + require.Equal(t, "'2023-01-05 00:00:00'", pd[2].LessThan[0]) + require.Equal(t, "'2023-01-07 00:00:00'", pd[3].LessThan[0]) + require.Equal(t, "'2023-01-09 00:00:00'", pd[4].LessThan[0]) + require.Equal(t, "'2023-01-11 00:00:00'", pd[5].LessThan[0]) + + // Test for day with time. + tk.MustExec(`create table t3 (id int, create_time datetime) + partition by range columns (create_time) + interval (2 day) + first partition less than ('2023-01-01 12:01:02') + last partition less than ('2023-01-05 12:01:02');`) + tbl, err = domain.GetDomain(ctx).InfoSchema().TableByName(model.NewCIStr("test"), model.NewCIStr("t3")) + require.NoError(t, err) + pd = tbl.Meta().Partition.Definitions + require.Equal(t, 3, len(pd)) + require.Equal(t, "'2023-01-01 12:01:02'", pd[0].LessThan[0]) + require.Equal(t, "'2023-01-03 12:01:02'", pd[1].LessThan[0]) + require.Equal(t, "'2023-01-05 12:01:02'", pd[2].LessThan[0]) + tk.MustExec("alter table t3 last partition less than ('2023-01-09 12:01:02')") + tbl, err = domain.GetDomain(ctx).InfoSchema().TableByName(model.NewCIStr("test"), model.NewCIStr("t3")) + require.NoError(t, err) + pd = tbl.Meta().Partition.Definitions + require.Equal(t, 5, len(pd)) + require.Equal(t, "'2023-01-01 12:01:02'", pd[0].LessThan[0]) + require.Equal(t, "'2023-01-03 12:01:02'", pd[1].LessThan[0]) + require.Equal(t, "'2023-01-05 12:01:02'", pd[2].LessThan[0]) + require.Equal(t, "'2023-01-07 12:01:02'", pd[3].LessThan[0]) + require.Equal(t, "'2023-01-09 12:01:02'", pd[4].LessThan[0]) + + // Some other test. + tk.MustExec(`create table t4 (id int, create_time datetime) + partition by range columns (create_time) + interval (48 hour) + first partition less than ('2023-01-01') + last partition less than ('2023-01-05');`) + tbl, err = domain.GetDomain(ctx).InfoSchema().TableByName(model.NewCIStr("test"), model.NewCIStr("t4")) + require.NoError(t, err) + pd = tbl.Meta().Partition.Definitions + require.Equal(t, 3, len(pd)) + require.Equal(t, "'2023-01-01 00:00:00'", pd[0].LessThan[0]) + require.Equal(t, "'2023-01-03 00:00:00'", pd[1].LessThan[0]) + require.Equal(t, "'2023-01-05 00:00:00'", pd[2].LessThan[0]) + tk.MustExec("alter table t4 last partition less than ('2023-01-09 00:00:00')") + tbl, err = domain.GetDomain(ctx).InfoSchema().TableByName(model.NewCIStr("test"), model.NewCIStr("t4")) + require.NoError(t, err) + pd = tbl.Meta().Partition.Definitions + require.Equal(t, 5, len(pd)) + require.Equal(t, "'2023-01-01 00:00:00'", pd[0].LessThan[0]) + require.Equal(t, "'2023-01-03 00:00:00'", pd[1].LessThan[0]) + require.Equal(t, "'2023-01-05 00:00:00'", pd[2].LessThan[0]) + require.Equal(t, "'2023-01-07 00:00:00'", pd[3].LessThan[0]) + require.Equal(t, "'2023-01-09 00:00:00'", pd[4].LessThan[0]) + tk.MustQuery("show create table t4").Check(testkit.Rows("t4 CREATE TABLE `t4` (\n" + + " `id` int(11) DEFAULT NULL,\n" + + " `create_time` datetime DEFAULT NULL\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin\n" + + "PARTITION BY RANGE COLUMNS(`create_time`)\n" + + "(PARTITION `P_LT_2023-01-01 00:00:00` VALUES LESS THAN ('2023-01-01 00:00:00'),\n" + + " PARTITION `P_LT_2023-01-03 00:00:00` VALUES LESS THAN ('2023-01-03 00:00:00'),\n" + + " PARTITION `P_LT_2023-01-05 00:00:00` VALUES LESS THAN ('2023-01-05 00:00:00'),\n" + + " PARTITION `P_LT_2023-01-07 00:00:00` VALUES LESS THAN ('2023-01-07 00:00:00'),\n" + + " PARTITION `P_LT_2023-01-09 00:00:00` VALUES LESS THAN ('2023-01-09 00:00:00'))")) + + tk.MustExec(`create table t5 (id int, create_time datetime) + partition by range columns (create_time) + interval (1 month) + first partition less than ('2023-01-01') + last partition less than ('2023-05-01');`) + tk.MustQuery("show create table t5").Check(testkit.Rows("t5 CREATE TABLE `t5` (\n" + + " `id` int(11) DEFAULT NULL,\n" + + " `create_time` datetime DEFAULT NULL\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin\n" + + "PARTITION BY RANGE COLUMNS(`create_time`)\n" + + "(PARTITION `P_LT_2023-01-01 00:00:00` VALUES LESS THAN ('2023-01-01 00:00:00'),\n" + + " PARTITION `P_LT_2023-02-01 00:00:00` VALUES LESS THAN ('2023-02-01 00:00:00'),\n" + + " PARTITION `P_LT_2023-03-01 00:00:00` VALUES LESS THAN ('2023-03-01 00:00:00'),\n" + + " PARTITION `P_LT_2023-04-01 00:00:00` VALUES LESS THAN ('2023-04-01 00:00:00'),\n" + + " PARTITION `P_LT_2023-05-01 00:00:00` VALUES LESS THAN ('2023-05-01 00:00:00'))")) + + tk.MustExec("CREATE TABLE `t6` (\n" + + " `id` int(11) DEFAULT NULL,\n" + + " `create_time` datetime DEFAULT NULL\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin\n" + + "PARTITION BY RANGE COLUMNS(`create_time`)\n" + + "(PARTITION `P_LT_2023-01-01` VALUES LESS THAN ('2023-01-01'),\n" + + " PARTITION `P_LT_2023-01-02` VALUES LESS THAN ('2023-01-02'))") + tk.MustExec("alter table t6 last partition less than ('2023-01-04')") + tk.MustQuery("show create table t6").Check(testkit.Rows("t6 CREATE TABLE `t6` (\n" + + " `id` int(11) DEFAULT NULL,\n" + + " `create_time` datetime DEFAULT NULL\n" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin\n" + + "PARTITION BY RANGE COLUMNS(`create_time`)\n" + + "(PARTITION `P_LT_2023-01-01` VALUES LESS THAN ('2023-01-01'),\n" + + " PARTITION `P_LT_2023-01-02` VALUES LESS THAN ('2023-01-02'),\n" + + " PARTITION `P_LT_2023-01-03 00:00:00` VALUES LESS THAN ('2023-01-03 00:00:00'),\n" + + " PARTITION `P_LT_2023-01-04 00:00:00` VALUES LESS THAN ('2023-01-04 00:00:00'))")) +} + // TODO: check EXCHANGE how it handles null (for all types of partitioning!!!) diff --git a/ddl/partition.go b/ddl/partition.go index 7450366ac81c0..5e4522ed843ce 100644 --- a/ddl/partition.go +++ b/ddl/partition.go @@ -711,12 +711,20 @@ func getPartitionIntervalFromTable(ctx sessionctx.Context, tbInfo *model.TableIn } // comparePartitionAstAndModel compares a generated *ast.PartitionOptions and a *model.PartitionInfo -func comparePartitionAstAndModel(ctx sessionctx.Context, pAst *ast.PartitionOptions, pModel *model.PartitionInfo) error { +func comparePartitionAstAndModel(ctx sessionctx.Context, pAst *ast.PartitionOptions, pModel *model.PartitionInfo, partCol *model.ColumnInfo) error { a := pAst.Definitions m := pModel.Definitions if len(pAst.Definitions) != len(pModel.Definitions) { return dbterror.ErrGeneralUnsupportedDDL.GenWithStackByArgs("INTERVAL partitioning: number of partitions generated != partition defined (%d != %d)", len(a), len(m)) } + + evalFn := func(expr ast.ExprNode) (types.Datum, error) { + val, err := expression.EvalAstExpr(ctx, ast.NewValueExpr(expr, "", "")) + if err != nil || partCol == nil { + return val, err + } + return val.ConvertTo(ctx.GetSessionVars().StmtCtx, &partCol.FieldType) + } for i := range pAst.Definitions { // Allow options to differ! (like Placement Rules) // Allow names to differ! @@ -739,16 +747,19 @@ func comparePartitionAstAndModel(ctx sessionctx.Context, pAst *ast.PartitionOpti if len(lessThan) > 1 && lessThan[:1] == "'" && lessThan[len(lessThan)-1:] == "'" { lessThan = driver.UnwrapFromSingleQuotes(lessThan) } - cmpExpr := &ast.BinaryOperationExpr{ - Op: opcode.EQ, - L: ast.NewValueExpr(lessThan, "", ""), - R: generatedExpr, + lessThanVal, err := evalFn(ast.NewValueExpr(lessThan, "", "")) + if err != nil { + return err } - cmp, err := expression.EvalAstExpr(ctx, cmpExpr) + generatedExprVal, err := evalFn(generatedExpr) if err != nil { return err } - if cmp.GetInt64() != 1 { + cmp, err := lessThanVal.Compare(ctx.GetSessionVars().StmtCtx, &generatedExprVal, collate.GetBinaryCollator()) + if err != nil { + return err + } + if cmp != 0 { return dbterror.ErrGeneralUnsupportedDDL.GenWithStackByArgs(fmt.Sprintf("INTERVAL partitioning: LESS THAN for partition %s differs between generated and defined", m[i].Name.O)) } } @@ -923,7 +934,7 @@ func generatePartitionDefinitionsFromInterval(ctx sessionctx.Context, partOption // Seems valid, so keep the defined so that the user defined names are kept etc. partOptions.Definitions = definedPartDefs } else if len(tbInfo.Partition.Definitions) > 0 { - err := comparePartitionAstAndModel(ctx, partOptions, tbInfo.Partition) + err := comparePartitionAstAndModel(ctx, partOptions, tbInfo.Partition, partCol) if err != nil { return err } @@ -997,6 +1008,12 @@ func GeneratePartDefsFromInterval(ctx sessionctx.Context, tp ast.AlterTableType, if err != nil { return err } + if partCol != nil { + lastVal, err = lastVal.ConvertTo(ctx.GetSessionVars().StmtCtx, &partCol.FieldType) + if err != nil { + return err + } + } var partDefs []*ast.PartitionDefinition if len(partitionOptions.Definitions) != 0 { partDefs = partitionOptions.Definitions @@ -1040,6 +1057,12 @@ func GeneratePartDefsFromInterval(ctx sessionctx.Context, tp ast.AlterTableType, if err != nil { return err } + if partCol != nil { + currVal, err = currVal.ConvertTo(ctx.GetSessionVars().StmtCtx, &partCol.FieldType) + if err != nil { + return err + } + } cmp, err := currVal.Compare(ctx.GetSessionVars().StmtCtx, &lastVal, collate.GetBinaryCollator()) if err != nil { return err