More testing.
parent
e15fe41a50
commit
f4d72247e4
|
@ -5,13 +5,14 @@ An in-process, in-memory database for Go.
|
|||
## TO DO
|
||||
|
||||
* mapindex_test.go
|
||||
* TestFullMapIndex
|
||||
* TestPartialMapIndex
|
||||
* ~~TestFullMapIndex~~
|
||||
* btreeindex_test.go
|
||||
* TestFullBTreeIndex
|
||||
* TestPartialBTreeIndex
|
||||
* btreeiterator_test.go
|
||||
* btreeiterator_test.go (?)
|
||||
* collection
|
||||
* database
|
||||
* WAL shipping
|
||||
* WAL shipping with network disconnects
|
||||
|
||||
* BTreeIndex:
|
||||
* Should insert panic if item is replaced?
|
||||
|
|
|
@ -29,7 +29,7 @@ func NewBTreeIndex[T any](
|
|||
include: include,
|
||||
}
|
||||
|
||||
btree := btree.NewG(64, less)
|
||||
btree := btree.NewG(32, less)
|
||||
t.bt.Store(btree)
|
||||
c.indices = append(c.indices, t)
|
||||
|
||||
|
|
|
@ -35,17 +35,103 @@ func (bt *BTreeIndex[T]) EqualsList(data []*T) error {
|
|||
return fmt.Errorf("Expected %d items, but found %d.", bt.Len(), len(data))
|
||||
}
|
||||
|
||||
it1 := bt.Ascend()
|
||||
defer it1.Close()
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ascend fully.
|
||||
it := bt.Ascend()
|
||||
for _, v1 := range data {
|
||||
it1.Next()
|
||||
v2 := it1.Value()
|
||||
it.Next()
|
||||
v2 := it.Value()
|
||||
|
||||
if !reflect.DeepEqual(v1, v2) {
|
||||
return fmt.Errorf("Value mismatch: %v != %v", v1, v2)
|
||||
if !reflect.DeepEqual(*v1, v2) {
|
||||
return fmt.Errorf("Value mismatch: %v != %v", *v1, v2)
|
||||
}
|
||||
}
|
||||
if it.Next() {
|
||||
return fmt.Errorf("Next returned true after full ascend.")
|
||||
}
|
||||
it.Close()
|
||||
|
||||
// Descend fully.
|
||||
it = bt.Descend()
|
||||
dataList := data
|
||||
for len(dataList) > 0 {
|
||||
v1 := dataList[len(dataList)-1]
|
||||
dataList = dataList[:len(dataList)-1]
|
||||
it.Next()
|
||||
v2 := it.Value()
|
||||
|
||||
if !reflect.DeepEqual(*v1, v2) {
|
||||
return fmt.Errorf("Value mismatch: %v != %v", *v1, v2)
|
||||
}
|
||||
}
|
||||
if it.Next() {
|
||||
return fmt.Errorf("Next returned true after full descend.")
|
||||
}
|
||||
it.Close()
|
||||
|
||||
// AscendAfter
|
||||
dataList = data
|
||||
for len(dataList) > 1 {
|
||||
dataList = dataList[1:]
|
||||
it = bt.AscendAfter(*dataList[0])
|
||||
|
||||
for _, v1 := range dataList {
|
||||
it.Next()
|
||||
v2 := it.Value()
|
||||
if !reflect.DeepEqual(*v1, v2) {
|
||||
return fmt.Errorf("Value mismatch: %v != %v", *v1, v2)
|
||||
}
|
||||
}
|
||||
if it.Next() {
|
||||
return fmt.Errorf("Next returned true after partial ascend.")
|
||||
}
|
||||
it.Close()
|
||||
}
|
||||
|
||||
// DescendAfter
|
||||
dataList = data
|
||||
for len(dataList) > 1 {
|
||||
dataList = dataList[:len(dataList)-1]
|
||||
it = bt.DescendAfter(*dataList[len(dataList)-1])
|
||||
|
||||
for i := len(dataList) - 1; i >= 0; i-- {
|
||||
v1 := dataList[i]
|
||||
it.Next()
|
||||
v2 := it.Value()
|
||||
if !reflect.DeepEqual(*v1, v2) {
|
||||
return fmt.Errorf("Value mismatch: %v != %v", *v1, v2)
|
||||
}
|
||||
}
|
||||
if it.Next() {
|
||||
return fmt.Errorf("Next returned true after partial descend: %#v", it.Value())
|
||||
}
|
||||
it.Close()
|
||||
}
|
||||
|
||||
// Using Get.
|
||||
for _, v1 := range data {
|
||||
v2, ok := bt.Get(*v1)
|
||||
if !ok || !reflect.DeepEqual(*v1, v2) {
|
||||
return fmt.Errorf("Value mismatch: %v != %v", *v1, v2)
|
||||
}
|
||||
}
|
||||
|
||||
// Min.
|
||||
v1 := data[0]
|
||||
v2, ok := bt.Min()
|
||||
if !ok || !reflect.DeepEqual(*v1, v2) {
|
||||
return fmt.Errorf("Value mismatch: %v != %v", *v1, v2)
|
||||
}
|
||||
|
||||
// Max.
|
||||
v1 = data[len(data)-1]
|
||||
v2, ok = bt.Max()
|
||||
if !ok || !reflect.DeepEqual(*v1, v2) {
|
||||
return fmt.Errorf("Value mismatch: %v != %v", *v1, v2)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,376 +1,133 @@
|
|||
package mdb
|
||||
|
||||
/*
|
||||
func TestBTreeIndex(t *testing.T) {
|
||||
type Item struct {
|
||||
ID uint64
|
||||
Name string
|
||||
}
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
checkIndexOne := func(idx *BTreeIndex[Item], expected ...Item) error {
|
||||
if idx.Len() != len(expected) {
|
||||
return fmt.Errorf("Expected %d items but found %d.", len(expected), idx.Len())
|
||||
}
|
||||
func TestFullBTreeIndex(t *testing.T) {
|
||||
|
||||
if len(expected) == 0 {
|
||||
return nil
|
||||
}
|
||||
// Test against the email index.
|
||||
run := func(name string, inner func(t *testing.T, db *DB) []*User) {
|
||||
testWithDB(t, name, func(t *testing.T, db *DB) {
|
||||
expected := inner(t, db)
|
||||
|
||||
for _, item := range expected {
|
||||
item2, ok := idx.Get(item)
|
||||
if !ok {
|
||||
return fmt.Errorf("Missing expected item: %v", item)
|
||||
if err := db.Users.emailBTree.EqualsList(expected); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(item, item2) {
|
||||
return fmt.Errorf("Items not equal: %v != %v", item2, item)
|
||||
|
||||
db.Close()
|
||||
db = OpenDB(db.root, true)
|
||||
|
||||
if err := db.Users.emailBTree.EqualsList(expected); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
item, ok := idx.Min()
|
||||
if !ok {
|
||||
return fmt.Errorf("Min item not found, expected: %v", expected[0])
|
||||
}
|
||||
if !reflect.DeepEqual(item, expected[0]) {
|
||||
return fmt.Errorf("Min items not equal: %v != %v", item, expected[0])
|
||||
}
|
||||
|
||||
item, ok = idx.Max()
|
||||
i := len(expected) - 1
|
||||
if !ok {
|
||||
return fmt.Errorf("Max item not found, expected: %v", expected[i])
|
||||
}
|
||||
if !reflect.DeepEqual(item, expected[i]) {
|
||||
return fmt.Errorf("Max items not equal: %v != %v", item, expected[i])
|
||||
}
|
||||
|
||||
i = 0
|
||||
|
||||
iter := idx.Ascend()
|
||||
defer iter.Close()
|
||||
for iter.Next() {
|
||||
if !reflect.DeepEqual(iter.Value(), expected[i]) {
|
||||
return fmt.Errorf("Items not equal (%d): %v != %v", i, iter.Value(), expected[i])
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
i = len(expected) - 1
|
||||
iter = idx.Descend()
|
||||
defer iter.Close()
|
||||
for iter.Next() {
|
||||
if !reflect.DeepEqual(iter.Value(), expected[i]) {
|
||||
return fmt.Errorf("Items not equal (%d): %v != %v", i, iter.Value(), expected[i])
|
||||
}
|
||||
i--
|
||||
}
|
||||
|
||||
i = 1
|
||||
iter = idx.AscendAfter(expected[1])
|
||||
defer iter.Close()
|
||||
for iter.Next() {
|
||||
if !reflect.DeepEqual(iter.Value(), expected[i]) {
|
||||
return fmt.Errorf("Items not equal (%d): %v != %v", i, iter.Value(), expected[i])
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
i = len(expected) - 2
|
||||
iter = idx.DescendAfter(expected[len(expected)-2])
|
||||
defer iter.Close()
|
||||
for iter.Next() {
|
||||
if !reflect.DeepEqual(iter.Value(), expected[i]) {
|
||||
return fmt.Errorf("Items not equal (%d): %v != %v", i, iter.Value(), expected[i])
|
||||
}
|
||||
i--
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
checkIndex := func(idx *BTreeIndex[Item], expected ...Item) error {
|
||||
idx.c.db.waitForWAL()
|
||||
|
||||
if err := checkIndexOne(idx, expected...); err != nil {
|
||||
return fmt.Errorf("%w: original", err)
|
||||
}
|
||||
|
||||
db := NewPrimary(idx.c.db.root)
|
||||
defer db.Close()
|
||||
c := NewCollection(db, "collection", func(i *Item) uint64 { return i.ID })
|
||||
idx = NewBTreeIndex(c,
|
||||
func(i, j *Item) bool { return i.Name < j.Name },
|
||||
func(i *Item) bool { return i.Name != "" })
|
||||
db.Start()
|
||||
|
||||
return checkIndexOne(idx, expected...)
|
||||
}
|
||||
|
||||
run := func(name string, inner func(t *testing.T, c *Collection[Item], idx *BTreeIndex[Item])) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
root := filepath.Join(os.TempDir(), randString())
|
||||
//defer os.RemoveAll(root)
|
||||
|
||||
db := NewPrimary(root)
|
||||
defer db.Close()
|
||||
|
||||
c := NewCollection(db, "collection", func(i *Item) uint64 { return i.ID })
|
||||
idx := NewBTreeIndex(c,
|
||||
func(i, j *Item) bool { return i.Name < j.Name },
|
||||
func(i *Item) bool { return i.Name != "" })
|
||||
|
||||
db.Start()
|
||||
|
||||
inner(t, c, idx)
|
||||
})
|
||||
}
|
||||
|
||||
run("no items", func(t *testing.T, c *Collection[Item], idx *BTreeIndex[Item]) {
|
||||
if err := checkIndex(idx); err != nil {
|
||||
t.Fatal(err)
|
||||
run("insert", func(t *testing.T, db *DB) (users []*User) {
|
||||
users = append(users,
|
||||
&User{ID: db.Users.c.NextID(), Email: "a@b.com", Name: "aaa"},
|
||||
&User{ID: db.Users.c.NextID(), Email: "c@d.com", Name: "ccc"})
|
||||
|
||||
for _, u := range users {
|
||||
u2, err := db.Users.c.Insert(*u)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(u2, *u) {
|
||||
t.Fatal(u2, *u)
|
||||
}
|
||||
}
|
||||
|
||||
return users
|
||||
})
|
||||
|
||||
run("insert some", func(t *testing.T, c *Collection[Item], idx *BTreeIndex[Item]) {
|
||||
item1 := Item{1, "one"}
|
||||
item2 := Item{2, ""}
|
||||
item3 := Item{3, "three"}
|
||||
item4 := Item{4, ""}
|
||||
item5 := Item{5, "five"}
|
||||
// Update
|
||||
|
||||
c.Insert(item1)
|
||||
c.Insert(item2)
|
||||
c.Insert(item3)
|
||||
c.Insert(item4)
|
||||
c.Insert(item5)
|
||||
run("update", func(t *testing.T, db *DB) (users []*User) {
|
||||
users = append(users,
|
||||
&User{ID: db.Users.c.NextID(), Email: "a@b.com", Name: "aaa"},
|
||||
&User{ID: db.Users.c.NextID(), Email: "e@f.com", Name: "eee"},
|
||||
&User{ID: db.Users.c.NextID(), Email: "c@d.com", Name: "ccc"})
|
||||
|
||||
if err := checkIndex(idx, item5, item1, item3); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
run("partial iteration", func(t *testing.T, c *Collection[Item], idx *BTreeIndex[Item]) {
|
||||
item1 := Item{1, "one"}
|
||||
item2 := Item{2, ""}
|
||||
item3 := Item{3, "three"}
|
||||
item4 := Item{4, ""}
|
||||
item5 := Item{5, "five"}
|
||||
|
||||
c.Insert(item1)
|
||||
c.Insert(item2)
|
||||
c.Insert(item3)
|
||||
c.Insert(item4)
|
||||
c.Insert(item5)
|
||||
|
||||
iter := idx.Ascend()
|
||||
defer iter.Close()
|
||||
|
||||
if !iter.Next() {
|
||||
t.Fatal("Expected", item5)
|
||||
for _, u := range users {
|
||||
if _, err := db.Users.c.Insert(*u); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(iter.Value(), item5) {
|
||||
t.Fatal(iter.Value(), item5)
|
||||
}
|
||||
})
|
||||
|
||||
run("get", func(t *testing.T, c *Collection[Item], idx *BTreeIndex[Item]) {
|
||||
item1 := Item{1, "one"}
|
||||
item2 := Item{2, ""}
|
||||
c.Insert(item1)
|
||||
c.Insert(item2)
|
||||
|
||||
item, ok := idx.Get(Item{0, "one"})
|
||||
if !ok || !reflect.DeepEqual(item, item1) {
|
||||
t.Fatal(ok, item, item1)
|
||||
}
|
||||
})
|
||||
|
||||
run("get not found", func(t *testing.T, c *Collection[Item], idx *BTreeIndex[Item]) {
|
||||
item1 := Item{1, "one"}
|
||||
item2 := Item{2, ""}
|
||||
c.Insert(item1)
|
||||
c.Insert(item2)
|
||||
|
||||
if item, ok := idx.Get(Item{0, "three"}); ok {
|
||||
t.Fatal(item)
|
||||
}
|
||||
})
|
||||
|
||||
run("min max on empty", func(t *testing.T, c *Collection[Item], idx *BTreeIndex[Item]) {
|
||||
item2 := Item{2, ""}
|
||||
c.Insert(item2)
|
||||
|
||||
if item, ok := idx.Min(); ok {
|
||||
t.Fatal(item)
|
||||
}
|
||||
if item, ok := idx.Max(); ok {
|
||||
t.Fatal(item)
|
||||
}
|
||||
})
|
||||
|
||||
run("min max with one item", func(t *testing.T, c *Collection[Item], idx *BTreeIndex[Item]) {
|
||||
item1 := Item{1, "one"}
|
||||
item2 := Item{2, ""}
|
||||
|
||||
c.Insert(item1)
|
||||
c.Insert(item2)
|
||||
|
||||
i1, ok := idx.Min()
|
||||
if !ok {
|
||||
t.Fatal(ok)
|
||||
}
|
||||
i2, ok := idx.Max()
|
||||
if !ok {
|
||||
t.Fatal(ok)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(i1, i2) {
|
||||
t.Fatal(i1, i2)
|
||||
}
|
||||
})
|
||||
|
||||
run("update outside of index", func(t *testing.T, c *Collection[Item], idx *BTreeIndex[Item]) {
|
||||
item1 := Item{1, "one"}
|
||||
item2 := Item{2, ""}
|
||||
item3 := Item{3, "three"}
|
||||
item4 := Item{4, ""}
|
||||
item5 := Item{5, "five"}
|
||||
|
||||
c.Insert(item1)
|
||||
c.Insert(item2)
|
||||
c.Insert(item3)
|
||||
c.Insert(item4)
|
||||
c.Insert(item5)
|
||||
|
||||
c.Update(2, func(in Item) (Item, error) {
|
||||
return in, nil
|
||||
})
|
||||
|
||||
if err := checkIndex(idx, item5, item1, item3); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
run("update into index", func(t *testing.T, c *Collection[Item], idx *BTreeIndex[Item]) {
|
||||
item1 := Item{1, "one"}
|
||||
item2 := Item{2, ""}
|
||||
item3 := Item{3, "three"}
|
||||
item4 := Item{4, ""}
|
||||
item5 := Item{5, "five"}
|
||||
|
||||
c.Insert(item1)
|
||||
c.Insert(item2)
|
||||
c.Insert(item3)
|
||||
c.Insert(item4)
|
||||
c.Insert(item5)
|
||||
|
||||
err := c.Update(2, func(in Item) (Item, error) {
|
||||
in.Name = "two"
|
||||
return in, nil
|
||||
err := db.Users.c.Update(users[2].ID, func(u User) (User, error) {
|
||||
u.Email = "g@h.com"
|
||||
return u, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
users[2].Email = "g@h.com"
|
||||
|
||||
item2.Name = "two"
|
||||
|
||||
if err := checkIndex(idx, item5, item1, item3, item2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return users
|
||||
})
|
||||
|
||||
run("update out of index", func(t *testing.T, c *Collection[Item], idx *BTreeIndex[Item]) {
|
||||
item1 := Item{1, "one"}
|
||||
item2 := Item{2, ""}
|
||||
item3 := Item{3, "three"}
|
||||
item4 := Item{4, ""}
|
||||
item5 := Item{5, "five"}
|
||||
run("delete", func(t *testing.T, db *DB) (users []*User) {
|
||||
users = append(users,
|
||||
&User{ID: db.Users.c.NextID(), Email: "a@b.com", Name: "aaa"},
|
||||
&User{ID: db.Users.c.NextID(), Email: "c@d.com", Name: "ccc"},
|
||||
&User{ID: db.Users.c.NextID(), Email: "e@f.com", Name: "eee"})
|
||||
|
||||
c.Insert(item1)
|
||||
c.Insert(item2)
|
||||
c.Insert(item3)
|
||||
c.Insert(item4)
|
||||
c.Insert(item5)
|
||||
|
||||
err := c.Update(1, func(in Item) (Item, error) {
|
||||
in.Name = ""
|
||||
return in, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
for _, u := range users {
|
||||
if _, err := db.Users.c.Insert(*u); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := checkIndex(idx, item5, item3); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
db.Users.c.Delete(users[0].ID)
|
||||
users = users[1:]
|
||||
|
||||
return users
|
||||
})
|
||||
|
||||
run("update within index", func(t *testing.T, c *Collection[Item], idx *BTreeIndex[Item]) {
|
||||
item1 := Item{1, "one"}
|
||||
item2 := Item{2, ""}
|
||||
item3 := Item{3, "three"}
|
||||
item4 := Item{4, ""}
|
||||
item5 := Item{5, "five"}
|
||||
run("get not found", func(t *testing.T, db *DB) (users []*User) {
|
||||
users = append(users,
|
||||
&User{ID: db.Users.c.NextID(), Email: "a@b.com", Name: "aaa"},
|
||||
&User{ID: db.Users.c.NextID(), Email: "c@d.com", Name: "ccc"},
|
||||
&User{ID: db.Users.c.NextID(), Email: "e@f.com", Name: "eee"})
|
||||
|
||||
c.Insert(item1)
|
||||
c.Insert(item2)
|
||||
c.Insert(item3)
|
||||
c.Insert(item4)
|
||||
c.Insert(item5)
|
||||
|
||||
err := c.Update(1, func(in Item) (Item, error) {
|
||||
in.Name = "xone"
|
||||
return in, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
for _, u := range users {
|
||||
if _, err := db.Users.c.Insert(*u); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
item1.Name = "xone"
|
||||
|
||||
if err := checkIndex(idx, item5, item3, item1); err != nil {
|
||||
t.Fatal(err)
|
||||
if u, ok := db.Users.emailBTree.Get(User{Email: "g@h.com"}); ok {
|
||||
t.Fatal(u, ok)
|
||||
}
|
||||
|
||||
return users
|
||||
})
|
||||
|
||||
run("delete outside index", func(t *testing.T, c *Collection[Item], idx *BTreeIndex[Item]) {
|
||||
item1 := Item{1, "one"}
|
||||
item2 := Item{2, ""}
|
||||
item3 := Item{3, "three"}
|
||||
item4 := Item{4, ""}
|
||||
item5 := Item{5, "five"}
|
||||
run("min/max empty", func(t *testing.T, db *DB) (users []*User) {
|
||||
|
||||
c.Insert(item1)
|
||||
c.Insert(item2)
|
||||
c.Insert(item3)
|
||||
c.Insert(item4)
|
||||
c.Insert(item5)
|
||||
|
||||
c.Delete(item2.ID)
|
||||
|
||||
if err := checkIndex(idx, item5, item1, item3); err != nil {
|
||||
t.Fatal(err)
|
||||
if u, ok := db.Users.emailBTree.Min(); ok {
|
||||
t.Fatal(u, ok)
|
||||
}
|
||||
})
|
||||
|
||||
run("delete within index", func(t *testing.T, c *Collection[Item], idx *BTreeIndex[Item]) {
|
||||
item1 := Item{1, "one"}
|
||||
item2 := Item{2, ""}
|
||||
item3 := Item{3, "three"}
|
||||
item4 := Item{4, ""}
|
||||
item5 := Item{5, "five"}
|
||||
|
||||
c.Insert(item1)
|
||||
c.Insert(item2)
|
||||
c.Insert(item3)
|
||||
c.Insert(item4)
|
||||
c.Insert(item5)
|
||||
|
||||
c.Delete(item1.ID)
|
||||
|
||||
if err := checkIndex(idx, item5, item3); err != nil {
|
||||
t.Fatal(err)
|
||||
if u, ok := db.Users.emailBTree.Max(); ok {
|
||||
t.Fatal(u, ok)
|
||||
}
|
||||
|
||||
return users
|
||||
})
|
||||
}
|
||||
*/
|
||||
|
||||
func TestPartialBTreeIndex(t *testing.T) {
|
||||
// empty
|
||||
// insert into
|
||||
// insert outside
|
||||
// upate out to in
|
||||
// update out to out
|
||||
// update in to in
|
||||
// update in to out
|
||||
// delete outside
|
||||
// delete in
|
||||
// load w/duplicate
|
||||
}
|
||||
|
|
|
@ -72,10 +72,6 @@ func (m *MapIndex[K, T]) Update(k K, update func(T) (T, error)) error {
|
|||
return item, err
|
||||
}
|
||||
|
||||
if m.getKey(&new) != k {
|
||||
return item, ErrMismatchedIDs
|
||||
}
|
||||
|
||||
return new, nil
|
||||
}
|
||||
|
||||
|
|
357
mapindex_test.go
357
mapindex_test.go
|
@ -51,8 +51,6 @@ func TestFullMapIndex(t *testing.T) {
|
|||
return users
|
||||
})
|
||||
|
||||
// TODO: insert duplicate
|
||||
|
||||
run("delete", func(t *testing.T, db *DB) map[string]*User {
|
||||
users := map[string]*User{}
|
||||
|
||||
|
@ -171,7 +169,7 @@ func TestFullMapIndex(t *testing.T) {
|
|||
return users
|
||||
})
|
||||
|
||||
run("update change key error", func(t *testing.T, db *DB) map[string]*User {
|
||||
run("update change id error", func(t *testing.T, db *DB) map[string]*User {
|
||||
users := map[string]*User{}
|
||||
|
||||
for i := uint64(1); i < 10; i++ {
|
||||
|
@ -194,7 +192,7 @@ func TestFullMapIndex(t *testing.T) {
|
|||
}
|
||||
|
||||
err := db.Users.emailMap.Update(email, func(u User) (User, error) {
|
||||
u.Email = "test@x.com"
|
||||
u.ID++
|
||||
return u, nil
|
||||
})
|
||||
if err != ErrMismatchedIDs {
|
||||
|
@ -304,13 +302,7 @@ func TestFullMapIndex(t *testing.T) {
|
|||
|
||||
run("insert conflict", func(t *testing.T, db *DB) map[string]*User {
|
||||
users := map[string]*User{}
|
||||
|
||||
user := &User{
|
||||
ID: db.Users.c.NextID(),
|
||||
Email: "a@b.com",
|
||||
Name: "a",
|
||||
ExtID: "",
|
||||
}
|
||||
user := &User{ID: db.Users.c.NextID(), Email: "a@b.com", Name: "a", ExtID: ""}
|
||||
|
||||
if _, err := db.Users.c.Insert(*user); err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -355,22 +347,339 @@ func TestFullMapIndex(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Users.emailMap.Update("x@y.com", func(u User) (User, error) {
|
||||
u.Email = "a@b.com"
|
||||
return u, nil
|
||||
})
|
||||
if !errors.Is(err, ErrDuplicate) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return users
|
||||
})
|
||||
// update conflict
|
||||
}
|
||||
|
||||
func TestPartialMapIndex(t *testing.T) {
|
||||
// insert into index
|
||||
// insert into index conflict
|
||||
// insert outside index
|
||||
// insert and delete in index
|
||||
// insert and delete outside index
|
||||
// update outside index
|
||||
// update into index
|
||||
// update function error
|
||||
// udpate ErrAbortUpdate
|
||||
// update ErrNotFound
|
||||
// update conflict in to in
|
||||
// update conflict out to in
|
||||
// Test against the extID map index.
|
||||
run := func(name string, inner func(t *testing.T, db *DB) map[string]*User) {
|
||||
testWithDB(t, name, func(t *testing.T, db *DB) {
|
||||
expected := inner(t, db)
|
||||
|
||||
if err := db.Users.extIDMap.EqualsMap(expected); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
db.Close()
|
||||
db = OpenDB(db.root, true)
|
||||
|
||||
if err := db.Users.extIDMap.EqualsMap(expected); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
run("insert", func(t *testing.T, db *DB) map[string]*User {
|
||||
users := map[string]*User{
|
||||
"x": {ID: db.Users.c.NextID(), Email: "x@y.com", Name: "x", ExtID: "x"},
|
||||
}
|
||||
user1 := &User{ID: db.Users.c.NextID(), Email: "a@b.com", Name: "a"}
|
||||
|
||||
if _, err := db.Users.c.Insert(*users["x"]); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Users.c.Insert(*user1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return users
|
||||
})
|
||||
|
||||
run("insert with conflict", func(t *testing.T, db *DB) map[string]*User {
|
||||
users := map[string]*User{
|
||||
"x": {ID: db.Users.c.NextID(), Email: "x@y.com", Name: "x", ExtID: "x"},
|
||||
}
|
||||
user1 := &User{ID: db.Users.c.NextID(), Email: "a@b.com", Name: "y", ExtID: "x"}
|
||||
|
||||
if _, err := db.Users.c.Insert(*users["x"]); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := db.Users.c.Insert(*user1); !errors.Is(err, ErrDuplicate) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return users
|
||||
})
|
||||
|
||||
run("insert and delete in index", func(t *testing.T, db *DB) map[string]*User {
|
||||
users := map[string]*User{
|
||||
"x": {ID: db.Users.c.NextID(), Email: "x@y.com", Name: "aa", ExtID: "x"},
|
||||
"y": {ID: db.Users.c.NextID(), Email: "q@r.com", Name: "bb", ExtID: "y"},
|
||||
"z": {ID: db.Users.c.NextID(), Email: "a@b.com", Name: "cc", ExtID: "z"},
|
||||
}
|
||||
|
||||
for _, u := range users {
|
||||
if _, err := db.Users.c.Insert(*u); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete from index and from collection.
|
||||
db.Users.extIDMap.Delete("x")
|
||||
db.Users.c.Delete(users["z"].ID)
|
||||
|
||||
delete(users, "x")
|
||||
delete(users, "z")
|
||||
|
||||
return users
|
||||
})
|
||||
|
||||
run("insert and delete outside index", func(t *testing.T, db *DB) map[string]*User {
|
||||
users := map[string]*User{
|
||||
"x": {ID: db.Users.c.NextID(), Email: "x@y.com", Name: "aa", ExtID: "x"},
|
||||
"y": {ID: db.Users.c.NextID(), Email: "q@r.com", Name: "bb", ExtID: "y"},
|
||||
"z": {ID: db.Users.c.NextID(), Email: "a@b.com", Name: "cc"},
|
||||
}
|
||||
|
||||
for _, u := range users {
|
||||
if _, err := db.Users.c.Insert(*u); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete from index and from collection.
|
||||
db.Users.extIDMap.Delete("x")
|
||||
db.Users.c.Delete(users["z"].ID)
|
||||
|
||||
delete(users, "x")
|
||||
delete(users, "z")
|
||||
|
||||
return users
|
||||
})
|
||||
|
||||
run("update outside index", func(t *testing.T, db *DB) map[string]*User {
|
||||
users := map[string]*User{
|
||||
"x": {ID: db.Users.c.NextID(), Email: "x@y.com", Name: "aa", ExtID: "x"},
|
||||
"y": {ID: db.Users.c.NextID(), Email: "q@r.com", Name: "bb", ExtID: "y"},
|
||||
"z": {ID: db.Users.c.NextID(), Email: "a@b.com", Name: "cc"},
|
||||
}
|
||||
|
||||
for _, u := range users {
|
||||
if _, err := db.Users.c.Insert(*u); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
err := db.Users.c.Update(users["z"].ID, func(u User) (User, error) {
|
||||
u.Name = "Whatever"
|
||||
return u, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
delete(users, "z") // No ExtID => not in index.
|
||||
return users
|
||||
})
|
||||
|
||||
run("update into index", func(t *testing.T, db *DB) map[string]*User {
|
||||
users := map[string]*User{
|
||||
"x": {ID: db.Users.c.NextID(), Email: "x@y.com", Name: "aa", ExtID: "x"},
|
||||
"y": {ID: db.Users.c.NextID(), Email: "q@r.com", Name: "bb", ExtID: "y"},
|
||||
"z": {ID: db.Users.c.NextID(), Email: "a@b.com", Name: "cc"},
|
||||
}
|
||||
|
||||
for _, u := range users {
|
||||
if _, err := db.Users.c.Insert(*u); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
err := db.Users.c.Update(users["z"].ID, func(u User) (User, error) {
|
||||
u.ExtID = "z"
|
||||
return u, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
users["z"].ExtID = "z"
|
||||
|
||||
return users
|
||||
})
|
||||
|
||||
run("update out of index", func(t *testing.T, db *DB) map[string]*User {
|
||||
users := map[string]*User{
|
||||
"x": {ID: db.Users.c.NextID(), Email: "x@y.com", Name: "aa", ExtID: "x"},
|
||||
"y": {ID: db.Users.c.NextID(), Email: "q@r.com", Name: "bb", ExtID: "y"},
|
||||
"z": {ID: db.Users.c.NextID(), Email: "a@b.com", Name: "cc"},
|
||||
}
|
||||
|
||||
for _, u := range users {
|
||||
if _, err := db.Users.c.Insert(*u); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
err := db.Users.extIDMap.Update("y", func(u User) (User, error) {
|
||||
u.ExtID = ""
|
||||
return u, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Users.c.Update(users["x"].ID, func(u User) (User, error) {
|
||||
u.ExtID = ""
|
||||
return u, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
delete(users, "x")
|
||||
delete(users, "z")
|
||||
delete(users, "y")
|
||||
|
||||
return users
|
||||
})
|
||||
|
||||
run("update function error", func(t *testing.T, db *DB) map[string]*User {
|
||||
users := map[string]*User{
|
||||
"x": {ID: db.Users.c.NextID(), Email: "x@y.com", Name: "aa", ExtID: "x"},
|
||||
"y": {ID: db.Users.c.NextID(), Email: "q@r.com", Name: "bb", ExtID: "y"},
|
||||
}
|
||||
|
||||
for _, u := range users {
|
||||
if _, err := db.Users.c.Insert(*u); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
myErr := errors.New("hello")
|
||||
|
||||
err := db.Users.extIDMap.Update("y", func(u User) (User, error) {
|
||||
u.Email = "blah"
|
||||
return u, myErr
|
||||
})
|
||||
if err != myErr {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return users
|
||||
})
|
||||
|
||||
run("update ErrAbortUpdate", func(t *testing.T, db *DB) map[string]*User {
|
||||
users := map[string]*User{
|
||||
"x": {ID: db.Users.c.NextID(), Email: "x@y.com", Name: "aa", ExtID: "x"},
|
||||
"y": {ID: db.Users.c.NextID(), Email: "q@r.com", Name: "bb", ExtID: "y"},
|
||||
}
|
||||
|
||||
for _, u := range users {
|
||||
if _, err := db.Users.c.Insert(*u); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
err := db.Users.extIDMap.Update("y", func(u User) (User, error) {
|
||||
u.Email = "blah"
|
||||
return u, ErrAbortUpdate
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return users
|
||||
})
|
||||
|
||||
run("update ErrNotFound", func(t *testing.T, db *DB) map[string]*User {
|
||||
users := map[string]*User{
|
||||
"x": {ID: db.Users.c.NextID(), Email: "x@y.com", Name: "aa", ExtID: "x"},
|
||||
"y": {ID: db.Users.c.NextID(), Email: "q@r.com", Name: "bb", ExtID: "y"},
|
||||
}
|
||||
|
||||
for _, u := range users {
|
||||
if _, err := db.Users.c.Insert(*u); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
err := db.Users.extIDMap.Update("z", func(u User) (User, error) {
|
||||
u.Name = "blah"
|
||||
return u, nil
|
||||
})
|
||||
if !errors.Is(err, ErrNotFound) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return users
|
||||
})
|
||||
|
||||
run("update conflict in to in", func(t *testing.T, db *DB) map[string]*User {
|
||||
users := map[string]*User{
|
||||
"x": {ID: db.Users.c.NextID(), Email: "x@y.com", Name: "aa", ExtID: "x"},
|
||||
"y": {ID: db.Users.c.NextID(), Email: "q@r.com", Name: "bb", ExtID: "y"},
|
||||
"z": {ID: db.Users.c.NextID(), Email: "z@z.com", Name: "zz", ExtID: "z"},
|
||||
}
|
||||
|
||||
for _, u := range users {
|
||||
if _, err := db.Users.c.Insert(*u); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
err := db.Users.extIDMap.Update("z", func(u User) (User, error) {
|
||||
u.ExtID = "x"
|
||||
return u, nil
|
||||
})
|
||||
if !errors.Is(err, ErrDuplicate) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return users
|
||||
})
|
||||
|
||||
run("update conflict out to in", func(t *testing.T, db *DB) map[string]*User {
|
||||
users := map[string]*User{
|
||||
"x": {ID: db.Users.c.NextID(), Email: "x@y.com", Name: "aa", ExtID: "x"},
|
||||
"y": {ID: db.Users.c.NextID(), Email: "q@r.com", Name: "bb", ExtID: "y"},
|
||||
"z": {ID: db.Users.c.NextID(), Email: "z@z.com", Name: "zz"},
|
||||
}
|
||||
|
||||
for _, u := range users {
|
||||
if _, err := db.Users.c.Insert(*u); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
err := db.Users.c.Update(users["z"].ID, func(u User) (User, error) {
|
||||
u.ExtID = "x"
|
||||
return u, nil
|
||||
})
|
||||
if !errors.Is(err, ErrDuplicate) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
delete(users, "z")
|
||||
|
||||
return users
|
||||
})
|
||||
}
|
||||
|
||||
func TestMapIndex_load_ErrDuplicate(t *testing.T) {
|
||||
testWithDB(t, "", func(t *testing.T, db *DB) {
|
||||
idx := NewMapIndex(
|
||||
db.Users.c,
|
||||
"broken",
|
||||
func(u *User) string { return u.Name },
|
||||
nil)
|
||||
|
||||
users := map[uint64]*User{
|
||||
1: {ID: 1, Email: "x@y.com", Name: "aa", ExtID: "x"},
|
||||
2: {ID: 2, Email: "b@c.com", Name: "aa", ExtID: "y"},
|
||||
}
|
||||
|
||||
if err := idx.load(users); err != ErrDuplicate {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
Reference in New Issue