mydb/adapter/postgresql/postgresql_test.go

1405 lines
30 KiB
Go
Raw Permalink Normal View History

2023-09-18 15:15:42 +08:00
package postgresql
import (
"context"
"database/sql"
"database/sql/driver"
"encoding/json"
"fmt"
"math/rand"
"strings"
"sync"
"testing"
"time"
"git.hexq.cn/tiglog/mydb"
"git.hexq.cn/tiglog/mydb/internal/testsuite"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
type customJSONBObjectArray []customJSONB
func (customJSONBObjectArray) ConvertValue(in interface{}) interface {
sql.Scanner
driver.Valuer
} {
return &JSONB{in}
}
type customJSONBObjectMap map[string]customJSONB
func (c customJSONBObjectMap) Value() (driver.Value, error) {
return JSONBValue(c)
}
func (c *customJSONBObjectMap) Scan(src interface{}) error {
return ScanJSONB(c, src)
}
type customJSONB struct {
N string `json:"name"`
V float64 `json:"value"`
*JSONBConverter
}
type int64Compat int64
type uintCompat uint
type stringCompat string
type uint8Compat uint8
type uint8CompatArray []uint8Compat
func (ua uint8CompatArray) Value() (driver.Value, error) {
v := make([]byte, len(ua))
for i := range ua {
v[i] = byte(ua[i])
}
return v, nil
}
func (ua *uint8CompatArray) Scan(src interface{}) error {
decoded := Bytea{}
if err := decoded.Scan(src); err != nil {
return nil
}
if len(decoded) < 1 {
*ua = nil
return nil
}
*ua = make([]uint8Compat, len(decoded))
for i := range decoded {
(*ua)[i] = uint8Compat(decoded[i])
}
return nil
}
type int64CompatArray []int64Compat
func (i64a int64CompatArray) Value() (driver.Value, error) {
v := make(Int64Array, len(i64a))
for i := range i64a {
v[i] = int64(i64a[i])
}
return v.Value()
}
func (i64a *int64CompatArray) Scan(src interface{}) error {
s := Int64Array{}
if err := s.Scan(src); err != nil {
return err
}
dst := make([]int64Compat, len(s))
for i := range s {
dst[i] = int64Compat(s[i])
}
if len(dst) < 1 {
return nil
}
*i64a = dst
return nil
}
type uintCompatArray []uintCompat
type AdapterTests struct {
testsuite.Suite
}
func (s *AdapterTests) SetupSuite() {
s.Helper = &Helper{}
}
func (s *AdapterTests) Test_Issue469_BadConnection() {
sess := s.Session()
// Ask the PostgreSQL server to disconnect sessions that remain inactive for more
// than 1 second.
_, err := sess.SQL().Exec(`SET SESSION idle_in_transaction_session_timeout=1000`)
s.NoError(err)
// Remain inactive for 2 seconds.
time.Sleep(time.Second * 2)
// A query should start a new connection, even if the server disconnected us.
_, err = sess.Collection("artist").Find().Count()
s.NoError(err)
// This is a new session, ask the PostgreSQL server to disconnect sessions that
// remain inactive for more than 1 second.
_, err = sess.SQL().Exec(`SET SESSION idle_in_transaction_session_timeout=1000`)
s.NoError(err)
// Remain inactive for 2 seconds.
time.Sleep(time.Second * 2)
// At this point the server should have disconnected us. Let's try to create
// a transaction anyway.
err = sess.Tx(func(sess mydb.Session) error {
var err error
_, err = sess.Collection("artist").Find().Count()
if err != nil {
return err
}
return nil
})
s.NoError(err)
// This is a new session, ask the PostgreSQL server to disconnect sessions that
// remain inactive for more than 1 second.
_, err = sess.SQL().Exec(`SET SESSION idle_in_transaction_session_timeout=1000`)
s.NoError(err)
err = sess.Tx(func(sess mydb.Session) error {
var err error
// This query should succeed.
_, err = sess.Collection("artist").Find().Count()
if err != nil {
panic(err.Error())
}
// Remain inactive for 2 seconds.
time.Sleep(time.Second * 2)
// This query should fail because the server disconnected us in the middle
// of a transaction.
_, err = sess.Collection("artist").Find().Count()
if err != nil {
return err
}
return nil
})
s.Error(err, "Expecting an error (can't recover from this)")
}
func testPostgreSQLTypes(t *testing.T, sess mydb.Session) {
type PGTypeInline struct {
IntegerArrayPtr *Int64Array `db:"integer_array_ptr,omitempty"`
StringArrayPtr *StringArray `db:"string_array_ptr,omitempty"`
JSONBMapPtr *JSONBMap `db:"jsonb_map_ptr,omitempty"`
}
type PGTypeAutoInline struct {
AutoIntegerArray []int64 `db:"auto_integer_array"`
AutoStringArray []string `db:"auto_string_array"`
AutoJSONBMap map[string]interface{} `db:"auto_jsonb_map"`
AutoJSONBMapString map[string]interface{} `db:"auto_jsonb_map_string"`
AutoJSONBMapInteger map[string]interface{} `db:"auto_jsonb_map_integer"`
}
type PGType struct {
ID int64 `db:"id,omitempty"`
UInt8Value uint8Compat `db:"uint8_value"`
UInt8ValueArray uint8CompatArray `db:"uint8_value_array"`
Int64Value int64Compat `db:"int64_value"`
Int64ValueArray *int64CompatArray `db:"int64_value_array"`
IntegerArray Int64Array `db:"integer_array"`
StringArray StringArray `db:"string_array,stringarray"`
JSONBMap JSONBMap `db:"jsonb_map"`
RawJSONBMap *json.RawMessage `db:"raw_jsonb_map,omitempty"`
RawJSONBText *json.RawMessage `db:"raw_jsonb_text,omitempty"`
PGTypeInline `db:",inline"`
PGTypeAutoInline `db:",inline"`
JSONBObject JSONB `db:"jsonb_object"`
JSONBArray JSONBArray `db:"jsonb_array"`
CustomJSONBObject customJSONB `db:"custom_jsonb_object"`
AutoCustomJSONBObject customJSONB `db:"auto_custom_jsonb_object"`
CustomJSONBObjectPtr *customJSONB `db:"custom_jsonb_object_ptr,omitempty"`
AutoCustomJSONBObjectPtr *customJSONB `db:"auto_custom_jsonb_object_ptr,omitempty"`
AutoCustomJSONBObjectArray *customJSONBObjectArray `db:"auto_custom_jsonb_object_array"`
AutoCustomJSONBObjectMap *customJSONBObjectMap `db:"auto_custom_jsonb_object_map"`
StringValue string `db:"string_value"`
IntegerValue int64 `db:"integer_value"`
VarcharValue string `db:"varchar_value"`
DecimalValue float64 `db:"decimal_value"`
Int64CompatValue int64Compat `db:"integer_compat_value"`
UIntCompatValue uintCompat `db:"uinteger_compat_value"`
StringCompatValue stringCompat `db:"string_compat_value"`
Int64CompatValueJSONBArray JSONBArray `db:"integer_compat_value_jsonb_array"`
UIntCompatValueJSONBArray JSONBArray `db:"uinteger_compat_value_jsonb_array"`
StringCompatValueJSONBArray JSONBArray `db:"string_compat_value_jsonb_array"`
StringValuePtr *string `db:"string_value_ptr,omitempty"`
IntegerValuePtr *int64 `db:"integer_value_ptr,omitempty"`
VarcharValuePtr *string `db:"varchar_value_ptr,omitempty"`
DecimalValuePtr *float64 `db:"decimal_value_ptr,omitempty"`
UUIDValueString *string `db:"uuid_value_string,omitempty"`
}
integerValue := int64(10)
stringValue := string("ten")
decimalValue := float64(10.0)
uuidStringValue := "52356d08-6a16-4839-9224-75f0a547e13c"
integerArrayValue := Int64Array{1, 2, 3, 4}
stringArrayValue := StringArray{"a", "b", "c"}
jsonbMapValue := JSONBMap{"Hello": "World"}
rawJSONBMap := json.RawMessage(`{"foo": "bar"}`)
rawJSONBText := json.RawMessage(`{"age": [{">": "1h"}]}`)
testValue := "Hello world!"
origPgTypeTests := []PGType{
PGType{
UUIDValueString: &uuidStringValue,
},
PGType{
UInt8Value: 7,
UInt8ValueArray: uint8CompatArray{1, 2, 3, 4, 5, 6},
},
PGType{
Int64Value: -1,
Int64ValueArray: &int64CompatArray{1, 2, 3, -4, 5, 6},
},
PGType{
UInt8Value: 1,
UInt8ValueArray: uint8CompatArray{1, 2, 3, 4, 5, 6},
},
PGType{
Int64Value: 1,
Int64ValueArray: &int64CompatArray{7, 7, 7},
},
PGType{
Int64Value: 1,
},
PGType{
Int64Value: 99,
Int64ValueArray: nil,
},
PGType{
Int64CompatValue: -5,
UIntCompatValue: 3,
StringCompatValue: "abc",
},
PGType{
Int64CompatValueJSONBArray: JSONBArray{1.0, -2.0, 3.0, -4.0},
UIntCompatValueJSONBArray: JSONBArray{1.0, 2.0, 3.0, 4.0},
StringCompatValueJSONBArray: JSONBArray{"a", "b", "", "c"},
},
PGType{
Int64CompatValueJSONBArray: JSONBArray(nil),
UIntCompatValueJSONBArray: JSONBArray(nil),
StringCompatValueJSONBArray: JSONBArray(nil),
},
PGType{
IntegerValuePtr: &integerValue,
StringValuePtr: &stringValue,
DecimalValuePtr: &decimalValue,
PGTypeAutoInline: PGTypeAutoInline{
AutoJSONBMapString: map[string]interface{}{"a": "x", "b": "67"},
AutoJSONBMapInteger: map[string]interface{}{"a": 12.0, "b": 13.0},
},
},
PGType{
RawJSONBMap: &rawJSONBMap,
RawJSONBText: &rawJSONBText,
},
PGType{
IntegerValue: integerValue,
StringValue: stringValue,
DecimalValue: decimalValue,
},
PGType{
IntegerArray: []int64{1, 2, 3, 4},
},
PGType{
PGTypeAutoInline: PGTypeAutoInline{
AutoIntegerArray: Int64Array{1, 2, 3, 4},
AutoStringArray: nil,
},
},
PGType{
AutoCustomJSONBObjectArray: &customJSONBObjectArray{
customJSONB{
N: "Hello",
},
customJSONB{
N: "World",
},
},
AutoCustomJSONBObjectMap: &customJSONBObjectMap{
"a": customJSONB{
N: "Hello",
},
"b": customJSONB{
N: "World",
},
},
PGTypeAutoInline: PGTypeAutoInline{
AutoJSONBMap: map[string]interface{}{
"Hello": "world",
"Roses": "red",
},
},
JSONBArray: JSONBArray{float64(1), float64(2), float64(3), float64(4)},
},
PGType{
PGTypeAutoInline: PGTypeAutoInline{
AutoIntegerArray: nil,
},
},
PGType{
PGTypeAutoInline: PGTypeAutoInline{
AutoJSONBMap: map[string]interface{}{},
},
JSONBArray: JSONBArray{},
},
PGType{
PGTypeAutoInline: PGTypeAutoInline{
AutoJSONBMap: map[string]interface{}(nil),
},
JSONBArray: JSONBArray(nil),
},
PGType{
PGTypeAutoInline: PGTypeAutoInline{
AutoStringArray: []string{"aaa", "bbb", "ccc"},
},
},
PGType{
PGTypeAutoInline: PGTypeAutoInline{
AutoStringArray: nil,
},
},
PGType{
PGTypeAutoInline: PGTypeAutoInline{
AutoJSONBMap: map[string]interface{}{"hello": "world!"},
},
},
PGType{
IntegerArray: []int64{1, 2, 3, 4},
StringArray: []string{"a", "boo", "bar"},
},
PGType{
StringValue: stringValue,
DecimalValue: decimalValue,
},
PGType{
IntegerArray: []int64{},
},
PGType{
StringArray: []string{},
},
PGType{
IntegerArray: []int64{},
StringArray: []string{},
},
PGType{},
PGType{
IntegerArray: []int64{1},
StringArray: []string{"a"},
},
PGType{
PGTypeInline: PGTypeInline{
IntegerArrayPtr: &integerArrayValue,
StringArrayPtr: &stringArrayValue,
JSONBMapPtr: &jsonbMapValue,
},
},
PGType{
IntegerArray: []int64{0, 0, 0, 0},
StringValue: testValue,
CustomJSONBObject: customJSONB{
N: "Hello",
},
AutoCustomJSONBObject: customJSONB{
N: "World",
},
StringArray: []string{"", "", "", ``, `""`},
},
PGType{
CustomJSONBObject: customJSONB{},
AutoCustomJSONBObject: customJSONB{},
},
PGType{
CustomJSONBObject: customJSONB{
N: "Hello 1",
},
AutoCustomJSONBObject: customJSONB{
N: "World 2",
},
},
PGType{
CustomJSONBObjectPtr: nil,
AutoCustomJSONBObjectPtr: nil,
},
PGType{
CustomJSONBObjectPtr: &customJSONB{},
AutoCustomJSONBObjectPtr: &customJSONB{},
},
PGType{
CustomJSONBObjectPtr: &customJSONB{
N: "Hello 3",
},
AutoCustomJSONBObjectPtr: &customJSONB{
N: "World 4",
},
},
PGType{
StringValue: testValue,
},
PGType{
IntegerValue: integerValue,
IntegerValuePtr: &integerValue,
CustomJSONBObject: customJSONB{
V: 4.4,
},
},
PGType{
StringArray: []string{"a", "boo", "bar"},
},
PGType{
StringArray: []string{"a", "boo", "bar", `""`},
CustomJSONBObject: customJSONB{},
},
PGType{
IntegerArray: []int64{0},
StringArray: []string{""},
},
PGType{
CustomJSONBObject: customJSONB{
N: "Peter",
V: 5.56,
},
},
}
for i := 0; i < 100; i++ {
pgTypeTests := make([]PGType, len(origPgTypeTests))
perm := rand.Perm(len(origPgTypeTests))
for i, v := range perm {
pgTypeTests[v] = origPgTypeTests[i]
}
for i := range pgTypeTests {
record, err := sess.Collection("pg_types").Insert(pgTypeTests[i])
assert.NoError(t, err)
var actual PGType
err = sess.Collection("pg_types").Find(record.ID()).One(&actual)
assert.NoError(t, err)
expected := pgTypeTests[i]
expected.ID = record.ID().(int64)
assert.Equal(t, expected, actual)
}
for i := range pgTypeTests {
row, err := sess.SQL().InsertInto("pg_types").Values(pgTypeTests[i]).Returning("id").QueryRow()
assert.NoError(t, err)
var id int64
err = row.Scan(&id)
assert.NoError(t, err)
var actual PGType
err = sess.Collection("pg_types").Find(id).One(&actual)
assert.NoError(t, err)
expected := pgTypeTests[i]
expected.ID = id
assert.Equal(t, expected, actual)
var actual2 PGType
err = sess.SQL().SelectFrom("pg_types").Where("id = ?", id).One(&actual2)
assert.NoError(t, err)
assert.Equal(t, expected, actual2)
}
inserter := sess.SQL().InsertInto("pg_types")
for i := range pgTypeTests {
inserter = inserter.Values(pgTypeTests[i])
}
_, err := inserter.Exec()
assert.NoError(t, err)
err = sess.Collection("pg_types").Truncate()
assert.NoError(t, err)
batch := sess.SQL().InsertInto("pg_types").Batch(50)
go func() {
defer batch.Done()
for i := range pgTypeTests {
batch.Values(pgTypeTests[i])
}
}()
err = batch.Wait()
assert.NoError(t, err)
var values []PGType
err = sess.SQL().SelectFrom("pg_types").All(&values)
assert.NoError(t, err)
for i := range values {
expected := pgTypeTests[i]
expected.ID = values[i].ID
assert.Equal(t, expected, values[i])
}
}
}
func (s *AdapterTests) TestOptionTypes() {
sess := s.Session()
optionTypes := sess.Collection("option_types")
err := optionTypes.Truncate()
s.NoError(err)
// TODO: lets do some benchmarking on these auto-wrapped option types..
// TODO: add nullable jsonb field mapped to a []string
// A struct with wrapped option types defined in the struct tags
// for postgres string array and jsonb types
type optionType struct {
ID int64 `db:"id,omitempty"`
Name string `db:"name"`
Tags []string `db:"tags"`
Settings map[string]interface{} `db:"settings"`
}
// Item 1
item1 := optionType{
Name: "Food",
Tags: []string{"toronto", "pizza"},
Settings: map[string]interface{}{"a": 1, "b": 2},
}
record, err := optionTypes.Insert(item1)
s.NoError(err)
if pk, ok := record.ID().(int64); !ok || pk == 0 {
s.T().Errorf("Expecting an ID.")
}
var item1Chk optionType
err = optionTypes.Find(record).One(&item1Chk)
s.NoError(err)
s.Equal(float64(1), item1Chk.Settings["a"])
s.Equal("toronto", item1Chk.Tags[0])
// Item 1 B
item1b := &optionType{
Name: "Golang",
Tags: []string{"love", "it"},
Settings: map[string]interface{}{"go": 1, "lang": 2},
}
record, err = optionTypes.Insert(item1b)
s.NoError(err)
if pk, ok := record.ID().(int64); !ok || pk == 0 {
s.T().Errorf("Expecting an ID.")
}
var item1bChk optionType
err = optionTypes.Find(record).One(&item1bChk)
s.NoError(err)
s.Equal(float64(1), item1bChk.Settings["go"])
s.Equal("love", item1bChk.Tags[0])
// Item 1 C
item1c := &optionType{
Name: "Sup", Tags: []string{}, Settings: map[string]interface{}{},
}
record, err = optionTypes.Insert(item1c)
s.NoError(err)
if pk, ok := record.ID().(int64); !ok || pk == 0 {
s.T().Errorf("Expecting an ID.")
}
var item1cChk optionType
err = optionTypes.Find(record).One(&item1cChk)
s.NoError(err)
s.Zero(len(item1cChk.Tags))
s.Zero(len(item1cChk.Settings))
// An option type to pointer jsonb field
type optionType2 struct {
ID int64 `db:"id,omitempty"`
Name string `db:"name"`
Tags StringArray `db:"tags"`
Settings *JSONBMap `db:"settings"`
}
item2 := optionType2{
Name: "JS", Tags: []string{"hi", "bye"}, Settings: nil,
}
record, err = optionTypes.Insert(item2)
s.NoError(err)
if pk, ok := record.ID().(int64); !ok || pk == 0 {
s.T().Errorf("Expecting an ID.")
}
var item2Chk optionType2
res := optionTypes.Find(record)
err = res.One(&item2Chk)
s.NoError(err)
s.Equal(record.ID().(int64), item2Chk.ID)
s.Equal(item2Chk.Name, item2.Name)
s.Equal(item2Chk.Tags[0], item2.Tags[0])
s.Equal(len(item2Chk.Tags), len(item2.Tags))
// Update the value
m := JSONBMap{}
m["lang"] = "javascript"
m["num"] = 31337
item2.Settings = &m
err = res.Update(item2)
s.NoError(err)
err = res.One(&item2Chk)
s.NoError(err)
s.Equal(float64(31337), (*item2Chk.Settings)["num"].(float64))
s.Equal("javascript", (*item2Chk.Settings)["lang"])
// An option type to pointer string array field
type optionType3 struct {
ID int64 `db:"id,omitempty"`
Name string `db:"name"`
Tags *StringArray `db:"tags"`
Settings JSONBMap `db:"settings"`
}
item3 := optionType3{
Name: "Julia",
Tags: nil,
Settings: JSONBMap{"girl": true, "lang": true},
}
record, err = optionTypes.Insert(item3)
s.NoError(err)
if pk, ok := record.ID().(int64); !ok || pk == 0 {
s.T().Errorf("Expecting an ID.")
}
var item3Chk optionType2
err = optionTypes.Find(record).One(&item3Chk)
s.NoError(err)
}
type Settings struct {
Name string `json:"name"`
Num int64 `json:"num"`
}
func (s *Settings) Scan(src interface{}) error {
return ScanJSONB(s, src)
}
func (s Settings) Value() (driver.Value, error) {
return JSONBValue(s)
}
func (s *AdapterTests) TestOptionTypeJsonbStruct() {
sess := s.Session()
optionTypes := sess.Collection("option_types")
err := optionTypes.Truncate()
s.NoError(err)
type OptionType struct {
ID int64 `db:"id,omitempty"`
Name string `db:"name"`
Tags StringArray `db:"tags"`
Settings Settings `db:"settings"`
}
item1 := &OptionType{
Name: "Hi",
Tags: []string{"aah", "ok"},
Settings: Settings{Name: "a", Num: 123},
}
record, err := optionTypes.Insert(item1)
s.NoError(err)
if pk, ok := record.ID().(int64); !ok || pk == 0 {
s.T().Errorf("Expecting an ID.")
}
var item1Chk OptionType
err = optionTypes.Find(record).One(&item1Chk)
s.NoError(err)
s.Equal(2, len(item1Chk.Tags))
s.Equal("aah", item1Chk.Tags[0])
s.Equal("a", item1Chk.Settings.Name)
s.Equal(int64(123), item1Chk.Settings.Num)
}
func (s *AdapterTests) TestSchemaCollection() {
sess := s.Session()
col := sess.Collection("test_schema.test")
_, err := col.Insert(map[string]int{"id": 9})
s.Equal(nil, err)
var dump []map[string]int
err = col.Find().All(&dump)
s.Nil(err)
s.Equal(1, len(dump))
s.Equal(9, dump[0]["id"])
}
func (s *AdapterTests) Test_Issue340_MaxOpenConns() {
sess := s.Session()
sess.SetMaxOpenConns(5)
var wg sync.WaitGroup
for i := 0; i < 30; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
_, err := sess.SQL().Exec(fmt.Sprintf(`SELECT pg_sleep(1.%d)`, i))
if err != nil {
s.T().Errorf("%v", err)
}
}(i)
}
wg.Wait()
sess.SetMaxOpenConns(0)
}
func (s *AdapterTests) Test_Issue370_InsertUUID() {
sess := s.Session()
{
type itemT struct {
ID *uuid.UUID `db:"id"`
Name string `db:"name"`
}
newUUID := uuid.New()
item1 := itemT{
ID: &newUUID,
Name: "Jonny",
}
col := sess.Collection("issue_370")
err := col.Truncate()
s.NoError(err)
err = col.InsertReturning(&item1)
s.NoError(err)
var item2 itemT
err = col.Find(item1.ID).One(&item2)
s.NoError(err)
s.Equal(item1.Name, item2.Name)
var item3 itemT
err = col.Find(mydb.Cond{"id": item1.ID}).One(&item3)
s.NoError(err)
s.Equal(item1.Name, item3.Name)
}
{
type itemT struct {
ID uuid.UUID `db:"id"`
Name string `db:"name"`
}
item1 := itemT{
ID: uuid.New(),
Name: "Jonny",
}
col := sess.Collection("issue_370")
err := col.Truncate()
s.NoError(err)
err = col.InsertReturning(&item1)
s.NoError(err)
var item2 itemT
err = col.Find(item1.ID).One(&item2)
s.NoError(err)
s.Equal(item1.Name, item2.Name)
var item3 itemT
err = col.Find(mydb.Cond{"id": item1.ID}).One(&item3)
s.NoError(err)
s.Equal(item1.Name, item3.Name)
}
{
type itemT struct {
ID Int64Array `db:"id"`
Name string `db:"name"`
}
item1 := itemT{
ID: Int64Array{1, 2, 3},
Name: "Vojtech",
}
col := sess.Collection("issue_370_2")
err := col.Truncate()
s.NoError(err)
err = col.InsertReturning(&item1)
s.NoError(err)
var item2 itemT
err = col.Find(item1.ID).One(&item2)
s.NoError(err)
s.Equal(item1.Name, item2.Name)
var item3 itemT
err = col.Find(mydb.Cond{"id": item1.ID}).One(&item3)
s.NoError(err)
s.Equal(item1.Name, item3.Name)
}
}
type issue602Organization struct {
ID string `json:"id" db:"id,omitempty"`
Name string `json:"name" db:"name"`
CreatedAt time.Time `json:"created_at,omitempty" db:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty" db:"updated_at,omitempty"`
}
type issue602OrganizationStore struct {
mydb.Store
}
func (r *issue602Organization) BeforeUpdate(mydb.Session) error {
return nil
}
func (r *issue602Organization) Store(sess mydb.Session) mydb.Store {
return issue602OrganizationStore{sess.Collection("issue_602_organizations")}
}
var _ interface {
mydb.Record
mydb.BeforeUpdateHook
} = &issue602Organization{}
func (s *AdapterTests) Test_Issue602_IncorrectBinaryFormat() {
settingsWithBinaryMode := ConnectionURL{
Database: settings.Database,
User: settings.User,
Password: settings.Password,
Host: settings.Host,
Options: map[string]string{
"timezone": testsuite.TimeZone,
//"binary_parameters": "yes",
},
}
sess, err := Open(settingsWithBinaryMode)
if err != nil {
s.T().Errorf("%v", err)
}
{
item := issue602Organization{
Name: "Jonny",
}
col := sess.Collection("issue_602_organizations")
err := col.Truncate()
s.NoError(err)
err = sess.Save(&item)
s.NoError(err)
}
{
item := issue602Organization{
Name: "Jonny",
}
col := sess.Collection("issue_602_organizations")
err := col.Truncate()
s.NoError(err)
err = col.InsertReturning(&item)
s.NoError(err)
}
{
newUUID := uuid.New()
item := issue602Organization{
ID: newUUID.String(),
Name: "Jonny",
}
col := sess.Collection("issue_602_organizations")
err := col.Truncate()
s.NoError(err)
id, err := col.Insert(item)
s.NoError(err)
s.NotZero(id)
}
{
item := issue602Organization{
Name: "Jonny",
}
col := sess.Collection("issue_602_organizations")
err := col.Truncate()
s.NoError(err)
err = sess.Save(&item)
s.NoError(err)
}
}
func (s *AdapterTests) TestInsertVarcharPrimaryKey() {
sess := s.Session()
{
type itemT struct {
Address string `db:"address"`
Name string `db:"name"`
}
item1 := itemT{
Address: "1234",
Name: "Jonny",
}
col := sess.Collection("varchar_primary_key")
err := col.Truncate()
s.NoError(err)
err = col.InsertReturning(&item1)
s.NoError(err)
var item2 itemT
err = col.Find(mydb.Cond{"address": item1.Address}).One(&item2)
s.NoError(err)
s.Equal(item1.Name, item2.Name)
var item3 itemT
err = col.Find(mydb.Cond{"address": item1.Address}).One(&item3)
s.NoError(err)
s.Equal(item1.Name, item3.Name)
}
}
func (s *AdapterTests) Test_Issue409_TxOptions() {
sess := s.Session()
{
err := sess.TxContext(context.Background(), func(tx mydb.Session) error {
col := tx.Collection("publication")
row := map[string]interface{}{
"title": "foo",
"author_id": 1,
}
err := col.InsertReturning(&row)
s.Error(err)
return err
}, &sql.TxOptions{
ReadOnly: true,
})
s.Error(err)
s.True(strings.Contains(err.Error(), "read-only transaction"))
}
}
func (s *AdapterTests) TestEscapeQuestionMark() {
sess := s.Session()
var val bool
{
res, err := sess.SQL().QueryRow(`SELECT '{"mykey":["val1", "val2"]}'::jsonb->'mykey' ?? ?`, "val2")
s.NoError(err)
err = res.Scan(&val)
s.NoError(err)
s.Equal(true, val)
}
{
res, err := sess.SQL().QueryRow(`SELECT ?::jsonb->'mykey' ?? ?`, `{"mykey":["val1", "val2"]}`, `val2`)
s.NoError(err)
err = res.Scan(&val)
s.NoError(err)
s.Equal(true, val)
}
{
res, err := sess.SQL().QueryRow(`SELECT ?::jsonb->? ?? ?`, `{"mykey":["val1", "val2"]}`, `mykey`, `val2`)
s.NoError(err)
err = res.Scan(&val)
s.NoError(err)
s.Equal(true, val)
}
}
func (s *AdapterTests) Test_Issue391_TextMode() {
testPostgreSQLTypes(s.T(), s.Session())
}
func (s *AdapterTests) Test_Issue391_BinaryMode() {
settingsWithBinaryMode := ConnectionURL{
Database: settings.Database,
User: settings.User,
Password: settings.Password,
Host: settings.Host,
Options: map[string]string{
"timezone": testsuite.TimeZone,
//"binary_parameters": "yes",
},
}
sess, err := Open(settingsWithBinaryMode)
if err != nil {
s.T().Errorf("%v", err)
}
defer sess.Close()
testPostgreSQLTypes(s.T(), sess)
}
func (s *AdapterTests) TestStringAndInt64Array() {
sess := s.Session()
driver := sess.Driver().(*sql.DB)
defer func() {
_, _ = driver.Exec(`DROP TABLE IF EXISTS array_types`)
}()
if _, err := driver.Exec(`
CREATE TABLE array_types (
id serial primary key,
integers bigint[] DEFAULT NULL,
strings varchar(64)[]
)`); err != nil {
s.NoError(err)
}
arrayTypes := sess.Collection("array_types")
err := arrayTypes.Truncate()
s.NoError(err)
type arrayType struct {
ID int64 `db:"id,pk"`
Integers Int64Array `db:"integers"`
Strings StringArray `db:"strings"`
}
tt := []arrayType{
// Test nil arrays.
arrayType{
ID: 1,
Integers: nil,
Strings: nil,
},
// Test empty arrays.
arrayType{
ID: 2,
Integers: []int64{},
Strings: []string{},
},
// Test non-empty arrays.
arrayType{
ID: 3,
Integers: []int64{1, 2, 3},
Strings: []string{"1", "2", "3"},
},
}
for _, item := range tt {
record, err := arrayTypes.Insert(item)
s.NoError(err)
if pk, ok := record.ID().(int64); !ok || pk == 0 {
s.T().Errorf("Expecting an ID.")
}
var itemCheck arrayType
err = arrayTypes.Find(record).One(&itemCheck)
s.NoError(err)
s.Len(itemCheck.Integers, len(item.Integers))
s.Len(itemCheck.Strings, len(item.Strings))
s.Equal(item, itemCheck)
}
}
func (s *AdapterTests) Test_Issue210() {
list := []string{
`DROP TABLE IF EXISTS testing123`,
`DROP TABLE IF EXISTS hello`,
`CREATE TABLE IF NOT EXISTS testing123 (
ID INT PRIMARY KEY NOT NULL,
NAME TEXT NOT NULL
)
`,
`CREATE TABLE IF NOT EXISTS hello (
ID INT PRIMARY KEY NOT NULL,
NAME TEXT NOT NULL
)`,
}
sess := s.Session()
err := sess.Tx(func(tx mydb.Session) error {
for i := range list {
_, err := tx.SQL().Exec(list[i])
s.NoError(err)
if err != nil {
return err
}
}
return nil
})
s.NoError(err)
_, err = sess.Collection("testing123").Find().Count()
s.NoError(err)
_, err = sess.Collection("hello").Find().Count()
s.NoError(err)
}
func (s *AdapterTests) TestPreparedStatements() {
sess := s.Session()
var val int
{
stmt, err := sess.SQL().Prepare(`SELECT 1`)
s.NoError(err)
s.NotNil(stmt)
q, err := stmt.Query()
s.NoError(err)
s.NotNil(q)
s.True(q.Next())
err = q.Scan(&val)
s.NoError(err)
err = q.Close()
s.NoError(err)
s.Equal(1, val)
err = stmt.Close()
s.NoError(err)
}
{
err := sess.Tx(func(tx mydb.Session) error {
stmt, err := tx.SQL().Prepare(`SELECT 2`)
s.NoError(err)
s.NotNil(stmt)
q, err := stmt.Query()
s.NoError(err)
s.NotNil(q)
s.True(q.Next())
err = q.Scan(&val)
s.NoError(err)
err = q.Close()
s.NoError(err)
s.Equal(2, val)
err = stmt.Close()
s.NoError(err)
return nil
})
s.NoError(err)
}
{
stmt, err := sess.SQL().Select(3).Prepare()
s.NoError(err)
s.NotNil(stmt)
q, err := stmt.Query()
s.NoError(err)
s.NotNil(q)
s.True(q.Next())
err = q.Scan(&val)
s.NoError(err)
err = q.Close()
s.NoError(err)
s.Equal(3, val)
err = stmt.Close()
s.NoError(err)
}
}
func (s *AdapterTests) TestNonTrivialSubqueries() {
sess := s.Session()
// Creating test data
artist := sess.Collection("artist")
artistNames := []string{"Ozzie", "Flea", "Slash", "Chrono"}
for _, artistName := range artistNames {
_, err := artist.Insert(map[string]string{
"name": artistName,
})
s.NoError(err)
}
{
q, err := sess.SQL().Query(`WITH test AS (?) ?`,
sess.SQL().Select("id AS foo").From("artist"),
sess.SQL().Select("foo").From("test").Where("foo > ?", 0),
)
s.NoError(err)
s.NotNil(q)
s.True(q.Next())
var number int
s.NoError(q.Scan(&number))
s.Equal(1, number)
s.NoError(q.Close())
}
{
builder := sess.SQL()
row, err := builder.QueryRow(`WITH test AS (?) ?`,
builder.Select("id AS foo").From("artist"),
builder.Select("foo").From("test").Where("foo > ?", 0),
)
s.NoError(err)
s.NotNil(row)
var number int
s.NoError(row.Scan(&number))
s.Equal(1, number)
}
{
res, err := sess.SQL().Exec(
`UPDATE artist a1 SET id = ?`,
sess.SQL().Select(mydb.Raw("id + 5")).
From("artist a2").
Where("a2.id = a1.id"),
)
s.NoError(err)
s.NotNil(res)
}
{
builder := sess.SQL()
q, err := builder.Query(mydb.Raw(`WITH test AS (?) ?`,
builder.Select("id AS foo").From("artist"),
builder.Select("foo").From("test").Where("foo > ?", 0).OrderBy("foo"),
))
s.NoError(err)
s.NotNil(q)
s.True(q.Next())
var number int
s.NoError(q.Scan(&number))
s.Equal(6, number)
s.NoError(q.Close())
}
{
res, err := sess.SQL().Exec(mydb.Raw(`UPDATE artist a1 SET id = ?`,
sess.SQL().Select(mydb.Raw("id + 7")).From("artist a2").Where("a2.id = a1.id"),
))
s.NoError(err)
s.NotNil(res)
}
}
func (s *AdapterTests) Test_Issue601_ErrorCarrying() {
var items []interface{}
var err error
sess := s.Session()
_, err = sess.SQL().Exec(`DROP TABLE IF EXISTS issue_601`)
s.NoError(err)
err = sess.Collection("issue_601").Find().All(&items)
s.Error(err)
_, err = sess.SQL().Exec(`CREATE TABLE issue_601 (id INTEGER PRIMARY KEY)`)
s.NoError(err)
err = sess.Collection("issue_601").Find().All(&items)
s.NoError(err)
}
func TestAdapter(t *testing.T) {
suite.Run(t, &AdapterTests{})
}