diff --git a/ddl/column.go b/ddl/column.go index bbcc653f10..ea8f5e55eb 100644 --- a/ddl/column.go +++ b/ddl/column.go @@ -276,7 +276,7 @@ func (d *ddl) backfillColumn(t table.Table, columnInfo *model.ColumnInfo, versio }) rk := kv.EncodeKey(lock) - it, err = kv.NextUntil(it, util.RowKeyPrefixFilter(rk)) + err = kv.NextUntil(it, util.RowKeyPrefixFilter(rk)) if errors2.ErrorEqual(err, kv.ErrNotExist) { break } else if err != nil { diff --git a/ddl/ddl.go b/ddl/ddl.go index 34de73e7d3..c03fd5cdb4 100644 --- a/ddl/ddl.go +++ b/ddl/ddl.go @@ -32,6 +32,7 @@ import ( "github.com/pingcap/tidb/model" "github.com/pingcap/tidb/mysql" "github.com/pingcap/tidb/parser/coldef" + "github.com/pingcap/tidb/privilege" "github.com/pingcap/tidb/table" "github.com/pingcap/tidb/util/charset" qerror "github.com/pingcap/tidb/util/errors" @@ -483,6 +484,15 @@ func (d *ddl) DropTable(ctx context.Context, ti table.Ident) (err error) { if err != nil { return errors.Trace(ErrNotExists) } + // Check Privilege + privChecker := privilege.GetPrivilegeChecker(ctx) + hasPriv, err := privChecker.Check(ctx, schema, tb.Meta(), mysql.DropPriv) + if err != nil { + return errors.Trace(err) + } + if !hasPriv { + return errors.Errorf("You do not have the privilege to drop table %s.%s.", ti.Schema, ti.Name) + } job := &model.Job{ SchemaID: schema.ID, diff --git a/ddl/index.go b/ddl/index.go index 41b79d56da..f207685725 100644 --- a/ddl/index.go +++ b/ddl/index.go @@ -401,7 +401,7 @@ func (d *ddl) getSnapshotRows(t table.Table, version uint64, seekHandle int64) ( break } - it, err = kv.NextUntil(it, util.RowKeyPrefixFilter(rk)) + err = kv.NextUntil(it, util.RowKeyPrefixFilter(rk)) if errors2.ErrorEqual(err, kv.ErrNotExist) { break } else if err != nil { @@ -452,7 +452,6 @@ func (d *ddl) backfillTableIndex(t table.Table, indexInfo *model.IndexInfo, hand } // create the index. - log.Errorf("create index %d", handle) err = kvX.Create(txn, vals, handle) return errors.Trace(err) }) diff --git a/expression/date_add.go b/expression/date_add.go deleted file mode 100644 index 95cd99a0de..0000000000 --- a/expression/date_add.go +++ /dev/null @@ -1,104 +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 expression - -import ( - "fmt" - "strings" - - "github.com/juju/errors" - "github.com/pingcap/tidb/context" - "github.com/pingcap/tidb/mysql" - "github.com/pingcap/tidb/util/types" -) - -// DateAdd is for time date_add function. -// See: https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_date-add -type DateAdd struct { - Unit string - Date Expression - Interval Expression -} - -// Clone implements the Expression Clone interface. -func (da *DateAdd) Clone() Expression { - n := *da - return &n -} - -// Eval implements the Expression Eval interface. -func (da *DateAdd) Eval(ctx context.Context, args map[interface{}]interface{}) (interface{}, error) { - dv, err := da.Date.Eval(ctx, args) - if dv == nil || err != nil { - return nil, errors.Trace(err) - } - - sv, err := types.ToString(dv) - if err != nil { - return nil, errors.Trace(err) - } - - f := types.NewFieldType(mysql.TypeDatetime) - f.Decimal = mysql.MaxFsp - - dv, err = types.Convert(sv, f) - if dv == nil || err != nil { - return nil, errors.Trace(err) - } - - t, ok := dv.(mysql.Time) - if !ok { - return nil, errors.Errorf("need time type, but got %T", dv) - } - - iv, err := da.Interval.Eval(ctx, args) - if iv == nil || err != nil { - return nil, errors.Trace(err) - } - - format, err := types.ToString(iv) - if err != nil { - return nil, errors.Trace(err) - } - - years, months, days, durations, err := mysql.ExtractTimeValue(da.Unit, strings.TrimSpace(format)) - if err != nil { - return nil, errors.Trace(err) - } - - t.Time = t.Time.Add(durations) - t.Time = t.Time.AddDate(int(years), int(months), int(days)) - - // "2011-11-11 10:10:20.000000" outputs "2011-11-11 10:10:20". - if t.Time.Nanosecond() == 0 { - t.Fsp = 0 - } - - return t, nil -} - -// IsStatic implements the Expression IsStatic interface. -func (da *DateAdd) IsStatic() bool { - return da.Date.IsStatic() && da.Interval.IsStatic() -} - -// String implements the Expression String interface. -func (da *DateAdd) String() string { - return fmt.Sprintf("DATE_ADD(%s, INTERVAL %s %s)", da.Date, da.Interval, strings.ToUpper(da.Unit)) -} - -// Accept implements the Visitor Accept interface. -func (da *DateAdd) Accept(v Visitor) (Expression, error) { - return v.VisitDateAdd(da) -} diff --git a/expression/date_add_test.go b/expression/date_add_test.go deleted file mode 100644 index 6b612dce40..0000000000 --- a/expression/date_add_test.go +++ /dev/null @@ -1,144 +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 expression - -import ( - . "github.com/pingcap/check" - "github.com/pingcap/tidb/mysql" -) - -var _ = Suite(&testDateAddSuite{}) - -type testDateAddSuite struct { -} - -func (t *testDateAddSuite) TestDateAdd(c *C) { - input := "2011-11-11 10:10:10" - e := &DateAdd{ - Unit: "DAY", - Date: Value{Val: input}, - Interval: Value{Val: "1"}, - } - c.Assert(e.String(), Equals, `DATE_ADD("2011-11-11 10:10:10", INTERVAL "1" DAY)`) - c.Assert(e.Clone(), NotNil) - c.Assert(e.IsStatic(), IsTrue) - - _, err := e.Eval(nil, nil) - c.Assert(err, IsNil) - - // Test null. - e = &DateAdd{ - Unit: "DAY", - Date: Value{Val: nil}, - Interval: Value{Val: "1"}, - } - - v, err := e.Eval(nil, nil) - c.Assert(err, IsNil) - c.Assert(v, IsNil) - - e = &DateAdd{ - Unit: "DAY", - Date: Value{Val: input}, - Interval: Value{Val: nil}, - } - - v, err = e.Eval(nil, nil) - c.Assert(err, IsNil) - c.Assert(v, IsNil) - - // Test eval. - tbl := []struct { - Unit string - Interval interface{} - Expect string - }{ - {"MICROSECOND", "1000", "2011-11-11 10:10:10.001000"}, - {"MICROSECOND", 1000, "2011-11-11 10:10:10.001000"}, - {"SECOND", "10", "2011-11-11 10:10:20"}, - {"MINUTE", "10", "2011-11-11 10:20:10"}, - {"HOUR", "10", "2011-11-11 20:10:10"}, - {"DAY", "11", "2011-11-22 10:10:10"}, - {"WEEK", "2", "2011-11-25 10:10:10"}, - {"MONTH", "2", "2012-01-11 10:10:10"}, - {"QUARTER", "4", "2012-11-11 10:10:10"}, - {"YEAR", "2", "2013-11-11 10:10:10"}, - {"SECOND_MICROSECOND", "10.00100000", "2011-11-11 10:10:20.100000"}, - {"SECOND_MICROSECOND", "10.0010000000", "2011-11-11 10:10:30"}, - {"SECOND_MICROSECOND", "10.0010000010", "2011-11-11 10:10:30.000010"}, - {"MINUTE_MICROSECOND", "10:10.100", "2011-11-11 10:20:20.100000"}, - {"MINUTE_SECOND", "10:10", "2011-11-11 10:20:20"}, - {"HOUR_MICROSECOND", "10:10:10.100", "2011-11-11 20:20:20.100000"}, - {"HOUR_SECOND", "10:10:10", "2011-11-11 20:20:20"}, - {"HOUR_MINUTE", "10:10", "2011-11-11 20:20:10"}, - {"DAY_MICROSECOND", "11 10:10:10.100", "2011-11-22 20:20:20.100000"}, - {"DAY_SECOND", "11 10:10:10", "2011-11-22 20:20:20"}, - {"DAY_MINUTE", "11 10:10", "2011-11-22 20:20:10"}, - {"DAY_HOUR", "11 10", "2011-11-22 20:10:10"}, - {"YEAR_MONTH", "11-1", "2022-12-11 10:10:10"}, - {"YEAR_MONTH", "11-11", "2023-10-11 10:10:10"}, - } - - for _, t := range tbl { - e := &DateAdd{ - Unit: t.Unit, - Date: Value{Val: input}, - Interval: Value{Val: t.Interval}, - } - - v, err := e.Eval(nil, nil) - c.Assert(err, IsNil) - - value, ok := v.(mysql.Time) - c.Assert(ok, IsTrue) - c.Assert(value.String(), Equals, t.Expect) - } - - // Test error. - errInput := "20111111 10:10:10" - errTbl := []struct { - Unit string - Interval interface{} - }{ - {"MICROSECOND", "abc1000"}, - {"MICROSECOND", ""}, - {"SECOND_MICROSECOND", "10"}, - {"MINUTE_MICROSECOND", "10.0000"}, - {"MINUTE_MICROSECOND", "10:10:10.0000"}, - - // MySQL support, but tidb not. - {"HOUR_MICROSECOND", "10:10.0000"}, - {"YEAR_MONTH", "10 1"}, - } - - for _, t := range errTbl { - e := &DateAdd{ - Unit: t.Unit, - Date: Value{Val: input}, - Interval: Value{Val: t.Interval}, - } - - _, err := e.Eval(nil, nil) - c.Assert(err, NotNil) - - e = &DateAdd{ - Unit: t.Unit, - Date: Value{Val: errInput}, - Interval: Value{Val: t.Interval}, - } - - v, err := e.Eval(nil, nil) - c.Assert(err, NotNil, Commentf("%s", v)) - } -} diff --git a/expression/date_arith.go b/expression/date_arith.go new file mode 100644 index 0000000000..0a9a6c0de3 --- /dev/null +++ b/expression/date_arith.go @@ -0,0 +1,139 @@ +// 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 expression + +import ( + "fmt" + "strings" + "time" + + "github.com/juju/errors" + "github.com/pingcap/tidb/context" + "github.com/pingcap/tidb/mysql" + "github.com/pingcap/tidb/util/types" +) + +// DateArithType is type for DateArith option. +type DateArithType byte + +const ( + // DateAdd is to run date_add function option. + // See: https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_date-add + DateAdd DateArithType = iota + 1 + // DateSub is to run date_sub function option. + // See: https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_date-sub + DateSub +) + +// DateArith is used for dealing with addition and substraction of time. +type DateArith struct { + Op DateArithType + Unit string + Date Expression + Interval Expression +} + +func (da *DateArith) isAdd() bool { + if da.Op == DateAdd { + return true + } + + return false +} + +// Clone implements the Expression Clone interface. +func (da *DateArith) Clone() Expression { + n := *da + return &n +} + +// IsStatic implements the Expression IsStatic interface. +func (da *DateArith) IsStatic() bool { + return da.Date.IsStatic() && da.Interval.IsStatic() +} + +// Accept implements the Visitor Accept interface. +func (da *DateArith) Accept(v Visitor) (Expression, error) { + return v.VisitDateArith(da) +} + +// String implements the Expression String interface. +func (da *DateArith) String() string { + var str string + if da.isAdd() { + str = "DATE_ADD" + } else { + str = "DATE_SUB" + } + + return fmt.Sprintf("%s(%s, INTERVAL %s %s)", str, da.Date, da.Interval, strings.ToUpper(da.Unit)) +} + +// Eval implements the Expression Eval interface. +func (da *DateArith) Eval(ctx context.Context, args map[interface{}]interface{}) (interface{}, error) { + t, years, months, days, durations, err := da.evalArgs(ctx, args) + if t.IsZero() || err != nil { + return nil, errors.Trace(err) + } + + if !da.isAdd() { + years, months, days, durations = -years, -months, -days, -durations + } + t.Time = t.Time.Add(durations) + t.Time = t.Time.AddDate(int(years), int(months), int(days)) + + // "2011-11-11 10:10:20.000000" outputs "2011-11-11 10:10:20". + if t.Time.Nanosecond() == 0 { + t.Fsp = 0 + } + + return t, nil +} + +func (da *DateArith) evalArgs(ctx context.Context, args map[interface{}]interface{}) ( + mysql.Time, int64, int64, int64, time.Duration, error) { + dVal, err := da.Date.Eval(ctx, args) + if dVal == nil || err != nil { + return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Trace(err) + } + dValStr, err := types.ToString(dVal) + if err != nil { + return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Trace(err) + } + f := types.NewFieldType(mysql.TypeDatetime) + f.Decimal = mysql.MaxFsp + dVal, err = types.Convert(dValStr, f) + if dVal == nil || err != nil { + return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Trace(err) + } + t, ok := dVal.(mysql.Time) + if !ok { + return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Errorf("need time type, but got %T", dVal) + } + + iVal, err := da.Interval.Eval(ctx, args) + if iVal == nil || err != nil { + return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Trace(err) + } + iValStr, err := types.ToString(iVal) + if err != nil { + return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Trace(err) + } + years, months, days, durations, err := mysql.ExtractTimeValue(da.Unit, strings.TrimSpace(iValStr)) + if err != nil { + return mysql.ZeroTimestamp, 0, 0, 0, 0, errors.Trace(err) + } + + return t, years, months, days, durations, nil +} diff --git a/expression/date_arith_test.go b/expression/date_arith_test.go new file mode 100644 index 0000000000..2775d7d914 --- /dev/null +++ b/expression/date_arith_test.go @@ -0,0 +1,162 @@ +// 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 expression + +import ( + . "github.com/pingcap/check" + "github.com/pingcap/tidb/mysql" +) + +var _ = Suite(&testDateArithSuite{}) + +type testDateArithSuite struct { +} + +func (t *testDateArithSuite) TestDateArith(c *C) { + input := "2011-11-11 10:10:10" + e := &DateArith{ + Op: DateAdd, + Unit: "DAY", + Date: Value{Val: input}, + Interval: Value{Val: "1"}, + } + c.Assert(e.String(), Equals, `DATE_ADD("2011-11-11 10:10:10", INTERVAL "1" DAY)`) + c.Assert(e.Clone(), NotNil) + c.Assert(e.IsStatic(), IsTrue) + _, err := e.Eval(nil, nil) + c.Assert(err, IsNil) + e.Op = DateSub + c.Assert(e.String(), Equals, `DATE_SUB("2011-11-11 10:10:10", INTERVAL "1" DAY)`) + + // Test null. + nullTbl := []struct { + Op DateArithType + Unit string + Date interface{} + Interval interface{} + }{ + {DateAdd, "DAY", nil, "1"}, + {DateAdd, "DAY", input, nil}, + } + for _, t := range nullTbl { + e := &DateArith{ + Op: t.Op, + Unit: t.Unit, + Date: Value{Val: t.Date}, + Interval: Value{Val: t.Interval}, + } + v, err := e.Eval(nil, nil) + c.Assert(err, IsNil) + c.Assert(v, IsNil) + e.Op = DateSub + v, err = e.Eval(nil, nil) + c.Assert(err, IsNil) + c.Assert(v, IsNil) + } + + // Test eval. + tbl := []struct { + Unit string + Interval interface{} + AddExpect string + SubExpect string + }{ + {"MICROSECOND", "1000", "2011-11-11 10:10:10.001000", "2011-11-11 10:10:09.999000"}, + {"MICROSECOND", 1000, "2011-11-11 10:10:10.001000", "2011-11-11 10:10:09.999000"}, + {"SECOND", "10", "2011-11-11 10:10:20", "2011-11-11 10:10:00"}, + {"MINUTE", "10", "2011-11-11 10:20:10", "2011-11-11 10:00:10"}, + {"HOUR", "10", "2011-11-11 20:10:10", "2011-11-11 00:10:10"}, + {"DAY", "11", "2011-11-22 10:10:10", "2011-10-31 10:10:10"}, + {"WEEK", "2", "2011-11-25 10:10:10", "2011-10-28 10:10:10"}, + {"MONTH", "2", "2012-01-11 10:10:10", "2011-09-11 10:10:10"}, + {"QUARTER", "4", "2012-11-11 10:10:10", "2010-11-11 10:10:10"}, + {"YEAR", "2", "2013-11-11 10:10:10", "2009-11-11 10:10:10"}, + {"SECOND_MICROSECOND", "10.00100000", "2011-11-11 10:10:20.100000", "2011-11-11 10:09:59.900000"}, + {"SECOND_MICROSECOND", "10.0010000000", "2011-11-11 10:10:30", "2011-11-11 10:09:50"}, + {"SECOND_MICROSECOND", "10.0010000010", "2011-11-11 10:10:30.000010", "2011-11-11 10:09:49.999990"}, + {"MINUTE_MICROSECOND", "10:10.100", "2011-11-11 10:20:20.100000", "2011-11-11 09:59:59.900000"}, + {"MINUTE_SECOND", "10:10", "2011-11-11 10:20:20", "2011-11-11 10:00:00"}, + {"HOUR_MICROSECOND", "10:10:10.100", "2011-11-11 20:20:20.100000", "2011-11-10 23:59:59.900000"}, + {"HOUR_SECOND", "10:10:10", "2011-11-11 20:20:20", "2011-11-11 00:00:00"}, + {"HOUR_MINUTE", "10:10", "2011-11-11 20:20:10", "2011-11-11 00:00:10"}, + {"DAY_MICROSECOND", "11 10:10:10.100", "2011-11-22 20:20:20.100000", "2011-10-30 23:59:59.900000"}, + {"DAY_SECOND", "11 10:10:10", "2011-11-22 20:20:20", "2011-10-31 00:00:00"}, + {"DAY_MINUTE", "11 10:10", "2011-11-22 20:20:10", "2011-10-31 00:00:10"}, + {"DAY_HOUR", "11 10", "2011-11-22 20:10:10", "2011-10-31 00:10:10"}, + {"YEAR_MONTH", "11-1", "2022-12-11 10:10:10", "2000-10-11 10:10:10"}, + {"YEAR_MONTH", "11-11", "2023-10-11 10:10:10", "1999-12-11 10:10:10"}, + } + for _, t := range tbl { + e := &DateArith{ + Op: DateAdd, + Unit: t.Unit, + Date: Value{Val: input}, + Interval: Value{Val: t.Interval}, + } + v, err := e.Eval(nil, nil) + c.Assert(err, IsNil) + value, ok := v.(mysql.Time) + c.Assert(ok, IsTrue) + c.Assert(value.String(), Equals, t.AddExpect) + + e.Op = DateSub + v, err = e.Eval(nil, nil) + c.Assert(err, IsNil) + value, ok = v.(mysql.Time) + c.Assert(ok, IsTrue) + c.Assert(value.String(), Equals, t.SubExpect) + } + + // Test error. + errInput := "20111111 10:10:10" + errTbl := []struct { + Unit string + Interval interface{} + }{ + {"MICROSECOND", "abc1000"}, + {"MICROSECOND", ""}, + {"SECOND_MICROSECOND", "10"}, + {"MINUTE_MICROSECOND", "10.0000"}, + {"MINUTE_MICROSECOND", "10:10:10.0000"}, + + // MySQL support, but tidb not. + {"HOUR_MICROSECOND", "10:10.0000"}, + {"YEAR_MONTH", "10 1"}, + } + for _, t := range errTbl { + e := &DateArith{ + Op: DateAdd, + Unit: t.Unit, + Date: Value{Val: input}, + Interval: Value{Val: t.Interval}, + } + _, err := e.Eval(nil, nil) + c.Assert(err, NotNil) + e.Date = Value{Val: errInput} + v, err := e.Eval(nil, nil) + c.Assert(err, NotNil, Commentf("%s", v)) + + e = &DateArith{ + Op: DateSub, + Unit: t.Unit, + Date: Value{Val: input}, + Interval: Value{Val: t.Interval}, + } + _, err = e.Eval(nil, nil) + c.Assert(err, NotNil) + e.Date = Value{Val: errInput} + v, err = e.Eval(nil, nil) + c.Assert(err, NotNil, Commentf("%s", v)) + } +} diff --git a/expression/visitor.go b/expression/visitor.go index 6ec16f765f..6646d59db7 100644 --- a/expression/visitor.go +++ b/expression/visitor.go @@ -107,8 +107,8 @@ type Visitor interface { // VisitFunctionTrim visits FunctionTrim expression. VisitFunctionTrim(v *FunctionTrim) (Expression, error) - // VisitDateAdd visits DateAdd expression. - VisitDateAdd(da *DateAdd) (Expression, error) + // VisitDateArith visits DateArith expression. + VisitDateArith(dc *DateArith) (Expression, error) } // BaseVisitor is the base implementation of Visitor. @@ -482,8 +482,8 @@ func (bv *BaseVisitor) VisitFunctionTrim(ss *FunctionTrim) (Expression, error) { return ss, nil } -// VisitDateAdd implements Visitor interface. -func (bv *BaseVisitor) VisitDateAdd(da *DateAdd) (Expression, error) { +// VisitDateArith implements Visitor interface. +func (bv *BaseVisitor) VisitDateArith(da *DateArith) (Expression, error) { var err error da.Date, err = da.Date.Accept(bv.V) if err != nil { diff --git a/kv/btree_buffer.go b/kv/btree_buffer.go index 21ff9b63e1..aeabe6fe8a 100644 --- a/kv/btree_buffer.go +++ b/kv/btree_buffer.go @@ -16,6 +16,8 @@ package kv import ( + "github.com/juju/errors" + "github.com/ngaut/log" "github.com/pingcap/tidb/kv/memkv" "github.com/pingcap/tidb/util/types" ) @@ -73,7 +75,11 @@ func (b *btreeBuffer) NewIterator(param interface{}) Iterator { } iter := &btreeIter{e: e} // the initial push... - iter.Next() + err = iter.Next() + if err != nil { + log.Error(err) + return &btreeIter{ok: false} + } return iter } @@ -93,10 +99,14 @@ func (i *btreeIter) Value() []byte { } // Next implements Iterator Next. -func (i *btreeIter) Next() (Iterator, error) { +func (i *btreeIter) Next() error { k, v, err := i.e.Next() - i.k, i.v, i.ok = string(fromIfaces(k)), fromIfaces(v), err == nil - return i, err + if err != nil { + i.ok = false + return errors.Trace(err) + } + i.k, i.v, i.ok = string(fromIfaces(k)), fromIfaces(v), true + return nil } // Valid implements Iterator Valid. diff --git a/kv/index_iter.go b/kv/index_iter.go index 1a06c41eae..8515a303bc 100644 --- a/kv/index_iter.go +++ b/kv/index_iter.go @@ -87,11 +87,10 @@ func (c *indexIter) Next() (k []interface{}, h int64, err error) { k = vv } // update new iter to next - newIt, err := c.it.Next() + err = c.it.Next() if err != nil { return nil, 0, errors.Trace(err) } - c.it = newIt return } @@ -209,7 +208,7 @@ func (c *kvIndex) Drop(txn Transaction) error { if err != nil { return errors.Trace(err) } - it, err = it.Next() + err = it.Next() if err != nil { return errors.Trace(err) } diff --git a/kv/iter.go b/kv/iter.go index 03a99c5a82..be669133ff 100644 --- a/kv/iter.go +++ b/kv/iter.go @@ -69,13 +69,13 @@ func DecodeValue(data []byte) ([]interface{}, error) { // 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 occur error -func NextUntil(it Iterator, fn FnKeyCmp) (Iterator, error) { +func NextUntil(it Iterator, fn FnKeyCmp) error { var err error for it.Valid() && !fn([]byte(it.Key())) { - it, err = it.Next() + err = it.Next() if err != nil { - return nil, errors.Trace(err) + return errors.Trace(err) } } - return it, nil + return nil } diff --git a/kv/kv.go b/kv/kv.go index 963821e0dd..ba822cf70a 100644 --- a/kv/kv.go +++ b/kv/kv.go @@ -175,7 +175,7 @@ type FnKeyCmp func(key Key) bool // Iterator is the interface for a interator on KV store. type Iterator interface { - Next() (Iterator, error) + Next() error Value() []byte Key() string Valid() bool diff --git a/kv/mem_buffer_test.go b/kv/mem_buffer_test.go index d2b4798d87..a088333f6c 100644 --- a/kv/mem_buffer_test.go +++ b/kv/mem_buffer_test.go @@ -90,13 +90,13 @@ func checkNewIterator(c *C, buffer MemBuffer) { c.Assert(iter.Key(), Equals, string(val)) c.Assert(valToStr(c, iter), Equals, string(val)) - next, err := iter.Next() + err := iter.Next() c.Assert(err, IsNil) - c.Assert(next.Valid(), IsTrue) + c.Assert(iter.Valid(), IsTrue) val = encodeInt((i + 1) * indexStep) - c.Assert(next.Key(), Equals, string(val)) - c.Assert(valToStr(c, next), Equals, string(val)) + c.Assert(iter.Key(), Equals, string(val)) + c.Assert(valToStr(c, iter), Equals, string(val)) iter.Close() } @@ -181,7 +181,7 @@ func (s *testKVSuite) TestNewIteratorMin(c *C) { it := buffer.NewIterator(nil) for it.Valid() { cnt++ - it, _ = it.Next() + it.Next() } c.Assert(cnt, Equals, 6) diff --git a/kv/memdb_buffer.go b/kv/memdb_buffer.go index 4043d17843..2fe94db120 100644 --- a/kv/memdb_buffer.go +++ b/kv/memdb_buffer.go @@ -69,9 +69,9 @@ func (m *memDbBuffer) Release() { } // Next implements the Iterator Next. -func (i *memDbIter) Next() (Iterator, error) { +func (i *memDbIter) Next() error { i.iter.Next() - return i, nil + return nil } // Valid implements the Iterator Valid. diff --git a/kv/union_iter.go b/kv/union_iter.go index bbb11207af..8cc60160b5 100644 --- a/kv/union_iter.go +++ b/kv/union_iter.go @@ -44,13 +44,13 @@ func newUnionIter(dirtyIt Iterator, snapshotIt Iterator) *UnionIter { // Go next and update valid status. func (iter *UnionIter) dirtyNext() { - iter.dirtyIt, _ = iter.dirtyIt.Next() + iter.dirtyIt.Next() iter.dirtyValid = iter.dirtyIt.Valid() } // Go next and update valid status. func (iter *UnionIter) snapshotNext() { - iter.snapshotIt, _ = iter.snapshotIt.Next() + iter.snapshotIt.Next() iter.snapshotValid = iter.snapshotIt.Valid() } @@ -115,14 +115,14 @@ func (iter *UnionIter) updateCur() { } // Next implements the Iterator Next interface. -func (iter *UnionIter) Next() (Iterator, error) { +func (iter *UnionIter) Next() error { if !iter.curIsDirty { iter.snapshotNext() } else { iter.dirtyNext() } iter.updateCur() - return iter, nil + return nil } // Value implements the Iterator Value interface. diff --git a/parser/parser.y b/parser/parser.y index 1a8c36bb9e..d918b17ee1 100644 --- a/parser/parser.y +++ b/parser/parser.y @@ -114,6 +114,7 @@ import ( database "DATABASE" databases "DATABASES" dateAdd "DATE_ADD" + dateSub "DATE_SUB" day "DAY" dayofmonth "DAYOFMONTH" dayofweek "DAYOFWEEK" @@ -250,6 +251,7 @@ import ( trim "TRIM" trueKwd "true" truncate "TRUNCATE" + underscoreCS "UNDERSCORE_CHARSET" unknown "UNKNOWN" union "UNION" unique "UNIQUE" @@ -746,6 +748,7 @@ Assignment: x, err := expression.NewAssignment($1.(string), $3.(expression.Expression)) if err != nil { yylex.(*lexer).errf("Parse Assignment error: %s", $1.(string)) + return 1 } $$ = x } @@ -1066,6 +1069,7 @@ CreateDatabaseStmt: ok := charset.ValidCharsetAndCollation(cs, co) if !ok { yylex.(*lexer).errf("Unknown character set %s or collate %s ", cs, co) + return 1 } dbopt := &coldef.CharsetOpt{Chs: cs, Col: co} @@ -1711,7 +1715,7 @@ UnReservedKeyword: | "NATIONAL" | "ROW" | "QUARTER" | "ESCAPE" | "GRANTS" NotKeywordToken: - "ABS" | "COALESCE" | "CONCAT" | "CONCAT_WS" | "COUNT" | "DAY" | "DATE_ADD" | "DAYOFMONTH" | "DAYOFWEEK" | "DAYOFYEAR" | "FOUND_ROWS" | "GROUP_CONCAT" + "ABS" | "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" | "SUBSTRING" %prec lowerThanLeftParen | "SUBSTRING_INDEX" | "SUM" | "TRIM" | "WEEKDAY" | "WEEKOFYEAR" | "YEARWEEK" @@ -1876,11 +1880,27 @@ Literal: tp := types.NewFieldType(mysql.TypeString) l := yylex.(*lexer) tp.Charset, tp.Collate = l.GetCharsetInfo() - d := &types.DataItem{ + $$ = &types.DataItem{ Type: tp, Data: $1.(string), } - $$ = d + } +| "UNDERSCORE_CHARSET" stringLit + { + // See: https://dev.mysql.com/doc/refman/5.7/en/charset-literal.html + tp := types.NewFieldType(mysql.TypeString) + tp.Charset = $1.(string) + co, err := charset.GetDefaultCollation(tp.Charset) + if err != nil { + l := yylex.(*lexer) + l.errf("Get collation error for charset: %s", tp.Charset) + return 1 + } + tp.Collate = co + $$ = &types.DataItem{ + Type: tp, + Data: $2.(string), + } } | hexLit | bitLit @@ -1915,6 +1935,7 @@ Operand: l := yylex.(*lexer) if !l.prepare { l.err("Can not accept placeholder when not parsing prepare sql") + return 1 } pm := &expression.ParamMarker{} l.ParamList = append(l.ParamList, pm) @@ -2286,12 +2307,22 @@ FunctionCallNonKeyword: } | "DATE_ADD" '(' Expression ',' "INTERVAL" Expression TimeUnit ')' { - $$ = &expression.DateAdd{ + $$ = &expression.DateArith{ + Op:expression.DateAdd, Unit: $7.(string), Date: $3.(expression.Expression), Interval: $6.(expression.Expression), } } +| "DATE_SUB" '(' Expression ',' "INTERVAL" Expression TimeUnit ')' + { + $$ = &expression.DateArith{ + Op:expression.DateSub, + Unit: $7.(string), + Date: $3.(expression.Expression), + Interval: $6.(expression.Expression), + } + } | "EXTRACT" '(' TimeUnit "FROM" Expression ')' { $$ = &expression.Extract{ @@ -3896,6 +3927,7 @@ NumericType: x.Flen = 1 } else if x.Flen > 64 { yylex.(*lexer).errf("invalid field length %d for bit type, must in [1, 64]", x.Flen) + return 1 } $$ = x } diff --git a/parser/parser_test.go b/parser/parser_test.go index d10ea5e172..8f5c0d10ee 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -321,6 +321,7 @@ func (s *testParserSuite) TestExpression(c *C) { {`select '\'a\'';`, true}, {`select "\"a\"";`, true}, {`select """a""";`, true}, + {`select _utf8"string";`, true}, // For comparison {"select 1 <=> 0, 1 <=> null, 1 = null", true}, } @@ -440,6 +441,28 @@ func (s *testParserSuite) TestBuiltin(c *C) { {`select date_add("2011-11-11 10:10:10.123456", interval "11 10:10" day_minute)`, true}, {`select date_add("2011-11-11 10:10:10.123456", interval "11 10" day_hour)`, true}, {`select date_add("2011-11-11 10:10:10.123456", interval "11-11" year_month)`, true}, + + // For date_sub + {`select date_sub("2011-11-11 10:10:10.123456", interval 10 microsecond)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval 10 second)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval 10 minute)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval 10 hour)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval 10 day)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval 1 week)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval 1 month)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval 1 quarter)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval 1 year)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval "10.10" second_microsecond)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval "10:10.10" minute_microsecond)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval "10:10" minute_second)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval "10:10:10.10" hour_microsecond)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval "10:10:10" hour_second)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval "10:10" hour_minute)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval "11 10:10:10.10" day_microsecond)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval "11 10:10:10" day_second)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval "11 10:10" day_minute)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval "11 10" day_hour)`, true}, + {`select date_sub("2011-11-11 10:10:10.123456", interval "11-11" year_month)`, true}, } s.RunTest(c, table) } diff --git a/parser/scanner.l b/parser/scanner.l index 2496317449..c1441b4650 100644 --- a/parser/scanner.l +++ b/parser/scanner.l @@ -27,6 +27,7 @@ import ( "github.com/pingcap/tidb/expression" "github.com/pingcap/tidb/util/stringutil" + "github.com/pingcap/tidb/util/charset" "github.com/pingcap/tidb/mysql" ) @@ -298,6 +299,7 @@ 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} date_add {d}{a}{t}{e}_{a}{d}{d} +date_sub {d}{a}{t}{e}_{s}{u}{b} day {d}{a}{y} dayofweek {d}{a}{y}{o}{f}{w}{e}{e}{k} dayofmonth {d}{a}{y}{o}{f}{m}{o}{n}{t}{h} @@ -649,6 +651,8 @@ year_month {y}{e}{a}{r}_{m}{o}{n}{t}{h} {databases} return databases {date_add} lval.item = string(l.val) return dateAdd +{date_sub} lval.item = string(l.val) + return dateSub {day} lval.item = string(l.val) return day {dayofweek} lval.item = string(l.val) @@ -1016,7 +1020,7 @@ year_month {y}{e}{a}{r}_{m}{o}{n}{t}{h} return integerType {ident} lval.item = string(l.val) - return identifier + return l.handleIdent(lval) . return c0 @@ -1107,3 +1111,19 @@ func (l *lexer) bit(lval *yySymType) int { lval.item = b return bitLit } + +func (l *lexer) handleIdent(lval *yySymType) int { + s := lval.item.(string) + // A character string literal may have an optional character set introducer and COLLATE clause: + // [_charset_name]'string' [COLLATE collation_name] + // See: https://dev.mysql.com/doc/refman/5.7/en/charset-literal.html + if !strings.HasPrefix(s, "_") { + return identifier + } + cs, _, err := charset.GetCharsetInfo(s[1:]) + if err != nil { + return identifier + } + lval.item = cs + return underscoreCS +} diff --git a/plan/plans/from.go b/plan/plans/from.go index 679a2fa18a..bc744b5176 100644 --- a/plan/plans/from.go +++ b/plan/plans/from.go @@ -84,7 +84,7 @@ func (r *TableNilPlan) Next(ctx context.Context) (row *plan.Row, err error) { // Even though the data is nil, we should return not nil row, // or the iteration will stop. row = &plan.Row{} - r.iter, err = kv.NextUntil(r.iter, util.RowKeyPrefixFilter(rk)) + err = kv.NextUntil(r.iter, util.RowKeyPrefixFilter(rk)) return } @@ -311,7 +311,7 @@ func (r *TableDefaultPlan) Next(ctx context.Context) (row *plan.Row, err error) row.RowKeys = append(row.RowKeys, rke) rk := r.T.RecordKey(handle, nil) - r.iter, err = kv.NextUntil(r.iter, util.RowKeyPrefixFilter(rk)) + err = kv.NextUntil(r.iter, util.RowKeyPrefixFilter(rk)) if err != nil { return nil, errors.Trace(err) } diff --git a/privilege/privileges/privileges.go b/privilege/privileges/privileges.go index 499d7e9ac6..e387e9ac70 100644 --- a/privilege/privileges/privileges.go +++ b/privilege/privileges/privileges.go @@ -166,6 +166,11 @@ func (p *UserPrivileges) Check(ctx context.Context, db *model.DBInfo, tbl *model if len(p.User) == 0 { // User current user p.User = variable.GetSessionVars(ctx).User + if len(p.User) == 0 { + // In embedded db mode, user does not need to login. So we do not have username. + // TODO: remove this check latter. + return true, nil + } } err := p.loadPrivileges(ctx) if err != nil { diff --git a/privilege/privileges/privileges_test.go b/privilege/privileges/privileges_test.go index 37c74ddca5..23d8ff0912 100644 --- a/privilege/privileges/privileges_test.go +++ b/privilege/privileges/privileges_test.go @@ -233,6 +233,29 @@ func (t *testPrivilegeSuite) TestShowGrants(c *C) { c.Assert(testutil.CompareUnorderedStringSlice(gs, expected), IsTrue) } +func (t *testPrivilegeSuite) TestDropTablePriv(c *C) { + se := newSession(c, t.store, t.dbName) + ctx, _ := se.(context.Context) + mustExec(c, se, `CREATE TABLE todrop(c int);`) + variable.GetSessionVars(ctx).User = "root@localhost" + mustExec(c, se, `CREATE USER 'drop'@'localhost' identified by '123';`) + mustExec(c, se, `GRANT Select ON test.todrop TO 'drop'@'localhost';`) + + variable.GetSessionVars(ctx).User = "drop@localhost" + mustExec(c, se, `SELECT * FROM todrop;`) + + _, err := se.Execute("DROP TABLE todrop;") + c.Assert(err, NotNil) + + variable.GetSessionVars(ctx).User = "root@localhost" + mustExec(c, se, `GRANT Drop ON test.todrop TO 'drop'@'localhost';`) + + se1 := newSession(c, t.store, t.dbName) + ctx1, _ := se1.(context.Context) + variable.GetSessionVars(ctx1).User = "drop@localhost" + mustExec(c, se1, `DROP TABLE todrop;`) +} + func mustExec(c *C, se tidb.Session, sql string) { _, err := se.Execute(sql) c.Assert(err, IsNil) diff --git a/stmt/stmts/select_test.go b/stmt/stmts/select_test.go index 536c7b0b29..1d7a76ca69 100644 --- a/stmt/stmts/select_test.go +++ b/stmt/stmts/select_test.go @@ -33,6 +33,13 @@ func (s *testStmtSuite) TestSelectWithoutFrom(c *C) { rows.Close() mustCommit(c, tx) + + tx = mustBegin(c, s.testDB) + rows, err = tx.Query(`select _utf8"string";`) + c.Assert(err, IsNil) + matchRows(c, rows, [][]interface{}{{"string"}}) + rows.Close() + mustCommit(c, tx) } func (s *testStmtSuite) TestSelectExplain(c *C) { diff --git a/store/localstore/kv_test.go b/store/localstore/kv_test.go index 0f167400d5..945d24705c 100644 --- a/store/localstore/kv_test.go +++ b/store/localstore/kv_test.go @@ -109,13 +109,13 @@ func checkSeek(c *C, txn kv.Transaction) { c.Assert(iter.Key(), Equals, string(val)) c.Assert(valToStr(c, iter), Equals, string(val)) - next, err := iter.Next() + err = iter.Next() c.Assert(err, IsNil) - c.Assert(next.Valid(), IsTrue) + c.Assert(iter.Valid(), IsTrue) val = encodeInt((i + 1) * indexStep) - c.Assert(next.Key(), Equals, string(val)) - c.Assert(valToStr(c, next), Equals, string(val)) + c.Assert(iter.Key(), Equals, string(val)) + c.Assert(valToStr(c, iter), Equals, string(val)) iter.Close() } @@ -280,7 +280,7 @@ func (s *testKVSuite) TestDelete2(c *C) { for it.Valid() { err = txn.Delete([]byte(it.Key())) c.Assert(err, IsNil) - it, err = it.Next() + err = it.Next() c.Assert(err, IsNil) } txn.Commit() @@ -415,7 +415,7 @@ func (s *testKVSuite) TestSeekMin(c *C) { it, err := txn.Seek(nil) for it.Valid() { fmt.Printf("%s, %s\n", it.Key(), it.Value()) - it, _ = it.Next() + it.Next() } it, err = txn.Seek([]byte("DATA_test_main_db_tbl_tbl_test_record__00000000000000000000")) diff --git a/store/localstore/mvcc_test.go b/store/localstore/mvcc_test.go index 3bfa584e3e..0afd3b6954 100644 --- a/store/localstore/mvcc_test.go +++ b/store/localstore/mvcc_test.go @@ -142,7 +142,7 @@ func (t *testMvccSuite) TestMvccNext(c *C) { c.Assert(err, IsNil) c.Assert(it.Valid(), IsTrue) for it.Valid() { - it, err = it.Next() + err = it.Next() c.Assert(err, IsNil) } txn.Commit() @@ -244,7 +244,7 @@ func (t *testMvccSuite) TestMvccSnapshotScan(c *C) { if string(it.Value()) == "new" { found = true } - it, err = it.Next() + err = it.Next() c.Assert(err, IsNil) } return found @@ -275,7 +275,7 @@ func (t *testMvccSuite) TestBufferedIterator(c *C) { c.Assert(err, IsNil) cnt := 0 for iter.Valid() { - iter, err = iter.Next() + err = iter.Next() c.Assert(err, IsNil) cnt++ } diff --git a/store/localstore/snapshot.go b/store/localstore/snapshot.go index 46ec3573b4..55c2e3274b 100644 --- a/store/localstore/snapshot.go +++ b/store/localstore/snapshot.go @@ -151,7 +151,7 @@ func newDBIter(s *dbSnapshot, startKey kv.Key, exceptedVer kv.Version) *dbIter { return it } -func (it *dbIter) Next() (kv.Iterator, error) { +func (it *dbIter) Next() error { encKey := codec.EncodeBytes(nil, it.startKey) var retErr error var engineIter engine.Iterator @@ -189,7 +189,7 @@ func (it *dbIter) Next() (kv.Iterator, error) { // Current key's all versions are deleted, just go next key. encKey = codec.EncodeBytes(nil, key.Next()) } - return it, errors.Trace(retErr) + return errors.Trace(retErr) } func (it *dbIter) Valid() bool { diff --git a/store/localstore/txn.go b/store/localstore/txn.go index 90b776cd86..60ce900d95 100644 --- a/store/localstore/txn.go +++ b/store/localstore/txn.go @@ -176,7 +176,7 @@ func (txn *dbTxn) Delete(k kv.Key) error { func (txn *dbTxn) each(f func(kv.Iterator) error) error { iter := txn.UnionStore.Dirty.NewIterator(nil) defer iter.Close() - for ; iter.Valid(); iter, _ = iter.Next() { + for ; iter.Valid(); iter.Next() { if err := f(iter); err != nil { return errors.Trace(err) } diff --git a/structure/hash.go b/structure/hash.go index 2dd24c77d0..1dfee5e668 100644 --- a/structure/hash.go +++ b/structure/hash.go @@ -249,7 +249,7 @@ func (t *TxStructure) iterateHash(key []byte, fn func(k []byte, v []byte) error) return errors.Trace(err) } - it, err = it.Next() + err = it.Next() if err != nil { return errors.Trace(err) } diff --git a/table/tables/tables.go b/table/tables/tables.go index ff108a7c04..d58d37782e 100644 --- a/table/tables/tables.go +++ b/table/tables/tables.go @@ -626,7 +626,7 @@ func (t *Table) IterRecords(ctx context.Context, startKey string, cols []*column } rk := t.RecordKey(handle, nil) - it, err = kv.NextUntil(it, util.RowKeyPrefixFilter(rk)) + err = kv.NextUntil(it, util.RowKeyPrefixFilter(rk)) if err != nil { return errors.Trace(err) } diff --git a/util/charset/charset.go b/util/charset/charset.go index 713ea8f9d0..500d026f7f 100644 --- a/util/charset/charset.go +++ b/util/charset/charset.go @@ -14,7 +14,9 @@ package charset import ( - "fmt" + "strings" + + "github.com/juju/errors" ) // Charset is a charset. @@ -118,11 +120,20 @@ func ValidCharsetAndCollation(cs string, co string) bool { func GetDefaultCollation(charset string) (string, error) { c, ok := charsets[charset] if !ok { - return "", fmt.Errorf("Unkown charset %s", charset) + return "", errors.Errorf("Unkown charset %s", charset) } return c.DefaultCollation.Name, nil } +// GetCharsetInfo returns charset and collation for cs as name. +func GetCharsetInfo(cs string) (string, string, error) { + c, ok := charsets[strings.ToLower(cs)] + if !ok { + return "", "", errors.Errorf("Unknown charset %s", cs) + } + return c.Name, c.DefaultCollation.Name, nil +} + // GetCollations returns a list for all collations. func GetCollations() []*Collation { return collations diff --git a/util/prefix_helper.go b/util/prefix_helper.go index 78294a1a40..192affd278 100644 --- a/util/prefix_helper.go +++ b/util/prefix_helper.go @@ -45,7 +45,10 @@ func ScanMetaWithPrefix(txn kv.Transaction, prefix string, filter func([]byte, [ if !filter([]byte(iter.Key()), iter.Value()) { break } - iter, err = iter.Next() + err = iter.Next() + if err != nil { + return errors.Trace(err) + } } else { break } @@ -75,7 +78,10 @@ func DelKeyWithPrefix(ctx context.Context, prefix string) error { if iter.Valid() && strings.HasPrefix(iter.Key(), prefix) { keys = append(keys, iter.Key()) - iter, err = iter.Next() + err = iter.Next() + if err != nil { + return errors.Trace(err) + } } else { break }