More testing.

master
jdl 2022-07-27 09:56:01 +02:00
parent e15fe41a50
commit f4d72247e4
6 changed files with 524 additions and 375 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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