diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 942344b977..2cfbfc2718 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -35,6 +35,10 @@ "ImportPath": "github.com/peterh/liner", "Rev": "1bb0d1c1a25ed393d8feb09bab039b2b1b1fbced" }, + { + "ImportPath": "github.com/petar/GoLLRB/llrb", + "Rev": "53be0d36a84c2a886ca057d34b6aa4468df9ccb4" + }, { "ImportPath": "github.com/pingcap/check", "Rev": "ce8a2f822ab1e245a4eefcef2996531c79c943f1" diff --git a/Godeps/_workspace/src/github.com/petar/GoLLRB/llrb/avgvar.go b/Godeps/_workspace/src/github.com/petar/GoLLRB/llrb/avgvar.go new file mode 100644 index 0000000000..2d7e2a3262 --- /dev/null +++ b/Godeps/_workspace/src/github.com/petar/GoLLRB/llrb/avgvar.go @@ -0,0 +1,39 @@ +// Copyright 2010 Petar Maymounkov. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package llrb + +import "math" + +// avgVar maintains the average and variance of a stream of numbers +// in a space-efficient manner. +type avgVar struct { + count int64 + sum, sumsq float64 +} + +func (av *avgVar) Init() { + av.count = 0 + av.sum = 0.0 + av.sumsq = 0.0 +} + +func (av *avgVar) Add(sample float64) { + av.count++ + av.sum += sample + av.sumsq += sample * sample +} + +func (av *avgVar) GetCount() int64 { return av.count } + +func (av *avgVar) GetAvg() float64 { return av.sum / float64(av.count) } + +func (av *avgVar) GetTotal() float64 { return av.sum } + +func (av *avgVar) GetVar() float64 { + a := av.GetAvg() + return av.sumsq/float64(av.count) - a*a +} + +func (av *avgVar) GetStdDev() float64 { return math.Sqrt(av.GetVar()) } diff --git a/Godeps/_workspace/src/github.com/petar/GoLLRB/llrb/iterator.go b/Godeps/_workspace/src/github.com/petar/GoLLRB/llrb/iterator.go new file mode 100644 index 0000000000..ee7b27f442 --- /dev/null +++ b/Godeps/_workspace/src/github.com/petar/GoLLRB/llrb/iterator.go @@ -0,0 +1,93 @@ +package llrb + +type ItemIterator func(i Item) bool + +//func (t *Tree) Ascend(iterator ItemIterator) { +// t.AscendGreaterOrEqual(Inf(-1), iterator) +//} + +func (t *LLRB) AscendRange(greaterOrEqual, lessThan Item, iterator ItemIterator) { + t.ascendRange(t.root, greaterOrEqual, lessThan, iterator) +} + +func (t *LLRB) ascendRange(h *Node, inf, sup Item, iterator ItemIterator) bool { + if h == nil { + return true + } + if !less(h.Item, sup) { + return t.ascendRange(h.Left, inf, sup, iterator) + } + if less(h.Item, inf) { + return t.ascendRange(h.Right, inf, sup, iterator) + } + + if !t.ascendRange(h.Left, inf, sup, iterator) { + return false + } + if !iterator(h.Item) { + return false + } + return t.ascendRange(h.Right, inf, sup, iterator) +} + +// AscendGreaterOrEqual will call iterator once for each element greater or equal to +// pivot in ascending order. It will stop whenever the iterator returns false. +func (t *LLRB) AscendGreaterOrEqual(pivot Item, iterator ItemIterator) { + t.ascendGreaterOrEqual(t.root, pivot, iterator) +} + +func (t *LLRB) ascendGreaterOrEqual(h *Node, pivot Item, iterator ItemIterator) bool { + if h == nil { + return true + } + if !less(h.Item, pivot) { + if !t.ascendGreaterOrEqual(h.Left, pivot, iterator) { + return false + } + if !iterator(h.Item) { + return false + } + } + return t.ascendGreaterOrEqual(h.Right, pivot, iterator) +} + +func (t *LLRB) AscendLessThan(pivot Item, iterator ItemIterator) { + t.ascendLessThan(t.root, pivot, iterator) +} + +func (t *LLRB) ascendLessThan(h *Node, pivot Item, iterator ItemIterator) bool { + if h == nil { + return true + } + if !t.ascendLessThan(h.Left, pivot, iterator) { + return false + } + if !iterator(h.Item) { + return false + } + if less(h.Item, pivot) { + return t.ascendLessThan(h.Left, pivot, iterator) + } + return true +} + +// DescendLessOrEqual will call iterator once for each element less than the +// pivot in descending order. It will stop whenever the iterator returns false. +func (t *LLRB) DescendLessOrEqual(pivot Item, iterator ItemIterator) { + t.descendLessOrEqual(t.root, pivot, iterator) +} + +func (t *LLRB) descendLessOrEqual(h *Node, pivot Item, iterator ItemIterator) bool { + if h == nil { + return true + } + if less(h.Item, pivot) || !less(pivot, h.Item) { + if !t.descendLessOrEqual(h.Right, pivot, iterator) { + return false + } + if !iterator(h.Item) { + return false + } + } + return t.descendLessOrEqual(h.Left, pivot, iterator) +} diff --git a/Godeps/_workspace/src/github.com/petar/GoLLRB/llrb/iterator_test.go b/Godeps/_workspace/src/github.com/petar/GoLLRB/llrb/iterator_test.go new file mode 100644 index 0000000000..db5e12c92e --- /dev/null +++ b/Godeps/_workspace/src/github.com/petar/GoLLRB/llrb/iterator_test.go @@ -0,0 +1,76 @@ +package llrb + +import ( + "reflect" + "testing" +) + +func TestAscendGreaterOrEqual(t *testing.T) { + tree := New() + tree.InsertNoReplace(Int(4)) + tree.InsertNoReplace(Int(6)) + tree.InsertNoReplace(Int(1)) + tree.InsertNoReplace(Int(3)) + var ary []Item + tree.AscendGreaterOrEqual(Int(-1), func(i Item) bool { + ary = append(ary, i) + return true + }) + expected := []Item{Int(1), Int(3), Int(4), Int(6)} + if !reflect.DeepEqual(ary, expected) { + t.Errorf("expected %v but got %v", expected, ary) + } + ary = nil + tree.AscendGreaterOrEqual(Int(3), func(i Item) bool { + ary = append(ary, i) + return true + }) + expected = []Item{Int(3), Int(4), Int(6)} + if !reflect.DeepEqual(ary, expected) { + t.Errorf("expected %v but got %v", expected, ary) + } + ary = nil + tree.AscendGreaterOrEqual(Int(2), func(i Item) bool { + ary = append(ary, i) + return true + }) + expected = []Item{Int(3), Int(4), Int(6)} + if !reflect.DeepEqual(ary, expected) { + t.Errorf("expected %v but got %v", expected, ary) + } +} + +func TestDescendLessOrEqual(t *testing.T) { + tree := New() + tree.InsertNoReplace(Int(4)) + tree.InsertNoReplace(Int(6)) + tree.InsertNoReplace(Int(1)) + tree.InsertNoReplace(Int(3)) + var ary []Item + tree.DescendLessOrEqual(Int(10), func(i Item) bool { + ary = append(ary, i) + return true + }) + expected := []Item{Int(6), Int(4), Int(3), Int(1)} + if !reflect.DeepEqual(ary, expected) { + t.Errorf("expected %v but got %v", expected, ary) + } + ary = nil + tree.DescendLessOrEqual(Int(4), func(i Item) bool { + ary = append(ary, i) + return true + }) + expected = []Item{Int(4), Int(3), Int(1)} + if !reflect.DeepEqual(ary, expected) { + t.Errorf("expected %v but got %v", expected, ary) + } + ary = nil + tree.DescendLessOrEqual(Int(5), func(i Item) bool { + ary = append(ary, i) + return true + }) + expected = []Item{Int(4), Int(3), Int(1)} + if !reflect.DeepEqual(ary, expected) { + t.Errorf("expected %v but got %v", expected, ary) + } +} diff --git a/Godeps/_workspace/src/github.com/petar/GoLLRB/llrb/llrb-stats.go b/Godeps/_workspace/src/github.com/petar/GoLLRB/llrb/llrb-stats.go new file mode 100644 index 0000000000..47126a3be9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/petar/GoLLRB/llrb/llrb-stats.go @@ -0,0 +1,46 @@ +// Copyright 2010 Petar Maymounkov. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package llrb + +// GetHeight() returns an item in the tree with key @key, and it's height in the tree +func (t *LLRB) GetHeight(key Item) (result Item, depth int) { + return t.getHeight(t.root, key) +} + +func (t *LLRB) getHeight(h *Node, item Item) (Item, int) { + if h == nil { + return nil, 0 + } + if less(item, h.Item) { + result, depth := t.getHeight(h.Left, item) + return result, depth + 1 + } + if less(h.Item, item) { + result, depth := t.getHeight(h.Right, item) + return result, depth + 1 + } + return h.Item, 0 +} + +// HeightStats() returns the average and standard deviation of the height +// of elements in the tree +func (t *LLRB) HeightStats() (avg, stddev float64) { + av := &avgVar{} + heightStats(t.root, 0, av) + return av.GetAvg(), av.GetStdDev() +} + +func heightStats(h *Node, d int, av *avgVar) { + if h == nil { + return + } + av.Add(float64(d)) + if h.Left != nil { + heightStats(h.Left, d+1, av) + } + if h.Right != nil { + heightStats(h.Right, d+1, av) + } +} diff --git a/Godeps/_workspace/src/github.com/petar/GoLLRB/llrb/llrb.go b/Godeps/_workspace/src/github.com/petar/GoLLRB/llrb/llrb.go new file mode 100644 index 0000000000..81373fbfdf --- /dev/null +++ b/Godeps/_workspace/src/github.com/petar/GoLLRB/llrb/llrb.go @@ -0,0 +1,456 @@ +// Copyright 2010 Petar Maymounkov. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// A Left-Leaning Red-Black (LLRB) implementation of 2-3 balanced binary search trees, +// based on the following work: +// +// http://www.cs.princeton.edu/~rs/talks/LLRB/08Penn.pdf +// http://www.cs.princeton.edu/~rs/talks/LLRB/LLRB.pdf +// http://www.cs.princeton.edu/~rs/talks/LLRB/Java/RedBlackBST.java +// +// 2-3 trees (and the run-time equivalent 2-3-4 trees) are the de facto standard BST +// algoritms found in implementations of Python, Java, and other libraries. The LLRB +// implementation of 2-3 trees is a recent improvement on the traditional implementation, +// observed and documented by Robert Sedgewick. +// +package llrb + +// Tree is a Left-Leaning Red-Black (LLRB) implementation of 2-3 trees +type LLRB struct { + count int + root *Node +} + +type Node struct { + Item + Left, Right *Node // Pointers to left and right child nodes + Black bool // If set, the color of the link (incoming from the parent) is black + // In the LLRB, new nodes are always red, hence the zero-value for node +} + +type Item interface { + Less(than Item) bool +} + +// +func less(x, y Item) bool { + if x == pinf { + return false + } + if x == ninf { + return true + } + return x.Less(y) +} + +// Inf returns an Item that is "bigger than" any other item, if sign is positive. +// Otherwise it returns an Item that is "smaller than" any other item. +func Inf(sign int) Item { + if sign == 0 { + panic("sign") + } + if sign > 0 { + return pinf + } + return ninf +} + +var ( + ninf = nInf{} + pinf = pInf{} +) + +type nInf struct{} + +func (nInf) Less(Item) bool { + return true +} + +type pInf struct{} + +func (pInf) Less(Item) bool { + return false +} + +// New() allocates a new tree +func New() *LLRB { + return &LLRB{} +} + +// SetRoot sets the root node of the tree. +// It is intended to be used by functions that deserialize the tree. +func (t *LLRB) SetRoot(r *Node) { + t.root = r +} + +// Root returns the root node of the tree. +// It is intended to be used by functions that serialize the tree. +func (t *LLRB) Root() *Node { + return t.root +} + +// Len returns the number of nodes in the tree. +func (t *LLRB) Len() int { return t.count } + +// Has returns true if the tree contains an element whose order is the same as that of key. +func (t *LLRB) Has(key Item) bool { + return t.Get(key) != nil +} + +// Get retrieves an element from the tree whose order is the same as that of key. +func (t *LLRB) Get(key Item) Item { + h := t.root + for h != nil { + switch { + case less(key, h.Item): + h = h.Left + case less(h.Item, key): + h = h.Right + default: + return h.Item + } + } + return nil +} + +// Min returns the minimum element in the tree. +func (t *LLRB) Min() Item { + h := t.root + if h == nil { + return nil + } + for h.Left != nil { + h = h.Left + } + return h.Item +} + +// Max returns the maximum element in the tree. +func (t *LLRB) Max() Item { + h := t.root + if h == nil { + return nil + } + for h.Right != nil { + h = h.Right + } + return h.Item +} + +func (t *LLRB) ReplaceOrInsertBulk(items ...Item) { + for _, i := range items { + t.ReplaceOrInsert(i) + } +} + +func (t *LLRB) InsertNoReplaceBulk(items ...Item) { + for _, i := range items { + t.InsertNoReplace(i) + } +} + +// ReplaceOrInsert inserts item into the tree. If an existing +// element has the same order, it is removed from the tree and returned. +func (t *LLRB) ReplaceOrInsert(item Item) Item { + if item == nil { + panic("inserting nil item") + } + var replaced Item + t.root, replaced = t.replaceOrInsert(t.root, item) + t.root.Black = true + if replaced == nil { + t.count++ + } + return replaced +} + +func (t *LLRB) replaceOrInsert(h *Node, item Item) (*Node, Item) { + if h == nil { + return newNode(item), nil + } + + h = walkDownRot23(h) + + var replaced Item + if less(item, h.Item) { // BUG + h.Left, replaced = t.replaceOrInsert(h.Left, item) + } else if less(h.Item, item) { + h.Right, replaced = t.replaceOrInsert(h.Right, item) + } else { + replaced, h.Item = h.Item, item + } + + h = walkUpRot23(h) + + return h, replaced +} + +// InsertNoReplace inserts item into the tree. If an existing +// element has the same order, both elements remain in the tree. +func (t *LLRB) InsertNoReplace(item Item) { + if item == nil { + panic("inserting nil item") + } + t.root = t.insertNoReplace(t.root, item) + t.root.Black = true + t.count++ +} + +func (t *LLRB) insertNoReplace(h *Node, item Item) *Node { + if h == nil { + return newNode(item) + } + + h = walkDownRot23(h) + + if less(item, h.Item) { + h.Left = t.insertNoReplace(h.Left, item) + } else { + h.Right = t.insertNoReplace(h.Right, item) + } + + return walkUpRot23(h) +} + +// Rotation driver routines for 2-3 algorithm + +func walkDownRot23(h *Node) *Node { return h } + +func walkUpRot23(h *Node) *Node { + if isRed(h.Right) && !isRed(h.Left) { + h = rotateLeft(h) + } + + if isRed(h.Left) && isRed(h.Left.Left) { + h = rotateRight(h) + } + + if isRed(h.Left) && isRed(h.Right) { + flip(h) + } + + return h +} + +// Rotation driver routines for 2-3-4 algorithm + +func walkDownRot234(h *Node) *Node { + if isRed(h.Left) && isRed(h.Right) { + flip(h) + } + + return h +} + +func walkUpRot234(h *Node) *Node { + if isRed(h.Right) && !isRed(h.Left) { + h = rotateLeft(h) + } + + if isRed(h.Left) && isRed(h.Left.Left) { + h = rotateRight(h) + } + + return h +} + +// DeleteMin deletes the minimum element in the tree and returns the +// deleted item or nil otherwise. +func (t *LLRB) DeleteMin() Item { + var deleted Item + t.root, deleted = deleteMin(t.root) + if t.root != nil { + t.root.Black = true + } + if deleted != nil { + t.count-- + } + return deleted +} + +// deleteMin code for LLRB 2-3 trees +func deleteMin(h *Node) (*Node, Item) { + if h == nil { + return nil, nil + } + if h.Left == nil { + return nil, h.Item + } + + if !isRed(h.Left) && !isRed(h.Left.Left) { + h = moveRedLeft(h) + } + + var deleted Item + h.Left, deleted = deleteMin(h.Left) + + return fixUp(h), deleted +} + +// DeleteMax deletes the maximum element in the tree and returns +// the deleted item or nil otherwise +func (t *LLRB) DeleteMax() Item { + var deleted Item + t.root, deleted = deleteMax(t.root) + if t.root != nil { + t.root.Black = true + } + if deleted != nil { + t.count-- + } + return deleted +} + +func deleteMax(h *Node) (*Node, Item) { + if h == nil { + return nil, nil + } + if isRed(h.Left) { + h = rotateRight(h) + } + if h.Right == nil { + return nil, h.Item + } + if !isRed(h.Right) && !isRed(h.Right.Left) { + h = moveRedRight(h) + } + var deleted Item + h.Right, deleted = deleteMax(h.Right) + + return fixUp(h), deleted +} + +// Delete deletes an item from the tree whose key equals key. +// The deleted item is return, otherwise nil is returned. +func (t *LLRB) Delete(key Item) Item { + var deleted Item + t.root, deleted = t.delete(t.root, key) + if t.root != nil { + t.root.Black = true + } + if deleted != nil { + t.count-- + } + return deleted +} + +func (t *LLRB) delete(h *Node, item Item) (*Node, Item) { + var deleted Item + if h == nil { + return nil, nil + } + if less(item, h.Item) { + if h.Left == nil { // item not present. Nothing to delete + return h, nil + } + if !isRed(h.Left) && !isRed(h.Left.Left) { + h = moveRedLeft(h) + } + h.Left, deleted = t.delete(h.Left, item) + } else { + if isRed(h.Left) { + h = rotateRight(h) + } + // If @item equals @h.Item and no right children at @h + if !less(h.Item, item) && h.Right == nil { + return nil, h.Item + } + // PETAR: Added 'h.Right != nil' below + if h.Right != nil && !isRed(h.Right) && !isRed(h.Right.Left) { + h = moveRedRight(h) + } + // If @item equals @h.Item, and (from above) 'h.Right != nil' + if !less(h.Item, item) { + var subDeleted Item + h.Right, subDeleted = deleteMin(h.Right) + if subDeleted == nil { + panic("logic") + } + deleted, h.Item = h.Item, subDeleted + } else { // Else, @item is bigger than @h.Item + h.Right, deleted = t.delete(h.Right, item) + } + } + + return fixUp(h), deleted +} + +// Internal node manipulation routines + +func newNode(item Item) *Node { return &Node{Item: item} } + +func isRed(h *Node) bool { + if h == nil { + return false + } + return !h.Black +} + +func rotateLeft(h *Node) *Node { + x := h.Right + if x.Black { + panic("rotating a black link") + } + h.Right = x.Left + x.Left = h + x.Black = h.Black + h.Black = false + return x +} + +func rotateRight(h *Node) *Node { + x := h.Left + if x.Black { + panic("rotating a black link") + } + h.Left = x.Right + x.Right = h + x.Black = h.Black + h.Black = false + return x +} + +// REQUIRE: Left and Right children must be present +func flip(h *Node) { + h.Black = !h.Black + h.Left.Black = !h.Left.Black + h.Right.Black = !h.Right.Black +} + +// REQUIRE: Left and Right children must be present +func moveRedLeft(h *Node) *Node { + flip(h) + if isRed(h.Right.Left) { + h.Right = rotateRight(h.Right) + h = rotateLeft(h) + flip(h) + } + return h +} + +// REQUIRE: Left and Right children must be present +func moveRedRight(h *Node) *Node { + flip(h) + if isRed(h.Left.Left) { + h = rotateRight(h) + flip(h) + } + return h +} + +func fixUp(h *Node) *Node { + if isRed(h.Right) { + h = rotateLeft(h) + } + + if isRed(h.Left) && isRed(h.Left.Left) { + h = rotateRight(h) + } + + if isRed(h.Left) && isRed(h.Right) { + flip(h) + } + + return h +} diff --git a/Godeps/_workspace/src/github.com/petar/GoLLRB/llrb/llrb_test.go b/Godeps/_workspace/src/github.com/petar/GoLLRB/llrb/llrb_test.go new file mode 100644 index 0000000000..b7bc978007 --- /dev/null +++ b/Godeps/_workspace/src/github.com/petar/GoLLRB/llrb/llrb_test.go @@ -0,0 +1,239 @@ +// Copyright 2010 Petar Maymounkov. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package llrb + +import ( + "math" + "math/rand" + "testing" +) + +func TestCases(t *testing.T) { + tree := New() + tree.ReplaceOrInsert(Int(1)) + tree.ReplaceOrInsert(Int(1)) + if tree.Len() != 1 { + t.Errorf("expecting len 1") + } + if !tree.Has(Int(1)) { + t.Errorf("expecting to find key=1") + } + + tree.Delete(Int(1)) + if tree.Len() != 0 { + t.Errorf("expecting len 0") + } + if tree.Has(Int(1)) { + t.Errorf("not expecting to find key=1") + } + + tree.Delete(Int(1)) + if tree.Len() != 0 { + t.Errorf("expecting len 0") + } + if tree.Has(Int(1)) { + t.Errorf("not expecting to find key=1") + } +} + +func TestReverseInsertOrder(t *testing.T) { + tree := New() + n := 100 + for i := 0; i < n; i++ { + tree.ReplaceOrInsert(Int(n - i)) + } + i := 0 + tree.AscendGreaterOrEqual(Int(0), func(item Item) bool { + i++ + if item.(Int) != Int(i) { + t.Errorf("bad order: got %d, expect %d", item.(Int), i) + } + return true + }) +} + +func TestRange(t *testing.T) { + tree := New() + order := []String{ + "ab", "aba", "abc", "a", "aa", "aaa", "b", "a-", "a!", + } + for _, i := range order { + tree.ReplaceOrInsert(i) + } + k := 0 + tree.AscendRange(String("ab"), String("ac"), func(item Item) bool { + if k > 3 { + t.Fatalf("returned more items than expected") + } + i1 := order[k] + i2 := item.(String) + if i1 != i2 { + t.Errorf("expecting %s, got %s", i1, i2) + } + k++ + return true + }) +} + +func TestRandomInsertOrder(t *testing.T) { + tree := New() + n := 1000 + perm := rand.Perm(n) + for i := 0; i < n; i++ { + tree.ReplaceOrInsert(Int(perm[i])) + } + j := 0 + tree.AscendGreaterOrEqual(Int(0), func(item Item) bool { + if item.(Int) != Int(j) { + t.Fatalf("bad order") + } + j++ + return true + }) +} + +func TestRandomReplace(t *testing.T) { + tree := New() + n := 100 + perm := rand.Perm(n) + for i := 0; i < n; i++ { + tree.ReplaceOrInsert(Int(perm[i])) + } + perm = rand.Perm(n) + for i := 0; i < n; i++ { + if replaced := tree.ReplaceOrInsert(Int(perm[i])); replaced == nil || replaced.(Int) != Int(perm[i]) { + t.Errorf("error replacing") + } + } +} + +func TestRandomInsertSequentialDelete(t *testing.T) { + tree := New() + n := 1000 + perm := rand.Perm(n) + for i := 0; i < n; i++ { + tree.ReplaceOrInsert(Int(perm[i])) + } + for i := 0; i < n; i++ { + tree.Delete(Int(i)) + } +} + +func TestRandomInsertDeleteNonExistent(t *testing.T) { + tree := New() + n := 100 + perm := rand.Perm(n) + for i := 0; i < n; i++ { + tree.ReplaceOrInsert(Int(perm[i])) + } + if tree.Delete(Int(200)) != nil { + t.Errorf("deleted non-existent item") + } + if tree.Delete(Int(-2)) != nil { + t.Errorf("deleted non-existent item") + } + for i := 0; i < n; i++ { + if u := tree.Delete(Int(i)); u == nil || u.(Int) != Int(i) { + t.Errorf("delete failed") + } + } + if tree.Delete(Int(200)) != nil { + t.Errorf("deleted non-existent item") + } + if tree.Delete(Int(-2)) != nil { + t.Errorf("deleted non-existent item") + } +} + +func TestRandomInsertPartialDeleteOrder(t *testing.T) { + tree := New() + n := 100 + perm := rand.Perm(n) + for i := 0; i < n; i++ { + tree.ReplaceOrInsert(Int(perm[i])) + } + for i := 1; i < n-1; i++ { + tree.Delete(Int(i)) + } + j := 0 + tree.AscendGreaterOrEqual(Int(0), func(item Item) bool { + switch j { + case 0: + if item.(Int) != Int(0) { + t.Errorf("expecting 0") + } + case 1: + if item.(Int) != Int(n-1) { + t.Errorf("expecting %d", n-1) + } + } + j++ + return true + }) +} + +func TestRandomInsertStats(t *testing.T) { + tree := New() + n := 100000 + perm := rand.Perm(n) + for i := 0; i < n; i++ { + tree.ReplaceOrInsert(Int(perm[i])) + } + avg, _ := tree.HeightStats() + expAvg := math.Log2(float64(n)) - 1.5 + if math.Abs(avg-expAvg) >= 2.0 { + t.Errorf("too much deviation from expected average height") + } +} + +func BenchmarkInsert(b *testing.B) { + tree := New() + for i := 0; i < b.N; i++ { + tree.ReplaceOrInsert(Int(b.N - i)) + } +} + +func BenchmarkDelete(b *testing.B) { + b.StopTimer() + tree := New() + for i := 0; i < b.N; i++ { + tree.ReplaceOrInsert(Int(b.N - i)) + } + b.StartTimer() + for i := 0; i < b.N; i++ { + tree.Delete(Int(i)) + } +} + +func BenchmarkDeleteMin(b *testing.B) { + b.StopTimer() + tree := New() + for i := 0; i < b.N; i++ { + tree.ReplaceOrInsert(Int(b.N - i)) + } + b.StartTimer() + for i := 0; i < b.N; i++ { + tree.DeleteMin() + } +} + +func TestInsertNoReplace(t *testing.T) { + tree := New() + n := 1000 + for q := 0; q < 2; q++ { + perm := rand.Perm(n) + for i := 0; i < n; i++ { + tree.InsertNoReplace(Int(perm[i])) + } + } + j := 0 + tree.AscendGreaterOrEqual(Int(0), func(item Item) bool { + if item.(Int) != Int(j/2) { + t.Fatalf("bad order") + } + j++ + return true + }) +} diff --git a/Godeps/_workspace/src/github.com/petar/GoLLRB/llrb/util.go b/Godeps/_workspace/src/github.com/petar/GoLLRB/llrb/util.go new file mode 100644 index 0000000000..63dbdb2df0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/petar/GoLLRB/llrb/util.go @@ -0,0 +1,17 @@ +// Copyright 2010 Petar Maymounkov. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package llrb + +type Int int + +func (x Int) Less(than Item) bool { + return x < than.(Int) +} + +type String string + +func (x String) Less(than Item) bool { + return x < than.(String) +} diff --git a/infoschema/infoschema.go b/infoschema/infoschema.go index 44b8b65d22..085500f40a 100644 --- a/infoschema/infoschema.go +++ b/infoschema/infoschema.go @@ -69,10 +69,6 @@ type infoSchema struct { // We should check version when change schema. schemaMetaVersion int64 - - // memory tables - isDB map[string]table.Table // Information_Schema tables - psDB map[string]table.Table // Performance_Schema tables } var _ InfoSchema = (*infoSchema)(nil) @@ -222,7 +218,6 @@ func genGlobalID(store kv.Storage) (int64, error) { } var ( - // memory tables // Information_Schema isDB *model.DBInfo schemataTbl table.Table @@ -235,7 +230,6 @@ var ( defTbl table.Table profilingTbl table.Table nameToTable map[string]table.Table - // TODO: Performance_Schema ) func setColumnID(meta *model.TableInfo, store kv.Storage) error { @@ -246,22 +240,21 @@ func setColumnID(meta *model.TableInfo, store kv.Storage) error { return errors.Trace(err) } } - return err + return nil } func initMemoryTables(store kv.Storage) error { - // build data - dbID := int64(0) - alloc := autoid.NewMemoryAllocator(dbID) - nameToTable = make(map[string]table.Table) - - // Schemata - isTables := make([]*model.TableInfo, 0, 8) - // initTable For each data + // Init Information_Schema var ( err error tbl table.Table ) + dbID, err := genGlobalID(store) + if err != nil { + return errors.Trace(err) + } + nameToTable = make(map[string]table.Table) + isTables := make([]*model.TableInfo, 0, len(tableNameToColumns)) for name, cols := range tableNameToColumns { meta := buildTableMeta(name, cols) isTables = append(isTables, meta) @@ -273,13 +266,13 @@ func initMemoryTables(store kv.Storage) error { if err != nil { return errors.Trace(err) } + alloc := autoid.NewMemoryAllocator(dbID) tbl, err = createMemoryTable(meta, alloc) if err != nil { return errors.Trace(err) } nameToTable[meta.Name.L] = tbl } - // Set global variables schemataTbl = nameToTable[strings.ToLower(tableSchemata)] tablesTbl = nameToTable[strings.ToLower(tableTables)] columnsTbl = nameToTable[strings.ToLower(tableColumns)] @@ -287,8 +280,7 @@ func initMemoryTables(store kv.Storage) error { charsetTbl = nameToTable[strings.ToLower(tableCharacterSets)] collationsTbl = nameToTable[strings.ToLower(tableCollations)] - // Some tables have static data. Init them now. - // charset + // CharacterSets/Collations contain static data. Init them now. err = insertData(charsetTbl, dataForCharacterSets()) if err != nil { return errors.Trace(err) @@ -299,13 +291,13 @@ func initMemoryTables(store kv.Storage) error { } // create db isDB = &model.DBInfo{ + ID: dbID, Name: model.NewCIStr(Name), Charset: mysql.DefaultCharset, Collate: mysql.DefaultCollationName, Tables: isTables, } - isDB.ID, err = genGlobalID(store) - return errors.Trace(err) + return nil } func insertData(tbl table.Table, rows [][]interface{}) error { @@ -365,13 +357,13 @@ func (h *Handle) Set(newInfo []*model.DBInfo, schemaMetaVersion int64) error { } } } - // add Information_Schema + // Build Information_Schema info.schemaNameToID[isDB.Name.L] = isDB.ID info.schemas[isDB.ID] = isDB for _, t := range isDB.Tables { tbl, ok := nameToTable[t.Name.L] if !ok { - return errors.New("Miss table") + return errors.Errorf("table `%s` is missing.", t.Name) } info.tables[t.ID] = tbl tname := tableName{isDB.Name.L, t.Name.L} @@ -381,29 +373,26 @@ func (h *Handle) Set(newInfo []*model.DBInfo, schemaMetaVersion int64) error { info.columnNameToID[columnName{tname, c.Name.L}] = c.ID } } - // Should refill some tables in Information_Schema + // Should refill some tables in Information_Schema. + // schemata/tables/columns/statistics dbNames := make([]string, 0, len(info.schemas)) dbInfos := make([]*model.DBInfo, 0, len(info.schemas)) for _, v := range info.schemas { dbNames = append(dbNames, v.Name.L) dbInfos = append(dbInfos, v) } - // Refill schemata err = refillTable(schemataTbl, dataForSchemata(dbNames)) if err != nil { return errors.Trace(err) } - // Refill tables err = refillTable(tablesTbl, dataForTables(dbInfos)) if err != nil { return errors.Trace(err) } - // Refill columns err = refillTable(columnsTbl, dataForColumns(dbInfos)) if err != nil { return errors.Trace(err) } - // Refill statistics err = refillTable(statisticsTbl, dataForStatistics(dbInfos)) if err != nil { return errors.Trace(err) diff --git a/infoschema/infoschema_test.go b/infoschema/infoschema_test.go index 2abd6c3e03..65723921e2 100644 --- a/infoschema/infoschema_test.go +++ b/infoschema/infoschema_test.go @@ -16,8 +16,11 @@ package infoschema_test import ( "testing" + "github.com/juju/errors" . "github.com/pingcap/check" "github.com/pingcap/tidb/infoschema" + "github.com/pingcap/tidb/kv" + "github.com/pingcap/tidb/meta" "github.com/pingcap/tidb/model" "github.com/pingcap/tidb/mysql" "github.com/pingcap/tidb/store/localstore" @@ -48,8 +51,10 @@ func (*testSuite) TestT(c *C) { idxName := model.NewCIStr("idx") noexist := model.NewCIStr("noexist") + colID, err := genGlobalID(store) + c.Assert(err, IsNil) colInfo := &model.ColumnInfo{ - ID: 3, + ID: colID, Name: colName, Offset: 0, FieldType: *types.NewFieldType(mysql.TypeLonglong), @@ -71,16 +76,20 @@ func (*testSuite) TestT(c *C) { State: model.StatePublic, } + tbID, err := genGlobalID(store) + c.Assert(err, IsNil) tblInfo := &model.TableInfo{ - ID: 2, + ID: tbID, Name: tbName, Columns: []*model.ColumnInfo{colInfo}, Indices: []*model.IndexInfo{idxInfo}, State: model.StatePublic, } + dbID, err := genGlobalID(store) + c.Assert(err, IsNil) dbInfo := &model.DBInfo{ - ID: 1, + ID: dbID, Name: dbName, Tables: []*model.TableInfo{tblInfo}, State: model.StatePublic, @@ -103,11 +112,11 @@ func (*testSuite) TestT(c *C) { c.Assert(is.SchemaExists(dbName), IsTrue) c.Assert(is.SchemaExists(noexist), IsFalse) - schema, ok := is.SchemaByID(1) + schema, ok := is.SchemaByID(dbID) c.Assert(ok, IsTrue) c.Assert(schema, NotNil) - schema, ok = is.SchemaByID(2) + schema, ok = is.SchemaByID(tbID) c.Assert(ok, IsFalse) c.Assert(schema, IsNil) @@ -122,11 +131,11 @@ func (*testSuite) TestT(c *C) { c.Assert(is.TableExists(dbName, tbName), IsTrue) c.Assert(is.TableExists(dbName, noexist), IsFalse) - tb, ok := is.TableByID(2) + tb, ok := is.TableByID(tbID) c.Assert(ok, IsTrue) c.Assert(tb, NotNil) - tb, ok = is.TableByID(100) + tb, ok = is.TableByID(dbID) c.Assert(ok, IsFalse) c.Assert(tb, IsNil) @@ -140,10 +149,14 @@ func (*testSuite) TestT(c *C) { c.Assert(is.ColumnExists(dbName, tbName, colName), IsTrue) c.Assert(is.ColumnExists(dbName, tbName, noexist), IsFalse) - col, ok := is.ColumnByID(3) + col, ok := is.ColumnByID(colID) c.Assert(ok, IsTrue) c.Assert(col, NotNil) + col, ok = is.ColumnByID(dbID) + c.Assert(ok, IsFalse) + c.Assert(col, IsNil) + col, ok = is.ColumnByName(dbName, tbName, colName) c.Assert(ok, IsTrue) c.Assert(col, NotNil) @@ -152,7 +165,7 @@ func (*testSuite) TestT(c *C) { c.Assert(ok, IsFalse) c.Assert(col, IsNil) - indices, ok := is.ColumnIndicesByID(3) + indices, ok := is.ColumnIndicesByID(colID) c.Assert(ok, IsTrue) c.Assert(len(indices), Equals, 1) @@ -166,3 +179,13 @@ func (*testSuite) TestT(c *C) { c.Assert(ok, IsTrue) c.Assert(idx, NotNil) } + +func genGlobalID(store kv.Storage) (int64, error) { + var globalID int64 + err := kv.RunInNewTxn(store, true, func(txn kv.Transaction) error { + var err error + globalID, err = meta.NewMeta(txn).GenGlobalID() + return errors.Trace(err) + }) + return globalID, errors.Trace(err) +} diff --git a/infoschema/tables.go b/infoschema/tables.go index e3a9cf9d5b..07c6d2f0ea 100644 --- a/infoschema/tables.go +++ b/infoschema/tables.go @@ -27,10 +27,6 @@ import ( "github.com/pingcap/tidb/util/types" ) -/* - * 1. Create tables for Information_Schema - * 2. Fill data for each table. - */ const ( tableSchemata = "SCHEMATA" tableTables = "TABLES" @@ -52,7 +48,6 @@ type columnInfo struct { elems []string } -//func buildColumnInfo(tableName, name string, tp byte, size int) *model.ColumnInfo { func buildColumnInfo(tableName string, col columnInfo) *model.ColumnInfo { mCharset := charset.CharsetBin mCollation := charset.CharsetBin @@ -77,7 +72,7 @@ func buildColumnInfo(tableName string, col columnInfo) *model.ColumnInfo { } func buildTableMeta(tableName string, cs []columnInfo) *model.TableInfo { - cols := make([]*model.ColumnInfo, 0, 5) + cols := make([]*model.ColumnInfo, 0, len(cs)) for _, c := range cs { cols = append(cols, buildColumnInfo(tableName, c)) } @@ -314,7 +309,7 @@ func dataForColumns(schemas []*model.DBInfo) [][]interface{} { rows := [][]interface{}{} for _, schema := range schemas { for _, table := range schema.Tables { - rs := fetchColumnsInTable(schema, table) + rs := dataForColumnsInTable(schema, table) for _, r := range rs { rows = append(rows, r) } @@ -323,7 +318,7 @@ func dataForColumns(schemas []*model.DBInfo) [][]interface{} { return rows } -func fetchColumnsInTable(schema *model.DBInfo, table *model.TableInfo) [][]interface{} { +func dataForColumnsInTable(schema *model.DBInfo, table *model.TableInfo) [][]interface{} { rows := [][]interface{}{} for i, col := range table.Columns { colLen := col.Flen @@ -371,7 +366,7 @@ func dataForStatistics(schemas []*model.DBInfo) [][]interface{} { rows := [][]interface{}{} for _, schema := range schemas { for _, table := range schema.Tables { - rs := fetchStatisticsInTable(schema, table) + rs := dataForStatisticsInTable(schema, table) for _, r := range rs { rows = append(rows, r) } @@ -380,7 +375,7 @@ func dataForStatistics(schemas []*model.DBInfo) [][]interface{} { return rows } -func fetchStatisticsInTable(schema *model.DBInfo, table *model.TableInfo) [][]interface{} { +func dataForStatisticsInTable(schema *model.DBInfo, table *model.TableInfo) [][]interface{} { rows := [][]interface{}{} if table.PKIsHandle { for _, col := range table.Columns { @@ -417,7 +412,6 @@ func fetchStatisticsInTable(schema *model.DBInfo, table *model.TableInfo) [][]in nonUnique = "0" } for i, key := range index.Columns { - //col, _ := is.ColumnByName(schema.Name, table.Name, key.Name) col := nameToCol[key.Name.L] nullable := "YES" if mysql.HasNotNullFlag(col.Flag) { diff --git a/meta/autoid/autoid.go b/meta/autoid/autoid.go index 113a46b6b2..5c663b8576 100644 --- a/meta/autoid/autoid.go +++ b/meta/autoid/autoid.go @@ -78,11 +78,10 @@ var ( ) type memoryAllocator struct { - mu sync.Mutex - store kv.Storage - base int64 - end int64 - dbID int64 + mu sync.Mutex + base int64 + end int64 + dbID int64 } // Alloc allocs the next autoID for table with tableID. @@ -101,7 +100,6 @@ func (alloc *memoryAllocator) Alloc(tableID int64) (int64, error) { memIDLock.Unlock() } alloc.base++ - log.Debugf("[kv] Alloc id %d, table ID:%d, from %p, database ID:%d", alloc.base, tableID, alloc, alloc.dbID) return alloc.base, nil } diff --git a/table/tables/memory_tables.go b/table/tables/memory_tables.go index 12620cd754..9b540f1378 100644 --- a/table/tables/memory_tables.go +++ b/table/tables/memory_tables.go @@ -18,39 +18,81 @@ package tables import ( + "sync" + "github.com/juju/errors" + "github.com/ngaut/log" + "github.com/petar/GoLLRB/llrb" "github.com/pingcap/tidb/column" "github.com/pingcap/tidb/context" "github.com/pingcap/tidb/kv" "github.com/pingcap/tidb/meta/autoid" "github.com/pingcap/tidb/model" "github.com/pingcap/tidb/table" + "github.com/pingcap/tidb/util/types" ) -var store kv.Storage +var ( + errRowNotFound = errors.New("Can not find the row") +) + +type itemKey int64 + +type itemPair struct { + handle itemKey + data []interface{} +} + +func (r *itemPair) Less(item llrb.Item) bool { + switch x := item.(type) { + case itemKey: + return r.handle < x + case *itemPair: + return r.handle < x.handle + } + log.Errorf("invalid type %T", item) + return true +} + +func (k itemKey) Less(item llrb.Item) bool { + switch x := item.(type) { + case itemKey: + return k < x + case *itemPair: + return k < x.handle + } + log.Errorf("invalid type %T", item) + return true +} // MemoryTable implements table.Table interface. type MemoryTable struct { - ID int64 - Name model.CIStr - Columns []*column.Col + ID int64 + Name model.CIStr + Columns []*column.Col + pkHandleCol *column.Col recordPrefix kv.Key alloc autoid.Allocator meta *model.TableInfo - //store kv.Storage - rows [][]interface{} + tree *llrb.LLRB + mu sync.RWMutex } // MemoryTableFromMeta creates a Table instance from model.TableInfo. func MemoryTableFromMeta(alloc autoid.Allocator, tblInfo *model.TableInfo) (table.Table, error) { columns := make([]*column.Col, 0, len(tblInfo.Columns)) + var pkHandleColumn *column.Col for _, colInfo := range tblInfo.Columns { col := &column.Col{ColumnInfo: *colInfo} columns = append(columns, col) + if col.IsPKHandleColumn(tblInfo) { + pkHandleColumn = col + } } t := newMemoryTable(tblInfo.ID, tblInfo.Name.O, columns, alloc) + t.pkHandleCol = pkHandleColumn t.meta = tblInfo return t, nil } @@ -64,58 +106,23 @@ func newMemoryTable(tableID int64, tableName string, cols []*column.Col, alloc a alloc: alloc, Columns: cols, recordPrefix: genTableRecordPrefix(tableID), - rows: [][]interface{}{}, + tree: llrb.New(), } return t } -/* -type iterator struct { - rows [][]interface{} - cursor int - tid int64 -} - -func (it *iterator) Valid() bool { - if it.cursor < 0 || it.cursor >= len(it.rows) { - return false - } - return true -} - -func (it *iterator) Key() kv.Key { - return EncodeRecordKey(it.tid, int64(it.cursor), 0) -} - -func (it *iterator) Value() []byte { - return nil -} - -func (it *iterator) Next() error { - it.cursor++ - return nil -} - -func (it *iterator) Close() { - it.cursor = -1 - return -} -*/ - // Seek seeks the handle func (t *MemoryTable) Seek(ctx context.Context, handle int64) (int64, bool, error) { - if handle < 0 { - handle = 0 - } - if handle >= int64(len(t.rows)) { - return 0, false, nil - } - return handle, true, nil -} - -// TableID implements table.Table TableID interface. -func (t *MemoryTable) TableID() int64 { - return t.ID + var found bool + var result int64 + t.mu.RLock() + t.tree.AscendGreaterOrEqual(itemKey(handle), func(item llrb.Item) bool { + found = true + result = int64(item.(*itemPair).handle) + return false + }) + t.mu.RUnlock() + return result, found, nil } // Indices implements table.Table Indices interface. @@ -123,16 +130,6 @@ func (t *MemoryTable) Indices() []*column.IndexedCol { return nil } -// AddIndex implements table.Table AddIndex interface. -func (t *MemoryTable) AddIndex(idxCol *column.IndexedCol) { - return -} - -// TableName implements table.Table TableName interface. -func (t *MemoryTable) TableName() model.CIStr { - return t.Name -} - // Meta implements table.Table Meta interface. func (t *MemoryTable) Meta() *model.TableInfo { return t.meta @@ -167,54 +164,60 @@ func (t *MemoryTable) FirstKey() kv.Key { return t.RecordKey(0, nil) } -// FindIndexByColName implements table.Table FindIndexByColName interface. -func (t *MemoryTable) FindIndexByColName(name string) *column.IndexedCol { - return nil -} - // Truncate implements table.Table Truncate interface. func (t *MemoryTable) Truncate(ctx context.Context) error { - t.rows = [][]interface{}{} + t.tree = llrb.New() return nil } // UpdateRecord implements table.Table UpdateRecord interface. func (t *MemoryTable) UpdateRecord(ctx context.Context, h int64, oldData []interface{}, newData []interface{}, touched map[int]bool) error { - // Unsupport - return nil -} - -// SetColValue implements table.Table SetColValue interface. -func (t *MemoryTable) SetColValue(rm kv.RetrieverMutator, key []byte, data interface{}) error { + t.mu.Lock() + defer t.mu.Unlock() + item := t.tree.Get(itemKey(h)) + if item == nil { + return errRowNotFound + } + pair := item.(*itemPair) + pair.data = newData return nil } // AddRecord implements table.Table AddRecord interface. func (t *MemoryTable) AddRecord(ctx context.Context, r []interface{}) (recordID int64, err error) { - recordID = int64(len(t.rows)) - t.rows = append(t.rows, r) + if t.pkHandleCol != nil { + recordID, err = types.ToInt64(r[t.pkHandleCol.Offset]) + if err != nil { + return 0, errors.Trace(err) + } + } else { + recordID, err = t.alloc.Alloc(t.ID) + if err != nil { + return 0, errors.Trace(err) + } + } + item := &itemPair{ + handle: itemKey(recordID), + data: r, + } + t.mu.Lock() + defer t.mu.Unlock() + if t.tree.Get(itemKey(recordID)) != nil { + return 0, kv.ErrKeyExists + } + t.tree.ReplaceOrInsert(item) return } -// EncodeValue implements table.Table EncodeValue interface. -func (t *MemoryTable) EncodeValue(raw interface{}) ([]byte, error) { - return nil, nil -} - -// DecodeValue implements table.Table DecodeValue interface. -func (t *MemoryTable) DecodeValue(data []byte, col *column.Col) (interface{}, error) { - return nil, nil -} - // RowWithCols implements table.Table RowWithCols interface. func (t *MemoryTable) RowWithCols(ctx context.Context, h int64, cols []*column.Col) ([]interface{}, error) { - if h >= int64(len(t.rows)) || h < 0 { - return nil, errors.New("Can not find the row") - } - row := t.rows[h] - if row == nil { - return nil, errors.New("Can not find row") + t.mu.RLock() + defer t.mu.RUnlock() + item := t.tree.Get(itemKey(h)) + if item == nil { + return nil, errRowNotFound } + row := item.(*itemPair).data v := make([]interface{}, len(cols)) for i, col := range cols { v[i] = row[col.Offset] @@ -238,20 +241,9 @@ func (t *MemoryTable) LockRow(ctx context.Context, h int64, forRead bool) error // RemoveRecord implements table.Table RemoveRecord interface. func (t *MemoryTable) RemoveRecord(ctx context.Context, h int64, r []interface{}) error { - if h >= int64(len(t.rows)) || h < 0 { - return errors.New("Can not find the row") - } - t.rows[h] = nil - return nil -} - -// RemoveRowIndex implements table.Table RemoveRowIndex interface. -func (t *MemoryTable) RemoveRowIndex(rm kv.RetrieverMutator, h int64, vals []interface{}, idx *column.IndexedCol) error { - return nil -} - -// BuildIndexForRow implements table.Table BuildIndexForRow interface. -func (t *MemoryTable) BuildIndexForRow(rm kv.RetrieverMutator, h int64, vals []interface{}, idx *column.IndexedCol) error { + t.mu.Lock() + t.tree.Delete(itemKey(h)) + t.mu.Unlock() return nil } diff --git a/table/tables/memory_tables_test.go b/table/tables/memory_tables_test.go index 4a0478abed..4dc0ce4ba1 100644 --- a/table/tables/memory_tables_test.go +++ b/table/tables/memory_tables_test.go @@ -1,4 +1,4 @@ -// Copyright 2015 PingCAP, Inc. +// Copyright 2016 PingCAP, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -74,9 +74,9 @@ func (ts *testMemoryTableSuite) SetUpSuite(c *C) { func (ts *testMemoryTableSuite) TestMemoryBasic(c *C) { ctx := ts.se.(context.Context) tb := ts.tbl + c.Assert(tb.Meta(), NotNil) c.Assert(tb.Meta().ID, Greater, int64(0)) c.Assert(tb.Meta().Name.L, Equals, "t") - c.Assert(tb.Meta(), NotNil) c.Assert(tb.Indices(), IsNil) c.Assert(string(tb.FirstKey()), Not(Equals), "") c.Assert(string(tb.RecordPrefix()), Not(Equals), "") @@ -87,7 +87,6 @@ func (ts *testMemoryTableSuite) TestMemoryBasic(c *C) { rid, err := tb.AddRecord(ctx, []interface{}{1, "abc"}) c.Assert(err, IsNil) - c.Assert(rid, Equals, int64(0)) row, err := tb.Row(ctx, rid) c.Assert(err, IsNil) c.Assert(len(row), Equals, 2) diff --git a/table/tables/tables.go b/table/tables/tables.go index 4b10eb1688..a50e29d2e7 100644 --- a/table/tables/tables.go +++ b/table/tables/tables.go @@ -107,11 +107,6 @@ func newTable(tableID int64, cols []*column.Col, alloc autoid.Allocator) *Table return t } -// TableID implements table.Table TableID interface. -func (t *Table) TableID() int64 { - return t.ID -} - // Indices implements table.Table Indices interface. func (t *Table) Indices() []*column.IndexedCol { return t.indices