User creation, DB transactions, createdb flag changes.
This commit is contained in:
parent
e1685bd32b
commit
c0b54d821e
11 changed files with 213 additions and 9 deletions
|
@ -22,3 +22,9 @@ func NewDatastore(dbh modl.SqlExecutor) *Datastore {
|
|||
d.Users = &usersStore{d}
|
||||
return d
|
||||
}
|
||||
|
||||
func NewMockDatastore() *Datastore {
|
||||
return &Datastore{
|
||||
Users: &models.MockUsersService{},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,3 +51,35 @@ func Drop() {
|
|||
// TODO(mrd): raise errors.
|
||||
DB.DropTables()
|
||||
}
|
||||
|
||||
// transact calls fn in a DB transaction. If dbh is a transaction, then it just calls
|
||||
// the function. Otherwise, it begins a transaction, rolling back on failure and
|
||||
// committing on success.
|
||||
func transact(dbh modl.SqlExecutor, fn func(fbh modl.SqlExecutor) error) error {
|
||||
var sharedTx bool
|
||||
tx, sharedTx := dbh.(*modl.Transaction)
|
||||
if !sharedTx {
|
||||
var err error
|
||||
tx, err = dbh.(*modl.DbMap).Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if err := fn(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !sharedTx {
|
||||
if err := tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
package datastore
|
||||
|
||||
import "github.com/thermokarst/bactdb/models"
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/modl"
|
||||
"github.com/thermokarst/bactdb/models"
|
||||
)
|
||||
|
||||
func init() {
|
||||
DB.AddTableWithName(models.User{}, "users").SetKeys(true, "Id")
|
||||
|
@ -24,6 +32,46 @@ func (s *usersStore) Get(id int64) (*models.User, error) {
|
|||
return users[0], nil
|
||||
}
|
||||
|
||||
func (s *usersStore) Create(user *models.User) (bool, error) {
|
||||
retries := 3
|
||||
var wantRetry bool
|
||||
|
||||
retry:
|
||||
retries--
|
||||
wantRetry = false
|
||||
if retries == 0 {
|
||||
return false, fmt.Errorf("failed to create user with username %q after retrying", user.UserName)
|
||||
}
|
||||
|
||||
var created bool
|
||||
err := transact(s.dbh, func(tx modl.SqlExecutor) error {
|
||||
var existing []*models.User
|
||||
if err := tx.Select(&existing, `SELECT * FROM users WHERE username=$1 LIMIT 1;`, user.UserName); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(existing) > 0 {
|
||||
*user = *existing[0]
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := tx.Insert(user); err != nil {
|
||||
if strings.Contains(err.Error(), `violates unique constraint "username_idx"`) {
|
||||
time.Sleep(time.Duration(rand.Intn(75)) * time.Millisecond)
|
||||
wantRetry = true
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
created = true
|
||||
return nil
|
||||
})
|
||||
if wantRetry {
|
||||
goto retry
|
||||
}
|
||||
return created, err
|
||||
}
|
||||
|
||||
func (s *usersStore) List(opt *models.UserListOptions) ([]*models.User, error) {
|
||||
if opt == nil {
|
||||
opt = &models.UserListOptions{}
|
||||
|
|
|
@ -12,6 +12,7 @@ func TestUsersStore_Get_db(t *testing.T) {
|
|||
|
||||
tx, _ := DB.Begin()
|
||||
defer tx.Rollback()
|
||||
|
||||
// Test on a clean database
|
||||
tx.Exec(`DELETE FROM users;`)
|
||||
if err := tx.Insert(want); err != nil {
|
||||
|
@ -30,10 +31,32 @@ func TestUsersStore_Get_db(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestUsersStore_Create_db(t *testing.T) {
|
||||
user := &models.User{Id: 1, UserName: "Test User"}
|
||||
|
||||
tx, _ := DB.Begin()
|
||||
defer tx.Rollback()
|
||||
|
||||
// Test on a clean database
|
||||
tx.Exec(`DELETE FROM users;`)
|
||||
|
||||
d := NewDatastore(tx)
|
||||
created, err := d.Users.Create(user)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !created {
|
||||
t.Error("!created")
|
||||
}
|
||||
if user.Id == 0 {
|
||||
t.Error("want nonzero user.Id after submitting")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUsersStore_List_db(t *testing.T) {
|
||||
want := []*models.User{{Id: 1, UserName: "Test User"}}
|
||||
|
||||
// tx := DBH
|
||||
tx, _ := DB.Begin()
|
||||
defer tx.Rollback()
|
||||
|
||||
|
|
Reference in a new issue