db/db_test.go

546 lines
13 KiB
Go

package db_test
import (
"os"
"testing"
"git.bit5.ru/backend/colog"
"git.bit5.ru/backend/db"
"git.bit5.ru/backend/errors"
"github.com/stretchr/testify/assert"
)
var settings = db.Settings{Host: "127.0.0.1", Port: "3306", User: "root", Pass: "test", Name: "tests"}
var logger = colog.NewCoLog(os.Stderr, "", 0)
func getDBC() *db.DBC {
dbc := db.GetDBC(logger, settings)
return dbc
}
func TestDefaultClientCharsetAndCollation(t *testing.T) {
dbc := getDBC()
defer dbc.Close()
var result = make(map[string]string)
characterSets, err := dbc.DB().Query("show variables where Variable_name in ('character_set_client', 'character_set_connection', 'character_set_results');")
assert.Nil(t, err)
for characterSets.Next() {
var variableName string
var value string
characterSets.Scan(&variableName, &value)
result[variableName] = value
}
collations, err := dbc.DB().Query("show variables where Variable_name = 'collation_connection';")
assert.Nil(t, err)
for collations.Next() {
var variableName string
var value string
collations.Scan(&variableName, &value)
result[variableName] = value
}
assert.Equal(t, "utf8", result["character_set_client"])
assert.Equal(t, "utf8", result["character_set_connection"])
assert.Equal(t, "utf8", result["character_set_results"])
assert.Equal(t, "utf8_general_ci", result["collation_connection"])
}
func TestClientCharsetAndCollation(t *testing.T) {
DSNWithLatinCollation := settings
DSNWithLatinCollation.Params = "?collation=latin1_swedish_ci"
var resultsLatin1 = make(map[string]string)
dbLatin1 := db.GetDBC(logger, DSNWithLatinCollation)
defer dbLatin1.Close()
characterSets, err := dbLatin1.DB().Query("show variables where Variable_name in ('character_set_client', 'character_set_connection', 'character_set_results');")
assert.Nil(t, err)
for characterSets.Next() {
var variableName string
var value string
characterSets.Scan(&variableName, &value)
resultsLatin1[variableName] = value
}
collations, err := dbLatin1.DB().Query("show variables where Variable_name = 'collation_connection';")
assert.Nil(t, err)
for collations.Next() {
var variableName string
var value string
collations.Scan(&variableName, &value)
resultsLatin1[variableName] = value
}
assert.Equal(t, "latin1", resultsLatin1["character_set_client"])
assert.Equal(t, "latin1", resultsLatin1["character_set_connection"])
assert.Equal(t, "latin1", resultsLatin1["character_set_results"])
assert.Equal(t, "latin1_swedish_ci", resultsLatin1["collation_connection"])
}
func TestCloseConn(t *testing.T) {
dbc := getDBC()
defer dbc.Close()
var res int
err := dbc.SelectBySQL("SELECT 1").LoadValue(&res)
assert.Nil(t, err)
assert.EqualValues(t, 1, res)
assert.True(t, dbc.IsOpen())
dbc.Close()
assert.False(t, dbc.IsOpen())
//connection is automatically restored
err = dbc.SelectBySQL("SELECT 1").LoadValue(&res)
assert.True(t, dbc.IsOpen())
assert.Nil(t, err)
assert.EqualValues(t, 1, res)
}
func TestDoubleCloseConnIsOk(t *testing.T) {
dbc := getDBC()
defer dbc.Close()
var res int
err := dbc.SelectBySQL("SELECT 1").LoadValue(&res)
assert.Nil(t, err)
assert.EqualValues(t, 1, res)
assert.True(t, dbc.IsOpen())
dbc.Close()
assert.False(t, dbc.IsOpen())
//connection is automatically restored
err = dbc.SelectBySQL("SELECT 1").LoadValue(&res)
assert.True(t, dbc.IsOpen())
assert.Nil(t, err)
assert.EqualValues(t, 1, res)
dbc.Close()
assert.False(t, dbc.IsOpen())
//connection is automatically restored
err = dbc.SelectBySQL("SELECT 1").LoadValue(&res)
assert.True(t, dbc.IsOpen())
assert.Nil(t, err)
assert.EqualValues(t, 1, res)
}
func createFooTable(t *testing.T) {
dbc := getDBC()
defer dbc.Close()
_, err := dbc.DB().Exec("DROP TABLE IF EXISTS foo")
assert.Nil(t, err)
_, err = dbc.DB().Exec("CREATE TABLE IF NOT EXISTS foo(id int not null)")
assert.Nil(t, err)
}
func TestTransactionCommit(t *testing.T) {
createFooTable(t)
dbc := getDBC()
defer dbc.Close()
assert.Nil(t, dbc.Begin())
dbc.UpdateBySQL("INSERT INTO foo(id) VALUES(1),(2)").Exec()
//let's try a fresh connection
dbc1 := getDBC()
defer dbc1.Close()
assert.EqualValues(t, 0, countFoos(t, dbc1))
assert.EqualValues(t, 2, countFoos(t, dbc))
dbc.Commit()
assert.EqualValues(t, 2, countFoos(t, dbc))
//let's try a fresh connection
dbc2 := getDBC()
defer dbc2.Close()
assert.EqualValues(t, 2, countFoos(t, dbc2))
}
func TestTransactionCommitNestedAllOk(t *testing.T) {
createFooTable(t)
dbc := getDBC()
defer dbc.Close()
//begin 1
assert.Nil(t, dbc.Begin())
dbc.UpdateBySQL("INSERT INTO foo(id) VALUES(1),(2)").Exec()
//let's try a fresh connection
dbc1 := getDBC()
defer dbc1.Close()
assert.EqualValues(t, 0, countFoos(t, dbc1))
//begin 2
assert.Nil(t, dbc.Begin())
dbc.UpdateBySQL("INSERT INTO foo(id) VALUES(3)").Exec()
//commit 2
dbc.Commit()
assert.EqualValues(t, 3, countFoos(t, dbc))
//let's try a fresh connection
dbc2 := getDBC()
defer dbc2.Close()
assert.EqualValues(t, 0, countFoos(t, dbc2))
//commit 1
dbc.Commit()
assert.EqualValues(t, 3, countFoos(t, dbc))
//let's try a fresh connection
db3 := getDBC()
defer db3.Close()
assert.EqualValues(t, 3, countFoos(t, db3))
}
func TestTransactionCommitNestedRollback(t *testing.T) {
createFooTable(t)
dbc := getDBC()
defer dbc.Close()
//begin 1
assert.Nil(t, dbc.Begin())
dbc.UpdateBySQL("INSERT INTO foo(id) VALUES(1),(2)").Exec()
//let's try a fresh connection
dbc1 := getDBC()
defer dbc1.Close()
assert.EqualValues(t, 0, countFoos(t, dbc1))
//begin 2
assert.Nil(t, dbc.Begin())
dbc.UpdateBySQL("INSERT INTO foo(id) VALUES(3)").Exec()
//rollback 2
dbc.Rollback()
//rollback above doesn't have an effect since we are in the
//nested transaction
assert.EqualValues(t, 3, countFoos(t, dbc))
//let's try a fresh connection
dbc2 := getDBC()
defer dbc2.Close()
assert.EqualValues(t, 0, countFoos(t, dbc2))
//rollback 1
dbc.Rollback()
assert.EqualValues(t, 0, countFoos(t, dbc))
//let's try a fresh connection
db3 := getDBC()
defer db3.Close()
assert.EqualValues(t, 0, countFoos(t, db3))
}
func TestTransactionRollbackOnDeferAllOK(t *testing.T) {
createFooTable(t)
dbc := getDBC()
defer dbc.Close()
fn := func() {
dbc.Begin()
defer dbc.RollbackOnDefer()
dbc.UpdateBySQL("INSERT INTO foo(id) VALUES(1),(2)").Exec()
assert.EqualValues(t, 2, countFoos(t, dbc))
fnNested := func() {
dbc.Begin()
defer dbc.RollbackOnDefer()
dbc.UpdateBySQL("INSERT INTO foo(id) VALUES(3)").Exec()
assert.EqualValues(t, 3, countFoos(t, dbc))
dbc.Commit()
}
fnNested()
dbc.Commit()
}
fn()
dbc1 := getDBC()
defer dbc1.Close()
assert.EqualValues(t, 3, countFoos(t, dbc1))
}
func TestTransactionRollbackOnDefer(t *testing.T) {
createFooTable(t)
dbc := getDBC()
defer dbc.Close()
fn := func() {
dbc.Begin()
defer dbc.RollbackOnDefer()
dbc.UpdateBySQL("INSERT INTO foo(id) VALUES(1),(2)").Exec()
assert.EqualValues(t, 2, countFoos(t, dbc))
fnNested := func() {
dbc.Begin()
defer dbc.RollbackOnDefer()
dbc.UpdateBySQL("INSERT INTO foo(id) VALUES(3)").Exec()
assert.EqualValues(t, 3, countFoos(t, dbc))
//commit is missing for some reason, emulating error
//dbc.Commit()
}
fnNested()
//commit is missing for some reason, emulating error
//dbc.Commit()
}
fn()
dbc1 := getDBC()
defer dbc1.Close()
assert.EqualValues(t, 0, countFoos(t, dbc1))
}
func TestTransaction(t *testing.T) {
createFooTable(t)
dbc := getDBC()
defer dbc.Close()
assert.Nil(t, dbc.Transaction(func(dbs *db.DBC) error {
dbs.UpdateBySQL("INSERT INTO foo(id) VALUES(1),(2)").Exec()
//let's try a fresh connection
dbc1 := getDBC()
defer dbc1.Close()
assert.EqualValues(t, 0, countFoos(t, dbc1))
assert.EqualValues(t, 2, countFoos(t, dbc))
return nil
}))
//let's try a fresh connection
dbc2 := getDBC()
defer dbc2.Close()
assert.EqualValues(t, 2, countFoos(t, dbc2))
}
func TestTransactionRollbackOnError(t *testing.T) {
createFooTable(t)
dbc := getDBC()
defer dbc.Close()
err := dbc.Transaction(func(dbs *db.DBC) error {
dbs.UpdateBySQL("INSERT INTO foo(id) VALUES(1),(2)").Exec()
assert.EqualValues(t, 2, countFoos(t, dbc))
return errors.New("Opps")
})
assert.EqualValues(t, "Opps", err.Error())
//let's try a fresh connection
dbc2 := getDBC()
defer dbc2.Close()
assert.EqualValues(t, 0, countFoos(t, dbc2))
}
func TestTransactionRollbackOnPanic(t *testing.T) {
createFooTable(t)
dbc := getDBC()
defer dbc.Close()
defer func() {
if r := recover(); r != nil {
str := r.(string)
assert.EqualValues(t, str, "Ooops")
//let's try a fresh connection
dbc2 := getDBC()
defer dbc2.Close()
assert.EqualValues(t, 0, countFoos(t, dbc2))
}
}()
dbc.Transaction(func(dbs *db.DBC) error {
dbs.UpdateBySQL("INSERT INTO foo(id) VALUES(1),(2)").Exec()
assert.EqualValues(t, 2, countFoos(t, dbc))
panic("Ooops")
return nil
})
}
func countFoos(t *testing.T, dbc *db.DBC) int {
var res int
err := dbc.SelectBySQL("SELECT COUNT(id) FROM foo").LoadValue(&res)
assert.Nil(t, err)
return res
}
func TestTransactionRollback(t *testing.T) {
createFooTable(t)
dbc := getDBC()
defer dbc.Close()
assert.Nil(t, dbc.Begin())
{
_, err := dbc.UpdateBySQL("INSERT INTO foo(id) VALUES(1),(2)").Exec()
assert.Nil(t, err)
}
var res int
{
err := dbc.SelectBySQL("SELECT COUNT(id) FROM foo").LoadValue(&res)
assert.Nil(t, err)
assert.EqualValues(t, 2, res)
}
dbc.Rollback()
{
err := dbc.SelectBySQL("SELECT COUNT(id) FROM foo").LoadValue(&res)
assert.Nil(t, err)
assert.EqualValues(t, 0, res)
}
//let's try a fresh connection
dbc2 := getDBC()
defer dbc2.Close()
{
err := dbc2.SelectBySQL("SELECT COUNT(id) FROM foo").LoadValue(&res)
assert.Nil(t, err)
assert.EqualValues(t, 0, res)
}
}
func TestTransactionNestedRollback(t *testing.T) {
createFooTable(t)
dbc := getDBC()
defer dbc.Close()
//begin 1
assert.Nil(t, dbc.Begin())
{
_, err := dbc.UpdateBySQL("INSERT INTO foo(id) VALUES(1)").Exec()
assert.Nil(t, err)
}
var res int
{
err := dbc.SelectBySQL("SELECT COUNT(id) FROM foo").LoadValue(&res)
assert.Nil(t, err)
assert.EqualValues(t, 1, res)
}
//begin 2
assert.Nil(t, dbc.Begin())
{
_, err := dbc.UpdateBySQL("INSERT INTO foo(id) VALUES(1)").Exec()
assert.Nil(t, err)
}
//no real rollback happens here since we are in a 'bigger' transaction
//rollback 2
dbc.Rollback()
{
err := dbc.SelectBySQL("SELECT COUNT(id) FROM foo").LoadValue(&res)
assert.Nil(t, err)
assert.EqualValues(t, 2, res)
}
//rollback 1
dbc.Rollback()
{
err := dbc.SelectBySQL("SELECT COUNT(id) FROM foo").LoadValue(&res)
assert.Nil(t, err)
assert.EqualValues(t, 0, res)
}
}
//func makePlayers(t *testing.T, env *env.Env, amount int) []uint32 {
// var players []uint32
// mainDb := env.MainDb()
//
// for i := 0; i < amount; i++ {
// shardPlayer, err := dbshrd.CreateShardPlayer(env.Settings.DB_SHARDS, mainDb, 1)
// require.NoError(t, err)
// players = append(players, shardPlayer.Id)
// }
//
// //Note: There is only one test shard db, so the shard id = 1 for all players
// shardDb, err := dbshrd.GetShardDb(env.Logger, env.Settings.DB_SHARDS, 1)
// require.NoError(t, err)
// require.NotNil(t, shardDb)
// defer shardDb.Close()
//
// for i := 0; i < len(players); i++ {
// player := autogen.NewDataPlayer()
// player.Id = players[i]
// err = dbmeta.SaveRow(shardDb, player)
// require.NoError(t, err)
// }
//
// return players
//}
//func TestSelectBySQLWithChunkedIN(t *testing.T) {
// env := tests.NewEnvCleanStorage()
// defer env.Close()
//
// //Note: There is only one test shard db, so the shard id = 1 for all players
// shardDb, err := dbshrd.GetShardDb(env.Logger, env.Settings.DB_SHARDS, 1)
// require.NoError(t, err)
// require.NotNil(t, shardDb)
// defer shardDb.Close()
//
// var playersAmount = 100
// var playerIds []uint32
// playerIds = makePlayers(t, env, playersAmount)
//
// for chunkSizeForIN := 1; chunkSizeForIN <= 101; chunkSizeForIN++ {
// var totalIds []uint32
// fullChunksAmount := playersAmount / chunkSizeForIN
// modulo := playersAmount % chunkSizeForIN
// sql := "SELECT id FROM player WHERE 1 = ? AND id IN ? AND 2 = ?"
// builders := shardDb.SelectBySQLWithChunkedIN(sql, chunkSizeForIN, 1, playerIds, 2)
//
// require.Len(t, builders, fullChunksAmount+util.BoolToInt(modulo > 0))
//
// for queryIndex, builder := range builders {
// var queryIds []uint32
// _, err := builder.LoadValues(&queryIds)
// require.NoError(t, err)
// require.NotNil(t, queryIds)
// totalIds = append(totalIds, queryIds...)
//
// if queryIndex < fullChunksAmount {
// require.Len(t, queryIds, chunkSizeForIN)
// } else {
// require.Len(t, queryIds, modulo)
// }
// }
//
// require.Len(t, totalIds, playersAmount)
// }
//}