ddl: fix timezone dependent check when creating partition table (#16343)

This commit is contained in:
tiancaiamao
2020-04-14 21:39:07 +08:00
committed by GitHub
parent dc139aedb4
commit 4e035fa34c
3 changed files with 95 additions and 28 deletions

View File

@ -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) {

View File

@ -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, "", "")

View File

@ -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