diff --git a/expression/builtin/builtin.go b/expression/builtin/builtin.go index a76688e976..3339d9981d 100644 --- a/expression/builtin/builtin.go +++ b/expression/builtin/builtin.go @@ -75,6 +75,7 @@ var Funcs = map[string]Func{ "month": {builtinMonth, 1, 1, true, false}, "now": {builtinNow, 0, 1, false, false}, "second": {builtinSecond, 1, 1, true, false}, + "sysdate": {builtinSysDate, 0, 1, false, false}, "week": {builtinWeek, 1, 2, true, false}, "weekday": {builtinWeekDay, 1, 1, true, false}, "weekofyear": {builtinWeekOfYear, 1, 1, true, false}, diff --git a/expression/builtin/time.go b/expression/builtin/time.go index c71e277519..8a7c961611 100644 --- a/expression/builtin/time.go +++ b/expression/builtin/time.go @@ -142,18 +142,14 @@ func builtinMonth(args []interface{}, ctx map[interface{}]interface{}) (interfac } func builtinNow(args []interface{}, ctx map[interface{}]interface{}) (interface{}, error) { - fsp := int64(0) + // TODO: if NOW is used in stored function or trigger, NOW will return the beginning time + // of the execution. + fsp := 0 if len(args) == 1 { var err error - fsp, err = types.ToInt64(args[0]) - if err != nil { + if fsp, err = checkFsp(args[0]); err != nil { return nil, errors.Trace(err) } - if int(fsp) > mysql.MaxFsp { - return nil, errors.Errorf("Too big precision %d specified. Maximum is 6.", fsp) - } else if fsp < 0 { - return nil, errors.Errorf("Invalid negative %d specified, must in [0, 6].", fsp) - } } t := mysql.Time{ @@ -297,3 +293,23 @@ func builtinYearWeek(args []interface{}, ctx map[interface{}]interface{}) (inter year, week := t.ISOWeek() return int64(year*100 + week), nil } + +func builtinSysDate(args []interface{}, ctx map[interface{}]interface{}) (interface{}, error) { + // SYSDATE is not the same as NOW if NOW is used in a stored function or trigger. + // But here we can just think they are the same because we don't support stored function + // and trigger now. + return builtinNow(args, ctx) +} + +func checkFsp(arg interface{}) (int, error) { + fsp, err := types.ToInt64(arg) + if err != nil { + return 0, errors.Trace(err) + } + if int(fsp) > mysql.MaxFsp { + return 0, errors.Errorf("Too big precision %d specified. Maximum is 6.", fsp) + } else if fsp < 0 { + return 0, errors.Errorf("Invalid negative %d specified, must in [0, 6].", fsp) + } + return int(fsp), nil +} diff --git a/expression/builtin/time_test.go b/expression/builtin/time_test.go index 42677d9791..5db28de3d3 100644 --- a/expression/builtin/time_test.go +++ b/expression/builtin/time_test.go @@ -15,6 +15,7 @@ package builtin import ( "strings" + "time" . "github.com/pingcap/check" mysql "github.com/pingcap/tidb/mysqldef" @@ -247,3 +248,21 @@ func (s *testBuiltinSuite) TestNow(c *C) { _, err = builtinNow([]interface{}{-2}, nil) c.Assert(err, NotNil) } + +func (s *testBuiltinSuite) TestSysDate(c *C) { + last := time.Now() + v, err := builtinSysDate(nil, nil) + c.Assert(err, IsNil) + n, ok := v.(mysql.Time) + c.Assert(ok, IsTrue) + c.Assert(n.String(), GreaterEqual, last.Format(mysql.TimeFormat)) + + v, err = builtinSysDate([]interface{}{6}, nil) + c.Assert(err, IsNil) + n, ok = v.(mysql.Time) + c.Assert(ok, IsTrue) + c.Assert(n.String(), GreaterEqual, last.Format(mysql.TimeFormat)) + + _, err = builtinSysDate([]interface{}{-2}, nil) + c.Assert(err, NotNil) +} diff --git a/parser/parser.y b/parser/parser.y index 034d687817..f498810157 100644 --- a/parser/parser.y +++ b/parser/parser.y @@ -116,6 +116,7 @@ import ( div "DIV" do "DO" drop "DROP" + dual "DUAL" duplicate "DUPLICATE" elseKwd "ELSE" end "END" @@ -209,6 +210,7 @@ import ( substring "SUBSTRING" sum "SUM" sysVar "SYS_VAR" + sysDate "SYSDATE" tableKwd "TABLE" tables "TABLES" then "THEN" @@ -325,7 +327,6 @@ import ( ConstraintKeywordOpt "Constraint Keyword or empty" ConstraintOpt "optional column value constraint" ConstraintOpts "optional column value constraints" - CreateDatabase "Create {DATABASE | SCHEMA}" CreateDatabaseStmt "Create Database Statement" CreateIndexStmt "CREATE INDEX statement" CreateIndexStmtUnique "CREATE INDEX optional UNIQUE clause" @@ -335,6 +336,7 @@ import ( CreateTableStmt "CREATE TABLE statement" CreateUserStmt "CREATE User statement" CrossOpt "Cross join option" + DatabaseSym "DATABASE or SCHEMA" DBName "Database Name" DeallocateSym "Deallocate or drop" DeallocateStmt "Deallocate prepared statement" @@ -975,9 +977,9 @@ IndexColNameList: * | [DEFAULT] COLLATE [=] collation_name *******************************************************************/ CreateDatabaseStmt: - CreateDatabase IfNotExists DBName CreateSpecListOpt + "CREATE" DatabaseSym IfNotExists DBName CreateSpecListOpt { - opts := $4.([]*coldef.DatabaseOpt) + opts := $5.([]*coldef.DatabaseOpt) //compose charset from x var cs, co string for _, x := range opts { @@ -994,8 +996,8 @@ CreateDatabaseStmt: dbopt := &coldef.CharsetOpt{Chs: cs, Col: co} $$ = &stmts.CreateDatabaseStmt{ - IfNotExists: $2.(bool), - Name: $3.(string), + IfNotExists: $3.(bool), + Name: $4.(string), Opt: dbopt} if yylex.(*lexer).root { @@ -1003,14 +1005,6 @@ CreateDatabaseStmt: } } -CreateDatabase: - "CREATE" "DATABASE" - { - } -| "CREATE" "SCHEMA" - { - } - DBName: Identifier @@ -1137,6 +1131,7 @@ DefaultOpt: DefaultKwdOpt: {} | "DEFAULT" + /****************************************************************** * Do statement * See: https://dev.mysql.com/doc/refman/5.7/en/do.html @@ -1220,10 +1215,12 @@ DeleteFromStmt: break } } - + +DatabaseSym: + "DATABASE" | "SCHEMA" DropDatabaseStmt: - "DROP" "DATABASE" IfExists Identifier + "DROP" DatabaseSym IfExists DBName { $$ = &stmts.DropDatabaseStmt{IfExists: $3.(bool), Name: $4.(string)} if yylex.(*lexer).root { @@ -2257,6 +2254,20 @@ FunctionCallNonKeyword: Len: $7.(expression.Expression), } } +| "SYSDATE" '(' ExpressionOpt ')' + { + args := []expression.Expression{} + if $3 != nil { + args = append(args, $3.(expression.Expression)) + } + var err error + $$, err = expression.NewCall($1.(string), args, false) + if err != nil { + l := yylex.(*lexer) + l.err(err) + return 1 + } + } | "WEEKDAY" '(' Expression ')' { args := []expression.Expression{$3.(expression.Expression)} @@ -2694,13 +2705,13 @@ RollbackStmt: } SelectStmt: - "SELECT" SelectStmtOpts SelectStmtFieldList SelectStmtLimit SelectLockOpt + "SELECT" SelectStmtOpts SelectStmtFieldList FromDual SelectStmtLimit SelectLockOpt { $$ = &stmts.SelectStmt { Distinct: $2.(bool), Fields: $3.([]*field.Field), From: nil, - Lock: $5.(coldef.LockType), + Lock: $6.(coldef.LockType), } } | "SELECT" SelectStmtOpts SelectStmtFieldList "FROM" @@ -2739,6 +2750,11 @@ SelectStmt: $$ = st } +FromDual: + /* Empty */ +| "FROM" "DUAL" + + FromClause: TableRefs { diff --git a/parser/parser_test.go b/parser/parser_test.go index 7a4d35d118..3b581eab4e 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -354,6 +354,20 @@ func (s *testParserSuite) TestParser0(c *C) { {"show collation like 'utf8%'", true}, {"show collation where Charset = 'utf8' and Collation = 'utf8_bin'", true}, + // For drop datbase/schema + {"create database xxx", true}, + {"create database if exists xxx", false}, + {"create database if not exists xxx", true}, + {"create schema xxx", true}, + {"create schema if exists xxx", false}, + {"create schema if not exists xxx", true}, + {"drop database xxx", true}, + {"drop database if exists xxx", true}, + {"drop database if not exists xxx", false}, + {"drop schema xxx", true}, + {"drop schema if exists xxx", true}, + {"drop schema if not exists xxx", false}, + // For issue 224 {`SELECT CAST('test collated returns' AS CHAR CHARACTER SET utf8) COLLATE utf8_bin;`, true}, @@ -363,6 +377,11 @@ func (s *testParserSuite) TestParser0(c *C) { {"select current_timestamp(6)", true}, {"select now()", true}, {"select now(6)", true}, + {"select sysdate(), sysdate(6)", true}, + + // For dual + {"select 1 from dual", true}, + {"select 1 from dual limit 1", true}, } for _, t := range table { diff --git a/parser/scanner.l b/parser/scanner.l index 7535c2464a..1eb53f169e 100644 --- a/parser/scanner.l +++ b/parser/scanner.l @@ -279,6 +279,7 @@ describe {d}{e}{s}{c}{r}{i}{b}{e} distinct {d}{i}{s}{t}{i}{n}{c}{t} div {d}{i}{v} do {d}{o} +dual {d}{u}{a}{l} duplicate {d}{u}{p}{l}{i}{c}{a}{t}{e} else {e}{l}{s}{e} end {e}{n}{d} @@ -354,6 +355,7 @@ some {s}{o}{m}{e} start {s}{t}{a}{r}{t} substring {s}{u}{b}{s}{t}{r}{i}{n}{g} sum {s}{u}{m} +sysdate {s}{y}{s}{d}{a}{t}{e} table {t}{a}{b}{l}{e} tables {t}{a}{b}{l}{e}{s} then {t}{h}{e}{n} @@ -567,6 +569,7 @@ sys_var "@@"(({global}".")|({session}".")|{local}".")?{ident} {div} return div {do} lval.item = string(l.val) return do +{dual} return dual {duplicate} lval.item = string(l.val) return duplicate {else} return elseKwd @@ -689,6 +692,8 @@ sys_var "@@"(({global}".")|({session}".")|{local}".")?{ident} return substring {sum} lval.item = string(l.val) return sum +{sysdate} lval.item = string(l.val) + return sysDate {table} return tableKwd {tables} lval.item = string(l.val) return tables diff --git a/plan/plans/fields.go b/plan/plans/fields.go index 3a84b381c6..a027ff33b2 100644 --- a/plan/plans/fields.go +++ b/plan/plans/fields.go @@ -30,7 +30,7 @@ import ( var ( _ plan.Plan = (*SelectFieldsDefaultPlan)(nil) - _ plan.Plan = (*SelectEmptyFieldListPlan)(nil) + _ plan.Plan = (*SelectFromDualPlan)(nil) ) // SelectFieldsDefaultPlan extracts specific fields from Src Plan. @@ -94,19 +94,19 @@ func (r *SelectFieldsDefaultPlan) Close() error { return r.Src.Close() } -// SelectEmptyFieldListPlan is the plan for "select expr, expr, ..."" with no FROM. -type SelectEmptyFieldListPlan struct { +// SelectFromDualPlan is the plan for "select expr, expr, ..."" or "select expr, expr, ... from dual". +type SelectFromDualPlan struct { Fields []*field.Field done bool } // Explain implements the plan.Plan Explain interface. -func (s *SelectEmptyFieldListPlan) Explain(w format.Formatter) { +func (s *SelectFromDualPlan) Explain(w format.Formatter) { // TODO: finish this } // GetFields implements the plan.Plan GetFields interface. -func (s *SelectEmptyFieldListPlan) GetFields() []*field.ResultField { +func (s *SelectFromDualPlan) GetFields() []*field.ResultField { ans := make([]*field.ResultField, 0, len(s.Fields)) if len(s.Fields) > 0 { for _, f := range s.Fields { @@ -119,12 +119,12 @@ func (s *SelectEmptyFieldListPlan) GetFields() []*field.ResultField { } // Filter implements the plan.Plan Filter interface. -func (s *SelectEmptyFieldListPlan) Filter(ctx context.Context, expr expression.Expression) (plan.Plan, bool, error) { +func (s *SelectFromDualPlan) Filter(ctx context.Context, expr expression.Expression) (plan.Plan, bool, error) { return s, false, nil } // Next implements plan.Plan Next interface. -func (s *SelectEmptyFieldListPlan) Next(ctx context.Context) (row *plan.Row, err error) { +func (s *SelectFromDualPlan) Next(ctx context.Context) (row *plan.Row, err error) { if s.done { return } @@ -138,7 +138,7 @@ func (s *SelectEmptyFieldListPlan) Next(ctx context.Context) (row *plan.Row, err } // Close implements plan.Plan Close interface. -func (s *SelectEmptyFieldListPlan) Close() error { +func (s *SelectFromDualPlan) Close() error { s.done = false return nil } diff --git a/plan/plans/fields_test.go b/plan/plans/fields_test.go index 22d10e3d06..5557e61a74 100644 --- a/plan/plans/fields_test.go +++ b/plan/plans/fields_test.go @@ -113,7 +113,7 @@ func (s *testFieldsSuit) TestDefaultFieldsPlan(c *C) { } func (s *testFieldsSuit) TestSelectExprPlan(c *C) { - pln := &plans.SelectEmptyFieldListPlan{ + pln := &plans.SelectFromDualPlan{ Fields: []*field.Field{ { Expr: &expression.Value{ diff --git a/plan/plans/show.go b/plan/plans/show.go index c39be29fad..2f59b6f886 100644 --- a/plan/plans/show.go +++ b/plan/plans/show.go @@ -101,8 +101,8 @@ func (s *ShowPlan) GetFields() []*field.ResultField { names = []string{"Variable_name", "Value"} case stmt.ShowCollation: names = []string{"Collation", "Charset", "Id", "Default", "Compiled", "Sortlen"} - types = []byte{mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeLong, - mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeLong} + types = []byte{mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeLonglong, + mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeLonglong} } fields := make([]*field.ResultField, 0, len(names)) for i, name := range names { diff --git a/plan/plans/show_test.go b/plan/plans/show_test.go index c703c624d8..7200bc8c73 100644 --- a/plan/plans/show_test.go +++ b/plan/plans/show_test.go @@ -156,7 +156,7 @@ func (p *testShowSuit) TestShowCollation(c *C) { pln.Target = stmt.ShowCollation fls := pln.GetFields() c.Assert(fls, HasLen, 6) - c.Assert(fls[2].Col.Tp, Equals, mysql.TypeLong) + c.Assert(fls[2].Col.Tp, Equals, mysql.TypeLonglong) pln.Pattern = &expression.PatternLike{ Pattern: &expression.Value{ diff --git a/rset/rsets/fields.go b/rset/rsets/fields.go index 2d08471832..2321c7965a 100644 --- a/rset/rsets/fields.go +++ b/rset/rsets/fields.go @@ -29,7 +29,7 @@ import ( var ( _ plan.Planner = (*SelectFieldsRset)(nil) - _ plan.Planner = (*FieldRset)(nil) + _ plan.Planner = (*SelectFromDualRset)(nil) ) // SelectFieldsRset is record set to select fields. @@ -80,12 +80,12 @@ func (r *SelectFieldsRset) Plan(ctx context.Context) (plan.Plan, error) { return p, nil } -// FieldRset is Recordset for select expression, like `select 1, 1+1`. -type FieldRset struct { +// SelectFromDualRset is Recordset for select from dual, like `select 1, 1+1` or `select 1 from dual`. +type SelectFromDualRset struct { Fields []*field.Field } // Plan gets SelectExprPlan. -func (r *FieldRset) Plan(ctx context.Context) (plan.Plan, error) { - return &plans.SelectEmptyFieldListPlan{Fields: r.Fields}, nil +func (r *SelectFromDualRset) Plan(ctx context.Context) (plan.Plan, error) { + return &plans.SelectFromDualPlan{Fields: r.Fields}, nil } diff --git a/rset/rsets/fields_test.go b/rset/rsets/fields_test.go index 9d69ed51d8..3fa8b9f407 100644 --- a/rset/rsets/fields_test.go +++ b/rset/rsets/fields_test.go @@ -26,7 +26,7 @@ var _ = Suite(&testSelectFieldsPlannerSuite{}) type testSelectFieldsPlannerSuite struct { sr *SelectFieldsRset - fr *FieldRset + fr *SelectFromDualRset } func (s *testSelectFieldsPlannerSuite) SetUpSuite(c *C) { @@ -47,7 +47,7 @@ func (s *testSelectFieldsPlannerSuite) SetUpSuite(c *C) { } s.sr = &SelectFieldsRset{Src: tblPlan, SelectList: selectList} - s.fr = &FieldRset{Fields: fields} + s.fr = &SelectFromDualRset{Fields: fields} } func (s *testSelectFieldsPlannerSuite) TestDistinctPlanner(c *C) { @@ -106,6 +106,6 @@ func (s *testSelectFieldsPlannerSuite) TestFieldPlanner(c *C) { p, err := s.fr.Plan(nil) c.Assert(err, IsNil) - _, ok := p.(*plans.SelectEmptyFieldListPlan) + _, ok := p.(*plans.SelectFromDualPlan) c.Assert(ok, IsTrue) } diff --git a/stmt/stmts/select.go b/stmt/stmts/select.go index a34cd13da3..7e6b81aa31 100644 --- a/stmt/stmts/select.go +++ b/stmt/stmts/select.go @@ -131,7 +131,7 @@ func (s *SelectStmt) Plan(ctx context.Context) (plan.Plan, error) { } } else if s.Fields != nil { // Only evaluate fields values. - fr := &rsets.FieldRset{Fields: s.Fields} + fr := &rsets.SelectFromDualRset{Fields: s.Fields} r, err = fr.Plan(ctx) if err != nil { return nil, err diff --git a/store/localstore/boltdb/boltdb.go b/store/localstore/boltdb/boltdb.go index 967f4e12c2..61b4e7de59 100644 --- a/store/localstore/boltdb/boltdb.go +++ b/store/localstore/boltdb/boltdb.go @@ -17,8 +17,8 @@ import ( "os" "path" + "github.com/boltdb/bolt" "github.com/juju/errors" - "github.com/ngaut/bolt" "github.com/ngaut/log" "github.com/pingcap/tidb/store/localstore/engine" ) diff --git a/store/localstore/boltdb/boltdb_test.go b/store/localstore/boltdb/boltdb_test.go index 5945c92b95..154be46cea 100644 --- a/store/localstore/boltdb/boltdb_test.go +++ b/store/localstore/boltdb/boltdb_test.go @@ -17,7 +17,7 @@ import ( "os" "testing" - "github.com/ngaut/bolt" + "github.com/boltdb/bolt" . "github.com/pingcap/check" "github.com/pingcap/tidb/store/localstore/engine" ) diff --git a/tidb_test.go b/tidb_test.go index 4689063f85..7820de4d2e 100644 --- a/tidb_test.go +++ b/tidb_test.go @@ -785,6 +785,16 @@ func (s *testSessionSuite) TestSelect(c *C) { _, err = se.Execute("select * from t as a join (select 1) as a") c.Assert(err, IsNil) + + r := mustExecSQL(c, se, "select 1, 2 from dual") + row, err := r.FirstRow() + c.Assert(err, IsNil) + match(c, row, 1, 2) + + r = mustExecSQL(c, se, "select 1, 2") + row, err = r.FirstRow() + c.Assert(err, IsNil) + match(c, row, 1, 2) } func (s *testSessionSuite) TestSubQuery(c *C) { @@ -847,7 +857,7 @@ func (s *testSessionSuite) TestTimeFunc(c *C) { se := newSession(c, store, s.dbName) last := time.Now().Format(mysql.TimeFormat) - r := mustExecSQL(c, se, "select now(), now(6), current_timestamp, current_timestamp(), current_timestamp(6)") + r := mustExecSQL(c, se, "select now(), now(6), current_timestamp, current_timestamp(), current_timestamp(6), sysdate(), sysdate(6)") row, err := r.FirstRow() c.Assert(err, IsNil) for _, t := range row { @@ -884,6 +894,29 @@ func (s *testSessionSuite) TestBootstrap(c *C) { mustExecSQL(c, se, "USE test;") } +func (s *testSessionSuite) TestDatabase(c *C) { + store := newStore(c, s.dbName) + se := newSession(c, store, s.dbName) + + // Test database. + mustExecSQL(c, se, "create database xxx") + mustExecSQL(c, se, "drop database xxx") + + mustExecSQL(c, se, "drop database if exists xxx") + mustExecSQL(c, se, "create database xxx") + mustExecSQL(c, se, "create database if not exists xxx") + mustExecSQL(c, se, "drop database if exists xxx") + + // Test schema. + mustExecSQL(c, se, "create schema xxx") + mustExecSQL(c, se, "drop schema xxx") + + mustExecSQL(c, se, "drop schema if exists xxx") + mustExecSQL(c, se, "create schema xxx") + mustExecSQL(c, se, "create schema if not exists xxx") + mustExecSQL(c, se, "drop schema if exists xxx") +} + func newSession(c *C, store kv.Storage, dbName string) Session { se, err := CreateSession(store) c.Assert(err, IsNil)