ddl: fix timezone dependent check when creating partition table (#16343)
This commit is contained in:
@ -260,6 +260,19 @@ func (s *testIntegrationSuite3) TestCreateTableWithPartition(c *C) {
|
||||
PARTITION p2 VALUES LESS THAN (2000),
|
||||
PARTITION p3 VALUES LESS THAN (2005)
|
||||
);`, tmysql.ErrBadField)
|
||||
|
||||
// Fix a timezone dependent check bug introduced in https://github.com/pingcap/tidb/pull/10655
|
||||
tk.MustExec(`create table t34 (dt timestamp(3)) partition by range (floor(unix_timestamp(dt))) (
|
||||
partition p0 values less than (unix_timestamp('2020-04-04 00:00:00')),
|
||||
partition p1 values less than (unix_timestamp('2020-04-05 00:00:00')));`)
|
||||
|
||||
tk.MustGetErrCode(`create table t34 (dt timestamp(3)) partition by range (unix_timestamp(date(dt))) (
|
||||
partition p0 values less than (unix_timestamp('2020-04-04 00:00:00')),
|
||||
partition p1 values less than (unix_timestamp('2020-04-05 00:00:00')));`, tmysql.ErrWrongExprInPartitionFunc)
|
||||
|
||||
tk.MustGetErrCode(`create table t34 (dt datetime) partition by range (unix_timestamp(dt)) (
|
||||
partition p0 values less than (unix_timestamp('2020-04-04 00:00:00')),
|
||||
partition p1 values less than (unix_timestamp('2020-04-05 00:00:00')));`, tmysql.ErrWrongExprInPartitionFunc)
|
||||
}
|
||||
|
||||
func (s *testIntegrationSuite2) TestCreateTableWithHashPartition(c *C) {
|
||||
|
||||
102
ddl/partition.go
102
ddl/partition.go
@ -294,32 +294,72 @@ func defaultTimezoneDependent(ctx sessionctx.Context, tblInfo *model.TableInfo,
|
||||
return !v, nil
|
||||
}
|
||||
|
||||
func checkPartitionFuncCallValid(ctx sessionctx.Context, tblInfo *model.TableInfo, expr *ast.FuncCallExpr) error {
|
||||
// We assume the result of any function that has a TIMESTAMP argument to be
|
||||
// timezone-dependent, since a TIMESTAMP value in both numeric and string
|
||||
// contexts is interpreted according to the current timezone.
|
||||
// The only exception is UNIX_TIMESTAMP() which returns the internal
|
||||
// representation of a TIMESTAMP argument verbatim, and thus does not depend on
|
||||
// the timezone.
|
||||
// See https://github.com/mysql/mysql-server/blob/5.7/sql/item_func.h#L445
|
||||
if expr.FnName.L != ast.UnixTimestamp {
|
||||
for _, arg := range expr.Args {
|
||||
if colName, ok := arg.(*ast.ColumnNameExpr); ok {
|
||||
col := findColumnByName(colName.Name.Name.L, tblInfo)
|
||||
if col == nil {
|
||||
return ErrBadField.GenWithStackByArgs(colName.Name.Name.O, "expression")
|
||||
}
|
||||
|
||||
if ok && col.FieldType.Tp == mysql.TypeTimestamp {
|
||||
return errors.Trace(errWrongExprInPartitionFunc)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check function which allowed in partitioning expressions
|
||||
// see https://dev.mysql.com/doc/mysql-partitioning-excerpt/5.7/en/partitioning-limitations-functions.html
|
||||
switch expr.FnName.L {
|
||||
// Mysql don't allow creating partitions with expressions with non matching
|
||||
// arguments as a (sub)partitioning function,
|
||||
// but we want to allow such expressions when opening existing tables for
|
||||
// easier maintenance. This exception should be deprecated at some point in future so that we always throw an error.
|
||||
// See https://github.com/mysql/mysql-server/blob/5.7/sql/sql_partition.cc#L1072
|
||||
case ast.Day, ast.DayOfMonth, ast.DayOfWeek, ast.DayOfYear, ast.Month, ast.Quarter, ast.ToDays, ast.ToSeconds,
|
||||
ast.Weekday, ast.Year, ast.YearWeek:
|
||||
return checkResultOK(hasDateField(ctx, tblInfo, expr))
|
||||
case ast.Hour, ast.MicroSecond, ast.Minute, ast.Second, ast.TimeToSec:
|
||||
return checkResultOK(hasTimeField(ctx, tblInfo, expr))
|
||||
case ast.UnixTimestamp:
|
||||
if len(expr.Args) != 1 {
|
||||
return errors.Trace(errWrongExprInPartitionFunc)
|
||||
}
|
||||
col, err := expression.RewriteSimpleExprWithTableInfo(ctx, tblInfo, expr.Args[0])
|
||||
if err != nil {
|
||||
return errors.Trace(err)
|
||||
}
|
||||
if col.GetType().Tp != mysql.TypeTimestamp {
|
||||
return errors.Trace(errWrongExprInPartitionFunc)
|
||||
}
|
||||
return nil
|
||||
case ast.Abs, ast.Ceiling, ast.DateDiff, ast.Extract, ast.Floor, ast.Mod:
|
||||
for _, arg := range expr.Args {
|
||||
if err := checkPartitionExprValid(ctx, tblInfo, arg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return errors.Trace(ErrPartitionFunctionIsNotAllowed)
|
||||
}
|
||||
|
||||
// checkPartitionExprValid checks partition expression validly.
|
||||
func checkPartitionExprValid(ctx sessionctx.Context, tblInfo *model.TableInfo, expr ast.ExprNode) error {
|
||||
switch v := expr.(type) {
|
||||
case *ast.FuncCastExpr, *ast.CaseExpr, *ast.SubqueryExpr, *ast.WindowFuncExpr, *ast.RowExpr, *ast.DefaultExpr, *ast.ValuesExpr:
|
||||
return errors.Trace(ErrPartitionFunctionIsNotAllowed)
|
||||
case *ast.FuncCallExpr:
|
||||
// check function which allowed in partitioning expressions
|
||||
// see https://dev.mysql.com/doc/mysql-partitioning-excerpt/5.7/en/partitioning-limitations-functions.html
|
||||
switch v.FnName.L {
|
||||
// Mysql don't allow creating partitions with expressions with non matching
|
||||
// arguments as a (sub)partitioning function,
|
||||
// but we want to allow such expressions when opening existing tables for
|
||||
// easier maintenance. This exception should be deprecated at some point in future so that we always throw an error.
|
||||
// See https://github.com/mysql/mysql-server/blob/5.7/sql/sql_partition.cc#L1072
|
||||
case ast.Day, ast.DayOfMonth, ast.DayOfWeek, ast.DayOfYear, ast.Month, ast.Quarter, ast.ToDays, ast.ToSeconds,
|
||||
ast.Weekday, ast.Year, ast.YearWeek:
|
||||
return checkPartitionFunc(hasDateField(ctx, tblInfo, expr))
|
||||
case ast.Hour, ast.MicroSecond, ast.Minute, ast.Second, ast.TimeToSec:
|
||||
return checkPartitionFunc(hasTimeField(ctx, tblInfo, expr))
|
||||
case ast.UnixTimestamp:
|
||||
return checkPartitionFunc(hasTimestampField(ctx, tblInfo, expr))
|
||||
case ast.Abs, ast.Ceiling, ast.DateDiff, ast.Extract, ast.Floor, ast.Mod:
|
||||
return checkPartitionFunc(defaultTimezoneDependent(ctx, tblInfo, expr))
|
||||
default:
|
||||
return errors.Trace(ErrPartitionFunctionIsNotAllowed)
|
||||
}
|
||||
return checkPartitionFuncCallValid(ctx, tblInfo, v)
|
||||
case *ast.BinaryOperationExpr:
|
||||
// The DIV operator (opcode.IntDiv) is also supported; the / operator ( opcode.Div ) is not permitted.
|
||||
// see https://dev.mysql.com/doc/refman/5.7/en/partitioning-limitations.html
|
||||
@ -361,12 +401,12 @@ func checkPartitionFuncValid(ctx sessionctx.Context, tblInfo *model.TableInfo, e
|
||||
// For partition tables, mysql do not support Constant, random or timezone-dependent expressions
|
||||
// Based on mysql code to check whether field is valid, every time related type has check_valid_arguments_processor function.
|
||||
// See https://github.com/mysql/mysql-server/blob/5.7/sql/item_timefunc.
|
||||
func checkPartitionFunc(isTimezoneDependent bool, err error) error {
|
||||
func checkResultOK(ok bool, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !isTimezoneDependent {
|
||||
if !ok {
|
||||
return errors.Trace(errWrongExprInPartitionFunc)
|
||||
}
|
||||
|
||||
@ -770,11 +810,10 @@ func (cne *columnNameExtractor) Enter(node ast.Node) (ast.Node, bool) {
|
||||
|
||||
func (cne *columnNameExtractor) Leave(node ast.Node) (ast.Node, bool) {
|
||||
if c, ok := node.(*ast.ColumnNameExpr); ok {
|
||||
for _, info := range cne.tblInfo.Columns {
|
||||
if info.Name.L == c.Name.Name.L {
|
||||
cne.extractedColumns = append(cne.extractedColumns, info)
|
||||
return node, true
|
||||
}
|
||||
info := findColumnByName(c.Name.Name.L, cne.tblInfo)
|
||||
if info != nil {
|
||||
cne.extractedColumns = append(cne.extractedColumns, info)
|
||||
return node, true
|
||||
}
|
||||
cne.err = ErrBadField.GenWithStackByArgs(c.Name.Name.O, "expression")
|
||||
return nil, false
|
||||
@ -782,6 +821,15 @@ func (cne *columnNameExtractor) Leave(node ast.Node) (ast.Node, bool) {
|
||||
return node, true
|
||||
}
|
||||
|
||||
func findColumnByName(colName string, tblInfo *model.TableInfo) *model.ColumnInfo {
|
||||
for _, info := range tblInfo.Columns {
|
||||
if info.Name.L == colName {
|
||||
return info
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractPartitionColumns(partExpr string, tblInfo *model.TableInfo) ([]*model.ColumnInfo, error) {
|
||||
partExpr = "select " + partExpr
|
||||
stmts, _, err := parser.New().Parse(partExpr, "", "")
|
||||
|
||||
@ -371,7 +371,13 @@ func makePartitionByFnCol(sctx sessionctx.Context, columns []*expression.Column,
|
||||
case *expression.ScalarFunction:
|
||||
if _, ok := monotoneIncFuncs[raw.FuncName.L]; ok {
|
||||
fn = raw
|
||||
col = fn.GetArgs()[0].(*expression.Column)
|
||||
args := fn.GetArgs()
|
||||
if len(args) > 0 {
|
||||
arg0 := args[0]
|
||||
if c, ok1 := arg0.(*expression.Column); ok1 {
|
||||
col = c
|
||||
}
|
||||
}
|
||||
}
|
||||
case *expression.Column:
|
||||
col = raw
|
||||
|
||||
Reference in New Issue
Block a user