package mysql import ( "database/sql" "database/sql/driver" "fmt" "math/rand" "strconv" "testing" "time" "git.hexq.cn/tiglog/mydb" "git.hexq.cn/tiglog/mydb/internal/testsuite" "github.com/stretchr/testify/suite" ) type int64Compat int64 type uintCompat uint type stringCompat string type uintCompatArray []uintCompat func (u *int64Compat) Scan(src interface{}) error { if src != nil { switch v := src.(type) { case int64: *u = int64Compat((src).(int64)) case []byte: i, err := strconv.ParseInt(string(v), 10, 64) if err != nil { return err } *u = int64Compat(i) default: panic(fmt.Sprintf("expected type %T", src)) } } return nil } type customJSON struct { N string `json:"name"` V float64 `json:"value"` } func (c customJSON) Value() (driver.Value, error) { return JSONValue(c) } func (c *customJSON) Scan(src interface{}) error { return ScanJSON(c, src) } type autoCustomJSON struct { N string `json:"name"` V float64 `json:"value"` *JSONConverter } var ( _ = driver.Valuer(&customJSON{}) _ = sql.Scanner(&customJSON{}) ) type AdapterTests struct { testsuite.Suite } func (s *AdapterTests) SetupSuite() { s.Helper = &Helper{} } func (s *AdapterTests) TestInsertReturningCompositeKey_Issue383() { sess := s.Session() type Admin struct { ID int `db:"ID,omitempty"` Accounts string `db:"Accounts"` LoginPassWord string `db:"LoginPassWord"` Date time.Time `db:"Date"` } dateNow := time.Now() a := Admin{ Accounts: "admin", LoginPassWord: "E10ADC3949BA59ABBE56E057F20F883E", Date: dateNow, } adminCollection := sess.Collection("admin") err := adminCollection.InsertReturning(&a) s.NoError(err) s.NotZero(a.ID) s.NotZero(a.Date) s.Equal("admin", a.Accounts) s.Equal("E10ADC3949BA59ABBE56E057F20F883E", a.LoginPassWord) b := Admin{ Accounts: "admin2", LoginPassWord: "E10ADC3949BA59ABBE56E057F20F883E", Date: dateNow, } err = adminCollection.InsertReturning(&b) s.NoError(err) s.NotZero(b.ID) s.NotZero(b.Date) s.Equal("admin2", b.Accounts) s.Equal("E10ADC3949BA59ABBE56E057F20F883E", a.LoginPassWord) } func (s *AdapterTests) TestIssue469_BadConnection() { var err error sess := s.Session() // Ask the MySQL server to disconnect sessions that remain inactive for more // than 1 second. _, err = sess.SQL().Exec(`SET SESSION wait_timeout=1`) 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 MySQL server to disconnect sessions that // remain inactive for more than 1 second. _, err = sess.SQL().Exec(`SET SESSION wait_timeout=1`) 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 MySQL server to disconnect sessions that // remain inactive for more than 1 second. _, err = sess.SQL().Exec(`SET SESSION wait_timeout=1`) 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 (s *AdapterTests) TestMySQLTypes() { sess := s.Session() type MyType struct { ID int64 `db:"id,omitempty"` JSONMap JSONMap `db:"json_map"` JSONObject JSONMap `db:"json_object"` JSONArray JSONArray `db:"json_array"` CustomJSONObject customJSON `db:"custom_json_object"` AutoCustomJSONObject autoCustomJSON `db:"auto_custom_json_object"` CustomJSONObjectPtr *customJSON `db:"custom_json_object_ptr,omitempty"` AutoCustomJSONObjectPtr *autoCustomJSON `db:"auto_custom_json_object_ptr,omitempty"` AutoCustomJSONObjectArray []autoCustomJSON `db:"auto_custom_json_object_array"` AutoCustomJSONObjectMap map[string]autoCustomJSON `db:"auto_custom_json_object_map"` Int64CompatValueJSONArray []int64Compat `db:"integer_compat_value_json_array"` UIntCompatValueJSONArray uintCompatArray `db:"uinteger_compat_value_json_array"` StringCompatValueJSONArray []stringCompat `db:"string_compat_value_json_array"` } origMyTypeTests := []MyType{ MyType{ Int64CompatValueJSONArray: []int64Compat{1, -2, 3, -4}, UIntCompatValueJSONArray: []uintCompat{1, 2, 3, 4}, StringCompatValueJSONArray: []stringCompat{"a", "b", "", "c"}, }, MyType{ Int64CompatValueJSONArray: []int64Compat(nil), UIntCompatValueJSONArray: []uintCompat(nil), StringCompatValueJSONArray: []stringCompat(nil), }, MyType{ AutoCustomJSONObjectArray: []autoCustomJSON{ autoCustomJSON{ N: "Hello", }, autoCustomJSON{ N: "World", }, }, AutoCustomJSONObjectMap: map[string]autoCustomJSON{ "a": autoCustomJSON{ N: "Hello", }, "b": autoCustomJSON{ N: "World", }, }, JSONArray: JSONArray{float64(1), float64(2), float64(3), float64(4)}, }, MyType{ JSONArray: JSONArray{}, }, MyType{ JSONArray: JSONArray(nil), }, MyType{}, MyType{ CustomJSONObject: customJSON{ N: "Hello", }, AutoCustomJSONObject: autoCustomJSON{ N: "World", }, }, MyType{ CustomJSONObject: customJSON{}, AutoCustomJSONObject: autoCustomJSON{}, }, MyType{ CustomJSONObject: customJSON{ N: "Hello 1", }, AutoCustomJSONObject: autoCustomJSON{ N: "World 2", }, }, MyType{ CustomJSONObjectPtr: nil, AutoCustomJSONObjectPtr: nil, }, MyType{ CustomJSONObjectPtr: &customJSON{}, AutoCustomJSONObjectPtr: &autoCustomJSON{}, }, MyType{ CustomJSONObjectPtr: &customJSON{ N: "Hello 3", }, AutoCustomJSONObjectPtr: &autoCustomJSON{ N: "World 4", }, }, MyType{}, MyType{ CustomJSONObject: customJSON{ V: 4.4, }, }, MyType{ CustomJSONObject: customJSON{}, }, MyType{ CustomJSONObject: customJSON{ N: "Peter", V: 5.56, }, }, } for i := 0; i < 100; i++ { myTypeTests := make([]MyType, len(origMyTypeTests)) perm := rand.Perm(len(origMyTypeTests)) for i, v := range perm { myTypeTests[v] = origMyTypeTests[i] } for i := range myTypeTests { result, err := sess.Collection("my_types").Insert(myTypeTests[i]) s.NoError(err) var actual MyType err = sess.Collection("my_types").Find(result).One(&actual) s.NoError(err) expected := myTypeTests[i] expected.ID = result.ID().(int64) s.Equal(expected, actual) } for i := range myTypeTests { res, err := sess.SQL().InsertInto("my_types").Values(myTypeTests[i]).Exec() s.NoError(err) id, err := res.LastInsertId() s.NoError(err) s.NotEqual(0, id) var actual MyType err = sess.Collection("my_types").Find(id).One(&actual) s.NoError(err) expected := myTypeTests[i] expected.ID = id s.Equal(expected, actual) var actual2 MyType err = sess.SQL().SelectFrom("my_types").Where("id = ?", id).One(&actual2) s.NoError(err) s.Equal(expected, actual2) } inserter := sess.SQL().InsertInto("my_types") for i := range myTypeTests { inserter = inserter.Values(myTypeTests[i]) } _, err := inserter.Exec() s.NoError(err) err = sess.Collection("my_types").Truncate() s.NoError(err) batch := sess.SQL().InsertInto("my_types").Batch(50) go func() { defer batch.Done() for i := range myTypeTests { batch.Values(myTypeTests[i]) } }() err = batch.Wait() s.NoError(err) var values []MyType err = sess.SQL().SelectFrom("my_types").All(&values) s.NoError(err) for i := range values { expected := myTypeTests[i] expected.ID = values[i].ID s.Equal(expected, values[i]) } } } func TestAdapter(t *testing.T) { suite.Run(t, &AdapterTests{}) }