Merge branch 'master' into disksing/url-format-dsn
Conflicts: store/localstore/kv.go
This commit is contained in:
2
Godeps/Godeps.json
generated
2
Godeps/Godeps.json
generated
@ -37,7 +37,7 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/pingcap/check",
|
||||
"Rev": "dbd36251e82f4d1aed5561647b402a448839c412"
|
||||
"Rev": "ce8a2f822ab1e245a4eefcef2996531c79c943f1"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rcrowley/go-metrics",
|
||||
|
||||
5
Godeps/_workspace/src/github.com/pingcap/check/checkers2.go
generated
vendored
5
Godeps/_workspace/src/github.com/pingcap/check/checkers2.go
generated
vendored
@ -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 {
|
||||
|
||||
9
Godeps/_workspace/src/github.com/pingcap/check/checkers2_test.go
generated
vendored
9
Godeps/_workspace/src/github.com/pingcap/check/checkers2_test.go
generated
vendored
@ -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})
|
||||
}
|
||||
|
||||
31
Godeps/_workspace/src/github.com/pingcap/check/compare.go
generated
vendored
31
Godeps/_workspace/src/github.com/pingcap/check/compare.go
generated
vendored
@ -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)
|
||||
}
|
||||
|
||||
27
Godeps/_workspace/src/github.com/pingcap/check/godropbox_license
generated
vendored
Normal file
27
Godeps/_workspace/src/github.com/pingcap/check/godropbox_license
generated
vendored
Normal file
@ -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.
|
||||
3
Makefile
3
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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -168,6 +168,7 @@ const (
|
||||
ShowCreateTable
|
||||
ShowGrants
|
||||
ShowTriggers
|
||||
ShowProcedureStatus
|
||||
)
|
||||
|
||||
// ShowStmt is a statement to provide information about databases, tables, columns and so on.
|
||||
|
||||
@ -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/
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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],
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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},
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
73
kv/error.go
Normal file
73
kv/error.go
Normal file
@ -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
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
22
kv/iter.go
22
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 {
|
||||
|
||||
47
kv/key.go
Normal file
47
kv/key.go
Normal file
@ -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))
|
||||
}
|
||||
143
kv/kv.go
143
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)
|
||||
}
|
||||
|
||||
21
kv/txn.go
21
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++ {
|
||||
|
||||
@ -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 {
|
||||
|
||||
51
kv/version.go
Normal file
51
kv/version.go
Normal file
@ -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
|
||||
}
|
||||
@ -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()
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
88
optimizer/typeinferer_test.go
Normal file
88
optimizer/typeinferer_test.go
Normal file
@ -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))
|
||||
}
|
||||
}
|
||||
@ -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:
|
||||
{
|
||||
|
||||
@ -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},
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -36,6 +36,9 @@ type SessionVars struct {
|
||||
// Client capability
|
||||
ClientCapability uint32
|
||||
|
||||
// Connection ID
|
||||
ConnectionID uint64
|
||||
|
||||
// Found rows
|
||||
FoundRows uint64
|
||||
|
||||
|
||||
@ -61,6 +61,7 @@ const (
|
||||
ShowCreateTable
|
||||
ShowGrants
|
||||
ShowTriggers
|
||||
ShowProcedureStatus
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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"))
|
||||
}
|
||||
|
||||
@ -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{}),
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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"))
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
@ -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())
|
||||
}
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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';`)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
81
util/segmentmap/segmentmap.go
Normal file
81
util/segmentmap/segmentmap.go
Normal file
@ -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
|
||||
}
|
||||
52
util/segmentmap/segmentmap_test.go
Normal file
52
util/segmentmap/segmentmap_test.go
Normal file
@ -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)
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user