diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 1ced2f3ca4..942344b977 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -37,7 +37,7 @@ }, { "ImportPath": "github.com/pingcap/check", - "Rev": "dbd36251e82f4d1aed5561647b402a448839c412" + "Rev": "ce8a2f822ab1e245a4eefcef2996531c79c943f1" }, { "ImportPath": "github.com/rcrowley/go-metrics", diff --git a/Godeps/_workspace/src/github.com/pingcap/check/checkers2.go b/Godeps/_workspace/src/github.com/pingcap/check/checkers2.go index 6535b8d53c..c09bcdc5eb 100644 --- a/Godeps/_workspace/src/github.com/pingcap/check/checkers2.go +++ b/Godeps/_workspace/src/github.com/pingcap/check/checkers2.go @@ -69,10 +69,7 @@ func (b *bytesEquals) Check(params []interface{}, names []string) (bool, string) return false, "Arguments to BytesEqual must both be bytestrings" } - if bytes.Equal(b1, b2) { - return true, "" - } - return false, "Byte arrays were different" + return bytes.Equal(b1, b2), "" } func (b *bytesEquals) Info() *CheckerInfo { diff --git a/Godeps/_workspace/src/github.com/pingcap/check/checkers2_test.go b/Godeps/_workspace/src/github.com/pingcap/check/checkers2_test.go index ffcd2e32b8..699ba47a62 100644 --- a/Godeps/_workspace/src/github.com/pingcap/check/checkers2_test.go +++ b/Godeps/_workspace/src/github.com/pingcap/check/checkers2_test.go @@ -1,6 +1,8 @@ package check_test import ( + "time" + "github.com/pingcap/check" ) @@ -34,4 +36,11 @@ func (s *CheckersS) TestCompare(c *check.C) { c.Assert("ABC", check.Less, "ABCD") c.Assert([]byte("ABC"), check.Less, []byte("ABCD")) c.Assert(3.14, check.Less, 3.145) + c.Assert(time.Duration(1), check.Greater, time.Duration(0)) + c.Assert(time.Now(), check.Less, time.Now().Add(10*time.Second)) +} + +func (s *CheckersS) TestBytes(c *check.C) { + c.Assert([]byte{0x00}, check.BytesEquals, []byte{0x00}) + c.Assert([]byte{0x00}, check.Not(check.BytesEquals), []byte{0x01}) } diff --git a/Godeps/_workspace/src/github.com/pingcap/check/compare.go b/Godeps/_workspace/src/github.com/pingcap/check/compare.go index 60041528b2..7005cba704 100644 --- a/Godeps/_workspace/src/github.com/pingcap/check/compare.go +++ b/Godeps/_workspace/src/github.com/pingcap/check/compare.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "reflect" + "time" ) type compareFunc func(v1 interface{}, v2 interface{}) (bool, error) @@ -33,9 +34,8 @@ func compare(v1 interface{}, v2 interface{}) (int, error) { return 1, nil } else if a1 == a2 { return 0, nil - } else { - return -1, nil } + return -1, nil case uint, uint8, uint16, uint32, uint64: a1 := value1.Uint() a2 := value2.Uint() @@ -43,9 +43,8 @@ func compare(v1 interface{}, v2 interface{}) (int, error) { return 1, nil } else if a1 == a2 { return 0, nil - } else { - return -1, nil } + return -1, nil case float32, float64: a1 := value1.Float() a2 := value2.Float() @@ -53,9 +52,8 @@ func compare(v1 interface{}, v2 interface{}) (int, error) { return 1, nil } else if a1 == a2 { return 0, nil - } else { - return -1, nil } + return -1, nil case string: a1 := value1.String() a2 := value2.String() @@ -63,13 +61,30 @@ func compare(v1 interface{}, v2 interface{}) (int, error) { return 1, nil } else if a1 == a2 { return 0, nil - } else { - return -1, nil } + return -1, nil case []byte: a1 := value1.Bytes() a2 := value2.Bytes() return bytes.Compare(a1, a2), nil + case time.Time: + a1 := v1.(time.Time) + a2 := v2.(time.Time) + if a1.After(a2) { + return 1, nil + } else if a1.Equal(a2) { + return 0, nil + } + return -1, nil + case time.Duration: + a1 := v1.(time.Duration) + a2 := v2.(time.Duration) + if a1 > a2 { + return 1, nil + } else if a1 == a2 { + return 0, nil + } + return -1, nil default: return 0, fmt.Errorf("type %T is not supported now", v1) } diff --git a/Godeps/_workspace/src/github.com/pingcap/check/godropbox_license b/Godeps/_workspace/src/github.com/pingcap/check/godropbox_license new file mode 100644 index 0000000000..04d42108ee --- /dev/null +++ b/Godeps/_workspace/src/github.com/pingcap/check/godropbox_license @@ -0,0 +1,27 @@ +Copyright (c) 2014 Dropbox, Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Makefile b/Makefile index a69e0a85e7..5df1c31318 100644 --- a/Makefile +++ b/Makefile @@ -55,12 +55,9 @@ install: $(GO) install ./... update: - go get -u github.com/tools/godep go get -u github.com/pingcap/go-hbase go get -u github.com/pingcap/go-themis go get -u github.com/ngaut/tso/client - go get -u github.com/qiuyesuifeng/goyacc - go get -u github.com/qiuyesuifeng/golex TEMP_FILE = temp_parser_file diff --git a/README.md b/README.md index 9ac43bc89a..88c4c3f782 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Grow TiDB as your business grows. You can increase the capacity simply by adding Evolve TiDB schemas as your requirement evolves. You can add new columns and indices without stopping or affecting the on-going operations. - __Consistent distributed transactions__ -Think TiDB as a single-machine RDBMS. You can start a transaction that acrosses multiple machines without worrying about consistency. TiDB makes your application code simple and robust. +Think TiDB as a single-machine RDBMS. You can start a transaction that crosses multiple machines without worrying about consistency. TiDB makes your application code simple and robust. - __Compatible with MySQL protocol__ Use TiDB as MySQL. You can replace MySQL with TiDB to power your application without changing a single line of code in most cases. @@ -22,10 +22,10 @@ Enjoy TiDB as much as we love Go. We believe Go code is both easy and enjoyable - __NewSQL over HBase__ -Turns HBase into NewSQL database +Turn HBase into NewSQL database - __Multiple storage engine support__ -Power TiDB with your most favorite engines. TiDB supports many popular storage engines in single-machine mode. You can choose from goleveldb, LevelDB, RocksDB, LMDB, BoltDB and even more to come. +Power TiDB with your most favorite engines. TiDB supports many popular storage engines in single-machine mode. You can choose from GolevelDB, LevelDB, RocksDB, LMDB, BoltDB and even more to come. ## Status diff --git a/ast/expressions.go b/ast/expressions.go index 3324368f5a..316a773d8d 100644 --- a/ast/expressions.go +++ b/ast/expressions.go @@ -14,13 +14,11 @@ package ast import ( - "fmt" "regexp" "github.com/pingcap/tidb/model" "github.com/pingcap/tidb/mysql" "github.com/pingcap/tidb/parser/opcode" - "github.com/pingcap/tidb/util/charset" "github.com/pingcap/tidb/util/types" ) @@ -59,46 +57,13 @@ type ValueExpr struct { func NewValueExpr(value interface{}) *ValueExpr { ve := &ValueExpr{} ve.Data = types.RawData(value) - // TODO: make it more precise. - switch x := value.(type) { - case nil: - ve.Type = types.NewFieldType(mysql.TypeNull) - case bool, int64, int: - ve.Type = types.NewFieldType(mysql.TypeLonglong) - case uint64: - ve.Type = types.NewFieldType(mysql.TypeLonglong) - ve.Type.Flag |= mysql.UnsignedFlag - case string, UnquoteString: + if _, ok := value.(UnquoteString); ok { ve.Type = types.NewFieldType(mysql.TypeVarchar) ve.Type.Charset = mysql.DefaultCharset ve.Type.Collate = mysql.DefaultCollationName - case float64: - ve.Type = types.NewFieldType(mysql.TypeDouble) - case []byte: - ve.Type = types.NewFieldType(mysql.TypeBlob) - ve.Type.Charset = charset.CharsetBin - ve.Type.Collate = charset.CharsetBin - case mysql.Bit: - ve.Type = types.NewFieldType(mysql.TypeBit) - case mysql.Hex: - ve.Type = types.NewFieldType(mysql.TypeVarchar) - ve.Type.Charset = charset.CharsetBin - ve.Type.Collate = charset.CharsetBin - case mysql.Time: - ve.Type = types.NewFieldType(x.Type) - case mysql.Duration: - ve.Type = types.NewFieldType(mysql.TypeDuration) - case mysql.Decimal: - ve.Type = types.NewFieldType(mysql.TypeNewDecimal) - case mysql.Enum: - ve.Type = types.NewFieldType(mysql.TypeEnum) - case mysql.Set: - ve.Type = types.NewFieldType(mysql.TypeSet) - case *types.DataItem: - ve.Type = value.(*types.DataItem).Type - default: - panic(fmt.Sprintf("illegal literal value type:%T", value)) + return ve } + ve.Type = types.DefaultTypeForValue(value) return ve } diff --git a/ast/misc.go b/ast/misc.go index c5415122da..be45df476c 100644 --- a/ast/misc.go +++ b/ast/misc.go @@ -168,6 +168,7 @@ const ( ShowCreateTable ShowGrants ShowTriggers + ShowProcedureStatus ) // ShowStmt is a statement to provide information about databases, tables, columns and so on. diff --git a/docs/QUICKSTART.md b/docs/QUICKSTART.md index faea863103..89c82717ae 100644 --- a/docs/QUICKSTART.md +++ b/docs/QUICKSTART.md @@ -2,10 +2,10 @@ #### Run TiDB with docker -You can quickly test tidb with docker, the source repository contains the Dockerfile which +You can quickly test TiDB with docker, the source repository contains the Dockerfile which contains local tidb-server. -Or you can pull TiDB docker image contains HBase standalone and then run TiDB as distributed database in a docker container. +Or you can pull TiDB docker image contains HBase standalone and then run TiDB as a distributed database in a docker container. To install docker on your system, you can read the document on https://docs.docker.com/ diff --git a/executor/builder.go b/executor/builder.go index 5848120299..41691995c1 100644 --- a/executor/builder.go +++ b/executor/builder.go @@ -84,7 +84,7 @@ func (b *executorBuilder) buildIndexScan(v *plan.IndexScan) Executor { } e := &IndexScanExec{ tbl: tbl, - idx: idx.X, + idx: idx, fields: v.Fields(), ctx: b.ctx, Desc: v.Desc, diff --git a/executor/converter/convert_expr.go b/executor/converter/convert_expr.go index 7d395b8361..4cc5bb799b 100644 --- a/executor/converter/convert_expr.go +++ b/executor/converter/convert_expr.go @@ -14,12 +14,14 @@ package converter import ( + "strings" + "github.com/juju/errors" + "github.com/ngaut/log" "github.com/pingcap/tidb/ast" "github.com/pingcap/tidb/expression" "github.com/pingcap/tidb/expression/subquery" "github.com/pingcap/tidb/model" - "strings" ) func convertExpr(converter *expressionConverter, expr ast.ExprNode) (expression.Expression, error) { @@ -106,6 +108,8 @@ func (c *expressionConverter) Leave(in ast.Node) (out ast.Node, ok bool) { c.funcCast(v) case *ast.FuncSubstringExpr: c.funcSubstring(v) + case *ast.FuncSubstringIndexExpr: + c.funcSubstringIndex(v) case *ast.FuncLocateExpr: c.funcLocate(v) case *ast.FuncTrimExpr: @@ -114,6 +118,8 @@ func (c *expressionConverter) Leave(in ast.Node) (out ast.Node, ok bool) { c.funcDateArith(v) case *ast.AggregateFuncExpr: c.aggregateFunc(v) + case ast.ExprNode: + log.Errorf("Unknown expr node %T", v) } return in, c.err == nil } @@ -363,6 +369,15 @@ func (c *expressionConverter) funcSubstring(v *ast.FuncSubstringExpr) { c.exprMap[v] = oldSubstring } +func (c *expressionConverter) funcSubstringIndex(v *ast.FuncSubstringIndexExpr) { + oldSubstrIdx := &expression.FunctionSubstringIndex{ + Delim: c.exprMap[v.Delim], + Count: c.exprMap[v.Count], + StrExpr: c.exprMap[v.StrExpr], + } + c.exprMap[v] = oldSubstrIdx +} + func (c *expressionConverter) funcLocate(v *ast.FuncLocateExpr) { oldLocate := &expression.FunctionLocate{ Pos: c.exprMap[v.Pos], diff --git a/executor/converter/convert_stmt.go b/executor/converter/convert_stmt.go index 6812416157..00fbb35345 100644 --- a/executor/converter/convert_stmt.go +++ b/executor/converter/convert_stmt.go @@ -878,6 +878,8 @@ func convertShow(converter *expressionConverter, v *ast.ShowStmt) (*stmts.ShowSt oldShow.Target = stmt.ShowTableStatus case ast.ShowTriggers: oldShow.Target = stmt.ShowTriggers + case ast.ShowProcedureStatus: + oldShow.Target = stmt.ShowProcedureStatus case ast.ShowNone: oldShow.Target = stmt.ShowNone } diff --git a/executor/executor.go b/executor/executor.go index a00940672e..50fc6cace9 100644 --- a/executor/executor.go +++ b/executor/executor.go @@ -19,6 +19,7 @@ import ( "github.com/juju/errors" "github.com/pingcap/tidb/ast" + "github.com/pingcap/tidb/column" "github.com/pingcap/tidb/context" "github.com/pingcap/tidb/kv" "github.com/pingcap/tidb/optimizer/evaluator" @@ -177,8 +178,8 @@ func (e *IndexRangeExec) Fields() []*ast.ResultField { // Next implements Executor Next interface. func (e *IndexRangeExec) Next() (*Row, error) { if e.iter == nil { - seekVals := make([]interface{}, len(e.lowVals)) - for i := 0; i < len(seekVals); i++ { + seekVals := make([]interface{}, len(e.scan.idx.Columns)) + for i := 0; i < len(e.lowVals); i++ { var err error if e.lowVals[i] == plan.MinNotNullVal { seekVals[i] = []byte{} @@ -189,12 +190,11 @@ func (e *IndexRangeExec) Next() (*Row, error) { } } } - txn, err := e.scan.ctx.GetTxn(false) if err != nil { return nil, errors.Trace(err) } - e.iter, _, err = e.scan.idx.Seek(txn, seekVals) + e.iter, _, err = e.scan.idx.X.Seek(txn, seekVals) if err != nil { return nil, types.EOFAsNil(err) } @@ -315,7 +315,7 @@ func (e *IndexRangeExec) Close() error { // IndexScanExec represents an index scan executor. type IndexScanExec struct { tbl table.Table - idx kv.Index + idx *column.IndexedCol fields []*ast.ResultField Ranges []*IndexRangeExec Desc bool diff --git a/expression/builtin/builtin.go b/expression/builtin/builtin.go index d5c73d5b4e..71530b2721 100644 --- a/expression/builtin/builtin.go +++ b/expression/builtin/builtin.go @@ -55,8 +55,10 @@ var Funcs = map[string]Func{ "coalesce": {builtinCoalesce, 1, -1, true, false}, // math functions - "abs": {builtinAbs, 1, 1, true, false}, - "rand": {builtinRand, 0, 1, true, false}, + "abs": {builtinAbs, 1, 1, true, false}, + "pow": {builtinPow, 2, 2, true, false}, + "power": {builtinPow, 2, 2, true, false}, + "rand": {builtinRand, 0, 1, true, false}, // group by functions "avg": {builtinAvg, 1, 1, false, true}, @@ -69,8 +71,10 @@ var Funcs = map[string]Func{ // time functions "curdate": {builtinCurrentDate, 0, 0, false, false}, "current_date": {builtinCurrentDate, 0, 0, false, false}, + "current_time": {builtinCurrentTime, 0, 1, false, false}, "current_timestamp": {builtinNow, 0, 1, false, false}, - "date": {builtinDate, 8, 8, true, false}, + "curtime": {builtinCurrentTime, 0, 1, false, false}, + "date": {builtinDate, 1, 1, true, false}, "day": {builtinDay, 1, 1, true, false}, "dayofmonth": {builtinDayOfMonth, 1, 1, true, false}, "dayofweek": {builtinDayOfWeek, 1, 1, true, false}, diff --git a/expression/builtin/info.go b/expression/builtin/info.go index 3e5e31a091..14687d6b42 100644 --- a/expression/builtin/info.go +++ b/expression/builtin/info.go @@ -22,7 +22,6 @@ import ( "github.com/pingcap/tidb/context" "github.com/pingcap/tidb/sessionctx/db" "github.com/pingcap/tidb/sessionctx/variable" - "github.com/pingcap/tidb/terror" ) // See: https://dev.mysql.com/doc/refman/5.7/en/information-functions.html @@ -69,30 +68,11 @@ func builtinUser(args []interface{}, data map[interface{}]interface{}) (v interf return variable.GetSessionVars(ctx).User, nil } -// connectionIDKeyType is a dummy type to avoid naming collision in context. -type connectionIDKeyType int - -// String defines a Stringer function for debugging and pretty printing. -func (k connectionIDKeyType) String() string { - return "connection_id" -} - -// ConnectionIDKey is the key for get connection id from context -const ConnectionIDKey connectionIDKeyType = 0 - func builtinConnectionID(args []interface{}, data map[interface{}]interface{}) (v interface{}, err error) { c, ok := data[ExprEvalArgCtx] if !ok { return nil, errors.Errorf("Missing ExprEvalArgCtx when evalue builtin") } ctx := c.(context.Context) - idValue := ctx.Value(ConnectionIDKey) - if idValue == nil { - return nil, terror.MissConnectionID - } - id, ok := idValue.(int64) - if !ok { - return nil, terror.MissConnectionID.Gen("connection id is not int64 but %T", idValue) - } - return id, nil + return variable.GetSessionVars(ctx).ConnectionID, nil } diff --git a/expression/builtin/info_test.go b/expression/builtin/info_test.go index 260596aff8..93eca4577b 100644 --- a/expression/builtin/info_test.go +++ b/expression/builtin/info_test.go @@ -86,9 +86,11 @@ func (s *testBuiltinSuite) TestConnectionID(c *C) { ctx := mock.NewContext() m := map[interface{}]interface{}{} variable.BindSessionVars(ctx) - ctx.SetValue(ConnectionIDKey, int64(1)) + sessionVars := variable.GetSessionVars(ctx) + sessionVars.ConnectionID = uint64(1) + m[ExprEvalArgCtx] = ctx v, err := builtinConnectionID(nil, m) c.Assert(err, IsNil) - c.Assert(v, Equals, int64(1)) + c.Assert(v, Equals, uint64(1)) } diff --git a/expression/builtin/math.go b/expression/builtin/math.go index d3cce6fe41..cbf7c6be92 100644 --- a/expression/builtin/math.go +++ b/expression/builtin/math.go @@ -63,3 +63,18 @@ func builtinRand(args []interface{}, ctx map[interface{}]interface{}) (v interfa return rand.Float64(), nil } + +func builtinPow(args []interface{}, ctx map[interface{}]interface{}) (v interface{}, err error) { + x, err := types.ToFloat64(args[0]) + if err != nil { + return nil, errors.Trace(err) + } + + y, err := types.ToFloat64(args[1]) + if err != nil { + return nil, errors.Trace(err) + } + + return math.Pow(x, y), nil + +} diff --git a/expression/builtin/math_test.go b/expression/builtin/math_test.go index 255238e70a..364c7ae491 100644 --- a/expression/builtin/math_test.go +++ b/expression/builtin/math_test.go @@ -43,3 +43,35 @@ func (s *testBuiltinSuite) TestRand(c *C) { c.Assert(v, Less, float64(1)) c.Assert(v, GreaterEqual, float64(0)) } + +func (s *testBuiltinSuite) TestPow(c *C) { + tbl := []struct { + Arg []interface{} + Ret float64 + }{ + {[]interface{}{1, 3}, 1}, + {[]interface{}{2, 2}, 4}, + {[]interface{}{4, 0.5}, 2}, + {[]interface{}{4, -2}, 0.0625}, + } + + for _, t := range tbl { + v, err := builtinPow(t.Arg, nil) + c.Assert(err, IsNil) + c.Assert(v, DeepEquals, t.Ret) + } + + errTbl := []struct { + Arg []interface{} + }{ + {[]interface{}{"test", "test"}}, + {[]interface{}{nil, nil}}, + {[]interface{}{1, "test"}}, + {[]interface{}{1, nil}}, + } + for _, t := range errTbl { + _, err := builtinPow(t.Arg, nil) + c.Assert(err, NotNil) + } + +} diff --git a/expression/builtin/time.go b/expression/builtin/time.go index ac4204e61d..69eec6e207 100644 --- a/expression/builtin/time.go +++ b/expression/builtin/time.go @@ -46,9 +46,9 @@ func convertToTime(arg interface{}, tp byte) (interface{}, error) { return t, nil } -func convertToDuration(arg interface{}) (interface{}, error) { +func convertToDuration(arg interface{}, fsp int) (interface{}, error) { f := types.NewFieldType(mysql.TypeDuration) - f.Decimal = mysql.MaxFsp + f.Decimal = fsp v, err := types.Convert(arg, f) if err != nil { @@ -79,7 +79,7 @@ func builtinDay(args []interface{}, ctx map[interface{}]interface{}) (interface{ // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_hour func builtinHour(args []interface{}, ctx map[interface{}]interface{}) (interface{}, error) { - v, err := convertToDuration(args[0]) + v, err := convertToDuration(args[0], mysql.MaxFsp) if err != nil || types.IsNil(v) { return v, err } @@ -91,7 +91,7 @@ func builtinHour(args []interface{}, ctx map[interface{}]interface{}) (interface // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_minute func builtinMinute(args []interface{}, ctx map[interface{}]interface{}) (interface{}, error) { - v, err := convertToDuration(args[0]) + v, err := convertToDuration(args[0], mysql.MaxFsp) if err != nil || types.IsNil(v) { return v, err } @@ -103,7 +103,7 @@ func builtinMinute(args []interface{}, ctx map[interface{}]interface{}) (interfa // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_second func builtinSecond(args []interface{}, ctx map[interface{}]interface{}) (interface{}, error) { - v, err := convertToDuration(args[0]) + v, err := convertToDuration(args[0], mysql.MaxFsp) if err != nil || types.IsNil(v) { return v, err } @@ -115,7 +115,7 @@ func builtinSecond(args []interface{}, ctx map[interface{}]interface{}) (interfa // See http://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_microsecond func builtinMicroSecond(args []interface{}, ctx map[interface{}]interface{}) (interface{}, error) { - v, err := convertToDuration(args[0]) + v, err := convertToDuration(args[0], mysql.MaxFsp) if err != nil || types.IsNil(v) { return v, err } @@ -309,6 +309,18 @@ func builtinCurrentDate(args []interface{}, ctx map[interface{}]interface{}) (in Type: mysql.TypeDate, Fsp: 0}, nil } +// See https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_curtime +func builtinCurrentTime(args []interface{}, ctx map[interface{}]interface{}) (interface{}, error) { + fsp := 0 + if len(args) == 1 { + var err error + if fsp, err = checkFsp(args[0]); err != nil { + return nil, errors.Trace(err) + } + } + return convertToDuration(time.Now().Format("15:04:05.000000"), fsp) +} + func checkFsp(arg interface{}) (int, error) { fsp, err := types.ToInt64(arg) if err != nil { diff --git a/expression/builtin/time_test.go b/expression/builtin/time_test.go index c3e662933f..a7d75c3115 100644 --- a/expression/builtin/time_test.go +++ b/expression/builtin/time_test.go @@ -275,3 +275,35 @@ func (s *testBuiltinSuite) TestCurrentDate(c *C) { c.Assert(ok, IsTrue) c.Assert(n.String(), GreaterEqual, last.Format(mysql.DateFormat)) } + +func (s *testBuiltinSuite) TestCurrentTime(c *C) { + tfStr := "15:04:05" + + last := time.Now() + v, err := builtinCurrentTime(nil, nil) + c.Assert(err, IsNil) + n, ok := v.(mysql.Duration) + c.Assert(ok, IsTrue) + c.Assert(n.String(), HasLen, 8) + c.Assert(n.String(), GreaterEqual, last.Format(tfStr)) + + v, err = builtinCurrentTime([]interface{}{3}, nil) + c.Assert(err, IsNil) + n, ok = v.(mysql.Duration) + c.Assert(ok, IsTrue) + c.Assert(n.String(), HasLen, 12) + c.Assert(n.String(), GreaterEqual, last.Format(tfStr)) + + v, err = builtinCurrentTime([]interface{}{6}, nil) + c.Assert(err, IsNil) + n, ok = v.(mysql.Duration) + c.Assert(ok, IsTrue) + c.Assert(n.String(), HasLen, 15) + c.Assert(n.String(), GreaterEqual, last.Format(tfStr)) + + v, err = builtinCurrentTime([]interface{}{-1}, nil) + c.Assert(err, NotNil) + + v, err = builtinCurrentTime([]interface{}{7}, nil) + c.Assert(err, NotNil) +} diff --git a/kv/compactor.go b/kv/compactor.go deleted file mode 100644 index 1687225e82..0000000000 --- a/kv/compactor.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2015 PingCAP, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// See the License for the specific language governing permissions and -// limitations under the License. - -package kv - -import "time" - -// CompactPolicy defines gc policy of MVCC storage. -type CompactPolicy struct { - // SafePoint specifies - SafePoint int - // TriggerInterval specifies how often should the compactor - // scans outdated data. - TriggerInterval time.Duration - // BatchDeleteCnt specifies the batch size for - // deleting outdated data transaction. - BatchDeleteCnt int -} - -// Compactor compacts MVCC storage. -type Compactor interface { - // OnGet is the hook point on Txn.Get. - OnGet(k Key) - // OnSet is the hook point on Txn.Set. - OnSet(k Key) - // OnDelete is the hook point on Txn.Delete. - OnDelete(k Key) - // Compact is the function removes the given key. - Compact(k Key) error -} diff --git a/kv/error.go b/kv/error.go new file mode 100644 index 0000000000..bd6a1923ea --- /dev/null +++ b/kv/error.go @@ -0,0 +1,73 @@ +// Copyright 2015 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package kv + +import ( + "errors" + "strings" + + "github.com/pingcap/go-themis" + "github.com/pingcap/tidb/terror" +) + +var ( + // ErrClosed is used when close an already closed txn. + ErrClosed = errors.New("Error: Transaction already closed") + // ErrNotExist is used when try to get an entry with an unexist key from KV store. + ErrNotExist = errors.New("Error: key not exist") + // ErrConditionNotMatch is used when condition is not met. + ErrConditionNotMatch = errors.New("Error: Condition not match") + // ErrLockConflict is used when try to lock an already locked key. + ErrLockConflict = errors.New("Error: Lock conflict") + // ErrLazyConditionPairsNotMatch is used when value in store differs from expect pairs. + ErrLazyConditionPairsNotMatch = errors.New("Error: Lazy condition pairs not match") + // ErrRetryable is used when KV store occurs RPC error or some other + // errors which SQL layer can safely retry. + ErrRetryable = errors.New("Error: KV error safe to retry") + // ErrCannotSetNilValue is the error when sets an empty value. + ErrCannotSetNilValue = errors.New("can not set nil value") + // ErrInvalidTxn is the error when commits or rollbacks in an invalid transaction. + ErrInvalidTxn = errors.New("invalid transaction") + + // ErrNotCommitted is the error returned by CommitVersion when this + // transaction is not committed. + ErrNotCommitted = errors.New("this transaction has not committed") +) + +// IsRetryableError checks if the err is a fatal error and the under going operation is worth to retry. +func IsRetryableError(err error) bool { + if err == nil { + return false + } + + if terror.ErrorEqual(err, ErrRetryable) || + terror.ErrorEqual(err, ErrLockConflict) || + terror.ErrorEqual(err, ErrConditionNotMatch) || + terror.ErrorEqual(err, themis.ErrRetryable) || + // HBase exception message will tell you if you should retry or not + strings.Contains(err.Error(), "try again later") { + return true + } + + return false +} + +// IsErrNotFound checks if err is a kind of NotFound error. +func IsErrNotFound(err error) bool { + if terror.ErrorEqual(err, ErrNotExist) { + return true + } + + return false +} diff --git a/kv/index_iter.go b/kv/index_iter.go index 929682c71d..c6db0f54a3 100644 --- a/kv/index_iter.go +++ b/kv/index_iter.go @@ -20,6 +20,7 @@ import ( "strings" "github.com/juju/errors" + "github.com/pingcap/tidb/terror" "github.com/pingcap/tidb/util/codec" ) @@ -28,6 +29,30 @@ var ( _ IndexIterator = (*indexIter)(nil) ) +// IndexIterator is the interface for iterator of index data on KV store. +type IndexIterator interface { + Next() (k []interface{}, h int64, err error) + Close() +} + +// Index is the interface for index data on KV store. +type Index interface { + // Create supports insert into statement. + Create(rm RetrieverMutator, indexedValues []interface{}, h int64) error + // Delete supports delete from statement. + Delete(m Mutator, indexedValues []interface{}, h int64) error + // Drop supports drop table, drop index statements. + Drop(rm RetrieverMutator) error + // Exist supports check index exists or not. + Exist(rm RetrieverMutator, indexedValues []interface{}, h int64) (bool, int64, error) + // GenIndexKey generates an index key. + GenIndexKey(indexedValues []interface{}, h int64) (key []byte, distinct bool, err error) + // Seek supports where clause. + Seek(r Retriever, indexedValues []interface{}) (iter IndexIterator, hit bool, err error) + // SeekFirst supports aggregate min and ascend order by. + SeekFirst(r Retriever) (iter IndexIterator, err error) +} + func encodeHandle(h int64) []byte { buf := &bytes.Buffer{} err := binary.Write(buf, binary.BigEndian, h) @@ -169,7 +194,7 @@ func (c *kvIndex) Create(rm RetrieverMutator, indexedValues []interface{}, h int return errors.Trace(err) } - return errors.Trace(ErrKeyExists) + return errors.Trace(terror.ErrKeyExists) } // Delete removes the entry for handle h and indexdValues from KV index. @@ -258,7 +283,7 @@ func (c *kvIndex) Exist(rm RetrieverMutator, indexedValues []interface{}, h int6 } if handle != h { - return true, handle, errors.Trace(ErrKeyExists) + return true, handle, errors.Trace(terror.ErrKeyExists) } return true, handle, nil diff --git a/kv/index_iter_test.go b/kv/index_iter_test.go index 969e2093d3..8817991b1c 100644 --- a/kv/index_iter_test.go +++ b/kv/index_iter_test.go @@ -143,3 +143,23 @@ func (s *testIndexSuite) TestIndex(c *C) { err = txn.Commit() c.Assert(err, IsNil) } + +func (s *testIndexSuite) TestCombineIndexSeek(c *C) { + index := kv.NewKVIndex("i", "test", 1, false) + + txn, err := s.s.Begin() + c.Assert(err, IsNil) + + values := []interface{}{"abc", "def"} + err = index.Create(txn, values, 1) + c.Assert(err, IsNil) + + index2 := kv.NewKVIndex("i", "test", 1, false) + iter, hit, err := index2.Seek(txn, []interface{}{"abc", nil}) + c.Assert(err, IsNil) + defer iter.Close() + c.Assert(hit, IsFalse) + _, h, err := iter.Next() + c.Assert(err, IsNil) + c.Assert(h, Equals, int64(1)) +} diff --git a/kv/iter.go b/kv/iter.go index b65d4e963e..d2d4dd7dcd 100644 --- a/kv/iter.go +++ b/kv/iter.go @@ -15,28 +15,6 @@ package kv import "github.com/juju/errors" -var ( - // ErrClosed is used when close an already closed txn. - ErrClosed = errors.New("Error: Transaction already closed") - // ErrNotExist is used when try to get an entry with an unexist key from KV store. - ErrNotExist = errors.New("Error: key not exist") - // ErrKeyExists is used when try to put an entry to KV store. - ErrKeyExists = errors.New("Error: key already exist") - // ErrConditionNotMatch is used when condition is not met. - ErrConditionNotMatch = errors.New("Error: Condition not match") - // ErrLockConflict is used when try to lock an already locked key. - ErrLockConflict = errors.New("Error: Lock conflict") - // ErrLazyConditionPairsNotMatch is used when value in store differs from expect pairs. - ErrLazyConditionPairsNotMatch = errors.New("Error: Lazy condition pairs not match") - // ErrRetryable is used when KV store occurs RPC error or some other - // errors which SQL layer can safely retry. - ErrRetryable = errors.New("Error: KV error safe to retry") - // ErrCannotSetNilValue is the error when sets an empty value. - ErrCannotSetNilValue = errors.New("can not set nil value") - // ErrInvalidTxn is the error when commits or rollbacks in an invalid transaction. - ErrInvalidTxn = errors.New("invalid transaction") -) - // NextUntil applies FnKeyCmp to each entry of the iterator until meets some condition. // It will stop when fn returns true, or iterator is invalid or an error occurs. func NextUntil(it Iterator, fn FnKeyCmp) error { diff --git a/kv/key.go b/kv/key.go new file mode 100644 index 0000000000..d76f505fd3 --- /dev/null +++ b/kv/key.go @@ -0,0 +1,47 @@ +// Copyright 2015 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package kv + +import "bytes" + +// Key represents high-level Key type. +type Key []byte + +// Next returns the next key in byte-order. +func (k Key) Next() Key { + // add 0x0 to the end of key + buf := make([]byte, len([]byte(k))+1) + copy(buf, []byte(k)) + return buf +} + +// Cmp returns the comparison result of two key. +// The result will be 0 if a==b, -1 if a < b, and +1 if a > b. +func (k Key) Cmp(another Key) int { + return bytes.Compare(k, another) +} + +// EncodedKey represents encoded key in low-level storage engine. +type EncodedKey []byte + +// Cmp returns the comparison result of two key. +// The result will be 0 if a==b, -1 if a < b, and +1 if a > b. +func (k EncodedKey) Cmp(another EncodedKey) int { + return bytes.Compare(k, another) +} + +// Next returns the next key in byte-order. +func (k EncodedKey) Next() EncodedKey { + return EncodedKey(bytes.Join([][]byte{k, Key{0}}, nil)) +} diff --git a/kv/kv.go b/kv/kv.go index 5b3db455fe..7152df744e 100644 --- a/kv/kv.go +++ b/kv/kv.go @@ -13,98 +13,6 @@ package kv -import ( - "bytes" - "math" - - "github.com/juju/errors" -) - -// Key represents high-level Key type. -type Key []byte - -// Next returns the next key in byte-order. -func (k Key) Next() Key { - // add 0x0 to the end of key - buf := make([]byte, len([]byte(k))+1) - copy(buf, []byte(k)) - return buf -} - -// Cmp returns the comparison result of two key. -// The result will be 0 if a==b, -1 if a < b, and +1 if a > b. -func (k Key) Cmp(another Key) int { - return bytes.Compare(k, another) -} - -// EncodedKey represents encoded key in low-level storage engine. -type EncodedKey []byte - -// Cmp returns the comparison result of two key. -// The result will be 0 if a==b, -1 if a < b, and +1 if a > b. -func (k EncodedKey) Cmp(another EncodedKey) int { - return bytes.Compare(k, another) -} - -// Next returns the next key in byte-order. -func (k EncodedKey) Next() EncodedKey { - return EncodedKey(bytes.Join([][]byte{k, Key{0}}, nil)) -} - -// VersionProvider provides increasing IDs. -type VersionProvider interface { - CurrentVersion() (Version, error) -} - -// Version is the wrapper of KV's version. -type Version struct { - Ver uint64 -} - -var ( - // MaxVersion is the maximum version, notice that it's not a valid version. - MaxVersion = Version{Ver: math.MaxUint64} - // MinVersion is the minimum version, it's not a valid version, too. - MinVersion = Version{Ver: 0} -) - -// NewVersion creates a new Version struct. -func NewVersion(v uint64) Version { - return Version{ - Ver: v, - } -} - -// Cmp returns the comparison result of two versions. -// The result will be 0 if a==b, -1 if a < b, and +1 if a > b. -func (v Version) Cmp(another Version) int { - if v.Ver > another.Ver { - return 1 - } else if v.Ver < another.Ver { - return -1 - } - return 0 -} - -// DecodeFn is a function that decodes data after fetching from store. -type DecodeFn func(raw interface{}) (interface{}, error) - -// EncodeFn is a function that encodes data before putting into store. -type EncodeFn func(raw interface{}) (interface{}, error) - -// ErrNotCommitted is the error returned by CommitVersion when this -// transaction is not committed. -var ErrNotCommitted = errors.New("this transaction is not committed") - -// Option is used for customizing kv store's behaviors during a transaction. -type Option int - -// Options is an interface of a set of options. Each option is associated with a value. -type Options interface { - // Get gets an option value. - Get(opt Option) (v interface{}, ok bool) -} - const ( // RangePrefetchOnCacheMiss directives that when dealing with a Get operation but failing to read data from cache, // it will launch a RangePrefetch to underlying storage instead of Get. The range starts from requested key and @@ -152,33 +60,6 @@ type MemBuffer interface { Release() } -// UnionStore is a store that wraps a snapshot for read and a BufferStore for buffered write. -// Also, it provides some transaction related utilities. -type UnionStore interface { - MemBuffer - // Inc increases the value for key k in KV storage by step. - Inc(k Key, step int64) (int64, error) - // GetInt64 get int64 which created by Inc method. - GetInt64(k Key) (int64, error) - // CheckLazyConditionPairs loads all lazy values from store then checks if all values are matched. - // Lazy condition pairs should be checked before transaction commit. - CheckLazyConditionPairs() error - // BatchPrefetch fetches values from KV storage to cache for later use. - BatchPrefetch(keys []Key) error - // RangePrefetch fetches values in the range [start, end] from KV storage - // to cache for later use. Maximum number of values is up to limit. - RangePrefetch(start, end Key, limit int) error - // WalkBuffer iterates all buffered kv pairs. - WalkBuffer(f func(k Key, v []byte) error) error - // SetOption sets an option with a value, when val is nil, uses the default - // value of this option. - SetOption(opt Option, val interface{}) - // DelOption deletes an option. - DelOption(opt Option) - // ReleaseSnapshot releases underlying snapshot. - ReleaseSnapshot() -} - // Transaction defines the interface for operations inside a Transaction. // This is not thread safe. type Transaction interface { @@ -242,27 +123,3 @@ type Iterator interface { Valid() bool Close() } - -// IndexIterator is the interface for iterator of index data on KV store. -type IndexIterator interface { - Next() (k []interface{}, h int64, err error) - Close() -} - -// Index is the interface for index data on KV store. -type Index interface { - // Create supports insert into statement. - Create(rm RetrieverMutator, indexedValues []interface{}, h int64) error - // Delete supports delete from statement. - Delete(m Mutator, indexedValues []interface{}, h int64) error - // Drop supports drop table, drop index statements. - Drop(rm RetrieverMutator) error - // Exist supports check index exists or not. - Exist(rm RetrieverMutator, indexedValues []interface{}, h int64) (bool, int64, error) - // GenIndexKey generates an index key. - GenIndexKey(indexedValues []interface{}, h int64) (key []byte, distinct bool, err error) - // Seek supports where clause. - Seek(r Retriever, indexedValues []interface{}) (iter IndexIterator, hit bool, err error) - // SeekFirst supports aggregate min and ascend order by. - SeekFirst(r Retriever) (iter IndexIterator, err error) -} diff --git a/kv/txn.go b/kv/txn.go index 2bf483970d..197a6fc5c5 100644 --- a/kv/txn.go +++ b/kv/txn.go @@ -16,33 +16,12 @@ package kv import ( "math" "math/rand" - "strings" "time" "github.com/juju/errors" "github.com/ngaut/log" - "github.com/pingcap/go-themis" - "github.com/pingcap/tidb/terror" ) -// IsRetryableError checks if the err is a fatal error and the under going operation is worth to retry. -func IsRetryableError(err error) bool { - if err == nil { - return false - } - - if terror.ErrorEqual(err, ErrRetryable) || - terror.ErrorEqual(err, ErrLockConflict) || - terror.ErrorEqual(err, ErrConditionNotMatch) || - terror.ErrorEqual(err, themis.ErrRetryable) || - // HBase exception message will tell you if you should retry or not - strings.Contains(err.Error(), "try again later") { - return true - } - - return false -} - // RunInNewTxn will run the f in a new transaction environment. func RunInNewTxn(store Storage, retryable bool, f func(txn Transaction) error) error { for i := 0; i < maxRetryCnt; i++ { diff --git a/kv/union_store.go b/kv/union_store.go index 7e11b13610..8c0e1d6f2d 100644 --- a/kv/union_store.go +++ b/kv/union_store.go @@ -22,21 +22,48 @@ import ( "github.com/pingcap/tidb/terror" ) +// UnionStore is a store that wraps a snapshot for read and a BufferStore for buffered write. +// Also, it provides some transaction related utilities. +type UnionStore interface { + MemBuffer + // Inc increases the value for key k in KV storage by step. + Inc(k Key, step int64) (int64, error) + // GetInt64 get int64 which created by Inc method. + GetInt64(k Key) (int64, error) + // CheckLazyConditionPairs loads all lazy values from store then checks if all values are matched. + // Lazy condition pairs should be checked before transaction commit. + CheckLazyConditionPairs() error + // BatchPrefetch fetches values from KV storage to cache for later use. + BatchPrefetch(keys []Key) error + // RangePrefetch fetches values in the range [start, end] from KV storage + // to cache for later use. Maximum number of values is up to limit. + RangePrefetch(start, end Key, limit int) error + // WalkBuffer iterates all buffered kv pairs. + WalkBuffer(f func(k Key, v []byte) error) error + // SetOption sets an option with a value, when val is nil, uses the default + // value of this option. + SetOption(opt Option, val interface{}) + // DelOption deletes an option. + DelOption(opt Option) + // ReleaseSnapshot releases underlying snapshot. + ReleaseSnapshot() +} + +// Option is used for customizing kv store's behaviors during a transaction. +type Option int + +// Options is an interface of a set of options. Each option is associated with a value. +type Options interface { + // Get gets an option value. + Get(opt Option) (v interface{}, ok bool) +} + var ( p = pool.NewCache("memdb pool", 100, func() interface{} { return NewMemDbBuffer() }) ) -// IsErrNotFound checks if err is a kind of NotFound error. -func IsErrNotFound(err error) bool { - if terror.ErrorEqual(err, ErrNotExist) { - return true - } - - return false -} - // UnionStore is an in-memory Store which contains a buffer for write and a // snapshot for read. type unionStore struct { @@ -186,7 +213,7 @@ func (us *unionStore) CheckLazyConditionPairs() error { for ; it.Valid(); it.Next() { if len(it.Value()) == 0 { if _, exist := values[it.Key()]; exist { - return errors.Trace(ErrKeyExists) + return errors.Trace(terror.ErrKeyExists) } } else { if bytes.Compare(values[it.Key()], it.Value()) != 0 { diff --git a/kv/version.go b/kv/version.go new file mode 100644 index 0000000000..f009215863 --- /dev/null +++ b/kv/version.go @@ -0,0 +1,51 @@ +// Copyright 2015 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package kv + +import "math" + +// VersionProvider provides increasing IDs. +type VersionProvider interface { + CurrentVersion() (Version, error) +} + +// Version is the wrapper of KV's version. +type Version struct { + Ver uint64 +} + +var ( + // MaxVersion is the maximum version, notice that it's not a valid version. + MaxVersion = Version{Ver: math.MaxUint64} + // MinVersion is the minimum version, it's not a valid version, too. + MinVersion = Version{Ver: 0} +) + +// NewVersion creates a new Version struct. +func NewVersion(v uint64) Version { + return Version{ + Ver: v, + } +} + +// Cmp returns the comparison result of two versions. +// The result will be 0 if a==b, -1 if a < b, and +1 if a > b. +func (v Version) Cmp(another Version) int { + if v.Ver > another.Ver { + return 1 + } else if v.Ver < another.Ver { + return -1 + } + return 0 +} diff --git a/optimizer/evaluator/evaluator.go b/optimizer/evaluator/evaluator.go index a3a0512c84..ec46727100 100644 --- a/optimizer/evaluator/evaluator.go +++ b/optimizer/evaluator/evaluator.go @@ -536,6 +536,10 @@ func (e *Evaluator) funcCall(v *ast.FuncCallExpr) bool { e.err = ErrInvalidOperation.Gen("unknown function %s", v.FnName.O) return false } + if len(v.Args) < f.MinArgs || (f.MaxArgs != -1 && len(v.Args) > f.MaxArgs) { + e.err = ErrInvalidOperation.Gen("number of function arguments must in [%d, %d].", f.MinArgs, f.MaxArgs) + return false + } a := make([]interface{}, len(v.Args)) for i, arg := range v.Args { a[i] = arg.GetValue() diff --git a/optimizer/evaluator/evaluator_test.go b/optimizer/evaluator/evaluator_test.go index f634ce81db..c6f87366a7 100644 --- a/optimizer/evaluator/evaluator_test.go +++ b/optimizer/evaluator/evaluator_test.go @@ -20,6 +20,7 @@ import ( . "github.com/pingcap/check" "github.com/pingcap/tidb/ast" + "github.com/pingcap/tidb/model" "github.com/pingcap/tidb/mysql" "github.com/pingcap/tidb/parser" "github.com/pingcap/tidb/parser/opcode" @@ -382,6 +383,56 @@ func (s *testEvaluatorSuite) TestConvert(c *C) { } } +func (s *testEvaluatorSuite) TestCall(c *C) { + ctx := mock.NewContext() + + // Test case for correct number of arguments + expr := &ast.FuncCallExpr{ + FnName: model.NewCIStr("date"), + Args: []ast.ExprNode{ast.NewValueExpr("2015-12-21 11:11:11")}, + } + v, err := Eval(ctx, expr) + c.Assert(err, IsNil) + value, ok := v.(mysql.Time) + c.Assert(ok, IsTrue) + c.Assert(value.String(), Equals, "2015-12-21") + + // Test case for unlimited upper bound + expr = &ast.FuncCallExpr{ + FnName: model.NewCIStr("concat"), + Args: []ast.ExprNode{ast.NewValueExpr("Ti"), + ast.NewValueExpr("D"), ast.NewValueExpr("B")}, + } + v, err = Eval(ctx, expr) + c.Assert(err, IsNil) + c.Assert(v, Equals, "TiDB") + + // Test case for unknown function + expr = &ast.FuncCallExpr{ + FnName: model.NewCIStr("unknown"), + Args: []ast.ExprNode{}, + } + _, err = Eval(ctx, expr) + c.Assert(err, NotNil) + + // Test case for invalid number of arguments, violating the lower bound + expr = &ast.FuncCallExpr{ + FnName: model.NewCIStr("date"), + Args: []ast.ExprNode{}, + } + _, err = Eval(ctx, expr) + c.Assert(err, NotNil) + + // Test case for invalid number of arguments, violating the upper bound + expr = &ast.FuncCallExpr{ + FnName: model.NewCIStr("date"), + Args: []ast.ExprNode{ast.NewValueExpr("2015-12-21"), + ast.NewValueExpr("2015-12-22")}, + } + _, err = Eval(ctx, expr) + c.Assert(err, NotNil) +} + func (s *testEvaluatorSuite) TestCast(c *C) { f := types.NewFieldType(mysql.TypeLonglong) diff --git a/optimizer/optimizer.go b/optimizer/optimizer.go index 089552c8dd..04a89a7584 100644 --- a/optimizer/optimizer.go +++ b/optimizer/optimizer.go @@ -34,7 +34,7 @@ func Optimize(is infoschema.InfoSchema, ctx context.Context, node ast.Node) (pla if err := ResolveName(node, is, ctx); err != nil { return nil, errors.Trace(err) } - if err := inferType(node); err != nil { + if err := InferType(node); err != nil { return nil, errors.Trace(err) } if err := preEvaluate(ctx, node); err != nil { diff --git a/optimizer/typeinferer.go b/optimizer/typeinferer.go index a6c09bfcc9..6b3166fd62 100644 --- a/optimizer/typeinferer.go +++ b/optimizer/typeinferer.go @@ -16,11 +16,14 @@ package optimizer import ( "github.com/pingcap/tidb/ast" "github.com/pingcap/tidb/context" + "github.com/pingcap/tidb/mysql" "github.com/pingcap/tidb/optimizer/evaluator" + "github.com/pingcap/tidb/parser/opcode" + "github.com/pingcap/tidb/util/types" ) -// inferType infers result type for ast.ExprNode. -func inferType(node ast.Node) error { +// InferType infers result type for ast.ExprNode. +func InferType(node ast.Node) error { var inferrer typeInferrer node.Accept(&inferrer) return inferrer.err @@ -48,14 +51,113 @@ func (v *typeInferrer) Leave(in ast.Node) (out ast.Node, ok bool) { case *ast.FuncCastExpr: x.SetType(x.Tp) case *ast.SelectStmt: - rf := x.GetResultFields() - for _, val := range rf { - if val.Column.ID == 0 && val.Expr.GetType() != nil { - val.Column.FieldType = *(val.Expr.GetType()) + v.selectStmt(x) + case *ast.ParamMarkerExpr: + x.SetType(types.DefaultTypeForValue(x.GetValue())) + case *ast.BinaryOperationExpr: + v.binaryOperation(x) + case *ast.UnaryOperationExpr: + v.unaryOperation(x) + case *ast.BetweenExpr: + x.SetType(types.NewFieldType(mysql.TypeLonglong)) + case *ast.CompareSubqueryExpr: + x.SetType(types.NewFieldType(mysql.TypeLonglong)) + case *ast.ExistsSubqueryExpr: + x.SetType(types.NewFieldType(mysql.TypeLonglong)) + case *ast.PatternInExpr: + x.SetType(types.NewFieldType(mysql.TypeLonglong)) + case *ast.PatternLikeExpr: + x.SetType(types.NewFieldType(mysql.TypeLonglong)) + case *ast.PatternRegexpExpr: + x.SetType(types.NewFieldType(mysql.TypeLonglong)) + case *ast.IsNullExpr: + x.SetType(types.NewFieldType(mysql.TypeLonglong)) + case *ast.IsTruthExpr: + x.SetType(types.NewFieldType(mysql.TypeLonglong)) + case *ast.ParenthesesExpr: + x.SetType(x.Expr.GetType()) + // TODO: handle all expression types. + } + return in, true +} + +func (v *typeInferrer) selectStmt(x *ast.SelectStmt) { + rf := x.GetResultFields() + for _, val := range rf { + // column ID is 0 means it is not a real column from table, but a temporary column, + // so its type is not pre-defined, we need to set it. + if val.Column.ID == 0 && val.Expr.GetType() != nil { + val.Column.FieldType = *(val.Expr.GetType()) + } + } +} + +func (v *typeInferrer) binaryOperation(x *ast.BinaryOperationExpr) { + switch x.Op { + case opcode.AndAnd, opcode.OrOr, opcode.LogicXor: + x.Type = types.NewFieldType(mysql.TypeLonglong) + case opcode.LT, opcode.LE, opcode.GE, opcode.GT, opcode.EQ, opcode.NE, opcode.NullEQ: + x.Type = types.NewFieldType(mysql.TypeLonglong) + case opcode.RightShift, opcode.LeftShift, opcode.And, opcode.Or, opcode.Xor: + x.Type = types.NewFieldType(mysql.TypeLonglong) + x.Type.Flag |= mysql.UnsignedFlag + case opcode.IntDiv: + x.Type = types.NewFieldType(mysql.TypeLonglong) + case opcode.Plus, opcode.Minus, opcode.Mul, opcode.Mod: + if x.L.GetType() != nil && x.R.GetType() != nil { + xTp := mergeArithType(x.L.GetType().Tp, x.R.GetType().Tp) + x.Type = types.NewFieldType(xTp) + leftUnsigned := x.L.GetType().Flag & mysql.UnsignedFlag + rightUnsigned := x.R.GetType().Flag & mysql.UnsignedFlag + // If both operands are unsigned, result is unsigned. + x.Type.Flag |= (leftUnsigned & rightUnsigned) + } + case opcode.Div: + if x.L.GetType() != nil && x.R.GetType() != nil { + xTp := mergeArithType(x.L.GetType().Tp, x.R.GetType().Tp) + if xTp == mysql.TypeLonglong { + xTp = mysql.TypeDecimal + } + x.Type = types.NewFieldType(xTp) + } + } +} + +func mergeArithType(a, b byte) byte { + switch a { + case mysql.TypeString, mysql.TypeVarchar, mysql.TypeVarString, mysql.TypeDouble, mysql.TypeFloat: + return mysql.TypeDouble + } + switch b { + case mysql.TypeString, mysql.TypeVarchar, mysql.TypeVarString, mysql.TypeDouble, mysql.TypeFloat: + return mysql.TypeDouble + } + if a == mysql.TypeNewDecimal || b == mysql.TypeNewDecimal { + return mysql.TypeNewDecimal + } + return mysql.TypeLonglong +} + +func (v *typeInferrer) unaryOperation(x *ast.UnaryOperationExpr) { + switch x.Op { + case opcode.Not: + x.Type = types.NewFieldType(mysql.TypeLonglong) + case opcode.BitNeg: + x.Type = types.NewFieldType(mysql.TypeLonglong) + x.Type.Flag |= mysql.UnsignedFlag + case opcode.Plus: + x.Type = x.V.GetType() + case opcode.Minus: + x.Type = types.NewFieldType(mysql.TypeLonglong) + if x.V.GetType() != nil { + switch x.V.GetType().Tp { + case mysql.TypeString, mysql.TypeVarchar, mysql.TypeVarString, mysql.TypeDouble, mysql.TypeFloat: + x.Type.Tp = mysql.TypeDouble + case mysql.TypeNewDecimal: + x.Type.Tp = mysql.TypeNewDecimal } } } - return in, true } type preEvaluator struct { diff --git a/optimizer/typeinferer_test.go b/optimizer/typeinferer_test.go new file mode 100644 index 0000000000..037669fd7d --- /dev/null +++ b/optimizer/typeinferer_test.go @@ -0,0 +1,88 @@ +// Copyright 2015 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package optimizer_test + +import ( + . "github.com/pingcap/check" + "github.com/pingcap/tidb" + "github.com/pingcap/tidb/ast" + "github.com/pingcap/tidb/context" + "github.com/pingcap/tidb/mysql" + "github.com/pingcap/tidb/optimizer" + "github.com/pingcap/tidb/sessionctx" + "github.com/pingcap/tidb/util/testkit" +) + +var _ = Suite(&testTypeInferrerSuite{}) + +type testTypeInferrerSuite struct { +} + +func (ts *testTypeInferrerSuite) TestInterType(c *C) { + store, err := tidb.NewStore(tidb.EngineGoLevelDBMemory) + c.Assert(err, IsNil) + defer store.Close() + testKit := testkit.NewTestKit(c, store) + testKit.MustExec("use test") + testKit.MustExec("create table t (c1 int, c2 double, c3 text)") + cases := []struct { + expr string + tp byte + }{ + {"c1", mysql.TypeLong}, + {"+1", mysql.TypeLonglong}, + {"-1", mysql.TypeLonglong}, + {"-'1'", mysql.TypeDouble}, + {"~1", mysql.TypeLonglong}, + {"!true", mysql.TypeLonglong}, + + {"c1 is true", mysql.TypeLonglong}, + {"c2 is null", mysql.TypeLonglong}, + {"cast(1 as decimal)", mysql.TypeNewDecimal}, + + {"1 and 1", mysql.TypeLonglong}, + {"1 or 1", mysql.TypeLonglong}, + {"1 xor 1", mysql.TypeLonglong}, + + {"'1' & 2", mysql.TypeLonglong}, + {"'1' | 2", mysql.TypeLonglong}, + {"'1' ^ 2", mysql.TypeLonglong}, + {"'1' << 1", mysql.TypeLonglong}, + {"'1' >> 1", mysql.TypeLonglong}, + + {"1 + '1'", mysql.TypeDouble}, + {"1 + 1.1", mysql.TypeNewDecimal}, + {"1 div 2", mysql.TypeLonglong}, + + {"1 > any (select 1)", mysql.TypeLonglong}, + {"exists (select 1)", mysql.TypeLonglong}, + {"1 in (2, 3)", mysql.TypeLonglong}, + {"'abc' like 'abc'", mysql.TypeLonglong}, + {"'abc' rlike 'abc'", mysql.TypeLonglong}, + {"(1+1)", mysql.TypeLonglong}, + } + for _, ca := range cases { + ctx := testKit.Se.(context.Context) + stmts, err := tidb.Parse(ctx, "select "+ca.expr+" from t") + c.Assert(err, IsNil) + c.Assert(stmts, HasLen, 1) + stmt := stmts[0].(*ast.SelectStmt) + is := sessionctx.GetDomain(ctx).InfoSchema() + err = optimizer.ResolveName(stmt, is, ctx) + c.Assert(err, IsNil) + optimizer.InferType(stmt) + tp := stmt.GetResultFields()[0].Column.Tp + c.Assert(tp, Equals, ca.tp, Commentf("for %s", ca.expr)) + } +} diff --git a/parser/parser.y b/parser/parser.y index b421325596..dca9fc76d8 100644 --- a/parser/parser.y +++ b/parser/parser.y @@ -104,6 +104,8 @@ import ( cross "CROSS" curDate "CURDATE" currentDate "CURRENT_DATE" + curTime "CUR_TIME" + currentTime "CURRENT_TIME" currentUser "CURRENT_USER" database "DATABASE" databases "DATABASES" @@ -210,8 +212,11 @@ import ( outer "OUTER" password "PASSWORD" placeholder "PLACEHOLDER" + pow "POW" + power "POWER" prepare "PREPARE" primary "PRIMARY" + procedure "PROCEDURE" quarter "QUARTER" quick "QUICK" rand "RAND" @@ -1680,14 +1685,14 @@ UnReservedKeyword: | "VALUE" | "WARNINGS" | "YEAR" | "MODE" | "WEEK" | "ANY" | "SOME" | "USER" | "IDENTIFIED" | "COLLATION" | "COMMENT" | "AVG_ROW_LENGTH" | "CONNECTION" | "CHECKSUM" | "COMPRESSION" | "KEY_BLOCK_SIZE" | "MAX_ROWS" | "MIN_ROWS" | "NATIONAL" | "ROW" | "QUARTER" | "ESCAPE" | "GRANTS" | "FIELDS" | "TRIGGERS" | "DELAY_KEY_WRITE" | "ISOLATION" -| "REPEATABLE" | "COMMITTED" | "UNCOMMITTED" | "ONLY" | "SERIALIZABLE" | "LEVEL" +| "REPEATABLE" | "COMMITTED" | "UNCOMMITTED" | "ONLY" | "SERIALIZABLE" | "LEVEL" | "VARIABLES" NotKeywordToken: "ABS" | "ADDDATE" | "COALESCE" | "CONCAT" | "CONCAT_WS" | "COUNT" | "DAY" | "DATE_ADD" | "DATE_SUB" | "DAYOFMONTH" | "DAYOFWEEK" | "DAYOFYEAR" | "FOUND_ROWS" | "GROUP_CONCAT"| "HOUR" | "IFNULL" | "LENGTH" | "LOCATE" | "MAX" -| "MICROSECOND" | "MIN" | "MINUTE" | "NULLIF" | "MONTH" | "NOW" | "RAND" | "SECOND" | "SQL_CALC_FOUND_ROWS" +| "MICROSECOND" | "MIN" | "MINUTE" | "NULLIF" | "MONTH" | "NOW" | "POW" | "POWER" | "RAND" | "SECOND" | "SQL_CALC_FOUND_ROWS" | "SUBDATE" | "SUBSTRING" %prec lowerThanLeftParen | "SUBSTRING_INDEX" | "SUM" | "TRIM" | "WEEKDAY" | "WEEKOFYEAR" -| "YEARWEEK" | "CONNECTION_ID" +| "YEARWEEK" | "CONNECTION_ID" | "CUR_TIME" /************************************************************************************ * @@ -2137,6 +2142,22 @@ FunctionCallNonKeyword: { $$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1.(string))} } +| "CUR_TIME" '(' ExpressionOpt ')' + { + args := []ast.ExprNode{} + if $3 != nil { + args = append(args, $3.(ast.ExprNode)) + } + $$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1.(string)), Args: args} + } +| "CURRENT_TIME" FuncDatetimePrec + { + args := []ast.ExprNode{} + if $2 != nil { + args = append(args, $2.(ast.ExprNode)) + } + $$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1.(string)), Args: args} + } | "CURRENT_TIMESTAMP" FuncDatetimePrec { args := []ast.ExprNode{} @@ -2257,6 +2278,16 @@ FunctionCallNonKeyword: { $$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1.(string)), Args: $3.([]ast.ExprNode)} } +| "POW" '(' Expression ',' Expression ')' + { + args := []ast.ExprNode{$3.(ast.ExprNode), $5.(ast.ExprNode)} + $$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1.(string)), Args: args} + } +| "POWER" '(' Expression ',' Expression ')' + { + args := []ast.ExprNode{$3.(ast.ExprNode), $5.(ast.ExprNode)} + $$ = &ast.FuncCallExpr{FnName: model.NewCIStr($1.(string)), Args: args} + } | "RAND" '(' ExpressionOpt ')' { @@ -3438,6 +3469,12 @@ ShowTargetFilterable: DBName: $2.(string), } } +| "PROCEDURE" "STATUS" + { + $$ = &ast.ShowStmt { + Tp: ast.ShowProcedureStatus, + } + } ShowLikeOrWhereOpt: { diff --git a/parser/parser_test.go b/parser/parser_test.go index d5bd73b7ba..86dfde3316 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -41,6 +41,7 @@ func (s *testParserSuite) TestSimple(c *C) { "collation", "comment", "avg_row_length", "checksum", "compression", "connection", "key_block_size", "max_rows", "min_rows", "national", "row", "quarter", "escape", "grants", "status", "fields", "triggers", "delay_key_write", "isolation", "repeatable", "committed", "uncommitted", "only", "serializable", "level", + "curtime", "variables", } for _, kw := range unreservedKws { src := fmt.Sprintf("SELECT %s FROM tbl;", kw) @@ -261,7 +262,8 @@ func (s *testParserSuite) TestDMLStmt(c *C) { {`SHOW COLUMNS FROM City;`, true}, {`SHOW FIELDS FROM City;`, true}, {`SHOW TRIGGERS LIKE 't'`, true}, - {"SHOW DATABASES LIKE 'test2'", true}, + {`SHOW DATABASES LIKE 'test2'`, true}, + {`SHOW PROCEDURE STATUS WHERE Db='test'`, true}, // For default value {"CREATE TABLE sbtest (id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, k integer UNSIGNED DEFAULT '0' NOT NULL, c char(120) DEFAULT '' NOT NULL, pad char(60) DEFAULT '' NOT NULL, PRIMARY KEY (id) )", true}, @@ -330,6 +332,10 @@ func (s *testParserSuite) TestBuiltin(c *C) { table := []testCase{ // For buildin functions {"SELECT DAYOFMONTH('2007-02-03');", true}, + {"SELECT POW(1, 2)", true}, + {"SELECT POW(1, 0.5)", true}, + {"SELECT POW(1, -1)", true}, + {"SELECT POW(-1, 1)", true}, {"SELECT RAND();", true}, {"SELECT RAND(1);", true}, @@ -394,6 +400,13 @@ func (s *testParserSuite) TestBuiltin(c *C) { {"select now(6)", true}, {"select sysdate(), sysdate(6)", true}, + // Select current_time + {"select current_time", true}, + {"select current_time()", true}, + {"select current_time(6)", true}, + {"select curtime()", true}, + {"select curtime(6)", true}, + // For time extract {`select extract(microsecond from "2011-11-11 10:10:10.123456")`, true}, {`select extract(second from "2011-11-11 10:10:10.123456")`, true}, diff --git a/parser/scanner.l b/parser/scanner.l index af5d5d6acd..ca30f01a61 100644 --- a/parser/scanner.l +++ b/parser/scanner.l @@ -323,6 +323,8 @@ create {c}{r}{e}{a}{t}{e} cross {c}{r}{o}{s}{s} curdate {c}{u}{r}{d}{a}{t}{e} current_date {c}{u}{r}{r}{e}{n}{t}_{d}{a}{t}{e} +curtime {c}{u}{r}{t}{i}{m}{e} +current_time {c}{u}{r}{r}{e}{n}{t}_{t}{i}{m}{e} current_user {c}{u}{r}{r}{e}{n}{t}_{u}{s}{e}{r} database {d}{a}{t}{a}{b}{a}{s}{e} databases {d}{a}{t}{a}{b}{a}{s}{e}{s} @@ -414,8 +416,11 @@ or {o}{r} order {o}{r}{d}{e}{r} outer {o}{u}{t}{e}{r} password {p}{a}{s}{s}{w}{o}{r}{d} +pow {p}{o}{w} +power {p}{o}{w}{e}{r} prepare {p}{r}{e}{p}{a}{r}{e} primary {p}{r}{i}{m}{a}{r}{y} +procedure {p}{r}{o}{c}{e}{d}{u}{r}{e} quarter {q}{u}{a}{r}{t}{e}{r} quick {q}{u}{i}{c}{k} rand {r}{a}{n}{d} @@ -692,6 +697,10 @@ year_month {y}{e}{a}{r}_{m}{o}{n}{t}{h} return curDate {current_date} lval.item = string(l.val) return currentDate +{curtime} lval.item = string(l.val) + return curTime +{current_time} lval.item = string(l.val) + return currentTime {current_user} lval.item = string(l.val) return currentUser {database} lval.item = string(l.val) @@ -850,9 +859,14 @@ year_month {y}{e}{a}{r}_{m}{o}{n}{t}{h} {outer} return outer {password} lval.item = string(l.val) return password +{pow} lval.item = string(l.val) + return pow +{power} lval.item = string(l.val) + return power {prepare} lval.item = string(l.val) return prepare {primary} return primary +{procedure} return procedure {quarter} lval.item = string(l.val) return quarter {quick} lval.item = string(l.val) diff --git a/plan/plans/index.go b/plan/plans/index.go index 3ea55f87cb..645efa44bf 100644 --- a/plan/plans/index.go +++ b/plan/plans/index.go @@ -436,12 +436,12 @@ func (r *indexPlan) pointLookup(ctx context.Context, val interface{}) (*plan.Row } var exist bool var h int64 - // We expect a kv.ErrKeyExists Error because we pass -1 as the handle which is not equal to the existed handle. + // We expect a terror.ErrKeyExists Error because we pass -1 as the handle which is not equal to the existed handle. exist, h, err = r.idx.Exist(txn, []interface{}{val}, -1) if !exist { return nil, errors.Trace(err) } - if terror.ErrorNotEqual(kv.ErrKeyExists, err) { + if terror.ErrorNotEqual(terror.ErrKeyExists, err) { return nil, errors.Trace(err) } var row *plan.Row diff --git a/plan/plans/info_test.go b/plan/plans/info_test.go index 5067b7529f..405e7acbbd 100644 --- a/plan/plans/info_test.go +++ b/plan/plans/info_test.go @@ -68,6 +68,14 @@ func mustQuery(c *C, currDB *sql.DB, s string) int { return cnt } +func mustFailQuery(c *C, currDB *sql.DB, s string) { + rows, err := currDB.Query(s) + c.Assert(err, IsNil) + rows.Next() + c.Assert(rows.Err(), NotNil) + rows.Close() +} + func mustExec(c *C, currDB *sql.DB, sql string) sql.Result { tx := mustBegin(c, currDB) r := mustExecuteSql(c, tx, sql) diff --git a/plan/plans/show.go b/plan/plans/show.go index 72ebaa5199..798241cfc4 100644 --- a/plan/plans/show.go +++ b/plan/plans/show.go @@ -128,6 +128,9 @@ func (s *ShowPlan) GetFields() []*field.ResultField { "sql_mode", "Definer", "character_set_client", "collation_connection", "Database Collation"} types = []byte{mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar} + case stmt.ShowProcedureStatus: + names = []string{} + types = []byte{} } fields := make([]*field.ResultField, 0, len(names)) for i, name := range names { @@ -193,6 +196,8 @@ func (s *ShowPlan) fetchAll(ctx context.Context) error { return s.fetchShowGrants(ctx) case stmt.ShowTriggers: return s.fetchShowTriggers(ctx) + case stmt.ShowProcedureStatus: + return s.fetchShowProcedureStatus(ctx) } return nil } @@ -356,9 +361,15 @@ func (s *ShowPlan) fetchShowTables(ctx context.Context) error { s.Pattern.Expr = expression.Value{Val: data[0]} } else if s.Where != nil { m[expression.ExprEvalIdentFunc] = func(name string) (interface{}, error) { + // The first column is Tables_in_{database}. + // If s.Full is true, there will be a column named Table_type at the second place. if s.Full && strings.EqualFold(name, "Table_type") { return data[1], nil } + fieldName := fmt.Sprintf("Tables_in_%s", dbName) + if strings.EqualFold(name, fieldName) { + return data[0], nil + } return nil, errors.Errorf("unknown field %s", name) } } @@ -660,3 +671,8 @@ func (s *ShowPlan) fetchShowGrants(ctx context.Context) error { func (s *ShowPlan) fetchShowTriggers(ctx context.Context) error { return nil } + +// Do nothing. +func (s *ShowPlan) fetchShowProcedureStatus(ctx context.Context) error { + return nil +} diff --git a/plan/plans/show_test.go b/plan/plans/show_test.go index 8f63a1f87e..041075a405 100644 --- a/plan/plans/show_test.go +++ b/plan/plans/show_test.go @@ -360,11 +360,13 @@ func (p *testShowSuit) TestShowTables(c *C) { c.Assert(cnt, Equals, 2) cnt = mustQuery(c, testDB, `show full tables where Table_type != 'VIEW';`) c.Assert(cnt, Equals, 3) + cnt = mustQuery(c, testDB, `show full tables where Tables_in_test='tab00' and Table_type != 'VIEW';`) + c.Assert(cnt, Equals, 1) + mustFailQuery(c, testDB, `show full tables where Tables_in_unknowndb ='tab00' and Table_type != 'VIEW';`) + mustFailQuery(c, testDB, `show tables where Tables_in_test ='tab00' and Table_type != 'VIEW';`) mustQuery(c, testDB, `show create table tab00;`) - rows, _ := testDB.Query(`show create table abc;`) - rows.Next() - c.Assert(rows.Err(), NotNil) + mustFailQuery(c, testDB, `show create table abc;`) } func (p *testShowSuit) TestShowGrants(c *C) { diff --git a/session.go b/session.go index 730b18e617..5fd018cef8 100644 --- a/session.go +++ b/session.go @@ -29,7 +29,6 @@ import ( "github.com/juju/errors" "github.com/ngaut/log" "github.com/pingcap/tidb/context" - "github.com/pingcap/tidb/expression/builtin" "github.com/pingcap/tidb/field" "github.com/pingcap/tidb/kv" "github.com/pingcap/tidb/meta" @@ -65,6 +64,7 @@ type Session interface { ExecutePreparedStmt(stmtID uint32, param ...interface{}) (rset.Recordset, error) DropPreparedStmt(stmtID uint32) error SetClientCapability(uint32) // Set client capability flags + SetConnectionID(uint64) Close() error Retry() error Auth(user string, auth []byte, salt []byte) bool @@ -145,6 +145,10 @@ func (s *session) SetClientCapability(capability uint32) { variable.GetSessionVars(s).ClientCapability = capability } +func (s *session) SetConnectionID(connectionID uint64) { + variable.GetSessionVars(s).ConnectionID = connectionID +} + func (s *session) FinishTxn(rollback bool) error { // transaction has already been committed or rolled back if s.txn == nil { @@ -580,9 +584,6 @@ func CreateSession(store kv.Storage) (Session, error) { variable.BindSessionVars(s) variable.GetSessionVars(s).SetStatusFlag(mysql.ServerStatusAutocommit, true) - // set connection id - s.SetValue(builtin.ConnectionIDKey, s.sid) - // session implements variable.GlobalVarAccessor. Bind it to ctx. variable.BindGlobalVarAccessor(s, s) diff --git a/session_test.go b/session_test.go index 7bfbaf8376..b4d986dc55 100644 --- a/session_test.go +++ b/session_test.go @@ -1364,10 +1364,26 @@ func (s *testSessionSuite) TestMultiColumnIndex(c *C) { checkPlan(c, se, sql, expectedExplain) mustExecMatch(c, se, sql, [][]interface{}{{1}}) + // Test varchar type. + mustExecSQL(c, se, "drop table t;") + mustExecSQL(c, se, "create table t (c1 varchar(64), c2 varchar(64), index c1_c2 (c1, c2));") + mustExecSQL(c, se, "insert into t values ('abc', 'def')") + sql = "select c1 from t where c1 = 'abc'" + mustExecMatch(c, se, sql, [][]interface{}{{[]byte("abc")}}) + err := se.Close() c.Assert(err, IsNil) } +func (s *testSessionSuite) TestSubstringIndexExpr(c *C) { + store := newStore(c, s.dbName) + se := newSession(c, store, s.dbName) + mustExecSQL(c, se, "drop table if exists t;") + mustExecSQL(c, se, "create table t (c varchar(128));") + mustExecSQL(c, se, `insert into t values ("www.pingcap.com");`) + mustExecMatch(c, se, "SELECT DISTINCT SUBSTRING_INDEX(c, '.', 2) from t;", [][]interface{}{{"www.pingcap"}}) +} + func (s *testSessionSuite) TestGlobalVarAccessor(c *C) { varName := "max_allowed_packet" diff --git a/sessionctx/variable/session.go b/sessionctx/variable/session.go index 23c04e0ebb..d0c3acfb9a 100644 --- a/sessionctx/variable/session.go +++ b/sessionctx/variable/session.go @@ -36,6 +36,9 @@ type SessionVars struct { // Client capability ClientCapability uint32 + // Connection ID + ConnectionID uint64 + // Found rows FoundRows uint64 diff --git a/stmt/stmt.go b/stmt/stmt.go index e0ab1d5e05..d051b405cc 100644 --- a/stmt/stmt.go +++ b/stmt/stmt.go @@ -61,6 +61,7 @@ const ( ShowCreateTable ShowGrants ShowTriggers + ShowProcedureStatus ) const ( diff --git a/stmt/stmts/insert.go b/stmt/stmts/insert.go index 7c298f1c06..831b990e5c 100644 --- a/stmt/stmts/insert.go +++ b/stmt/stmts/insert.go @@ -215,7 +215,7 @@ func (s *InsertIntoStmt) Exec(ctx context.Context) (_ rset.Recordset, err error) continue } - if len(s.OnDuplicate) == 0 || !terror.ErrorEqual(err, kv.ErrKeyExists) { + if len(s.OnDuplicate) == 0 || !terror.ErrorEqual(err, terror.ErrKeyExists) { return nil, errors.Trace(err) } if err = execOnDuplicateUpdate(ctx, t, row, h, toUpdateColumns); err != nil { diff --git a/stmt/stmts/replace.go b/stmt/stmts/replace.go index a6018f6771..07793a08d0 100644 --- a/stmt/stmts/replace.go +++ b/stmt/stmts/replace.go @@ -16,7 +16,6 @@ package stmts import ( "github.com/juju/errors" "github.com/pingcap/tidb/context" - "github.com/pingcap/tidb/kv" "github.com/pingcap/tidb/rset" "github.com/pingcap/tidb/sessionctx/variable" "github.com/pingcap/tidb/table" @@ -83,7 +82,7 @@ func (s *ReplaceIntoStmt) Exec(ctx context.Context) (_ rset.Recordset, err error if err == nil { continue } - if err != nil && !terror.ErrorEqual(err, kv.ErrKeyExists) { + if err != nil && !terror.ErrorEqual(err, terror.ErrKeyExists) { return nil, errors.Trace(err) } diff --git a/store/hbase/kv.go b/store/hbase/kv.go index 5865da4ea4..65153aa0db 100644 --- a/store/hbase/kv.go +++ b/store/hbase/kv.go @@ -182,7 +182,7 @@ func (d Driver) Open(path string) (kv.Storage, error) { } if !b { // Create new hbase table for store. - t := hbase.NewTableDesciptor(hbase.NewTableNameWithDefaultNS(tableName)) + t := hbase.NewTableDesciptor(tableName) cf := hbase.NewColumnFamilyDescriptor(hbaseColFamily) cf.AddAttr("THEMIS_ENABLE", "true") t.AddColumnDesc(cf) diff --git a/store/localstore/boltdb/boltdb.go b/store/localstore/boltdb/boltdb.go index bbc4d95d24..4095f9c2fe 100644 --- a/store/localstore/boltdb/boltdb.go +++ b/store/localstore/boltdb/boltdb.go @@ -53,11 +53,29 @@ func (d *db) Get(key []byte) ([]byte, error) { func (d *db) MultiSeek(keys [][]byte) []*engine.MSeekResult { res := make([]*engine.MSeekResult, 0, len(keys)) - for _, key := range keys { - r := &engine.MSeekResult{} - r.Key, r.Value, r.Err = d.Seek(key) - res = append(res, r) - } + d.DB.View(func(tx *bolt.Tx) error { + b := tx.Bucket(bucketName) + c := b.Cursor() + for _, key := range keys { + var k, v []byte + if key == nil { + k, v = c.First() + } else { + k, v = c.Seek(key) + } + + r := &engine.MSeekResult{} + if k == nil { + r.Err = engine.ErrNotFound + } else { + r.Key, r.Value, r.Err = bytes.CloneBytes(k), bytes.CloneBytes(v), nil + } + + res = append(res, r) + } + return nil + }) + return res } diff --git a/store/localstore/boltdb/boltdb_test.go b/store/localstore/boltdb/boltdb_test.go index 862c4eedc7..54bbf0a27f 100644 --- a/store/localstore/boltdb/boltdb_test.go +++ b/store/localstore/boltdb/boltdb_test.go @@ -95,7 +95,7 @@ func (s *testSuite) TestPutNilAndDelete(c *C) { c.Assert(found, Equals, false) } -func (s *testSuite) TestDB(c *C) { +func (s *testSuite) TestGetSet(c *C) { db := s.db b := db.NewBatch() @@ -113,29 +113,55 @@ func (s *testSuite) TestDB(c *C) { v, err = db.Get([]byte("a")) c.Assert(err, IsNil) c.Assert(v, DeepEquals, []byte("1")) +} - k, v, err := db.Seek(nil) +func (s *testSuite) TestSeek(c *C) { + b := s.db.NewBatch() + b.Put([]byte("a"), []byte("1")) + b.Put([]byte("b"), []byte("2")) + err := s.db.Commit(b) + c.Assert(err, IsNil) + + k, v, err := s.db.Seek(nil) c.Assert(err, IsNil) c.Assert(k, BytesEquals, []byte("a")) c.Assert(v, BytesEquals, []byte("1")) - k, v, err = db.Seek([]byte("a")) + k, v, err = s.db.Seek([]byte("a")) c.Assert(err, IsNil) c.Assert(k, BytesEquals, []byte("a")) c.Assert(v, BytesEquals, []byte("1")) - k, v, err = db.Seek([]byte("b")) + k, v, err = s.db.Seek([]byte("b")) c.Assert(err, IsNil) c.Assert(k, BytesEquals, []byte("b")) c.Assert(v, BytesEquals, []byte("2")) - k, v, err = db.Seek([]byte("a1")) + k, v, err = s.db.Seek([]byte("a1")) c.Assert(err, IsNil) c.Assert(k, BytesEquals, []byte("b")) c.Assert(v, BytesEquals, []byte("2")) - k, v, err = db.Seek([]byte("c1")) + k, v, err = s.db.Seek([]byte("c1")) c.Assert(err, NotNil) c.Assert(k, IsNil) c.Assert(v, IsNil) } + +func (s *testSuite) TestMultiSeek(c *C) { + b := s.db.NewBatch() + b.Put([]byte("a"), []byte("1")) + b.Put([]byte("b"), []byte("2")) + err := s.db.Commit(b) + c.Assert(err, IsNil) + + m := s.db.MultiSeek([][]byte{[]byte("z"), []byte("a"), []byte("a1")}) + c.Assert(m, HasLen, 3) + c.Assert(m[0].Err, NotNil) + c.Assert(m[1].Err, IsNil) + c.Assert(m[1].Key, BytesEquals, []byte("a")) + c.Assert(m[1].Value, BytesEquals, []byte("1")) + c.Assert(m[2].Err, IsNil) + c.Assert(m[2].Key, BytesEquals, []byte("b")) + c.Assert(m[2].Value, BytesEquals, []byte("2")) +} diff --git a/store/localstore/compactor.go b/store/localstore/compactor.go index 2a70276261..97698fd831 100644 --- a/store/localstore/compactor.go +++ b/store/localstore/compactor.go @@ -25,13 +25,23 @@ import ( "github.com/pingcap/tidb/util/bytes" ) -var _ kv.Compactor = (*localstoreCompactor)(nil) - const ( deleteWorkerCnt = 3 ) -var localCompactDefaultPolicy = kv.CompactPolicy{ +// compactPolicy defines gc policy of MVCC storage. +type compactPolicy struct { + // SafePoint specifies + SafePoint int + // TriggerInterval specifies how often should the compactor + // scans outdated data. + TriggerInterval time.Duration + // BatchDeleteCnt specifies the batch size for + // deleting outdated data transaction. + BatchDeleteCnt int +} + +var localCompactDefaultPolicy = compactPolicy{ SafePoint: 20 * 1000, // in ms TriggerInterval: 10 * time.Second, BatchDeleteCnt: 100, @@ -45,7 +55,7 @@ type localstoreCompactor struct { workerWaitGroup *sync.WaitGroup ticker *time.Ticker db engine.DB - policy kv.CompactPolicy + policy compactPolicy } func (gc *localstoreCompactor) OnSet(k kv.Key) { @@ -196,7 +206,7 @@ func (gc *localstoreCompactor) Stop() { gc.workerWaitGroup.Wait() } -func newLocalCompactor(policy kv.CompactPolicy, db engine.DB) *localstoreCompactor { +func newLocalCompactor(policy compactPolicy, db engine.DB) *localstoreCompactor { return &localstoreCompactor{ recentKeys: make(map[string]struct{}), stopCh: make(chan struct{}), diff --git a/store/localstore/compactor_test.go b/store/localstore/compactor_test.go index 0f4da22d13..b3bf436b6f 100644 --- a/store/localstore/compactor_test.go +++ b/store/localstore/compactor_test.go @@ -46,7 +46,7 @@ func (s *localstoreCompactorTestSuite) TestCompactor(c *C) { db := store.(*dbStore).db store.(*dbStore).compactor.Stop() - policy := kv.CompactPolicy{ + policy := compactPolicy{ SafePoint: 500, BatchDeleteCnt: 1, TriggerInterval: 100 * time.Millisecond, @@ -119,7 +119,7 @@ func (s *localstoreCompactorTestSuite) TestStartStop(c *C) { db := store.(*dbStore).db for i := 0; i < 10000; i++ { - policy := kv.CompactPolicy{ + policy := compactPolicy{ SafePoint: 500, BatchDeleteCnt: 1, TriggerInterval: 100 * time.Millisecond, diff --git a/store/localstore/goleveldb/goleveldb_test.go b/store/localstore/goleveldb/goleveldb_test.go index 1e53166037..23a3392902 100644 --- a/store/localstore/goleveldb/goleveldb_test.go +++ b/store/localstore/goleveldb/goleveldb_test.go @@ -43,7 +43,7 @@ func (s *testSuite) TearDownSuite(c *C) { s.db.Close() } -func (s *testSuite) TestDB(c *C) { +func (s *testSuite) TestGetSet(c *C) { db := s.db b := db.NewBatch() @@ -70,29 +70,55 @@ func (s *testSuite) TestDB(c *C) { v, err = db.Get([]byte("a")) c.Assert(err, IsNil) c.Assert(v, DeepEquals, []byte("2")) +} - k, v, err := db.Seek(nil) +func (s *testSuite) TestSeek(c *C) { + b := s.db.NewBatch() + b.Put([]byte("a"), []byte("1")) + b.Put([]byte("b"), []byte("2")) + err := s.db.Commit(b) + c.Assert(err, IsNil) + + k, v, err := s.db.Seek(nil) c.Assert(err, IsNil) c.Assert(k, BytesEquals, []byte("a")) - c.Assert(v, BytesEquals, []byte("2")) + c.Assert(v, BytesEquals, []byte("1")) - k, v, err = db.Seek([]byte("a")) + k, v, err = s.db.Seek([]byte("a")) c.Assert(err, IsNil) c.Assert(k, BytesEquals, []byte("a")) - c.Assert(v, BytesEquals, []byte("2")) + c.Assert(v, BytesEquals, []byte("1")) - k, v, err = db.Seek([]byte("b")) + k, v, err = s.db.Seek([]byte("b")) c.Assert(err, IsNil) c.Assert(k, BytesEquals, []byte("b")) c.Assert(v, BytesEquals, []byte("2")) - k, v, err = db.Seek([]byte("a1")) + k, v, err = s.db.Seek([]byte("a1")) c.Assert(err, IsNil) c.Assert(k, BytesEquals, []byte("b")) c.Assert(v, BytesEquals, []byte("2")) - k, v, err = db.Seek([]byte("c1")) + k, v, err = s.db.Seek([]byte("c1")) c.Assert(err, NotNil) c.Assert(k, IsNil) c.Assert(v, IsNil) } + +func (s *testSuite) TestMultiSeek(c *C) { + b := s.db.NewBatch() + b.Put([]byte("a"), []byte("1")) + b.Put([]byte("b"), []byte("2")) + err := s.db.Commit(b) + c.Assert(err, IsNil) + + m := s.db.MultiSeek([][]byte{[]byte("z"), []byte("a"), []byte("a1")}) + c.Assert(m, HasLen, 3) + c.Assert(m[0].Err, NotNil) + c.Assert(m[1].Err, IsNil) + c.Assert(m[1].Key, BytesEquals, []byte("a")) + c.Assert(m[1].Value, BytesEquals, []byte("1")) + c.Assert(m[2].Err, IsNil) + c.Assert(m[2].Key, BytesEquals, []byte("b")) + c.Assert(m[2].Value, BytesEquals, []byte("2")) +} diff --git a/store/localstore/kv.go b/store/localstore/kv.go index 46e1939b86..484733120f 100644 --- a/store/localstore/kv.go +++ b/store/localstore/kv.go @@ -18,11 +18,13 @@ import ( "path/filepath" "runtime/debug" "sync" + "time" "github.com/juju/errors" "github.com/ngaut/log" "github.com/pingcap/tidb/kv" "github.com/pingcap/tidb/store/localstore/engine" + "github.com/pingcap/tidb/util/segmentmap" "github.com/twinj/uuid" ) @@ -39,6 +41,8 @@ const ( const ( maxSeekWorkers = 3 + + lowerWaterMark = 10 // second ) type command struct { @@ -65,8 +69,6 @@ type seekArgs struct { type commitArgs struct { } -//scheduler - // Seek searches for the first key in the engine which is >= key in byte order, returns (nil, nil, ErrNotFound) // if such key is not found. func (s *dbStore) Seek(key []byte) ([]byte, []byte, error) { @@ -103,7 +105,8 @@ func (s *dbStore) CommitTxn(txn *dbTxn) error { return errors.Trace(err) } -func (s *dbStore) seekWorker(seekCh chan *command) { +func (s *dbStore) seekWorker(wg *sync.WaitGroup, seekCh chan *command) { + defer wg.Done() for { var pending []*command select { @@ -133,10 +136,17 @@ func (s *dbStore) seekWorker(seekCh chan *command) { func (s *dbStore) scheduler() { closed := false seekCh := make(chan *command, 1000) + wgSeekWorkers := &sync.WaitGroup{} + wgSeekWorkers.Add(maxSeekWorkers) for i := 0; i < maxSeekWorkers; i++ { - go s.seekWorker(seekCh) + go s.seekWorker(wgSeekWorkers, seekCh) } + segmentIndex := 0 + + tick := time.NewTicker(time.Second) + defer tick.Stop() + for { select { case cmd := <-s.commandCh: @@ -152,9 +162,30 @@ func (s *dbStore) scheduler() { } case <-s.closeCh: closed = true - s.wg.Done() // notify seek worker to exit close(seekCh) + wgSeekWorkers.Wait() + s.wg.Done() + case <-tick.C: + segmentIndex = segmentIndex % s.recentUpdates.SegmentCount() + s.cleanRecentUpdates(segmentIndex) + segmentIndex++ + } + } +} + +func (s *dbStore) cleanRecentUpdates(segmentIndex int) { + m, err := s.recentUpdates.GetSegment(segmentIndex) + if err != nil { + log.Error(err) + return + } + + now := time.Now().Unix() + for k, v := range m { + dis := now - version2Second(v.(kv.Version)) + if dis > lowerWaterMark { + delete(m, k) } } } @@ -166,12 +197,12 @@ func (s *dbStore) tryLock(txn *dbTxn) (err error) { return errors.Trace(kv.ErrLockConflict) } - lastVer, ok := s.recentUpdates[k] + lastVer, ok := s.recentUpdates.Get([]byte(k)) if !ok { continue } // If there's newer version of this key, returns error. - if lastVer.Cmp(kv.Version{Ver: txn.tid}) > 0 { + if lastVer.(kv.Version).Cmp(kv.Version{Ver: txn.tid}) > 0 { return errors.Trace(kv.ErrConditionNotMatch) } } @@ -233,15 +264,13 @@ func (s *dbStore) NewBatch() engine.Batch { return s.db.NewBatch() } -//end of scheduler - type dbStore struct { db engine.DB txns map[uint64]*dbTxn keysLocked map[string]uint64 // TODO: clean up recentUpdates - recentUpdates map[string]kv.Version + recentUpdates *segmentmap.SegmentMap uuid string path string compactor *localstoreCompactor @@ -309,17 +338,21 @@ func (d Driver) Open(path string) (kv.Storage, error) { log.Info("[kv] New store", engineSchema) s := &dbStore{ - txns: make(map[uint64]*dbTxn), - keysLocked: make(map[string]uint64), - uuid: uuid.NewV4().String(), - path: engineSchema, - db: db, - compactor: newLocalCompactor(localCompactDefaultPolicy, db), - commandCh: make(chan *command, 1000), - closed: false, - closeCh: make(chan struct{}), - recentUpdates: make(map[string]kv.Version), - wg: &sync.WaitGroup{}, + txns: make(map[uint64]*dbTxn), + keysLocked: make(map[string]uint64), + uuid: uuid.NewV4().String(), + path: engineSchema, + db: db, + compactor: newLocalCompactor(localCompactDefaultPolicy, db), + commandCh: make(chan *command, 1000), + closed: false, + closeCh: make(chan struct{}), + wg: &sync.WaitGroup{}, + } + s.recentUpdates, err = segmentmap.NewSegmentMap(100) + if err != nil { + return nil, errors.Trace(err) + } mc.cache[engineSchema] = s s.compactor.Start() @@ -433,7 +466,7 @@ func (s *dbStore) unLockKeys(txn *dbTxn) error { } delete(s.keysLocked, k) - s.recentUpdates[k] = txn.version + s.recentUpdates.Set([]byte(k), txn.version, true) } return nil diff --git a/store/localstore/local_version_provider.go b/store/localstore/local_version_provider.go index 8450647678..6a2cdb8cd4 100644 --- a/store/localstore/local_version_provider.go +++ b/store/localstore/local_version_provider.go @@ -27,6 +27,14 @@ const ( timePrecisionOffset = 18 ) +func time2TsPhysical(t time.Time) uint64 { + return uint64((t.UnixNano() / int64(time.Millisecond)) << timePrecisionOffset) +} + +func version2Second(v kv.Version) int64 { + return int64(v.Ver>>timePrecisionOffset) / 1000 +} + // CurrentVersion implements the VersionProvider's GetCurrentVer interface. func (l *LocalVersionProvider) CurrentVersion() (kv.Version, error) { l.mu.Lock() @@ -34,7 +42,7 @@ func (l *LocalVersionProvider) CurrentVersion() (kv.Version, error) { for { var ts uint64 - ts = uint64((time.Now().UnixNano() / int64(time.Millisecond)) << timePrecisionOffset) + ts = time2TsPhysical(time.Now()) if l.lastTimestamp > ts { log.Error("[kv] invalid physical time stamp") diff --git a/table/tables/tables.go b/table/tables/tables.go index 920636a845..267f65d82e 100644 --- a/table/tables/tables.go +++ b/table/tables/tables.go @@ -430,7 +430,7 @@ func (t *Table) AddRecord(ctx context.Context, r []interface{}, h int64) (record } colVals, _ := v.FetchValues(r) if err = v.X.Create(bs, colVals, recordID); err != nil { - if terror.ErrorEqual(err, kv.ErrKeyExists) { + if terror.ErrorEqual(err, terror.ErrKeyExists) { // Get the duplicate row handle // For insert on duplicate syntax, we should update the row iter, _, err1 := v.X.Seek(bs, colVals) diff --git a/terror/terror.go b/terror/terror.go index c4818d3b76..0208aea6d6 100644 --- a/terror/terror.go +++ b/terror/terror.go @@ -19,6 +19,8 @@ import ( "strconv" "github.com/juju/errors" + "github.com/ngaut/log" + "github.com/pingcap/tidb/mysql" ) // Common base error instances. @@ -34,6 +36,8 @@ var ( UnknownSystemVar = ClassVariable.New(CodeUnknownSystemVar, "unknown system variable") MissConnectionID = ClassExpression.New(CodeMissConnectionID, "miss connection id information") + + ErrKeyExists = ClassKV.New(CodeKeyExists, "key already exist") ) // ErrCode represents a specific error type in a error class. @@ -57,6 +61,7 @@ const ( const ( CodeIncompatibleDBFormat ErrCode = iota + 1 CodeNoDataForHandle + CodeKeyExists ) // Variable error codes. @@ -190,6 +195,56 @@ func (e *Error) NotEqual(err error) bool { return !e.Equal(err) } +// ToSQLError convert Error to mysql.SQLError. +func (e *Error) ToSQLError() *mysql.SQLError { + code := e.getMySQLErrorCode() + return mysql.NewErrf(code, e.message) +} + +var defaultMySQLErrorCode uint16 + +func (e *Error) getMySQLErrorCode() uint16 { + codeMap, ok := errClassToMySQLCodes[e.class] + if !ok { + log.Warnf("Unknown error class: %v", e.class) + return defaultMySQLErrorCode + } + code, ok := codeMap[e.code] + if !ok { + log.Warnf("Unknown error class: %v", e.class) + return defaultMySQLErrorCode + } + return code +} + +var ( + // ErrCode to mysql error code map. + parserMySQLErrCodes = map[ErrCode]uint16{} + schemaMySQLErrCodes = map[ErrCode]uint16{} + optimizerMySQLErrCodes = map[ErrCode]uint16{} + executorMySQLErrCodes = map[ErrCode]uint16{} + kvMySQLErrCodes = map[ErrCode]uint16{ + CodeKeyExists: mysql.ErrDupEntry, + } + serverMySQLErrCodes = map[ErrCode]uint16{} + expressionMySQLErrCodes = map[ErrCode]uint16{} + + // ErrClass to code-map map. + errClassToMySQLCodes map[ErrClass](map[ErrCode]uint16) +) + +func init() { + errClassToMySQLCodes = make(map[ErrClass](map[ErrCode]uint16)) + errClassToMySQLCodes[ClassParser] = parserMySQLErrCodes + errClassToMySQLCodes[ClassSchema] = schemaMySQLErrCodes + errClassToMySQLCodes[ClassOptimizer] = optimizerMySQLErrCodes + errClassToMySQLCodes[ClassExecutor] = executorMySQLErrCodes + errClassToMySQLCodes[ClassKV] = kvMySQLErrCodes + errClassToMySQLCodes[ClassServer] = serverMySQLErrCodes + errClassToMySQLCodes[ClassExpression] = expressionMySQLErrCodes + defaultMySQLErrorCode = mysql.ErrUnknown +} + // ErrorEqual returns a boolean indicating whether err1 is equal to err2. func ErrorEqual(err1, err2 error) bool { e1 := errors.Cause(err1) diff --git a/terror/terror_test.go b/terror/terror_test.go index 22ed3f63eb..30a1aa616e 100644 --- a/terror/terror_test.go +++ b/terror/terror_test.go @@ -19,6 +19,7 @@ import ( "github.com/juju/errors" . "github.com/pingcap/check" + "github.com/pingcap/tidb/mysql" ) func TestT(t *testing.T) { @@ -110,3 +111,10 @@ func (s *testTErrorSuite) TestErrorEqual(c *C) { c.Assert(ErrorEqual(te1, te3), IsFalse) c.Assert(ErrorEqual(te3, te4), IsFalse) } + +func (s *testTErrorSuite) TestMySQLErrorCode(c *C) { + ke := ErrKeyExists.Gen("key exists") + me := ke.ToSQLError() + c.Assert(me.Code, Equals, uint16(mysql.ErrDupEntry)) + c.Assert(me.Message, Equals, "key exists") +} diff --git a/tidb-server/server/conn.go b/tidb-server/server/conn.go index 8775920230..3943173b17 100644 --- a/tidb-server/server/conn.go +++ b/tidb-server/server/conn.go @@ -191,7 +191,7 @@ func (cc *clientConn) readHandshakeResponse() error { } } // Open session and do auth - cc.ctx, err = cc.server.driver.OpenCtx(cc.capability, uint8(cc.collation), cc.dbname) + cc.ctx, err = cc.server.driver.OpenCtx(uint64(cc.connectionID), cc.capability, uint8(cc.collation), cc.dbname) if err != nil { cc.Close() return errors.Trace(err) @@ -319,10 +319,15 @@ func (cc *clientConn) writeOK() error { } func (cc *clientConn) writeError(e error) error { - var m *mysql.SQLError - var ok bool + var ( + m *mysql.SQLError + te *terror.Error + ok bool + ) originErr := errors.Cause(e) - if m, ok = originErr.(*mysql.SQLError); !ok { + if te, ok = originErr.(*terror.Error); ok { + m = te.ToSQLError() + } else { m = mysql.NewErrf(mysql.ErrUnknown, e.Error()) } diff --git a/tidb-server/server/driver.go b/tidb-server/server/driver.go index 1f1c9a15ff..d00444c46c 100644 --- a/tidb-server/server/driver.go +++ b/tidb-server/server/driver.go @@ -15,8 +15,8 @@ package server // IDriver opens IContext. type IDriver interface { - // OpenCtx opens an IContext with client capability, collation and dbname. - OpenCtx(capability uint32, collation uint8, dbname string) (IContext, error) + // OpenCtx opens an IContext with connection id, client capability, collation and dbname. + OpenCtx(connID uint64, capability uint32, collation uint8, dbname string) (IContext, error) } // IContext is the interface to execute commant. diff --git a/tidb-server/server/driver_tidb.go b/tidb-server/server/driver_tidb.go index c7f6fce4d6..aa59f6f189 100644 --- a/tidb-server/server/driver_tidb.go +++ b/tidb-server/server/driver_tidb.go @@ -110,9 +110,10 @@ func (ts *TiDBStatement) Close() error { } // OpenCtx implements IDriver. -func (qd *TiDBDriver) OpenCtx(capability uint32, collation uint8, dbname string) (IContext, error) { +func (qd *TiDBDriver) OpenCtx(connID uint64, capability uint32, collation uint8, dbname string) (IContext, error) { session, _ := tidb.CreateSession(qd.store) session.SetClientCapability(capability) + session.SetConnectionID(connID) if dbname != "" { _, err := session.Execute("use " + dbname) if err != nil { diff --git a/tidb-server/server/server_test.go b/tidb-server/server/server_test.go index 6b824d2046..ab2a95a4eb 100644 --- a/tidb-server/server/server_test.go +++ b/tidb-server/server/server_test.go @@ -17,8 +17,9 @@ import ( "database/sql" "testing" - _ "github.com/go-sql-driver/mysql" + "github.com/go-sql-driver/mysql" . "github.com/pingcap/check" + tmysql "github.com/pingcap/tidb/mysql" ) func TestT(t *testing.T) { @@ -220,6 +221,25 @@ func runTestConcurrentUpdate(c *C) { }) } +func runTestErrorCode(c *C) { + runTests(c, dsn, func(dbt *DBTest) { + dbt.mustExec("create table test (c int PRIMARY KEY);") + dbt.mustExec("insert into test values (1);") + txn1, err := dbt.db.Begin() + c.Assert(err, IsNil) + _, err = txn1.Exec("insert into test values(1)") + c.Assert(err, IsNil) + err = txn1.Commit() + checkErrorCode(c, err, tmysql.ErrDupEntry) + }) +} + +func checkErrorCode(c *C, e error, code uint16) { + me, ok := e.(*mysql.MySQLError) + c.Assert(ok, IsTrue) + c.Assert(me.Number, Equals, code) +} + func runTestAuth(c *C) { runTests(c, dsn, func(dbt *DBTest) { dbt.mustExec(`CREATE USER 'test'@'%' IDENTIFIED BY '123';`) diff --git a/tidb-server/server/tidb_test.go b/tidb-server/server/tidb_test.go index 10579a7abc..adb0a2f371 100644 --- a/tidb-server/server/tidb_test.go +++ b/tidb-server/server/tidb_test.go @@ -43,7 +43,9 @@ func (ts *TidbTestSuite) SetUpSuite(c *C) { } func (ts *TidbTestSuite) TearDownSuite(c *C) { - ts.server.Close() + if ts.server != nil { + ts.server.Close() + } } func (ts *TidbTestSuite) TestRegression(c *C) { @@ -68,6 +70,10 @@ func (ts *TidbTestSuite) TestConcurrentUpdate(c *C) { runTestConcurrentUpdate(c) } +func (ts *TidbTestSuite) TestErrorCode(c *C) { + runTestErrorCode(c) +} + func (ts *TidbTestSuite) TestAuth(c *C) { runTestAuth(c) } diff --git a/util/segmentmap/segmentmap.go b/util/segmentmap/segmentmap.go new file mode 100644 index 0000000000..11edbab382 --- /dev/null +++ b/util/segmentmap/segmentmap.go @@ -0,0 +1,81 @@ +// Copyright 2015 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package segmentmap + +import ( + "hash/crc32" + + "github.com/juju/errors" +) + +// SegmentMap is used for handle a big map slice by slice. +// It's not thread safe. +type SegmentMap struct { + size int + maps []map[string]interface{} + + crcTable *crc32.Table +} + +// NewSegmentMap create a new SegmentMap. +func NewSegmentMap(size int) (*SegmentMap, error) { + if size <= 0 { + return nil, errors.Errorf("Invalid size: %d", size) + } + + sm := &SegmentMap{ + maps: make([]map[string]interface{}, size), + size: size, + } + for i := 0; i < size; i++ { + sm.maps[i] = make(map[string]interface{}) + } + + sm.crcTable = crc32.MakeTable(crc32.Castagnoli) + return sm, nil +} + +// Get is the same as map[k]. +func (sm *SegmentMap) Get(key []byte) (interface{}, bool) { + idx := int(crc32.Checksum(key, sm.crcTable)) % sm.size + val, ok := sm.maps[idx][string(key)] + return val, ok +} + +// GetSegment gets the map specific by index. +func (sm *SegmentMap) GetSegment(index int) (map[string]interface{}, error) { + if index >= sm.size || index < 0 { + return nil, errors.Errorf("index out of bound: %d", index) + } + + return sm.maps[index], nil +} + +// Set if key not exists, returns whether already exists. +func (sm *SegmentMap) Set(key []byte, value interface{}, force bool) bool { + idx := int(crc32.Checksum(key, sm.crcTable)) % sm.size + k := string(key) + _, exist := sm.maps[idx][k] + if exist && !force { + return exist + } + + sm.maps[idx][k] = value + return exist +} + +// SegmentCount returns how many inner segments. +func (sm *SegmentMap) SegmentCount() int { + return sm.size +} diff --git a/util/segmentmap/segmentmap_test.go b/util/segmentmap/segmentmap_test.go new file mode 100644 index 0000000000..2a86786999 --- /dev/null +++ b/util/segmentmap/segmentmap_test.go @@ -0,0 +1,52 @@ +// Copyright 2015 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package segmentmap + +import ( + . "github.com/pingcap/check" +) + +var _ = Suite(&testSegmentMapSuite{}) + +type testSegmentMapSuite struct { +} + +func (s *testSegmentMapSuite) TestSegment(c *C) { + segs := 2 + m, err := NewSegmentMap(segs) + c.Assert(err, IsNil) + c.Assert(m.SegmentCount(), Equals, segs) + k := []byte("k") + v := []byte("v") + val, exist := m.Get(k) + c.Assert(exist, IsFalse) + + exist = m.Set(k, v, false) + c.Assert(exist, IsFalse) + + val, exist = m.Get(k) + c.Assert(v, DeepEquals, val.([]byte)) + c.Assert(exist, IsTrue) + + m0, err := m.GetSegment(0) + c.Assert(err, IsNil) + + m1, err := m.GetSegment(1) + c.Assert(err, IsNil) + + c.Assert(len(m0)+len(m1), Equals, 1) + + _, err = m.GetSegment(3) + c.Assert(err, NotNil) +} diff --git a/util/types/field_type.go b/util/types/field_type.go index fb6531c3b7..64667b6231 100644 --- a/util/types/field_type.go +++ b/util/types/field_type.go @@ -21,6 +21,7 @@ import ( "fmt" "strings" + "github.com/ngaut/log" "github.com/pingcap/tidb/mysql" "github.com/pingcap/tidb/util/charset" ) @@ -142,3 +143,48 @@ func IsDataItem(d interface{}) bool { _, ok := d.(*DataItem) return ok } + +// DefaultTypeForValue returns the default FieldType for the value. +func DefaultTypeForValue(value interface{}) *FieldType { + switch x := value.(type) { + case nil: + return NewFieldType(mysql.TypeNull) + case bool, int64, int: + return NewFieldType(mysql.TypeLonglong) + case uint64: + tp := NewFieldType(mysql.TypeLonglong) + tp.Flag |= mysql.UnsignedFlag + return tp + case string: + tp := NewFieldType(mysql.TypeVarchar) + tp.Charset = mysql.DefaultCharset + tp.Collate = mysql.DefaultCollationName + return tp + case float64: + return NewFieldType(mysql.TypeNewDecimal) + case []byte: + tp := NewFieldType(mysql.TypeBlob) + tp.Charset = charset.CharsetBin + tp.Collate = charset.CharsetBin + case mysql.Bit: + return NewFieldType(mysql.TypeBit) + case mysql.Hex: + tp := NewFieldType(mysql.TypeVarchar) + tp.Charset = charset.CharsetBin + tp.Collate = charset.CharsetBin + case mysql.Time: + return NewFieldType(x.Type) + case mysql.Duration: + return NewFieldType(mysql.TypeDuration) + case mysql.Decimal: + return NewFieldType(mysql.TypeNewDecimal) + case mysql.Enum: + return NewFieldType(mysql.TypeEnum) + case mysql.Set: + return NewFieldType(mysql.TypeSet) + case *DataItem: + return x.Type + } + log.Errorf("Unknown value type %T for default field type.", value) + return nil +}