Skip to content

Commit

Permalink
ddl, expression: disable nondeterministic function calls for generate…
Browse files Browse the repository at this point in the history
…d columns (#9239)
  • Loading branch information
spongedu authored and zz-jason committed Mar 23, 2019
1 parent 5137c4d commit 9251724
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 6 deletions.
3 changes: 3 additions & 0 deletions ddl/ddl.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ var (
ErrUnsupportedAddPartition = terror.ClassDDL.New(codeUnsupportedAddPartition, "unsupported add partitions")
// ErrUnsupportedCoalescePartition returns for does not support coalesce partitions.
ErrUnsupportedCoalescePartition = terror.ClassDDL.New(codeUnsupportedCoalescePartition, "unsupported coalesce partitions")
// ErrGeneratedColumnFunctionIsNotAllowed returns for unsupported functions for generated columns.
ErrGeneratedColumnFunctionIsNotAllowed = terror.ClassDDL.New(codeErrGeneratedColumnFunctionIsNotAllowed, "Expression of generated column '%s' contains a disallowed function.")
// ErrUnsupportedPartitionByRangeColumns returns for does unsupported partition by range columns.
ErrUnsupportedPartitionByRangeColumns = terror.ClassDDL.New(codeUnsupportedPartitionByRangeColumns,
"unsupported partition by range columns")
Expand Down Expand Up @@ -707,6 +709,7 @@ const (
codeWarnDataTruncated = terror.ErrCode(mysql.WarnDataTruncated)
codeCoalesceOnlyOnHashPartition = terror.ErrCode(mysql.ErrCoalesceOnlyOnHashPartition)
codeUnknownPartition = terror.ErrCode(mysql.ErrUnknownPartition)
codeErrGeneratedColumnFunctionIsNotAllowed = terror.ErrCode(mysql.ErrGeneratedColumnFunctionIsNotAllowed)
codeErrGeneratedColumnRefAutoInc = terror.ErrCode(mysql.ErrGeneratedColumnRefAutoInc)
codeNotSupportedAlterOperation = terror.ErrCode(mysql.ErrAlterOperationNotSupportedReason)
codeFieldNotFoundPart = terror.ErrCode(mysql.ErrFieldNotFoundPart)
Expand Down
19 changes: 15 additions & 4 deletions ddl/ddl_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,13 @@ func checkGeneratedColumn(colDefs []*ast.ColumnDef) error {
var exists bool
var autoIncrementColumn string
for i, colDef := range colDefs {
for _, option := range colDef.Options {
if option.Tp == ast.ColumnOptionGenerated {
if err := checkIllegalFn4GeneratedColumn(colDef.Name.Name.L, option.Expr); err != nil {
return errors.Trace(err)
}
}
}
if checkIsAutoIncrementColumn(colDef) {
exists, autoIncrementColumn = true, colDef.Name.Name.L
}
Expand Down Expand Up @@ -1840,6 +1847,9 @@ func (d *ddl) AddColumn(ctx sessionctx.Context, ti ast.Ident, spec *ast.AlterTab
// generated columns occurring later in table.
for _, option := range specNewColumn.Options {
if option.Tp == ast.ColumnOptionGenerated {
if err := checkIllegalFn4GeneratedColumn(specNewColumn.Name.Name.L, option.Expr); err != nil {
return errors.Trace(err)
}
referableColNames := make(map[string]struct{}, len(t.Cols()))
for _, col := range t.Cols() {
referableColNames[col.Name.L] = struct{}{}
Expand Down Expand Up @@ -2188,8 +2198,8 @@ func setColumnComment(ctx sessionctx.Context, col *table.Column, option *ast.Col
return errors.Trace(err)
}

// setDefaultAndComment is only used in getModifiableColumnJob.
func setDefaultAndComment(ctx sessionctx.Context, col *table.Column, options []*ast.ColumnOption) error {
// processColumnOptions is only used in getModifiableColumnJob.
func processColumnOptions(ctx sessionctx.Context, col *table.Column, options []*ast.ColumnOption) error {
if len(options) == 0 {
return nil
}
Expand Down Expand Up @@ -2232,6 +2242,7 @@ func setDefaultAndComment(ctx sessionctx.Context, col *table.Column, options []*
col.GeneratedExprString = buf.String()
col.GeneratedStored = opt.Stored
col.Dependences = make(map[string]struct{})
col.GeneratedExpr = opt.Expr
for _, colName := range findColumnNamesInExpr(opt.Expr) {
col.Dependences[colName.Name.L] = struct{}{}
}
Expand Down Expand Up @@ -2281,7 +2292,7 @@ func (d *ddl) getModifiableColumnJob(ctx sessionctx.Context, ident ast.Ident, or
}

// Constraints in the new column means adding new constraints. Errors should thrown,
// which will be done by `setDefaultAndComment` later.
// which will be done by `processColumnOptions` later.
if specNewColumn.Tp == nil {
// Make sure the column definition is simple field type.
return nil, errors.Trace(errUnsupportedModifyColumn)
Expand Down Expand Up @@ -2322,7 +2333,7 @@ func (d *ddl) getModifiableColumnJob(ctx sessionctx.Context, ident ast.Ident, or
if err != nil {
return nil, errors.Trace(err)
}
if err = setDefaultAndComment(ctx, newCol, specNewColumn.Options); err != nil {
if err = processColumnOptions(ctx, newCol, specNewColumn.Options); err != nil {
return nil, errors.Trace(err)
}

Expand Down
40 changes: 40 additions & 0 deletions ddl/generated_column.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/pingcap/errors"
"github.com/pingcap/parser/ast"
"github.com/pingcap/parser/model"
"github.com/pingcap/tidb/expression"
"github.com/pingcap/tidb/table"
)

Expand Down Expand Up @@ -104,6 +105,7 @@ func (c *generatedColumnChecker) Leave(inNode ast.Node) (node ast.Node, ok bool)
// old and new is valid or not by such rules:
// 1. the modification can't change stored status;
// 2. if the new is generated, check its refer rules.
// 3. check if the modified expr contains non-deterministic functions
func checkModifyGeneratedColumn(originCols []*table.Column, oldCol, newCol *table.Column) error {
// rule 1.
var stored = [2]bool{false, false}
Expand Down Expand Up @@ -152,6 +154,44 @@ func checkModifyGeneratedColumn(originCols []*table.Column, oldCol, newCol *tabl
return errors.Trace(err)
}
}

// rule 3
if newCol.IsGenerated() {
if err := checkIllegalFn4GeneratedColumn(newCol.Name.L, newCol.GeneratedExpr); err != nil {
return errors.Trace(err)
}
}
return nil
}

type illegalFunctionChecker struct {
found bool
}

func (c *illegalFunctionChecker) Enter(inNode ast.Node) (outNode ast.Node, skipChildren bool) {
switch node := inNode.(type) {
case *ast.FuncCallExpr:
if _, found := expression.IllegalFunctions4GeneratedColumns[node.FnName.L]; found {
c.found = true
return inNode, true
}
}
return inNode, false
}

func (c *illegalFunctionChecker) Leave(inNode ast.Node) (node ast.Node, ok bool) {
return inNode, true
}

func checkIllegalFn4GeneratedColumn(colName string, expr ast.ExprNode) error {
if expr == nil {
return nil
}
var c illegalFunctionChecker
expr.Accept(&c)
if c.found {
return ErrGeneratedColumnFunctionIsNotAllowed.GenWithStackByArgs(colName)
}
return nil
}

Expand Down
45 changes: 45 additions & 0 deletions executor/ddl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,51 @@ func (s *testSuite3) TestSetDDLReorgBatchSize(c *C) {
res.Check(testkit.Rows("1000"))
}

func (s *testSuite3) TestIllegalFunctionCall4GeneratedColumns(c *C) {
tk := testkit.NewTestKit(c, s.store)
tk.MustExec("use test")
// Test create an exist database
_, err := tk.Exec("CREATE database test")
c.Assert(err, NotNil)

_, err = tk.Exec("create table t1 (b double generated always as (rand()) virtual);")
c.Assert(err.Error(), Equals, ddl.ErrGeneratedColumnFunctionIsNotAllowed.GenWithStackByArgs("b").Error())

_, err = tk.Exec("create table t1 (a varchar(64), b varchar(1024) generated always as (load_file(a)) virtual);")
c.Assert(err.Error(), Equals, ddl.ErrGeneratedColumnFunctionIsNotAllowed.GenWithStackByArgs("b").Error())

_, err = tk.Exec("create table t1 (a datetime generated always as (curdate()) virtual);")
c.Assert(err.Error(), Equals, ddl.ErrGeneratedColumnFunctionIsNotAllowed.GenWithStackByArgs("a").Error())

_, err = tk.Exec("create table t1 (a datetime generated always as (current_time()) virtual);")
c.Assert(err.Error(), Equals, ddl.ErrGeneratedColumnFunctionIsNotAllowed.GenWithStackByArgs("a").Error())

_, err = tk.Exec("create table t1 (a datetime generated always as (current_timestamp()) virtual);")
c.Assert(err.Error(), Equals, ddl.ErrGeneratedColumnFunctionIsNotAllowed.GenWithStackByArgs("a").Error())

_, err = tk.Exec("create table t1 (a datetime, b varchar(10) generated always as (localtime()) virtual);")
c.Assert(err.Error(), Equals, ddl.ErrGeneratedColumnFunctionIsNotAllowed.GenWithStackByArgs("b").Error())

_, err = tk.Exec("create table t1 (a varchar(1024) generated always as (uuid()) virtual);")
c.Assert(err.Error(), Equals, ddl.ErrGeneratedColumnFunctionIsNotAllowed.GenWithStackByArgs("a").Error())

_, err = tk.Exec("create table t1 (a varchar(1024), b varchar(1024) generated always as (is_free_lock(a)) virtual);")
c.Assert(err.Error(), Equals, ddl.ErrGeneratedColumnFunctionIsNotAllowed.GenWithStackByArgs("b").Error())

tk.MustExec("create table t1 (a bigint not null primary key auto_increment, b bigint, c bigint as (b + 1));")

_, err = tk.Exec("alter table t1 add column d varchar(1024) generated always as (database());")
c.Assert(err.Error(), Equals, ddl.ErrGeneratedColumnFunctionIsNotAllowed.GenWithStackByArgs("d").Error())

tk.MustExec("alter table t1 add column d bigint generated always as (b + 1); ")

_, err = tk.Exec("alter table t1 modify column d bigint generated always as (connection_id());")
c.Assert(err.Error(), Equals, ddl.ErrGeneratedColumnFunctionIsNotAllowed.GenWithStackByArgs("d").Error())

_, err = tk.Exec("alter table t1 change column c cc bigint generated always as (connection_id());")
c.Assert(err.Error(), Equals, ddl.ErrGeneratedColumnFunctionIsNotAllowed.GenWithStackByArgs("cc").Error())
}

func (s *testSuite3) TestGeneratedColumnRelatedDDL(c *C) {
tk := testkit.NewTestKit(c, s.store)
tk.MustExec("use test")
Expand Down
4 changes: 2 additions & 2 deletions expression/constant_propagation.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ func tryToReplaceCond(ctx sessionctx.Context, src *Column, tgt *Column, cond Exp
}
args[idx] = tgt
} else {
subReplaced, isNonDeterminisitic, subExpr := tryToReplaceCond(ctx, src, tgt, expr)
if isNonDeterminisitic {
subReplaced, isNonDeterministic, subExpr := tryToReplaceCond(ctx, src, tgt, expr)
if isNonDeterministic {
return false, true, cond
} else if subReplaced {
replaced = true
Expand Down
42 changes: 42 additions & 0 deletions expression/function_traits.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,48 @@ var DisableFoldFunctions = map[string]struct{}{
ast.Benchmark: {},
}

// IllegalFunctions4GeneratedColumns stores functions that is illegal for generated columns.
// See https://github.com/mysql/mysql-server/blob/5.7/mysql-test/suite/gcol/inc/gcol_blocked_sql_funcs_main.inc for details
var IllegalFunctions4GeneratedColumns = map[string]struct{}{
ast.ConnectionID: {},
ast.LoadFile: {},
ast.LastInsertId: {},
ast.Rand: {},
ast.UUID: {},
ast.UUIDShort: {},
ast.Curdate: {},
ast.CurrentDate: {},
ast.Curtime: {},
ast.CurrentTime: {},
ast.CurrentTimestamp: {},
ast.LocalTime: {},
ast.LocalTimestamp: {},
ast.Now: {},
ast.UnixTimestamp: {},
ast.UTCDate: {},
ast.UTCTime: {},
ast.UTCTimestamp: {},
ast.Benchmark: {},
ast.CurrentUser: {},
ast.Database: {},
ast.FoundRows: {},
ast.GetLock: {},
ast.IsFreeLock: {},
ast.IsUsedLock: {},
ast.MasterPosWait: {},
ast.NameConst: {},
ast.ReleaseLock: {},
ast.RowCount: {},
ast.Schema: {},
ast.SessionUser: {},
ast.Sleep: {},
ast.SystemUser: {},
ast.User: {},
ast.Values: {},
ast.Encrypt: {},
ast.Version: {},
}

// DeferredFunctions stores non-deterministic functions, which can be deferred only when the plan cache is enabled.
var DeferredFunctions = map[string]struct{}{
ast.Now: {},
Expand Down

0 comments on commit 9251724

Please sign in to comment.