Merge branch 'master' into disksing/url-format-dsn

Conflicts:
	store/localstore/kv.go
This commit is contained in:
disksing
2015-12-23 10:16:23 +08:00
68 changed files with 1292 additions and 420 deletions

2
Godeps/Godeps.json generated
View File

@ -37,7 +37,7 @@
},
{
"ImportPath": "github.com/pingcap/check",
"Rev": "dbd36251e82f4d1aed5561647b402a448839c412"
"Rev": "ce8a2f822ab1e245a4eefcef2996531c79c943f1"
},
{
"ImportPath": "github.com/rcrowley/go-metrics",

View File

@ -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 {

View File

@ -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})
}

View File

@ -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)
}

View 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.

View File

@ -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

View 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

View File

@ -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
}

View File

@ -168,6 +168,7 @@ const (
ShowCreateTable
ShowGrants
ShowTriggers
ShowProcedureStatus
)
// ShowStmt is a statement to provide information about databases, tables, columns and so on.

View File

@ -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/

View File

@ -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,

View File

@ -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],

View File

@ -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
}

View File

@ -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

View File

@ -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},

View File

@ -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
}

View File

@ -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))
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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)
}

View File

@ -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
View 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
}

View File

@ -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

View File

@ -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))
}

View File

@ -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
View 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
View File

@ -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)
}

View File

@ -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++ {

View File

@ -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
View 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
}

View File

@ -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()

View File

@ -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)

View File

@ -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 {

View File

@ -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 {

View 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))
}
}

View File

@ -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:
{

View File

@ -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},

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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
}

View File

@ -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) {

View File

@ -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)

View File

@ -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"

View File

@ -36,6 +36,9 @@ type SessionVars struct {
// Client capability
ClientCapability uint32
// Connection ID
ConnectionID uint64
// Found rows
FoundRows uint64

View File

@ -61,6 +61,7 @@ const (
ShowCreateTable
ShowGrants
ShowTriggers
ShowProcedureStatus
)
const (

View File

@ -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 {

View File

@ -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)
}

View File

@ -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)

View File

@ -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
}

View File

@ -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"))
}

View File

@ -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{}),

View File

@ -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,

View File

@ -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"))
}

View File

@ -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

View File

@ -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")

View File

@ -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)

View File

@ -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)

View File

@ -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")
}

View File

@ -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())
}

View File

@ -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.

View File

@ -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 {

View File

@ -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';`)

View File

@ -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)
}

View 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
}

View 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)
}

View File

@ -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
}