Newer
Older
// Copyright (c) 2012-2014 José Carlos Nieto, https://menteslibres.net/xiam
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package db_test
import (
"database/sql"
"errors"
José Carlos Nieto
committed
"os"
"github.com/jmoiron/sqlx"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
"upper.io/db/postgresql"
sqlite.Adapter,
mysql.Adapter,
postgresql.Adapter,
//`mongo`,
const (
José Carlos Nieto
committed
testAllWrappers = `all`
)
var (
errDriverErr = errors.New(`Driver error`)
)
var settings map[string]db.ConnectionURL
José Carlos Nieto
committed
// Getting settings from the environment.
José Carlos Nieto
committed
var host string
if host = os.Getenv("TEST_HOST"); host == "" {
host = "localhost"
}
var wrapper string
if wrapper = os.Getenv("TEST_WRAPPER"); wrapper == "" {
wrapper = testAllWrappers
}
José Carlos Nieto
committed
log.Printf("Running tests against host %s.\n", host)
settings = map[string]db.ConnectionURL{
Database: `upperio_tests.db`,
},
`mongo`: &db.Settings{
Database: `upperio_tests`,
José Carlos Nieto
committed
Host: host,
User: `upperio`,
Password: `upperio`,
},
User: `upperio`,
Password: `upperio`,
Options: map[string]string{
"parseTime": "true",
},
`postgresql`: &postgresql.ConnectionURL{
Address: db.Host(host),
User: `upperio`,
Password: `upperio`,
Options: map[string]string{
"timezone": "UTC",
},
`ql`: &ql.ConnectionURL{
Database: `upperio_test.ql`,
José Carlos Nieto
committed
if wrapper != testAllWrappers {
wrappers = []string{wrapper}
log.Printf("Testing wrapper %s.", wrapper)
}
}
var setupFn = map[string]func(driver interface{}) error{
`mongo`: func(driver interface{}) error {
if mgod, ok := driver.(*mgo.Session); ok {
var col *mgo.Collection
col = mgod.DB("upperio_tests").C("birthdays")
col.DropCollection()
col = mgod.DB("upperio_tests").C("fibonacci")
col = mgod.DB("upperio_tests").C("is_even")
col.DropCollection()
José Carlos Nieto
committed
col = mgod.DB("upperio_tests").C("CaSe_TesT")
col.DropCollection()
return nil
}
return errDriverErr
},
`postgresql`: func(driver interface{}) error {
_, err = sqld.Exec(`DROP TABLE IF EXISTS "birthdays"`)
if err != nil {
return err
}
_, err = sqld.Exec(`CREATE TABLE "birthdays" (
"id" serial primary key,
"born" TIMESTAMP WITH TIME ZONE,
_, err = sqld.Exec(`DROP TABLE IF EXISTS "fibonacci"`)
if err != nil {
return err
}
_, err = sqld.Exec(`CREATE TABLE "fibonacci" (
"id" serial primary key,
"input" NUMERIC,
"output" NUMERIC
)`)
if err != nil {
return err
}
_, err = sqld.Exec(`DROP TABLE IF EXISTS "is_even"`)
if err != nil {
return err
}
_, err = sqld.Exec(`CREATE TABLE "is_even" (
"input" NUMERIC,
)`)
if err != nil {
return err
}
José Carlos Nieto
committed
_, err = sqld.Exec(`DROP TABLE IF EXISTS "CaSe_TesT"`)
if err != nil {
return err
}
_, err = sqld.Exec(`CREATE TABLE "CaSe_TesT" (
"id" SERIAL PRIMARY KEY,
"case_test" VARCHAR(60)
José Carlos Nieto
committed
)`)
if err != nil {
return err
}
return fmt.Errorf("Expecting *sqlx.DB got %T (%#v).", driver, driver)
José Carlos Nieto
committed
_, err = sqld.Exec(`DROP TABLE IF EXISTS ` + "`" + `birthdays` + "`" + ``)
_, err = sqld.Exec(`CREATE TABLE ` + "`" + `birthdays` + "`" + ` (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY(id),
born DATE,
born_ut BIGINT(20) SIGNED
) CHARSET=utf8`)
if err != nil {
return err
}
José Carlos Nieto
committed
_, err = sqld.Exec(`DROP TABLE IF EXISTS ` + "`" + `fibonacci` + "`" + ``)
if err != nil {
return err
}
_, err = sqld.Exec(`CREATE TABLE ` + "`" + `fibonacci` + "`" + ` (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY(id),
input BIGINT(20) UNSIGNED NOT NULL,
output BIGINT(20) UNSIGNED NOT NULL
) CHARSET=utf8`)
José Carlos Nieto
committed
_, err = sqld.Exec(`DROP TABLE IF EXISTS ` + "`" + `is_even` + "`" + ``)
if err != nil {
return err
}
_, err = sqld.Exec(`CREATE TABLE ` + "`" + `is_even` + "`" + ` (
input BIGINT(20) UNSIGNED NOT NULL,
is_even TINYINT(1)
) CHARSET=utf8`)
if err != nil {
return err
}
José Carlos Nieto
committed
_, err = sqld.Exec(`DROP TABLE IF EXISTS ` + "`" + `CaSe_TesT` + "`" + ``)
José Carlos Nieto
committed
if err != nil {
return err
}
_, err = sqld.Exec(`CREATE TABLE ` + "`" + `CaSe_TesT` + "`" + ` (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY(id),
case_test VARCHAR(60)
José Carlos Nieto
committed
) CHARSET=utf8`)
if err != nil {
return err
}
return fmt.Errorf("Expecting *sqlx.DB got %T (%#v).", driver, driver)
José Carlos Nieto
committed
_, err = sqld.Exec(`DROP TABLE IF EXISTS "birthdays"`)
if err != nil {
return err
}
_, err = sqld.Exec(`CREATE TABLE "birthdays" (
"id" INTEGER PRIMARY KEY,
"name" VARCHAR(50) DEFAULT NULL,
"born_ut" INTEGER
José Carlos Nieto
committed
_, err = sqld.Exec(`DROP TABLE IF EXISTS "fibonacci"`)
if err != nil {
return err
}
_, err = sqld.Exec(`CREATE TABLE "fibonacci" (
"id" INTEGER PRIMARY KEY,
"input" INTEGER,
"output" INTEGER
)`)
if err != nil {
return err
}
José Carlos Nieto
committed
_, err = sqld.Exec(`DROP TABLE IF EXISTS "is_even"`)
if err != nil {
return err
}
_, err = sqld.Exec(`CREATE TABLE "is_even" (
"input" INTEGER,
"is_even" INTEGER
)`)
if err != nil {
return err
}
José Carlos Nieto
committed
_, err = sqld.Exec(`DROP TABLE IF EXISTS "CaSe_TesT"`)
if err != nil {
return err
}
_, err = sqld.Exec(`CREATE TABLE "CaSe_TesT" (
"id" INTEGER PRIMARY KEY,
"case_test" VARCHAR
José Carlos Nieto
committed
)`)
if err != nil {
return err
}
var err error
var tx *sql.Tx
if tx, err = sqld.Begin(); err != nil {
return err
}
_, err = tx.Exec(`DROP TABLE IF EXISTS birthdays`)
if err != nil {
return err
}
_, err = tx.Exec(`CREATE TABLE birthdays (
name string,
born time,
born_ut int
)`)
if err != nil {
return err
}
_, err = tx.Exec(`DROP TABLE IF EXISTS fibonacci`)
if err != nil {
return err
}
_, err = tx.Exec(`CREATE TABLE fibonacci (
input int,
output int
)`)
if err != nil {
return err
}
_, err = tx.Exec(`DROP TABLE IF EXISTS is_even`)
if err != nil {
return err
}
_, err = tx.Exec(`CREATE TABLE is_even (
input int,
is_even bool
)`)
if err != nil {
return err
}
José Carlos Nieto
committed
_, err = tx.Exec(`DROP TABLE IF EXISTS CaSe_TesT`)
if err != nil {
return err
}
_, err = tx.Exec(`CREATE TABLE CaSe_TesT (
José Carlos Nieto
committed
)`)
if err != nil {
return err
}
if err = tx.Commit(); err != nil {
return err
}
return nil
}
return errDriverErr
},
type birthday struct {
Name string // Must match by name.
Born time.Time // Must match by name.
BornUT timeType `db:"born_ut"`
OmitMe bool `json:"omit_me" db:"-" bson:"-"`
type fibonacci struct {
Input uint64 `db:"input"`
Output uint64 `db:"output"`
// Test for BSON option.
OmitMe bool `json:"omitme" db:",bson,omitempty" bson:"omit_me,omitempty"`
}
type oddEven struct {
// Test for JSON option.
Input int `json:"input"`
// Test for JSON option.
// The "bson" tag is required by mgo.
IsEven bool `json:"is_even" db:",json" bson:"is_even"`
OmitMe bool `json:"omit_me" db:"-" bson:"-"`
}
José Carlos Nieto
committed
// Struct that relies on explicit mapping.
type mapE struct {
ID uint `db:"id,omitempty" bson:"-"`
MongoID bson.ObjectId `db:"-" bson:"_id,omitempty"`
José Carlos Nieto
committed
}
// Struct that will fallback to default mapping.
type mapN struct {
ID uint `db:"id,omitempty"`
MongoID bson.ObjectId `db:"-" bson:"_id,omitempty"`
Case_TEST string
José Carlos Nieto
committed
}
// Struct for testing marshalling.
type timeType struct {
// Time is handled internally as time.Time but saved as an (integer) unix
// timestamp.
value time.Time
}
// time.Time -> unix timestamp
func (u timeType) MarshalDB() (interface{}, error) {
return u.value.Unix(), nil
}
// unix timestamp -> time.Time
func (u *timeType) UnmarshalDB(v interface{}) error {
var unixTime int64
switch t := v.(type) {
case int64:
unixTime = t
default:
return db.ErrUnsupportedValue
}
t := time.Unix(unixTime, 0).In(time.UTC)
*u = timeType{t}
return nil
}
var _ db.Marshaler = timeType{}
var _ db.Unmarshaler = &timeType{}
func even(i int) bool {
if i%2 == 0 {
return true
}
return false
}
func fib(i uint64) uint64 {
if i == 0 {
return 0
} else if i == 1 {
return 1
}
return fib(i-1) + fib(i-2)
}
func TestOpen(t *testing.T) {
var err error
for _, wrapper := range wrappers {
t.Logf("Testing wrapper: %q", wrapper)
if settings[wrapper] == nil {
t.Fatalf(`No such settings entry for wrapper %s.`, wrapper)
} else {
var sess db.Database
sess, err = db.Open(wrapper, settings[wrapper])
José Carlos Nieto
committed
t.Fatalf(`Test for wrapper %s failed: %q`, wrapper, err)
José Carlos Nieto
committed
t.Fatalf(`Test for wrapper %s failed: %q`, wrapper, err)
}
}
}
}
func TestSetup(t *testing.T) {
var err error
for _, wrapper := range wrappers {
t.Logf("Testing wrapper: %q", wrapper)
if settings[wrapper] == nil {
t.Fatalf(`No such settings entry for wrapper %s.`, wrapper)
} else {
var sess db.Database
sess, err = db.Open(wrapper, settings[wrapper])
José Carlos Nieto
committed
t.Fatalf(`Test for wrapper %s failed: %q`, wrapper, err)
}
if setupFn[wrapper] == nil {
t.Fatalf(`Missing setup function for wrapper %s.`, wrapper)
} else {
José Carlos Nieto
committed
if err = setupFn[wrapper](sess.Driver()); err != nil {
t.Fatalf(`Failed to setup wrapper %s: %q`, wrapper, err)
err = sess.Close()
if err != nil {
José Carlos Nieto
committed
t.Fatalf(`Could not close %s: %q`, wrapper, err)
func TestSimpleCRUD(t *testing.T) {
var controlItem birthday
for _, wrapper := range wrappers {
if settings[wrapper] == nil {
t.Fatalf(`No such settings entry for wrapper %s.`, wrapper)
} else {
t.Logf("Testing wrapper: %q", wrapper)
sess, err = db.Open(wrapper, settings[wrapper])
José Carlos Nieto
committed
t.Fatalf(`Test for wrapper %s failed: %q`, wrapper, err)
born := time.Date(1941, time.January, 5, 0, 0, 0, 0, time.UTC)
controlItem = birthday{
Name: "Hayao Miyazaki",
Born: born,
BornUT: timeType{born},
col, err := sess.Collection(`birthdays`)
if err != nil {
if wrapper == `mongo` && err == db.ErrCollectionDoesNotExist {
José Carlos Nieto
committed
t.Fatalf(`Could not use collection with wrapper %s: %q`, wrapper, err)
José Carlos Nieto
committed
t.Fatalf(`Could not append item with wrapper %s: %q`, wrapper, err)
res = col.Find(db.Cond{"_id": id.(bson.ObjectId)})
case `ql`:
res = col.Find(db.Cond{"id()": id})
default:
res = col.Find(db.Cond{"id": id})
if total != 1 {
t.Fatalf("%s: Expecting one row.", wrapper)
}
// No support for Marshaler and Unmarshaler is implemeted for QL and
// MongoDB.
if wrapper == `ql` || wrapper == `mongo` {
continue
}
var testItem birthday
err = res.One(&testItem)
if err != nil {
t.Fatalf("%s One(): %s", wrapper, err)
}
if wrapper == `sqlite` {
// SQLite does not save time zone info, so you have to do this by hand.
testItem.Born = testItem.Born.In(time.UTC)
}
if reflect.DeepEqual(testItem, controlItem) == false {
t.Errorf("%s: controlItem (inserted): %v (ts: %v)\n", wrapper, controlItem, controlItem.BornUT.value.Unix())
var testItems []birthday
err = res.All(&testItems)
if err != nil {
t.Fatalf("%s All(): %s", wrapper, err)
}
if len(testItems) == 0 {
t.Fatalf("%s All(): Expecting at least one row.", wrapper)
}
for _, testItem = range testItems {
if wrapper == `sqlite` {
// SQLite does not save time zone info, so you have to do this by hand.
testItem.Born = testItem.Born.In(time.UTC)
}
if reflect.DeepEqual(testItem, controlItem) == false {
t.Errorf("%s: testItem: %v\n", wrapper, testItem)
t.Errorf("%s: controlItem: %v\n", wrapper, controlItem)
t.Fatalf("%s: Structs are different", wrapper)
}
}
controlItem.Name = `宮崎駿`
err = res.Update(controlItem)
if err != nil {
José Carlos Nieto
committed
t.Fatalf(`Could not update with wrapper %s: %q`, wrapper, err)
err = res.One(&testItem)
if err != nil {
t.Fatalf("%s One(): %s", wrapper, err)
}
if wrapper == `sqlite` {
// SQLite does not save time zone info, so you have to do this by hand.
testItem.Born = testItem.Born.In(time.UTC)
}
if reflect.DeepEqual(testItem, controlItem) == false {
t.Fatalf("Struct is different with wrapper %s.", wrapper)
}
err = res.Remove()
if err != nil {
José Carlos Nieto
committed
t.Fatalf(`Could not remove with wrapper %s: %q`, wrapper, err)
}
total, err = res.Count()
if total != 0 {
José Carlos Nieto
committed
t.Fatalf(`Expecting no items %s: %q`, wrapper, err)
}
err = res.Close()
if err != nil {
José Carlos Nieto
committed
t.Errorf("Failed to close result %s: %q.", wrapper, err)
}
err = sess.Close()
if err != nil {
José Carlos Nieto
committed
t.Errorf("Failed to close %s: %q.", wrapper, err)
}
}
}
}
func TestFibonacci(t *testing.T) {
José Carlos Nieto
committed
var res db.Result
var total uint64
for _, wrapper := range wrappers {
t.Logf("Testing wrapper: %q", wrapper)
if settings[wrapper] == nil {
t.Fatalf(`No such settings entry for wrapper %s.`, wrapper)
} else {
sess, err = db.Open(wrapper, settings[wrapper])
José Carlos Nieto
committed
t.Fatalf(`Test for wrapper %s failed: %q`, wrapper, err)
var col db.Collection
col, err = sess.Collection("fibonacci")
if err != nil {
if wrapper == `mongo` && err == db.ErrCollectionDoesNotExist {
// Expected error with mongodb.
} else {
José Carlos Nieto
committed
t.Fatalf(`Could not use collection with wrapper %s: %q`, wrapper, err)
}
}
// Adding some items.
var i uint64
for i = 0; i < 10; i++ {
item := fibonacci{Input: i, Output: fib(i)}
_, err = col.Append(item)
if err != nil {
José Carlos Nieto
committed
t.Fatalf(`Could not append item with wrapper %s: %q`, wrapper, err)
José Carlos Nieto
committed
// Testing sorting by function.
res = col.Find(
// 5, 6, 7, 3
db.Or{
db.And{
db.Cond{"input >=": 5},
db.Cond{"input <=": 7},
},
db.Cond{"input": 3},
},
)
// Testing sort by function.
switch wrapper {
case `postgresql`:
res = res.Sort(db.Raw{`RANDOM()`})
case `sqlite`:
res = res.Sort(db.Raw{`RANDOM()`})
case `mysql`:
res = res.Sort(db.Raw{`RAND()`})
}
total, err = res.Count()
if err != nil {
José Carlos Nieto
committed
t.Fatalf(`%s: %q`, wrapper, err)
José Carlos Nieto
committed
}
if total != 4 {
t.Fatalf("%s: Expecting a count of 4, got %d.", wrapper, total)
}
// Find() with IN/$in
var whereIn db.Cond
switch wrapper {
case `mongo`:
whereIn = db.Cond{"input": db.Func{"$in", []int{3, 5, 6, 7}}}
default:
whereIn = db.Cond{"input": db.Func{"IN", []int{3, 5, 6, 7}}}
}
res = col.Find(whereIn).Sort("input")
total, err = res.Count()
if err != nil {
José Carlos Nieto
committed
t.Fatalf(`%s: %q`, wrapper, err)
}
if total != 4 {
t.Fatalf(`Expecting a count of 4.`)
}
var item fibonacci
err = res.Next(&item)
if err == nil {
switch item.Input {
case 5:
case 6:
if fib(item.Input) != item.Output {
t.Fatalf(`Unexpected value in item with wrapper %s.`, wrapper)
}
default:
t.Fatalf(`Unexpected item: %v with wrapper %s.`, item, wrapper)
}
} else if err == db.ErrNoMoreRows {
break
} else {
José Carlos Nieto
committed
t.Fatalf(`%s: %q`, wrapper, err)
}
}
// Find() with range
res = col.Find(
// 5, 6, 7, 3
db.Or{
db.And{
db.Cond{"input >=": 5},
db.Cond{"input <=": 7},
},
db.Cond{"input": 3},
},
if total, err = res.Count(); err != nil {
José Carlos Nieto
committed
t.Fatalf(`%s: %q`, wrapper, err)
}
if total != 4 {
t.Fatalf(`Expecting a count of 4.`)
}
// Skipping.
res = res.Skip(1).Limit(2)
var item fibonacci
err = res.Next(&item)
if err == nil {
switch item.Input {
case 5:
case 6:
if fib(item.Input) != item.Output {
t.Fatalf(`Unexpected value in item with wrapper %s.`, wrapper)
}
default:
t.Fatalf(`Unexpected item: %v with wrapper %s.`, item, wrapper)
}
} else if err == db.ErrNoMoreRows {
break
} else {
José Carlos Nieto
committed
t.Fatalf(`%s: %q`, wrapper, err)
if err = res.Remove(); err != nil {
José Carlos Nieto
committed
t.Fatalf(`%s: %q`, wrapper, err)
José Carlos Nieto
committed
t.Fatalf(`%s: %q`, wrapper, err)
res = col.Find()
total, err = res.Count()
if total != 6 {
var items []fibonacci
err = res.All(&items)
if err != nil {
José Carlos Nieto
committed
t.Fatalf(`%s: %q`, wrapper, err)
}
for _, item := range items {
switch item.Input {
case 0:
case 1:
case 2:
case 4:
case 8:
case 9:
if fib(item.Input) != item.Output {
t.Fatalf(`Unexpected value in item with wrapper %s.`, wrapper)
}
default:
t.Fatalf(`Unexpected item: %v with wrapper %s.`, item, wrapper)
}
}
err = res.Close()
if err != nil {
José Carlos Nieto
committed
t.Errorf("Failed to close result %s: %q.", wrapper, err)
}
err = sess.Close()
if err != nil {
José Carlos Nieto
committed
t.Errorf("Failed to close %s: %q.", wrapper, err)
func TestEven(t *testing.T) {
var err error
for _, wrapper := range wrappers {
t.Logf("Testing wrapper: %q", wrapper)
if settings[wrapper] == nil {
t.Fatalf(`No such settings entry for wrapper %s.`, wrapper)
} else {
var sess db.Database
sess, err = db.Open(wrapper, settings[wrapper])
if err != nil {
José Carlos Nieto
committed
t.Fatalf(`Test for wrapper %s failed: %q`, wrapper, err)
}
defer sess.Close()
var col db.Collection
col, err = sess.Collection("is_even")
if err != nil {
if wrapper == `mongo` && err == db.ErrCollectionDoesNotExist {
// Expected error with mongodb.
} else {
José Carlos Nieto
committed
t.Fatalf(`Could not use collection with wrapper %s: %q`, wrapper, err)
}
}
// Adding some items.
var i int
for i = 1; i < 100; i++ {
item := oddEven{Input: i, IsEven: even(i)}
_, err = col.Append(item)
if err != nil {
José Carlos Nieto
committed
t.Fatalf(`Could not append item with wrapper %s: %q`, wrapper, err)
}
}
// Retrieving items
res := col.Find(db.Cond{"is_even": true})
for {
var item oddEven
err = res.Next(&item)
if err != nil {
if err == db.ErrNoMoreRows {
break
} else {
t.Fatalf(`%s: %v`, wrapper, err)
}
}
if item.Input%2 != 0 {
t.Fatalf("Expecting even numbers with wrapper %s. Got: %v\n", wrapper, item)
}
}
if err = res.Remove(); err != nil {
José Carlos Nieto
committed
t.Fatalf(`Could not remove with wrapper %s: %q`, wrapper, err)
}
res = col.Find()
for {
// Testing named inputs (using tags).
var item struct {
Value uint `db:"input" bson:"input"` // The "bson" tag is required by mgo.
}
err = res.Next(&item)
if err != nil {
if err == db.ErrNoMoreRows {
break
} else {
t.Fatalf(`%s: %v`, wrapper, err)
}
}
if item.Value%2 == 0 {
t.Fatalf("Expecting odd numbers only with wrapper %s. Got: %v\n", wrapper, item)
}
}
for {
// Testing inline tag.
var item struct {
oddEven `db:",inline" bson:",inline"`
}
err = res.Next(&item)
if err != nil {
if err == db.ErrNoMoreRows {
break
} else {
t.Fatalf(`%s: %v`, wrapper, err)
}
}
if item.Input%2 == 0 {
t.Fatalf("Expecting odd numbers only with wrapper %s. Got: %v\n", wrapper, item)
}
}
// Testing omision tag.
for {
var item struct {
Value uint `db:"-"`
}
err = res.Next(&item)
if err != nil {
if err == db.ErrNoMoreRows {