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