Working in PG datastore.
This commit is contained in:
parent
76d5264474
commit
1e283e7cd6
8 changed files with 210 additions and 1 deletions
24
datastore/datastore.go
Normal file
24
datastore/datastore.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
package datastore
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/modl"
|
||||
"github.com/thermokarst/bactdb/models"
|
||||
)
|
||||
|
||||
// A datastore access point (in PostgreSQL)
|
||||
type Datastore struct {
|
||||
Users models.UsersService
|
||||
dbh modl.SqlExecutor
|
||||
}
|
||||
|
||||
// NewDatastore creates a new client for accessing the datastore (in PostgreSQL).
|
||||
// If dbh is nil, it uses the global DB handle.
|
||||
func NewDatastore(dbh modl.SqlExecutor) *Datastore {
|
||||
if dbh == nil {
|
||||
dbh = DBH
|
||||
}
|
||||
|
||||
d := &Datastore{dbh: dbh}
|
||||
d.Users = &usersStore{d}
|
||||
return d
|
||||
}
|
9
datastore/datastore_test.go
Normal file
9
datastore/datastore_test.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package datastore
|
||||
|
||||
import "time"
|
||||
|
||||
func normalizeTime(t ...*time.Time) {
|
||||
for _, v := range t {
|
||||
*v = v.In(time.UTC)
|
||||
}
|
||||
}
|
53
datastore/db.go
Normal file
53
datastore/db.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
package datastore
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
|
||||
"github.com/jmoiron/modl"
|
||||
"github.com/jmoiron/sqlx"
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
// DB is the global database
|
||||
var DB = &modl.DbMap{Dialect: modl.PostgresDialect{}}
|
||||
|
||||
// DBH is a modl.SqlExecutor interface to DB, the global database. It is better to
|
||||
// use DBH instead of DB because it prevents you from calling methods that could
|
||||
// not later be wrapped in a transaction.
|
||||
var DBH modl.SqlExecutor = DB
|
||||
|
||||
var connectOnce sync.Once
|
||||
|
||||
// Connect connects to the PostgreSQL database specified by the PG* environment
|
||||
// variables. It calls log.Fatal if it encounters an error.
|
||||
func Connect() {
|
||||
connectOnce.Do(func() {
|
||||
var err error
|
||||
DB.Dbx, err = sqlx.Open("postgres", "")
|
||||
if err != nil {
|
||||
log.Fatal("Error connecting to PostgreSQL database (using PG* environment variables): ", err)
|
||||
}
|
||||
DB.Db = DB.Dbx.DB
|
||||
})
|
||||
}
|
||||
|
||||
var createSQL []string
|
||||
|
||||
// Create the database schema. It calls log.Fatal if it encounters an error.
|
||||
func Create() {
|
||||
if err := DB.CreateTablesIfNotExists(); err != nil {
|
||||
log.Fatal("Error creating tables: ", err)
|
||||
}
|
||||
for _, query := range createSQL {
|
||||
if _, err := DB.Exec(query); err != nil {
|
||||
log.Fatalf("Error running query %q: %s", query, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Drop the database schema
|
||||
func Drop() {
|
||||
// TODO(mrd): raise errors.
|
||||
DB.DropTables()
|
||||
}
|
26
datastore/db_test.go
Normal file
26
datastore/db_test.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
package datastore
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Make sure we don't run the tests on the main DB (will destroy the data)
|
||||
dbname := os.Getenv("PGDATABASE")
|
||||
if dbname == "" {
|
||||
dbname = "bactdbtest"
|
||||
}
|
||||
if !strings.HasSuffix(dbname, "test") {
|
||||
dbname += "test"
|
||||
}
|
||||
if err := os.Setenv("PGDATABASE", dbname); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Reset DB
|
||||
Connect()
|
||||
Drop()
|
||||
Create()
|
||||
}
|
34
datastore/users.go
Normal file
34
datastore/users.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package datastore
|
||||
|
||||
import "github.com/thermokarst/bactdb/models"
|
||||
|
||||
func init() {
|
||||
DB.AddTableWithName(models.User{}, "users").SetKeys(false, "Id")
|
||||
createSQL = append(createSQL,
|
||||
`CREATE UNIQUE INDEX username_idx ON users (username);`,
|
||||
)
|
||||
}
|
||||
|
||||
type usersStore struct {
|
||||
*Datastore
|
||||
}
|
||||
|
||||
func (s *usersStore) Get(id int64) (*models.User, error) {
|
||||
var users []*models.User
|
||||
if err := s.dbh.Select(&users, `SELECT * FROM users WHERE id=$1;`, id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(users) == 0 {
|
||||
return nil, models.ErrUserNotFound
|
||||
}
|
||||
return users[0], nil
|
||||
}
|
||||
|
||||
func (s *usersStore) List(opt *models.UserListOptions) ([]*models.User, error) {
|
||||
var users []*models.User
|
||||
err := s.dbh.Select(&users, `SELECT * FROM users LIMIT $1 OFFSET $2;`, opt.PerPageOrDefault(), opt.Offset())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return users, nil
|
||||
}
|
58
datastore/users_test.go
Normal file
58
datastore/users_test.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
package datastore
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/thermokarst/bactdb/models"
|
||||
)
|
||||
|
||||
func TestUsersStore_Get_db(t *testing.T) {
|
||||
want := &models.User{Id: 1, UserName: "Test User"}
|
||||
|
||||
tx, _ := DB.Begin()
|
||||
defer tx.Rollback()
|
||||
// Test on a clean database
|
||||
tx.Exec(`DELETE FROM users;`)
|
||||
if err := tx.Insert(want); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
d := NewDatastore(tx)
|
||||
user, err := d.Users.Get(1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
normalizeTime(&want.CreatedAt, &want.UpdatedAt, &want.DeletedAt)
|
||||
if !reflect.DeepEqual(user, want) {
|
||||
t.Errorf("got user %+v, want %+v", user, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUsersStore_List_db(t *testing.T) {
|
||||
want := []*models.User{{Id: 1, UserName: "Test User"}}
|
||||
|
||||
// tx := DBH
|
||||
tx, _ := DB.Begin()
|
||||
defer tx.Rollback()
|
||||
|
||||
// Test on a clean database
|
||||
tx.Exec(`DELETE FROM users;`)
|
||||
if err := tx.Insert(want[0]); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
d := NewDatastore(tx)
|
||||
users, err := d.Users.List(&models.UserListOptions{ListOptions: models.ListOptions{Page: 1, PerPage: 10}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, u := range want {
|
||||
normalizeTime(&u.CreatedAt, &u.UpdatedAt, &u.DeletedAt)
|
||||
}
|
||||
if !reflect.DeepEqual(users, want) {
|
||||
t.Errorf("got users %+v, want %+v", users, want)
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
|
@ -25,6 +26,10 @@ type UsersService interface {
|
|||
List(opt *UserListOptions) ([]*User, error)
|
||||
}
|
||||
|
||||
var (
|
||||
ErrUserNotFound = errors.New("user not found")
|
||||
)
|
||||
|
||||
type usersService struct {
|
||||
client *Client
|
||||
}
|
||||
|
|
2
test.sh
2
test.sh
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
go test ./...
|
||||
PGTZ=UTC PGSSLMODE=disable go test ./...
|
||||
|
||||
|
|
Reference in a new issue