This repository has been archived on 2022-07-30. You can view files and clone it, but cannot push or open issues/pull-requests.
mdb/mapindex_test.go

686 lines
15 KiB
Go

package mdb
import (
"errors"
"fmt"
"reflect"
"testing"
)
func TestFullMapIndex(t *testing.T) {
// Test against the emailMap 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.emailMap.EqualsMap(expected); err != nil {
t.Fatal(err)
}
db.Close()
db = OpenDB(db.root, true)
if err := db.Users.emailMap.EqualsMap(expected); err != nil {
t.Fatal(err)
}
})
}
run("insert", func(t *testing.T, db *DB) map[string]*User {
users := map[string]*User{}
for i := uint64(1); i < 10; i++ {
user := &User{
ID: db.Users.c.NextID(),
Email: fmt.Sprintf("a.%d@c.com", i),
Name: fmt.Sprintf("name.%d", i),
ExtID: fmt.Sprintf("EXTID.%d", i),
}
user2, err := db.Users.c.Insert(*user)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(*user, user2) {
t.Fatal(*user, user2)
}
users[user.Email] = user
}
return users
})
run("delete", func(t *testing.T, db *DB) map[string]*User {
users := map[string]*User{}
for i := uint64(1); i < 10; i++ {
user := &User{
ID: db.Users.c.NextID(),
Email: fmt.Sprintf("a.%d@c.com", i),
Name: fmt.Sprintf("name.%d", i),
ExtID: fmt.Sprintf("EXTID.%d", i),
}
user2, err := db.Users.c.Insert(*user)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(*user, user2) {
t.Fatal(*user, user2)
}
users[user.Email] = user
}
var id string
for key := range users {
id = key
break
}
delete(users, id)
db.Users.emailMap.Delete(id)
return users
})
run("update non-indexed field", func(t *testing.T, db *DB) map[string]*User {
users := map[string]*User{}
for i := uint64(1); i < 10; i++ {
user := &User{
ID: db.Users.c.NextID(),
Email: fmt.Sprintf("a.%d@c.com", i),
Name: fmt.Sprintf("name.%d", i),
ExtID: fmt.Sprintf("EXTID.%d", i),
}
user2, err := db.Users.c.Insert(*user)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(*user, user2) {
t.Fatal(*user, user2)
}
users[user.Email] = user
}
var id string
for key := range users {
id = key
break
}
err := db.Users.emailMap.Update(id, func(u User) (User, error) {
u.Name = "UPDATED"
return u, nil
})
if err != nil {
t.Fatal(err)
}
users[id].Name = "UPDATED"
return users
})
run("update indexed field", func(t *testing.T, db *DB) map[string]*User {
users := map[string]*User{}
for i := uint64(1); i < 10; i++ {
user := &User{
ID: db.Users.c.NextID(),
Email: fmt.Sprintf("a.%d@c.com", i),
Name: fmt.Sprintf("name.%d", i),
ExtID: fmt.Sprintf("EXTID.%d", i),
}
user2, err := db.Users.c.Insert(*user)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(*user, user2) {
t.Fatal(*user, user2)
}
users[user.Email] = user
}
var id uint64
var email string
for key := range users {
email = key
id = users[key].ID
break
}
err := db.Users.c.Update(id, func(u User) (User, error) {
u.Email = "test@x.com"
return u, nil
})
if err != nil {
t.Fatal(err)
}
user := users[email]
user.Email = "test@x.com"
delete(users, email)
users[user.Email] = user
return users
})
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++ {
user := &User{
ID: db.Users.c.NextID(),
Email: fmt.Sprintf("a.%d@c.com", i),
Name: fmt.Sprintf("name.%d", i),
ExtID: fmt.Sprintf("EXTID.%d", i),
}
if _, err := db.Users.c.Insert(*user); err != nil {
t.Fatal(err)
}
users[user.Email] = user
}
var email string
for key := range users {
email = key
break
}
err := db.Users.emailMap.Update(email, func(u User) (User, error) {
u.ID++
return u, nil
})
if err != ErrMismatchedIDs {
t.Fatal(err)
}
return users
})
run("update function error", func(t *testing.T, db *DB) map[string]*User {
users := map[string]*User{}
for i := uint64(1); i < 10; i++ {
user := &User{
ID: db.Users.c.NextID(),
Email: fmt.Sprintf("a.%d@c.com", i),
Name: fmt.Sprintf("name.%d", i),
ExtID: fmt.Sprintf("EXTID.%d", i),
}
if _, err := db.Users.c.Insert(*user); err != nil {
t.Fatal(err)
}
users[user.Email] = user
}
var email string
for key := range users {
email = key
break
}
myErr := errors.New("hello")
err := db.Users.emailMap.Update(email, func(u User) (User, error) {
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{}
for i := uint64(1); i < 10; i++ {
user := &User{
ID: db.Users.c.NextID(),
Email: fmt.Sprintf("a.%d@c.com", i),
Name: fmt.Sprintf("name.%d", i),
ExtID: fmt.Sprintf("EXTID.%d", i),
}
if _, err := db.Users.c.Insert(*user); err != nil {
t.Fatal(err)
}
users[user.Email] = user
}
var email string
for key := range users {
email = key
break
}
err := db.Users.emailMap.Update(email, func(u User) (User, error) {
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{}
for i := uint64(1); i < 10; i++ {
user := &User{
ID: db.Users.c.NextID(),
Email: fmt.Sprintf("a.%d@c.com", i),
Name: fmt.Sprintf("name.%d", i),
ExtID: fmt.Sprintf("EXTID.%d", i),
}
if _, err := db.Users.c.Insert(*user); err != nil {
t.Fatal(err)
}
users[user.Email] = user
}
var email string
for key := range users {
email = key
break
}
err := db.Users.emailMap.Update(email+"x", func(u User) (User, error) {
return u, nil
})
if err != ErrNotFound {
t.Fatal(err)
}
return users
})
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: ""}
if _, err := db.Users.c.Insert(*user); err != nil {
t.Fatal(err)
}
users[user.Email] = user
user2 := User{
ID: db.Users.c.NextID(),
Email: "a@b.com",
Name: "someone else",
ExtID: "123",
}
_, err := db.Users.c.Insert(user2)
if !errors.Is(err, ErrDuplicate) {
t.Fatal(err)
}
return users
})
run("update conflict", func(t *testing.T, db *DB) map[string]*User {
users := map[string]*User{}
user1 := &User{ID: db.Users.c.NextID(), Email: "a@b.com", Name: "a"}
user2 := &User{ID: db.Users.c.NextID(), Email: "x@y.com", Name: "x"}
users[user1.Email] = user1
users[user2.Email] = user2
for _, u := range users {
if _, err := db.Users.c.Insert(*u); err != nil {
t.Fatal(err)
}
}
err := db.Users.c.Update(user2.ID, func(u User) (User, error) {
u.Email = "a@b.com"
return u, nil
})
if !errors.Is(err, ErrDuplicate) {
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
})
}
func TestPartialMapIndex(t *testing.T) {
// 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)
}
})
}