Permalink
Browse files

Detect field for unique constraint error (#12)

* detect field for unique constraint error

* use must

* update

* UpdateConstraint

* refactor constraint check

* refactor

* added test
  • Loading branch information...
Fs02 committed May 14, 2018
1 parent 6aa2664 commit f27872d16ca55c8b0031b6132320a4cab17f11f2
@@ -19,6 +19,7 @@ import (
"github.com/Fs02/grimoire"
"github.com/Fs02/grimoire/adapter/sql"
"github.com/Fs02/grimoire/errors"
"github.com/Fs02/grimoire/internal"
"github.com/go-sql-driver/mysql"
)

@@ -57,7 +58,7 @@ func errorFunc(err error) error {
if err == nil {
return nil
} else if e, ok := err.(*mysql.MySQLError); ok && e.Number == 1062 {
return errors.DuplicateError(e.Message, "")
return errors.UniqueConstraintError(e.Message, internal.ExtractString(e.Message, "key '", "'"))
}

return err
@@ -7,8 +7,6 @@ import (
"github.com/Fs02/go-paranoid"
"github.com/Fs02/grimoire"
"github.com/Fs02/grimoire/adapter/specs"
"github.com/Fs02/grimoire/errors"
"github.com/go-sql-driver/mysql"
"github.com/stretchr/testify/assert"
)

@@ -26,12 +24,14 @@ func init() {

_, _, err = adapter.Exec(`CREATE TABLE users (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
slug VARCHAR(30) DEFAULT NULL,
name VARCHAR(30) NOT NULL,
gender VARCHAR(10) NOT NULL,
age INT NOT NULL,
note varchar(50),
created_at DATETIME,
updated_at DATETIME
updated_at DATETIME,
UNIQUE (slug)
);`, nil)
paranoid.Panic(err)

@@ -77,11 +77,13 @@ func TestSpecs(t *testing.T) {
specs.Insert(t, repo)
specs.InsertAll(t, repo)
specs.InsertSet(t, repo)
specs.InsertConstraint(t, repo)

// Update Specs
specs.Update(t, repo)
specs.UpdateWhere(t, repo)
specs.UpdateSet(t, repo)
specs.UpdateConstraint(t, repo)

// Put Specs
specs.SaveInsert(t, repo)
@@ -156,17 +158,3 @@ func TestAdapterExecError(t *testing.T) {
_, _, err = adapter.Exec("error", nil)
assert.NotNil(t, err)
}

func TestErrorFunc(t *testing.T) {
// error nil
assert.Nil(t, errorFunc(nil))

// 1062 error
rawerr := &mysql.MySQLError{Message: "duplicate", Number: 1062}
duperr := errors.DuplicateError(rawerr.Message, "")
assert.Equal(t, duperr, errorFunc(rawerr))

// other errors
err := errors.UnexpectedError("error")
assert.Equal(t, err, errorFunc(err))
}
@@ -18,6 +18,7 @@ import (
"github.com/Fs02/grimoire"
"github.com/Fs02/grimoire/adapter/sql"
"github.com/Fs02/grimoire/errors"
"github.com/Fs02/grimoire/internal"
"github.com/lib/pq"
)

@@ -89,7 +90,7 @@ func errorFunc(err error) error {
if err == nil {
return nil
} else if e, ok := err.(*pq.Error); ok && e.Code == "23505" {
return errors.DuplicateError(e.Message, e.Column)
return errors.UniqueConstraintError(e.Message, internal.ExtractString(e.Message, "constraint \"", "\""))
}

return err
@@ -7,8 +7,6 @@ import (
"github.com/Fs02/go-paranoid"
"github.com/Fs02/grimoire"
"github.com/Fs02/grimoire/adapter/specs"
"github.com/Fs02/grimoire/errors"
"github.com/lib/pq"
"github.com/stretchr/testify/assert"
)

@@ -26,12 +24,14 @@ func init() {

_, _, err = adapter.Exec(`CREATE TABLE users (
id SERIAL NOT NULL PRIMARY KEY,
slug VARCHAR(30) DEFAULT NULL,
name VARCHAR(30) NOT NULL DEFAULT '',
gender VARCHAR(10) NOT NULL DEFAULT 'male',
age INT NOT NULL DEFAULT 0,
note varchar(50),
created_at TIMESTAMP,
updated_at TIMESTAMP
updated_at TIMESTAMP,
UNIQUE(slug)
);`, nil)
paranoid.Panic(err)

@@ -76,11 +76,13 @@ func TestSpecs(t *testing.T) {
specs.Insert(t, repo)
specs.InsertAll(t, repo)
specs.InsertSet(t, repo)
specs.InsertConstraint(t, repo)

// Update Specs
specs.Update(t, repo)
specs.UpdateWhere(t, repo)
specs.UpdateSet(t, repo)
specs.UpdateConstraint(t, repo)

// Put Specs
specs.SaveInsert(t, repo)
@@ -155,17 +157,3 @@ func TestAdapterExecError(t *testing.T) {
_, _, err = adapter.Exec("error", nil)
assert.NotNil(t, err)
}

func TestErrorFunc(t *testing.T) {
// error nil
assert.Nil(t, errorFunc(nil))

// Duplicate error
rawerr := &pq.Error{Message: "unique_violation", Code: "23505"}
duperr := errors.DuplicateError(rawerr.Message, "")
assert.Equal(t, duperr, errorFunc(rawerr))

// other errors
err := errors.UnexpectedError("error")
assert.Equal(t, err, errorFunc(err))
}
@@ -13,7 +13,7 @@ import (
func Count(t *testing.T, repo grimoire.Repo) {
// preparte tests data
user := User{Name: "name1", Gender: "male", Age: 10}
assert.Nil(t, repo.From(users).Save(&user))
repo.From(users).MustSave(&user)

tests := []grimoire.Query{
repo.From(users).Where(c.Eq(id, user.ID)),
@@ -12,10 +12,10 @@ import (
// Delete tests delete specifications.
func Delete(t *testing.T, repo grimoire.Repo) {
record := User{Name: "delete", Age: 100}
assert.Nil(t, repo.From(users).Save(&record))
assert.Nil(t, repo.From(users).Save(&User{Name: "delete", Age: 100}))
assert.Nil(t, repo.From(users).Save(&User{Name: "delete", Age: 100}))
assert.Nil(t, repo.From(users).Save(&User{Name: "other delete", Age: 110}))
repo.From(users).MustSave(&record)
repo.From(users).MustSave(&User{Name: "delete", Age: 100})
repo.From(users).MustSave(&User{Name: "delete", Age: 100})
repo.From(users).MustSave(&User{Name: "other delete", Age: 110})

tests := []grimoire.Query{
repo.From(users).Find(record.ID),
@@ -8,13 +8,14 @@ import (
"github.com/Fs02/grimoire"
"github.com/Fs02/grimoire/adapter/sql"
"github.com/Fs02/grimoire/changeset"
"github.com/Fs02/grimoire/errors"
"github.com/stretchr/testify/assert"
)

// Insert tests insert specifications.
func Insert(t *testing.T, repo grimoire.Repo) {
user := User{}
assert.Nil(t, repo.From(users).Save(&user))
repo.From(users).MustSave(&user)

tests := []struct {
query grimoire.Query
@@ -50,7 +51,7 @@ func Insert(t *testing.T, repo grimoire.Repo) {
// InsertAll tests insert multiple specifications.
func InsertAll(t *testing.T, repo grimoire.Repo) {
user := User{}
assert.Nil(t, repo.From(users).Save(&user))
repo.From(users).MustSave(&user)

tests := []struct {
query grimoire.Query
@@ -85,7 +86,7 @@ func InsertAll(t *testing.T, repo grimoire.Repo) {
// InsertSet tests insert specifications only using Set query.
func InsertSet(t *testing.T, repo grimoire.Repo) {
user := User{}
assert.Nil(t, repo.From(users).Save(&user))
repo.From(users).MustSave(&user)
now := time.Now()

tests := []struct {
@@ -110,3 +111,23 @@ func InsertSet(t *testing.T, repo grimoire.Repo) {
})
}
}

// InsertConstraint tests insert constraint specifications.
func InsertConstraint(t *testing.T, repo grimoire.Repo) {
repo.From(users).Set("slug", "insert-taken").MustInsert(nil)

tests := []struct {
name string
query grimoire.Query
field string
code int
}{
{"UniqueConstraintError", repo.From(users).Set("slug", "insert-taken"), "slug", errors.UniqueConstraintErrorCode},
}

for _, test := range tests {
t.Run("InsertConstraint|"+test.name, func(t *testing.T) {
checkConstraint(t, test.query.Insert(nil), test.code, test.field)
})
}
}
@@ -12,17 +12,17 @@ import (
func Preload(t *testing.T, repo grimoire.Repo) {
// preparte tests data
user := User{Name: "preload", Gender: "male", Age: 10}
assert.Nil(t, repo.From(users).Save(&user))
repo.From(users).MustSave(&user)

userAddresses := []Address{
{Address: "preload1", UserID: &user.ID},
{Address: "preload2", UserID: &user.ID},
{Address: "preload3", UserID: &user.ID},
}

assert.Nil(t, repo.From(addresses).Save(&userAddresses[0]))
assert.Nil(t, repo.From(addresses).Save(&userAddresses[1]))
assert.Nil(t, repo.From(addresses).Save(&userAddresses[2]))
repo.From(addresses).MustSave(&userAddresses[0])
repo.From(addresses).MustSave(&userAddresses[1])
repo.From(addresses).MustSave(&userAddresses[2])

assert.Nil(t, user.Addresses)

@@ -14,16 +14,16 @@ import (
func Query(t *testing.T, repo grimoire.Repo) {
// preparte tests data
user := User{Name: "name1", Gender: "male", Age: 10}
assert.Nil(t, repo.From(users).Save(&user))
assert.Nil(t, repo.From(users).Save(&User{Name: "name2", Gender: "male", Age: 20}))
assert.Nil(t, repo.From(users).Save(&User{Name: "name3", Gender: "male", Age: 30}))
assert.Nil(t, repo.From(users).Save(&User{Name: "name4", Gender: "female", Age: 40}))
assert.Nil(t, repo.From(users).Save(&User{Name: "name5", Gender: "female", Age: 50}))
assert.Nil(t, repo.From(users).Save(&User{Name: "name6", Gender: "female", Age: 60}))
repo.From(users).MustSave(&user)
repo.From(users).MustSave(&User{Name: "name2", Gender: "male", Age: 20})
repo.From(users).MustSave(&User{Name: "name3", Gender: "male", Age: 30})
repo.From(users).MustSave(&User{Name: "name4", Gender: "female", Age: 40})
repo.From(users).MustSave(&User{Name: "name5", Gender: "female", Age: 50})
repo.From(users).MustSave(&User{Name: "name6", Gender: "female", Age: 60})

assert.Nil(t, repo.From(addresses).Save(&Address{Address: "address1", UserID: &user.ID}))
assert.Nil(t, repo.From(addresses).Save(&Address{Address: "address2", UserID: &user.ID}))
assert.Nil(t, repo.From(addresses).Save(&Address{Address: "address3", UserID: &user.ID}))
repo.From(addresses).MustSave(&Address{Address: "address1", UserID: &user.ID})
repo.From(addresses).MustSave(&Address{Address: "address2", UserID: &user.ID})
repo.From(addresses).MustSave(&Address{Address: "address3", UserID: &user.ID})

tests := []grimoire.Query{
repo.From(users).Where(c.Eq(id, user.ID)),
@@ -49,9 +49,9 @@ func SaveInsertAll(t *testing.T, repo grimoire.Repo) {
// SaveUpdate tests update specifications.
func SaveUpdate(t *testing.T, repo grimoire.Repo) {
record := User{Name: "save update", Age: 100}
assert.Nil(t, repo.From(users).Save(&record))
assert.Nil(t, repo.From(users).Save(&User{Name: "save update", Age: 100}))
assert.Nil(t, repo.From(users).Save(&User{Name: "save update", Age: 100}))
repo.From(users).MustSave(&record)
repo.From(users).MustSave(&User{Name: "save update", Age: 100})
repo.From(users).MustSave(&User{Name: "save update", Age: 100})

tests := []grimoire.Query{
repo.From(users).Find(record.ID),
@@ -2,14 +2,19 @@
package specs

import (
"strings"
"testing"
"time"

"github.com/Fs02/grimoire/c"
"github.com/Fs02/grimoire/errors"
"github.com/stretchr/testify/assert"
)

// User defines users schema.
type User struct {
ID int64
Slug *string
Name string
Gender string
Age int
@@ -41,3 +46,10 @@ const (
createdAt = c.I("created_at")
address = c.I("address")
)

func checkConstraint(t *testing.T, err error, code int, field string) {
assert.NotNil(t, err)
gerr, _ := err.(errors.Error)
assert.True(t, strings.Contains(gerr.Field, field))
assert.Equal(t, code, gerr.Code)
}
@@ -7,16 +7,17 @@ import (
"github.com/Fs02/grimoire/adapter/sql"
"github.com/Fs02/grimoire/c"
"github.com/Fs02/grimoire/changeset"
"github.com/Fs02/grimoire/errors"
"github.com/stretchr/testify/assert"
)

// Update tests update specifications.
func Update(t *testing.T, repo grimoire.Repo) {
user := User{Name: "update"}
assert.Nil(t, repo.From(users).Save(&user))
repo.From(users).MustSave(&user)

address := Address{Address: "update"}
assert.Nil(t, repo.From(addresses).Save(&address))
repo.From(addresses).MustSave(&address)

tests := []struct {
query grimoire.Query
@@ -47,10 +48,10 @@ func Update(t *testing.T, repo grimoire.Repo) {
// UpdateWhere tests update specifications.
func UpdateWhere(t *testing.T, repo grimoire.Repo) {
user := User{Name: "update all"}
assert.Nil(t, repo.From(users).Save(&user))
repo.From(users).MustSave(&user)

address := Address{Address: "update all"}
assert.Nil(t, repo.From(addresses).Save(&address))
repo.From(addresses).MustSave(&address)

tests := []struct {
query grimoire.Query
@@ -78,10 +79,10 @@ func UpdateWhere(t *testing.T, repo grimoire.Repo) {
// UpdateSet tests update specifications using Set query.
func UpdateSet(t *testing.T, repo grimoire.Repo) {
user := User{Name: "update"}
assert.Nil(t, repo.From(users).Save(&user))
repo.From(users).MustSave(&user)

address := Address{Address: "update"}
assert.Nil(t, repo.From(addresses).Save(&address))
repo.From(addresses).MustSave(&address)

tests := []struct {
query grimoire.Query
@@ -103,3 +104,26 @@ func UpdateSet(t *testing.T, repo grimoire.Repo) {
})
}
}

// UpdateConstraint tests update constraint specifications.
func UpdateConstraint(t *testing.T, repo grimoire.Repo) {
user := User{}
repo.From(users).MustSave(&user)

repo.From(users).Set("slug", "update-taken").MustInsert(nil)

tests := []struct {
name string
query grimoire.Query
field string
code int
}{
{"UniqueConstraintError", repo.From(users).Find(user.ID).Set("slug", "update-taken"), "slug", errors.UniqueConstraintErrorCode},
}

for _, test := range tests {
t.Run("UpdateConstraint|"+test.name, func(t *testing.T) {
checkConstraint(t, test.query.Insert(nil), test.code, test.field)
})
}
}
Oops, something went wrong.

0 comments on commit f27872d

Please sign in to comment.