-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move uniqueness constraints on user to service level
While it originally thought out as a business level constraint it can be enforced to more effect on the service level. As we know not only have exact matches but also make sure that emails no matter in what spelling won't be duplicated.
- Loading branch information
Alexander Simmerl
committed
Nov 22, 2016
1 parent
954c923
commit 2bfa7d7
Showing
7 changed files
with
126 additions
and
83 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,16 +15,24 @@ const MetaNamespace = "tg" | |
// TimeFormat can be used to extract and store time in a reproducible way. | ||
const TimeFormat = "2006-01-02 15:04:05.000000 UTC" | ||
|
||
// URLTest can be used for consistent local testing. | ||
const URLTest = "postgres://%[email protected]:5432/tapglue_test?sslmode=disable&connect_timeout=5" | ||
|
||
const ( | ||
codeDuplicateKeyViolation = "23505" | ||
codeRelationNotFound = "42P01" | ||
|
||
fmtClause = "\nAND " | ||
fmtWHERE = "WHERE\n%s" | ||
) | ||
|
||
// ErrRelationNotFound is returned as equivalent to the Postgres error. | ||
var ErrRelationNotFound = errors.New("relation not found") | ||
|
||
// ErrNotUnique indicates that the attempted update violates a unique constrain | ||
// on a table. | ||
var ErrNotUnique = errors.New("entity not unique") | ||
|
||
// To ensure idempotence we want to create the index only if it doesn't exist, | ||
// while this feature is about to hit Postgres in 9.5 it is not yet available. | ||
// We fallback to a conditional create taken from: | ||
|
@@ -53,6 +61,11 @@ func GuardIndex(namespace, index, query string) string { | |
) | ||
} | ||
|
||
// IsNotUnique indicates if err is ErrNotUnique. | ||
func IsNotUnique(err error) bool { | ||
return err == ErrNotUnique | ||
} | ||
|
||
// IsRelationNotFound indicates if err is ErrRelationNotFound. | ||
func IsRelationNotFound(err error) bool { | ||
return err == ErrRelationNotFound | ||
|
@@ -61,8 +74,13 @@ func IsRelationNotFound(err error) bool { | |
// WrapError check the given error if it indicates that the relation wasn't | ||
// present, otherwise returns the original error. | ||
func WrapError(err error) error { | ||
if err, ok := err.(*pq.Error); ok && err.Code == "42P01" { | ||
return ErrRelationNotFound | ||
if err, ok := err.(*pq.Error); ok { | ||
switch err.Code { | ||
case codeDuplicateKeyViolation: | ||
return ErrNotUnique | ||
case codeRelationNotFound: | ||
return ErrRelationNotFound | ||
} | ||
} | ||
|
||
return err | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
CREATE EXTENSION fuzzystrmatch; | ||
CREATE EXTENSION postgis; | ||
CREATE EXTENSION postgis_topology; | ||
CREATE EXTENSION postgis_tiger_geocoder; | ||
CREATE EXTENSION pg_trgm; | ||
CREATE EXTENSION IF NOT EXISTS citext; | ||
CREATE EXTENSION IF NOT EXISTS fuzzystrmatch; | ||
CREATE EXTENSION IF NOT EXISTS postgis; | ||
CREATE EXTENSION IF NOT EXISTS postgis_topology; | ||
CREATE EXTENSION IF NOT EXISTS postgis_tiger_geocoder; | ||
CREATE EXTENSION IF NOT EXISTS pg_trgm; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -239,6 +239,44 @@ func testServicePut(t *testing.T, p prepareFunc) { | |
} | ||
} | ||
|
||
func testServicePutEmailUnique(t *testing.T, p prepareFunc) { | ||
var ( | ||
namespace = "service_put_email" | ||
service = p(t, namespace) | ||
user = testUser() | ||
lowerCase = "[email protected]" | ||
mixedCase = "[email protected]" | ||
) | ||
|
||
user.Email = lowerCase | ||
|
||
_, err := service.Put(namespace, user) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
us, err := service.Query(namespace, QueryOptions{ | ||
Emails: []string{ | ||
mixedCase, | ||
}, | ||
}) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if have, want := len(us), 1; have != want { | ||
t.Errorf("have %v, want %v", have, want) | ||
} | ||
|
||
second := testUser() | ||
second.Email = mixedCase | ||
|
||
_, err = service.Put(namespace, second) | ||
if have, want := err, ErrNotUnique; !IsNotUnique(err) { | ||
t.Errorf("have %v, want %v\n%#v", have, want, err) | ||
} | ||
} | ||
|
||
func testServicePutLastRead(t *testing.T, p prepareFunc) { | ||
var ( | ||
namespace = "service_put_last_read" | ||
|
@@ -279,6 +317,44 @@ func testServicePutLastRead(t *testing.T, p prepareFunc) { | |
} | ||
} | ||
|
||
func testServicePutUsernameUnique(t *testing.T, p prepareFunc) { | ||
var ( | ||
namespace = "service_put_email" | ||
service = p(t, namespace) | ||
user = testUser() | ||
lowerCase = "xla1234" | ||
mixedCase = "XlA1234" | ||
) | ||
|
||
user.Username = lowerCase | ||
|
||
_, err := service.Put(namespace, user) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
us, err := service.Query(namespace, QueryOptions{ | ||
Usernames: []string{ | ||
mixedCase, | ||
}, | ||
}) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if have, want := len(us), 1; have != want { | ||
t.Errorf("have %v, want %v", have, want) | ||
} | ||
|
||
second := testUser() | ||
second.Username = mixedCase | ||
|
||
_, err = service.Put(namespace, second) | ||
if have, want := err, ErrNotUnique; !IsNotUnique(err) { | ||
t.Errorf("have %v, want %v\n%#v", have, want, err) | ||
} | ||
} | ||
|
||
func testServiceQuery(t *testing.T, p prepareFunc) { | ||
var ( | ||
customID = generate.RandomString(12) | ||
|
@@ -443,6 +519,7 @@ func testUser() *User { | |
), | ||
Enabled: true, | ||
Password: generate.RandomString(8), | ||
Username: generate.RandomString(8), | ||
} | ||
} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters