diff --git a/evaluator/builtin.go b/evaluator/builtin.go index e5831971a8..dc3a5e4b22 100644 --- a/evaluator/builtin.go +++ b/evaluator/builtin.go @@ -121,6 +121,8 @@ var Funcs = map[string]Func{ "nullif": {builtinNullIf, 2, 2}, // miscellaneous functions + "sleep": {builtinSleep, 1, 1}, + // get_lock() and release_lock() is parsed but do nothing. // It is used for preventing error in Ruby's activerecord migrations. "get_lock": {builtinLock, 2, 2}, diff --git a/evaluator/builtin_other.go b/evaluator/builtin_other.go index 3944c9dbaf..b96463ceae 100644 --- a/evaluator/builtin_other.go +++ b/evaluator/builtin_other.go @@ -16,6 +16,7 @@ package evaluator import ( "regexp" "strings" + "time" "github.com/juju/errors" "github.com/pingcap/tidb/context" @@ -25,6 +26,42 @@ import ( "github.com/pingcap/tidb/util/types" ) +// See http://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_sleep +func builtinSleep(args []types.Datum, ctx context.Context) (d types.Datum, err error) { + if ctx == nil { + return d, errors.Errorf("Missing context when evalue builtin") + } + + sessVars := variable.GetSessionVars(ctx) + if args[0].IsNull() { + if sessVars.StrictSQLMode { + return d, errors.New("Incorrect arguments to sleep.") + } + d.SetInt64(0) + return + } + // processing argument is negative + zero := types.NewIntDatum(0) + ret, err := args[0].CompareDatum(zero) + if err != nil { + return d, errors.Trace(err) + } + if ret == -1 { + if sessVars.StrictSQLMode { + return d, errors.New("Incorrect arguments to sleep.") + } + d.SetInt64(0) + return + } + + // TODO: consider it's interrupted using KILL QUERY from other session, or + // interrupted by time out. + duration := time.Duration(args[0].GetFloat64() * float64(time.Second.Nanoseconds())) + time.Sleep(duration) + d.SetInt64(0) + return +} + func builtinAndAnd(args []types.Datum, _ context.Context) (d types.Datum, err error) { leftDatum := args[0] rightDatum := args[1] diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index fdd2860998..3b03ad7ecf 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -78,6 +78,44 @@ func (s *testEvaluatorSuite) TestBetween(c *C) { s.runTests(c, cases) } +func (s *testEvaluatorSuite) TestSleep(c *C) { + defer testleak.AfterTest(c)() + ctx := mock.NewContext() + variable.BindSessionVars(ctx) + sessVars := variable.GetSessionVars(ctx) + + // non-strict model + sessVars.StrictSQLMode = false + d := make([]types.Datum, 1) + _, err := builtinSleep(d, nil) + c.Assert(err, NotNil) + ret, err := builtinSleep(d, ctx) + c.Assert(err, IsNil) + c.Assert(ret, DeepEquals, types.NewIntDatum(0)) + d[0].SetInt64(-1) + ret, err = builtinSleep(d, ctx) + c.Assert(err, IsNil) + c.Assert(ret, DeepEquals, types.NewIntDatum(0)) + + // for error case under the strict model + sessVars.StrictSQLMode = true + d[0].SetNull() + _, err = builtinSleep(d, ctx) + c.Assert(err, NotNil) + d[0].SetFloat64(-2.5) + _, err = builtinSleep(d, ctx) + c.Assert(err, NotNil) + + // strict model + d[0].SetFloat64(0.5) + start := time.Now() + ret, err = builtinSleep(d, ctx) + c.Assert(err, IsNil) + c.Assert(ret, DeepEquals, types.NewIntDatum(0)) + sub := time.Now().Sub(start) + c.Assert(sub.Nanoseconds(), GreaterEqual, int64(0.5*1e9)) +} + func (s *testEvaluatorSuite) TestBinopComparison(c *C) { defer testleak.AfterTest(c)() ctx := mock.NewContext() diff --git a/parser/parser.y b/parser/parser.y index 6999c8dc3a..708f993977 100644 --- a/parser/parser.y +++ b/parser/parser.y @@ -270,6 +270,7 @@ import ( set "SET" share "SHARE" show "SHOW" + sleep "SLEEP" signed "SIGNED" some "SOME" space "SPACE" @@ -1964,11 +1965,12 @@ UnReservedKeyword: NotKeywordToken: "ABS" | "ADDDATE" | "ADMIN" | "COALESCE" | "CONCAT" | "CONCAT_WS" | "CONNECTION_ID" | "CUR_TIME"| "COUNT" | "DAY" -| "DATE_ADD" | "DATE_FORMAT" | "DATE_SUB" | "DAYNAME" | "DAYOFMONTH" | "DAYOFWEEK" | "DAYOFYEAR" | "FOUND_ROWS" | "GROUP_CONCAT"| "HOUR" -| "IFNULL" | "ISNULL" | "LAST_INSERT_ID" | "LCASE" | "LENGTH" | "LOCATE" | "LOWER" | "LTRIM" | "MAX" | "MICROSECOND" | "MIN" -| "MINUTE" | "NULLIF" | "MONTH" | "MONTHNAME" | "NOW" | "POW" | "POWER" | "RAND" | "SECOND" | "SQL_CALC_FOUND_ROWS" | "SUBDATE" -| "SUBSTRING" %prec lowerThanLeftParen | "SUBSTRING_INDEX" | "SUM" | "TRIM" | "RTRIM" | "UCASE" | "UPPER" | "VERSION" -| "WEEKDAY" | "WEEKOFYEAR" | "YEARWEEK" | "ROUND" | "STATS_PERSISTENT" | "GET_LOCK" | "RELEASE_LOCK" +| "DATE_ADD" | "DATE_FORMAT" | "DATE_SUB" | "DAYNAME" | "DAYOFMONTH" | "DAYOFWEEK" | "DAYOFYEAR" | "FOUND_ROWS" +| "GROUP_CONCAT"| "HOUR" | "IFNULL" | "ISNULL" | "LAST_INSERT_ID" | "LCASE" | "LENGTH" | "LOCATE" | "LOWER" | "LTRIM" +| "MAX" | "MICROSECOND" | "MIN" | "MINUTE" | "NULLIF" | "MONTH" | "MONTHNAME" | "NOW" | "POW" | "POWER" | "RAND" +| "SECOND" | "SLEEP" | "SQL_CALC_FOUND_ROWS" | "SUBDATE" | "SUBSTRING" %prec lowerThanLeftParen | "SUBSTRING_INDEX" +| "SUM" | "TRIM" | "RTRIM" | "UCASE" | "UPPER" | "VERSION" | "WEEKDAY" | "WEEKOFYEAR" | "YEARWEEK" | "ROUND" +| "STATS_PERSISTENT" | "GET_LOCK" | "RELEASE_LOCK" /************************************************************************************ * @@ -2653,6 +2655,10 @@ FunctionCallNonKeyword: { $$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1.(string)), Args: []ast.ExprNode{$3.(ast.ExprNode)}} } +| "SLEEP" '(' Expression ')' + { + $$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1.(string)), Args: []ast.ExprNode{$3.(ast.ExprNode)}} + } | "SPACE" '(' Expression ')' { $$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1.(string)), Args: []ast.ExprNode{$3.(ast.ExprNode)}} diff --git a/parser/parser_test.go b/parser/parser_test.go index 7eb204ad9e..8eb0d84699 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -48,7 +48,7 @@ func (s *testParserSuite) TestSimple(c *C) { "delay_key_write", "isolation", "repeatable", "committed", "uncommitted", "only", "serializable", "level", "curtime", "variables", "dayname", "version", "btree", "hash", "row_format", "dynamic", "fixed", "compressed", "compact", "redundant", "sql_no_cache sql_no_cache", "sql_cache sql_cache", "action", "round", - "enable", "disable", "reverse", "space", "privileges", "get_lock", "release_lock", + "enable", "disable", "reverse", "space", "privileges", "get_lock", "release_lock", "sleep", } for _, kw := range unreservedKws { src := fmt.Sprintf("SELECT %s FROM tbl;", kw) @@ -526,6 +526,9 @@ func (s *testParserSuite) TestBuiltin(c *C) { // Repeat {`SELECT REPEAT("a", 10);`, true}, + // Sleep + {`SELECT SLEEP(10);`, true}, + // For date_add {`select date_add("2011-11-11 10:10:10.123456", interval 10 microsecond)`, true}, {`select date_add("2011-11-11 10:10:10.123456", interval 10 second)`, true}, diff --git a/parser/scanner.l b/parser/scanner.l index b69e133e3f..a38d7ec1dd 100644 --- a/parser/scanner.l +++ b/parser/scanner.l @@ -472,6 +472,7 @@ session {s}{e}{s}{s}{i}{o}{n} set {s}{e}{t} share {s}{h}{a}{r}{e} show {s}{h}{o}{w} +sleep {s}{l}{e}{e}{p} some {s}{o}{m}{e} space {s}{p}{a}{c}{e} start {s}{t}{a}{r}{t} @@ -1026,6 +1027,8 @@ redundant lval.item = string(l.val) {set} return set {share} return share {show} return show +{sleep} lval.item = string(l.val) + return sleep {subdate} lval.item = string(l.val) return subDate {strcmp} lval.item = string(l.val) diff --git a/session_test.go b/session_test.go index f311be3331..f2866aa5fa 100644 --- a/session_test.go +++ b/session_test.go @@ -1975,6 +1975,24 @@ func (s *testSessionSuite) TestRetryPreparedStmt(c *C) { c.Assert(err, IsNil) } +func (s *testSessionSuite) TestSleep(c *C) { + defer testleak.AfterTest(c)() + store := newStore(c, s.dbName) + se := newSession(c, store, s.dbName) + + mustExecSQL(c, se, "select sleep(0.01);") + mustExecSQL(c, se, "drop table if exists t;") + mustExecSQL(c, se, "create table t (a int);") + mustExecSQL(c, se, "insert t values (sleep(0.02));") + r := mustExecSQL(c, se, "select * from t;") + row, err := r.Next() + c.Assert(err, IsNil) + match(c, row.Data, 0) + + err = store.Close() + c.Assert(err, IsNil) +} + func (s *testSessionSuite) TestIssue893(c *C) { defer testleak.AfterTest(c)() store := newStore(c, s.dbName)