mydb/internal/testsuite/generic_suite.go
2023-09-18 15:15:42 +08:00

890 lines
18 KiB
Go

package testsuite
import (
"database/sql/driver"
"time"
"git.hexq.cn/tiglog/mydb"
"github.com/stretchr/testify/suite"
"gopkg.in/mgo.v2/bson"
)
type birthday struct {
Name string `db:"name"`
Born time.Time `db:"born"`
BornUT *unixTimestamp `db:"born_ut,omitempty"`
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:"omit_me" db:"omit_me,bson,omitempty" bson:"omit_me,omitempty"`
}
type oddEven struct {
// Test for JSON option.
Input int `json:"input" db:"input"`
// Test for JSON option.
// The "bson" tag is required by mgo.
IsEven bool `json:"is_even" db:"is_even,json" bson:"is_even"`
OmitMe bool `json:"omit_me" db:"-" bson:"-"`
}
// Struct that relies on explicit mapping.
type mapE struct {
ID uint `db:"id,omitempty" bson:"-"`
MongoID bson.ObjectId `db:"-" bson:"_id,omitempty"`
CaseTest string `db:"case_test" bson:"case_test"`
}
// 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 `db:"case_test"`
}
// Struct for testing marshalling.
type unixTimestamp struct {
// Time is handled internally as time.Time but saved as an (integer) unix
// timestamp.
value time.Time
}
func (u unixTimestamp) Value() (driver.Value, error) {
return u.value.UTC().Unix(), nil
}
func (u *unixTimestamp) Scan(v interface{}) error {
var unixTime int64
switch t := v.(type) {
case int64:
unixTime = t
case nil:
return nil
default:
return mydb.ErrUnsupportedValue
}
t := time.Unix(unixTime, 0).In(time.UTC)
*u = unixTimestamp{t}
return nil
}
func newUnixTimestamp(t time.Time) *unixTimestamp {
return &unixTimestamp{t.UTC()}
}
func even(i int) bool {
return i%2 == 0
}
func fib(i uint64) uint64 {
if i == 0 {
return 0
} else if i == 1 {
return 1
}
return fib(i-1) + fib(i-2)
}
type GenericTestSuite struct {
suite.Suite
Helper
}
func (s *GenericTestSuite) AfterTest(suiteName, testName string) {
err := s.TearDown()
s.NoError(err)
}
func (s *GenericTestSuite) BeforeTest(suiteName, testName string) {
err := s.TearUp()
s.NoError(err)
}
func (s *GenericTestSuite) TestDatesAndUnicode() {
sess := s.Session()
testTimeZone := time.Local
switch s.Adapter() {
case "mysql", "cockroachdb", "postgresql":
testTimeZone = defaultTimeLocation
case "sqlite", "ql", "mssql":
testTimeZone = time.UTC
}
born := time.Date(1941, time.January, 5, 0, 0, 0, 0, testTimeZone)
controlItem := birthday{
Name: "Hayao Miyazaki",
Born: born,
BornUT: newUnixTimestamp(born),
}
col := sess.Collection(`birthdays`)
record, err := col.Insert(controlItem)
s.NoError(err)
s.NotZero(record.ID())
var res mydb.Result
switch s.Adapter() {
case "mongo":
res = col.Find(mydb.Cond{"_id": record.ID().(bson.ObjectId)})
case "ql":
res = col.Find(mydb.Cond{"id()": record.ID()})
default:
res = col.Find(mydb.Cond{"id": record.ID()})
}
var total uint64
total, err = res.Count()
s.NoError(err)
s.Equal(uint64(1), total)
switch s.Adapter() {
case "mongo":
s.T().Skip()
}
var testItem birthday
err = res.One(&testItem)
s.NoError(err)
switch s.Adapter() {
case "sqlite", "ql", "mssql":
testItem.Born = testItem.Born.In(time.UTC)
}
s.Equal(controlItem.Born, testItem.Born)
s.Equal(controlItem.BornUT, testItem.BornUT)
s.Equal(controlItem, testItem)
var testItems []birthday
err = res.All(&testItems)
s.NoError(err)
s.NotZero(len(testItems))
for _, testItem = range testItems {
switch s.Adapter() {
case "sqlite", "ql", "mssql":
testItem.Born = testItem.Born.In(time.UTC)
}
s.Equal(controlItem, testItem)
}
controlItem.Name = `宮崎駿`
err = res.Update(controlItem)
s.NoError(err)
err = res.One(&testItem)
s.NoError(err)
switch s.Adapter() {
case "sqlite", "ql", "mssql":
testItem.Born = testItem.Born.In(time.UTC)
}
s.Equal(controlItem, testItem)
err = res.Delete()
s.NoError(err)
total, err = res.Count()
s.NoError(err)
s.Zero(total)
err = res.Close()
s.NoError(err)
}
func (s *GenericTestSuite) TestFibonacci() {
var err error
var res mydb.Result
var total uint64
sess := s.Session()
col := sess.Collection("fibonacci")
// Adding some items.
var i uint64
for i = 0; i < 10; i++ {
item := fibonacci{Input: i, Output: fib(i)}
_, err = col.Insert(item)
s.NoError(err)
}
// Testing sorting by function.
res = col.Find(
// 5, 6, 7, 3
mydb.Or(
mydb.And(
mydb.Cond{"input": mydb.Gte(5)},
mydb.Cond{"input": mydb.Lte(7)},
),
mydb.Cond{"input": mydb.Eq(3)},
),
)
// Testing sort by function.
switch s.Adapter() {
case "postgresql":
res = res.OrderBy(mydb.Raw("RANDOM()"))
case "sqlite":
res = res.OrderBy(mydb.Raw("RANDOM()"))
case "mysql":
res = res.OrderBy(mydb.Raw("RAND()"))
case "mssql":
res = res.OrderBy(mydb.Raw("NEWID()"))
}
total, err = res.Count()
s.NoError(err)
s.Equal(uint64(4), total)
// Find() with IN/$in
res = col.Find(mydb.Cond{"input IN": []int{3, 5, 6, 7}}).OrderBy("input")
total, err = res.Count()
s.NoError(err)
s.Equal(uint64(4), total)
res = res.Offset(1).Limit(2)
var item fibonacci
for res.Next(&item) {
switch item.Input {
case 5:
case 6:
s.Equal(fib(item.Input), item.Output)
default:
s.T().Errorf(`Unexpected item: %v.`, item)
}
}
s.NoError(res.Err())
// Find() with range
res = col.Find(
// 5, 6, 7, 3
mydb.Or(
mydb.And(
mydb.Cond{"input >=": 5},
mydb.Cond{"input <=": 7},
),
mydb.Cond{"input": 3},
),
).OrderBy("-input")
total, err = res.Count()
s.NoError(err)
s.Equal(uint64(4), total)
// Skipping.
res = res.Offset(1).Limit(2)
var item2 fibonacci
for res.Next(&item2) {
switch item2.Input {
case 5:
case 6:
s.Equal(fib(item2.Input), item2.Output)
default:
s.T().Errorf(`Unexpected item: %v.`, item2)
}
}
err = res.Err()
s.NoError(err)
err = res.Delete()
s.NoError(err)
{
total, err := res.Count()
s.NoError(err)
s.Zero(total)
}
// Find() with no arguments.
res = col.Find()
{
total, err := res.Count()
s.NoError(err)
s.Equal(uint64(6), total)
}
// Skipping mongodb as the results of this are not defined there.
if s.Adapter() != `mongo` {
// Find() with empty mydb.Cond.
{
total, err := col.Find(mydb.Cond{}).Count()
s.NoError(err)
s.Equal(uint64(6), total)
}
// Find() with empty expression
{
total, err := col.Find(mydb.Or(mydb.And(mydb.Cond{}, mydb.Cond{}), mydb.Or(mydb.Cond{}))).Count()
s.NoError(err)
s.Equal(uint64(6), total)
}
// Find() with explicit IS NULL
{
total, err := col.Find(mydb.Cond{"input IS": nil}).Count()
s.NoError(err)
s.Equal(uint64(0), total)
}
// Find() with implicit IS NULL
{
total, err := col.Find(mydb.Cond{"input": nil}).Count()
s.NoError(err)
s.Equal(uint64(0), total)
}
// Find() with explicit = NULL
{
total, err := col.Find(mydb.Cond{"input =": nil}).Count()
s.NoError(err)
s.Equal(uint64(0), total)
}
// Find() with implicit IN
{
total, err := col.Find(mydb.Cond{"input": []int{1, 2, 3, 4}}).Count()
s.NoError(err)
s.Equal(uint64(3), total)
}
// Find() with implicit NOT IN
{
total, err := col.Find(mydb.Cond{"input NOT IN": []int{1, 2, 3, 4}}).Count()
s.NoError(err)
s.Equal(uint64(3), total)
}
}
var items []fibonacci
err = res.All(&items)
s.NoError(err)
for _, item := range items {
switch item.Input {
case 0:
case 1:
case 2:
case 4:
case 8:
case 9:
s.Equal(fib(item.Input), item.Output)
default:
s.T().Errorf(`Unexpected item: %v`, item)
}
}
err = res.Close()
s.NoError(err)
}
func (s *GenericTestSuite) TestOddEven() {
sess := s.Session()
col := sess.Collection("is_even")
// Adding some items.
var i int
for i = 1; i < 100; i++ {
item := oddEven{Input: i, IsEven: even(i)}
_, err := col.Insert(item)
s.NoError(err)
}
// Retrieving items
res := col.Find(mydb.Cond{"is_even": true})
var item oddEven
for res.Next(&item) {
s.Zero(item.Input % 2)
}
err := res.Err()
s.NoError(err)
err = res.Delete()
s.NoError(err)
// Testing named inputs (using tags).
res = col.Find()
var item2 struct {
Value uint `db:"input" bson:"input"` // The "bson" tag is required by mgo.
}
for res.Next(&item2) {
s.NotZero(item2.Value % 2)
}
err = res.Err()
s.NoError(err)
// Testing inline tag.
res = col.Find()
var item3 struct {
OddEven oddEven `db:",inline" bson:",inline"`
}
for res.Next(&item3) {
s.NotZero(item3.OddEven.Input % 2)
s.NotZero(item3.OddEven.Input)
}
err = res.Err()
s.NoError(err)
// Testing inline tag.
type OddEven oddEven
res = col.Find()
var item31 struct {
OddEven `db:",inline" bson:",inline"`
}
for res.Next(&item31) {
s.NotZero(item31.Input % 2)
s.NotZero(item31.Input)
}
s.NoError(res.Err())
// Testing omision tag.
res = col.Find()
var item4 struct {
Value uint `db:"-"`
}
for res.Next(&item4) {
s.Zero(item4.Value)
}
s.NoError(res.Err())
}
func (s *GenericTestSuite) TestExplicitAndDefaultMapping() {
var err error
var res mydb.Result
var testE mapE
var testN mapN
sess := s.Session()
col := sess.Collection("CaSe_TesT")
if err = col.Truncate(); err != nil {
if s.Adapter() != "mongo" {
s.NoError(err)
}
}
// Testing explicit mapping.
testE = mapE{
CaseTest: "Hello!",
}
_, err = col.Insert(testE)
s.NoError(err)
res = col.Find(mydb.Cond{"case_test": "Hello!"})
if s.Adapter() == "ql" {
res = res.Select("id() as id", "case_test")
}
err = res.One(&testE)
s.NoError(err)
if s.Adapter() == "mongo" {
s.True(testE.MongoID.Valid())
} else {
s.NotZero(testE.ID)
}
// Testing default mapping.
testN = mapN{
Case_TEST: "World!",
}
_, err = col.Insert(testN)
s.NoError(err)
if s.Adapter() == `mongo` {
res = col.Find(mydb.Cond{"case_test": "World!"})
} else {
res = col.Find(mydb.Cond{"case_test": "World!"})
}
if s.Adapter() == `ql` {
res = res.Select(`id() as id`, `case_test`)
}
err = res.One(&testN)
s.NoError(err)
if s.Adapter() == `mongo` {
s.True(testN.MongoID.Valid())
} else {
s.NotZero(testN.ID)
}
}
func (s *GenericTestSuite) TestComparisonOperators() {
sess := s.Session()
birthdays := sess.Collection("birthdays")
err := birthdays.Truncate()
if err != nil {
if s.Adapter() != "mongo" {
s.NoError(err)
}
}
// Insert data for testing
birthdaysDataset := []birthday{
{
Name: "Marie Smith",
Born: time.Date(1956, time.August, 5, 0, 0, 0, 0, defaultTimeLocation),
},
{
Name: "Peter",
Born: time.Date(1967, time.July, 23, 0, 0, 0, 0, defaultTimeLocation),
},
{
Name: "Eve Smith",
Born: time.Date(1911, time.February, 8, 0, 0, 0, 0, defaultTimeLocation),
},
{
Name: "Alex López",
Born: time.Date(2001, time.May, 5, 0, 0, 0, 0, defaultTimeLocation),
},
{
Name: "Rose Smith",
Born: time.Date(1944, time.December, 9, 0, 0, 0, 0, defaultTimeLocation),
},
{
Name: "Daria López",
Born: time.Date(1923, time.March, 23, 0, 0, 0, 0, defaultTimeLocation),
},
{
Name: "",
Born: time.Date(1945, time.December, 1, 0, 0, 0, 0, defaultTimeLocation),
},
{
Name: "Colin",
Born: time.Date(2010, time.May, 6, 0, 0, 0, 0, defaultTimeLocation),
},
}
for _, birthday := range birthdaysDataset {
_, err := birthdays.Insert(birthday)
s.NoError(err)
}
// Test: equal
{
var item birthday
err := birthdays.Find(mydb.Cond{
"name": mydb.Eq("Colin"),
}).One(&item)
s.NoError(err)
s.NotNil(item)
s.Equal("Colin", item.Name)
}
// Test: not equal
{
var item birthday
err := birthdays.Find(mydb.Cond{
"name": mydb.NotEq("Colin"),
}).One(&item)
s.NoError(err)
s.NotNil(item)
s.NotEqual("Colin", item.Name)
}
// Test: greater than
{
var items []birthday
ref := time.Date(1967, time.July, 23, 0, 0, 0, 0, defaultTimeLocation)
err := birthdays.Find(mydb.Cond{
"born": mydb.Gt(ref),
}).All(&items)
s.NoError(err)
s.NotZero(len(items))
s.Equal(2, len(items))
for _, item := range items {
s.True(item.Born.After(ref))
}
}
// Test: less than
{
var items []birthday
ref := time.Date(1967, time.July, 23, 0, 0, 0, 0, defaultTimeLocation)
err := birthdays.Find(mydb.Cond{
"born": mydb.Lt(ref),
}).All(&items)
s.NoError(err)
s.NotZero(len(items))
s.Equal(5, len(items))
for _, item := range items {
s.True(item.Born.Before(ref))
}
}
// Test: greater than or equal to
{
var items []birthday
ref := time.Date(1967, time.July, 23, 0, 0, 0, 0, defaultTimeLocation)
err := birthdays.Find(mydb.Cond{
"born": mydb.Gte(ref),
}).All(&items)
s.NoError(err)
s.NotZero(len(items))
s.Equal(3, len(items))
for _, item := range items {
s.True(item.Born.After(ref) || item.Born.Equal(ref))
}
}
// Test: less than or equal to
{
var items []birthday
ref := time.Date(1967, time.July, 23, 0, 0, 0, 0, defaultTimeLocation)
err := birthdays.Find(mydb.Cond{
"born": mydb.Lte(ref),
}).All(&items)
s.NoError(err)
s.NotZero(len(items))
s.Equal(6, len(items))
for _, item := range items {
s.True(item.Born.Before(ref) || item.Born.Equal(ref))
}
}
// Test: between
{
var items []birthday
dateA := time.Date(1911, time.February, 8, 0, 0, 0, 0, defaultTimeLocation)
dateB := time.Date(1967, time.July, 23, 0, 0, 0, 0, defaultTimeLocation)
err := birthdays.Find(mydb.Cond{
"born": mydb.Between(dateA, dateB),
}).All(&items)
s.NoError(err)
s.Equal(6, len(items))
for _, item := range items {
s.True(item.Born.After(dateA) || item.Born.Equal(dateA))
s.True(item.Born.Before(dateB) || item.Born.Equal(dateB))
}
}
// Test: not between
{
var items []birthday
dateA := time.Date(1911, time.February, 8, 0, 0, 0, 0, defaultTimeLocation)
dateB := time.Date(1967, time.July, 23, 0, 0, 0, 0, defaultTimeLocation)
err := birthdays.Find(mydb.Cond{
"born": mydb.NotBetween(dateA, dateB),
}).All(&items)
s.NoError(err)
s.Equal(2, len(items))
for _, item := range items {
s.False(item.Born.Before(dateA) || item.Born.Equal(dateA))
s.False(item.Born.Before(dateB) || item.Born.Equal(dateB))
}
}
// Test: in
{
var items []birthday
names := []interface{}{"Peter", "Eve Smith", "Daria López", "Alex López"}
err := birthdays.Find(mydb.Cond{
"name": mydb.In(names...),
}).All(&items)
s.NoError(err)
s.Equal(4, len(items))
for _, item := range items {
inArray := false
for _, name := range names {
if name == item.Name {
inArray = true
}
}
s.True(inArray)
}
}
// Test: not in
{
var items []birthday
names := []interface{}{"Peter", "Eve Smith", "Daria López", "Alex López"}
err := birthdays.Find(mydb.Cond{
"name": mydb.NotIn(names...),
}).All(&items)
s.NoError(err)
s.Equal(4, len(items))
for _, item := range items {
inArray := false
for _, name := range names {
if name == item.Name {
inArray = true
}
}
s.False(inArray)
}
}
// Test: not in
{
var items []birthday
names := []interface{}{"Peter", "Eve Smith", "Daria López", "Alex López"}
err := birthdays.Find(mydb.Cond{
"name": mydb.NotIn(names...),
}).All(&items)
s.NoError(err)
s.Equal(4, len(items))
for _, item := range items {
inArray := false
for _, name := range names {
if name == item.Name {
inArray = true
}
}
s.False(inArray)
}
}
// Test: is and is not
{
var items []birthday
err := birthdays.Find(mydb.And(
mydb.Cond{"name": mydb.Is(nil)},
mydb.Cond{"name": mydb.IsNot(nil)},
)).All(&items)
s.NoError(err)
s.Equal(0, len(items))
}
// Test: is nil
{
var items []birthday
err := birthdays.Find(mydb.And(
mydb.Cond{"born_ut": mydb.IsNull()},
)).All(&items)
s.NoError(err)
s.Equal(8, len(items))
}
// Test: like and not like
{
var items []birthday
var q mydb.Result
switch s.Adapter() {
case "ql", "mongo":
q = birthdays.Find(mydb.And(
mydb.Cond{"name": mydb.Like(".*ari.*")},
mydb.Cond{"name": mydb.NotLike(".*Smith")},
))
default:
q = birthdays.Find(mydb.And(
mydb.Cond{"name": mydb.Like("%ari%")},
mydb.Cond{"name": mydb.NotLike("%Smith")},
))
}
err := q.All(&items)
s.NoError(err)
s.Equal(1, len(items))
s.Equal("Daria López", items[0].Name)
}
if s.Adapter() != "sqlite" && s.Adapter() != "mssql" {
// Test: regexp
{
var items []birthday
err := birthdays.Find(mydb.And(
mydb.Cond{"name": mydb.RegExp("^[D|C|M]")},
)).OrderBy("name").All(&items)
s.NoError(err)
s.Equal(3, len(items))
s.Equal("Colin", items[0].Name)
s.Equal("Daria López", items[1].Name)
s.Equal("Marie Smith", items[2].Name)
}
// Test: not regexp
{
var items []birthday
names := []string{"Daria López", "Colin", "Marie Smith"}
err := birthdays.Find(mydb.And(
mydb.Cond{"name": mydb.NotRegExp("^[D|C|M]")},
)).OrderBy("name").All(&items)
s.NoError(err)
s.Equal(5, len(items))
for _, item := range items {
for _, name := range names {
s.NotEqual(item.Name, name)
}
}
}
}
// Test: after
{
ref := time.Date(1944, time.December, 9, 0, 0, 0, 0, defaultTimeLocation)
var items []birthday
err := birthdays.Find(mydb.Cond{
"born": mydb.After(ref),
}).All(&items)
s.NoError(err)
s.Equal(5, len(items))
}
// Test: on or after
{
ref := time.Date(1944, time.December, 9, 0, 0, 0, 0, defaultTimeLocation)
var items []birthday
err := birthdays.Find(mydb.Cond{
"born": mydb.OnOrAfter(ref),
}).All(&items)
s.NoError(err)
s.Equal(6, len(items))
}
// Test: before
{
ref := time.Date(1944, time.December, 9, 0, 0, 0, 0, defaultTimeLocation)
var items []birthday
err := birthdays.Find(mydb.Cond{
"born": mydb.Before(ref),
}).All(&items)
s.NoError(err)
s.Equal(2, len(items))
}
// Test: on or before
{
ref := time.Date(1944, time.December, 9, 0, 0, 0, 0, defaultTimeLocation)
var items []birthday
err := birthdays.Find(mydb.Cond{
"born": mydb.OnOrBefore(ref),
}).All(&items)
s.NoError(err)
s.Equal(3, len(items))
}
}