fork github.com/doug-martin

This commit is contained in:
2025-03-22 23:02:05 +08:00
commit f14642a736
131 changed files with 34555 additions and 0 deletions
+87
View File
@@ -0,0 +1,87 @@
package mysql
import (
"git.fsdpf.net/go/db/v2"
"git.fsdpf.net/go/db/v2/exp"
)
func DialectOptions() *db.SQLDialectOptions {
opts := db.DefaultDialectOptions()
opts.SupportsReturn = false
opts.SupportsOrderByOnUpdate = true
opts.SupportsLimitOnUpdate = true
opts.SupportsLimitOnDelete = true
opts.SupportsOrderByOnDelete = true
opts.SupportsConflictUpdateWhere = false
opts.SupportsInsertIgnoreSyntax = true
opts.SupportsConflictTarget = false
opts.SupportsWithCTE = false
opts.SupportsWithCTERecursive = false
opts.SupportsDistinctOn = false
opts.SupportsWindowFunction = false
opts.SupportsDeleteTableHint = true
opts.UseFromClauseForMultipleUpdateTables = false
opts.PlaceHolderFragment = []byte("?")
opts.IncludePlaceholderNum = false
opts.QuoteRune = '`'
opts.DefaultValuesFragment = []byte("")
opts.True = []byte("1")
opts.False = []byte("0")
opts.TimeFormat = "2006-01-02 15:04:05"
opts.BooleanOperatorLookup = map[exp.BooleanOperation][]byte{
exp.EqOp: []byte("="),
exp.NeqOp: []byte("!="),
exp.GtOp: []byte(">"),
exp.GteOp: []byte(">="),
exp.LtOp: []byte("<"),
exp.LteOp: []byte("<="),
exp.InOp: []byte("IN"),
exp.NotInOp: []byte("NOT IN"),
exp.IsOp: []byte("IS"),
exp.IsNotOp: []byte("IS NOT"),
exp.LikeOp: []byte("LIKE BINARY"),
exp.NotLikeOp: []byte("NOT LIKE BINARY"),
exp.ILikeOp: []byte("LIKE"),
exp.NotILikeOp: []byte("NOT LIKE"),
exp.RegexpLikeOp: []byte("REGEXP BINARY"),
exp.RegexpNotLikeOp: []byte("NOT REGEXP BINARY"),
exp.RegexpILikeOp: []byte("REGEXP"),
exp.RegexpNotILikeOp: []byte("NOT REGEXP"),
}
opts.BitwiseOperatorLookup = map[exp.BitwiseOperation][]byte{
exp.BitwiseInversionOp: []byte("~"),
exp.BitwiseOrOp: []byte("|"),
exp.BitwiseAndOp: []byte("&"),
exp.BitwiseXorOp: []byte("^"),
exp.BitwiseLeftShiftOp: []byte("<<"),
exp.BitwiseRightShiftOp: []byte(">>"),
}
opts.EscapedRunes = map[rune][]byte{
'\'': []byte("\\'"),
'"': []byte("\\\""),
'\\': []byte("\\\\"),
'\n': []byte("\\n"),
'\r': []byte("\\r"),
0: []byte("\\x00"),
0x1a: []byte("\\x1a"),
}
opts.InsertIgnoreClause = []byte("INSERT IGNORE INTO")
opts.ConflictFragment = []byte("")
opts.ConflictDoUpdateFragment = []byte(" ON DUPLICATE KEY UPDATE ")
opts.ConflictDoNothingFragment = []byte("")
return opts
}
func DialectOptionsV8() *db.SQLDialectOptions {
opts := DialectOptions()
opts.SupportsWindowFunction = true
return opts
}
func init() {
db.RegisterDialect("mysql", DialectOptions())
db.RegisterDialect("mysql8", DialectOptionsV8())
}
+143
View File
@@ -0,0 +1,143 @@
package mysql_test
import (
"regexp"
"testing"
dbv2 "git.fsdpf.net/go/db/v2"
"git.fsdpf.net/go/db/v2/exp"
"github.com/stretchr/testify/suite"
)
type (
mysqlDialectSuite struct {
suite.Suite
}
sqlTestCase struct {
ds exp.SQLExpression
sql string
err string
isPrepared bool
args []interface{}
}
)
func (mds *mysqlDialectSuite) GetDs(table string) *dbv2.SelectDataset {
return dbv2.Dialect("mysql").From(table)
}
func (mds *mysqlDialectSuite) assertSQL(cases ...sqlTestCase) {
for i, c := range cases {
actualSQL, actualArgs, err := c.ds.ToSQL()
if c.err == "" {
mds.NoError(err, "test case %d failed", i)
} else {
mds.EqualError(err, c.err, "test case %d failed", i)
}
mds.Equal(c.sql, actualSQL, "test case %d failed", i)
if c.isPrepared && c.args != nil || len(c.args) > 0 {
mds.Equal(c.args, actualArgs, "test case %d failed", i)
} else {
mds.Empty(actualArgs, "test case %d failed", i)
}
}
}
func (mds *mysqlDialectSuite) TestIdentifiers() {
ds := mds.GetDs("test")
mds.assertSQL(
sqlTestCase{ds: ds.Select(
"a",
dbv2.I("a.b.c"),
dbv2.I("c.d"),
dbv2.C("test").As("test"),
), sql: "SELECT `a`, `a`.`b`.`c`, `c`.`d`, `test` AS `test` FROM `test`"},
)
}
func (mds *mysqlDialectSuite) TestLiteralString() {
ds := mds.GetDs("test")
col := dbv2.C("a")
mds.assertSQL(
sqlTestCase{ds: ds.Where(col.Eq("test")), sql: "SELECT * FROM `test` WHERE (`a` = 'test')"},
sqlTestCase{ds: ds.Where(col.Eq("test'test")), sql: "SELECT * FROM `test` WHERE (`a` = 'test\\'test')"},
sqlTestCase{ds: ds.Where(col.Eq(`test"test`)), sql: "SELECT * FROM `test` WHERE (`a` = 'test\\\"test')"},
sqlTestCase{ds: ds.Where(col.Eq(`test\test`)), sql: "SELECT * FROM `test` WHERE (`a` = 'test\\\\test')"},
sqlTestCase{ds: ds.Where(col.Eq("test\ntest")), sql: "SELECT * FROM `test` WHERE (`a` = 'test\\ntest')"},
sqlTestCase{ds: ds.Where(col.Eq("test\rtest")), sql: "SELECT * FROM `test` WHERE (`a` = 'test\\rtest')"},
sqlTestCase{ds: ds.Where(col.Eq("test\x00test")), sql: "SELECT * FROM `test` WHERE (`a` = 'test\\x00test')"},
sqlTestCase{ds: ds.Where(col.Eq("test\x1atest")), sql: "SELECT * FROM `test` WHERE (`a` = 'test\\x1atest')"},
)
}
func (mds *mysqlDialectSuite) TestLiteralBytes() {
col := dbv2.C("a")
ds := mds.GetDs("test")
mds.assertSQL(
sqlTestCase{ds: ds.Where(col.Eq([]byte("test"))), sql: "SELECT * FROM `test` WHERE (`a` = 'test')"},
sqlTestCase{ds: ds.Where(col.Eq([]byte("test'test"))), sql: "SELECT * FROM `test` WHERE (`a` = 'test\\'test')"},
sqlTestCase{ds: ds.Where(col.Eq([]byte(`test"test`))), sql: "SELECT * FROM `test` WHERE (`a` = 'test\\\"test')"},
sqlTestCase{ds: ds.Where(col.Eq([]byte(`test\test`))), sql: "SELECT * FROM `test` WHERE (`a` = 'test\\\\test')"},
sqlTestCase{ds: ds.Where(col.Eq([]byte("test\ntest"))), sql: "SELECT * FROM `test` WHERE (`a` = 'test\\ntest')"},
sqlTestCase{ds: ds.Where(col.Eq([]byte("test\rtest"))), sql: "SELECT * FROM `test` WHERE (`a` = 'test\\rtest')"},
sqlTestCase{ds: ds.Where(col.Eq([]byte("test\x00test"))), sql: "SELECT * FROM `test` WHERE (`a` = 'test\\x00test')"},
sqlTestCase{ds: ds.Where(col.Eq([]byte("test\x1atest"))), sql: "SELECT * FROM `test` WHERE (`a` = 'test\\x1atest')"},
)
}
func (mds *mysqlDialectSuite) TestBooleanOperations() {
col := dbv2.C("a")
ds := mds.GetDs("test")
mds.assertSQL(
sqlTestCase{ds: ds.Where(col.Eq(true)), sql: "SELECT * FROM `test` WHERE (`a` IS TRUE)"},
sqlTestCase{ds: ds.Where(col.Eq(false)), sql: "SELECT * FROM `test` WHERE (`a` IS FALSE)"},
sqlTestCase{ds: ds.Where(col.Is(true)), sql: "SELECT * FROM `test` WHERE (`a` IS TRUE)"},
sqlTestCase{ds: ds.Where(col.Is(false)), sql: "SELECT * FROM `test` WHERE (`a` IS FALSE)"},
sqlTestCase{ds: ds.Where(col.IsTrue()), sql: "SELECT * FROM `test` WHERE (`a` IS TRUE)"},
sqlTestCase{ds: ds.Where(col.IsFalse()), sql: "SELECT * FROM `test` WHERE (`a` IS FALSE)"},
sqlTestCase{ds: ds.Where(col.Neq(true)), sql: "SELECT * FROM `test` WHERE (`a` IS NOT TRUE)"},
sqlTestCase{ds: ds.Where(col.Neq(false)), sql: "SELECT * FROM `test` WHERE (`a` IS NOT FALSE)"},
sqlTestCase{ds: ds.Where(col.IsNot(true)), sql: "SELECT * FROM `test` WHERE (`a` IS NOT TRUE)"},
sqlTestCase{ds: ds.Where(col.IsNot(false)), sql: "SELECT * FROM `test` WHERE (`a` IS NOT FALSE)"},
sqlTestCase{ds: ds.Where(col.IsNotTrue()), sql: "SELECT * FROM `test` WHERE (`a` IS NOT TRUE)"},
sqlTestCase{ds: ds.Where(col.IsNotFalse()), sql: "SELECT * FROM `test` WHERE (`a` IS NOT FALSE)"},
sqlTestCase{ds: ds.Where(col.Like("a%")), sql: "SELECT * FROM `test` WHERE (`a` LIKE BINARY 'a%')"},
sqlTestCase{ds: ds.Where(col.NotLike("a%")), sql: "SELECT * FROM `test` WHERE (`a` NOT LIKE BINARY 'a%')"},
sqlTestCase{ds: ds.Where(col.ILike("a%")), sql: "SELECT * FROM `test` WHERE (`a` LIKE 'a%')"},
sqlTestCase{ds: ds.Where(col.NotILike("a%")), sql: "SELECT * FROM `test` WHERE (`a` NOT LIKE 'a%')"},
sqlTestCase{ds: ds.Where(col.Like(regexp.MustCompile("[ab]"))), sql: "SELECT * FROM `test` WHERE (`a` REGEXP BINARY '[ab]')"},
sqlTestCase{ds: ds.Where(col.NotLike(regexp.MustCompile("[ab]"))), sql: "SELECT * FROM `test` WHERE (`a` NOT REGEXP BINARY '[ab]')"},
sqlTestCase{ds: ds.Where(col.ILike(regexp.MustCompile("[ab]"))), sql: "SELECT * FROM `test` WHERE (`a` REGEXP '[ab]')"},
sqlTestCase{ds: ds.Where(col.NotILike(regexp.MustCompile("[ab]"))), sql: "SELECT * FROM `test` WHERE (`a` NOT REGEXP '[ab]')"},
)
}
func (mds *mysqlDialectSuite) TestBitwiseOperations() {
col := dbv2.C("a")
ds := mds.GetDs("test")
mds.assertSQL(
sqlTestCase{ds: ds.Where(col.BitwiseInversion()), sql: "SELECT * FROM `test` WHERE (~ `a`)"},
sqlTestCase{ds: ds.Where(col.BitwiseAnd(1)), sql: "SELECT * FROM `test` WHERE (`a` & 1)"},
sqlTestCase{ds: ds.Where(col.BitwiseOr(1)), sql: "SELECT * FROM `test` WHERE (`a` | 1)"},
sqlTestCase{ds: ds.Where(col.BitwiseXor(1)), sql: "SELECT * FROM `test` WHERE (`a` ^ 1)"},
sqlTestCase{ds: ds.Where(col.BitwiseLeftShift(1)), sql: "SELECT * FROM `test` WHERE (`a` << 1)"},
sqlTestCase{ds: ds.Where(col.BitwiseRightShift(1)), sql: "SELECT * FROM `test` WHERE (`a` >> 1)"},
)
}
func (mds *mysqlDialectSuite) TestUpdateSQL() {
ds := mds.GetDs("test").Update()
mds.assertSQL(
sqlTestCase{
ds: ds.
Set(dbv2.Record{"foo": "bar"}).
From("test_2").
Where(dbv2.I("test.id").Eq(dbv2.I("test_2.test_id"))),
sql: "UPDATE `test`,`test_2` SET `foo`='bar' WHERE (`test`.`id` = `test_2`.`test_id`)",
},
)
}
func TestDatasetAdapterSuite(t *testing.T) {
suite.Run(t, new(mysqlDialectSuite))
}
+509
View File
@@ -0,0 +1,509 @@
package mysql_test
import (
"database/sql"
"fmt"
"os"
"strconv"
"strings"
"testing"
"time"
dbv2 "git.fsdpf.net/go/db/v2"
"git.fsdpf.net/go/db/v2/dialect/mysql"
_ "github.com/go-sql-driver/mysql"
"github.com/stretchr/testify/suite"
)
const (
dropTable = "DROP TABLE IF EXISTS `entry`;"
createTable = "CREATE TABLE `entry` (" +
"`id` INT NOT NULL AUTO_INCREMENT ," +
"`int` INT NOT NULL UNIQUE," +
"`float` FLOAT NOT NULL ," +
"`string` VARCHAR(255) NOT NULL ," +
"`time` DATETIME NOT NULL ," +
"`bool` TINYINT NOT NULL ," +
"`bytes` BLOB NOT NULL ," +
"PRIMARY KEY (`id`) );"
insertDefaultReords = "INSERT INTO `entry` (`int`, `float`, `string`, `time`, `bool`, `bytes`) VALUES" +
"(0, 0.000000, '0.000000', '2015-02-22 18:19:55', TRUE, '0.000000')," +
"(1, 0.100000, '0.100000', '2015-02-22 19:19:55', FALSE, '0.100000')," +
"(2, 0.200000, '0.200000', '2015-02-22 20:19:55', TRUE, '0.200000')," +
"(3, 0.300000, '0.300000', '2015-02-22 21:19:55', FALSE, '0.300000')," +
"(4, 0.400000, '0.400000', '2015-02-22 22:19:55', TRUE, '0.400000')," +
"(5, 0.500000, '0.500000', '2015-02-22 23:19:55', FALSE, '0.500000')," +
"(6, 0.600000, '0.600000', '2015-02-23 00:19:55', TRUE, '0.600000')," +
"(7, 0.700000, '0.700000', '2015-02-23 01:19:55', FALSE, '0.700000')," +
"(8, 0.800000, '0.800000', '2015-02-23 02:19:55', TRUE, '0.800000')," +
"(9, 0.900000, '0.900000', '2015-02-23 03:19:55', FALSE, '0.900000');"
)
const defaultDBURI = "root@/dbv2mysql?parseTime=true"
type (
mysqlTest struct {
suite.Suite
db *dbv2.Database
}
entry struct {
ID uint32 `db:"id" ff:"skipinsert,skipupdate"`
Int int `db:"int"`
Float float64 `db:"float"`
String string `db:"string"`
Time time.Time `db:"time"`
Bool bool `db:"bool"`
Bytes []byte `db:"bytes"`
}
entryTestCase struct {
ds *dbv2.SelectDataset
len int
check func(entry entry, index int)
err string
}
)
func (mt *mysqlTest) SetupSuite() {
dbURI := os.Getenv("MYSQL_URI")
if dbURI == "" {
dbURI = defaultDBURI
}
db, err := sql.Open("mysql", dbURI)
if err != nil {
panic(err.Error())
}
mt.db = dbv2.New("mysql", db)
}
func (mt *mysqlTest) assertSQL(cases ...sqlTestCase) {
for i, c := range cases {
actualSQL, actualArgs, err := c.ds.ToSQL()
if c.err == "" {
mt.NoError(err, "test case %d failed", i)
} else {
mt.EqualError(err, c.err, "test case %d failed", i)
}
mt.Equal(c.sql, actualSQL, "test case %d failed", i)
if c.isPrepared && c.args != nil || len(c.args) > 0 {
mt.Equal(c.args, actualArgs, "test case %d failed", i)
} else {
mt.Empty(actualArgs, "test case %d failed", i)
}
}
}
func (mt *mysqlTest) assertEntries(cases ...entryTestCase) {
for i, c := range cases {
var entries []entry
err := c.ds.ScanStructs(&entries)
if c.err == "" {
mt.NoError(err, "test case %d failed", i)
} else {
mt.EqualError(err, c.err, "test case %d failed", i)
}
mt.Len(entries, c.len)
for index, entry := range entries {
c.check(entry, index)
}
}
}
func (mt *mysqlTest) SetupTest() {
if _, err := mt.db.Exec(dropTable); err != nil {
panic(err)
}
if _, err := mt.db.Exec(createTable); err != nil {
panic(err)
}
if _, err := mt.db.Exec(insertDefaultReords); err != nil {
panic(err)
}
}
func (mt *mysqlTest) TestToSQL() {
ds := mt.db.From("entry")
mt.assertSQL(
sqlTestCase{ds: ds.Select("id", "float", "string", "time", "bool"), sql: "SELECT `id`, `float`, `string`, `time`, `bool` FROM `entry`"},
sqlTestCase{ds: ds.Where(dbv2.C("int").Eq(10)), sql: "SELECT * FROM `entry` WHERE (`int` = 10)"},
sqlTestCase{
ds: ds.Prepared(true).Where(dbv2.L("? = ?", dbv2.C("int"), 10)),
sql: "SELECT * FROM `entry` WHERE `int` = ?", args: []interface{}{int64(10)},
},
)
}
func (mt *mysqlTest) TestQuery() {
ds := mt.db.From("entry")
floatVal := float64(0)
baseDate, err := time.Parse(
"2006-01-02 15:04:05",
"2015-02-22 18:19:55",
)
mt.NoError(err)
mt.assertEntries(
entryTestCase{ds: ds.Order(dbv2.C("id").Asc()), len: 10, check: func(entry entry, index int) {
f := fmt.Sprintf("%f", floatVal)
mt.Equal(uint32(index+1), entry.ID)
mt.Equal(index, entry.Int)
mt.Equal(f, fmt.Sprintf("%f", entry.Float))
mt.Equal(f, entry.String)
mt.Equal([]byte(f), entry.Bytes)
mt.Equal(index%2 == 0, entry.Bool)
mt.Equal(baseDate.Add(time.Duration(index)*time.Hour).Unix(), entry.Time.Unix())
floatVal += float64(0.1)
}},
entryTestCase{ds: ds.Where(dbv2.C("bool").IsTrue()).Order(dbv2.C("id").Asc()), len: 5, check: func(entry entry, _ int) {
mt.True(entry.Bool)
}},
entryTestCase{ds: ds.Where(dbv2.C("int").Gt(4)).Order(dbv2.C("id").Asc()), len: 5, check: func(entry entry, _ int) {
mt.True(entry.Int > 4)
}},
entryTestCase{ds: ds.Where(dbv2.C("int").Gte(5)).Order(dbv2.C("id").Asc()), len: 5, check: func(entry entry, _ int) {
mt.True(entry.Int >= 5)
}},
entryTestCase{ds: ds.Where(dbv2.C("int").Lt(5)).Order(dbv2.C("id").Asc()), len: 5, check: func(entry entry, _ int) {
mt.True(entry.Int < 5)
}},
entryTestCase{ds: ds.Where(dbv2.C("int").Lte(4)).Order(dbv2.C("id").Asc()), len: 5, check: func(entry entry, _ int) {
mt.True(entry.Int <= 4)
}},
entryTestCase{ds: ds.Where(dbv2.C("int").Between(dbv2.Range(3, 6))).Order(dbv2.C("id").Asc()), len: 4, check: func(entry entry, _ int) {
mt.True(entry.Int >= 3)
mt.True(entry.Int <= 6)
}},
entryTestCase{ds: ds.Where(dbv2.C("string").Eq("0.100000")).Order(dbv2.C("id").Asc()), len: 1, check: func(entry entry, _ int) {
mt.Equal(entry.String, "0.100000")
}},
entryTestCase{ds: ds.Where(dbv2.C("string").Like("0.1%")).Order(dbv2.C("id").Asc()), len: 1, check: func(entry entry, _ int) {
mt.Equal(entry.String, "0.100000")
}},
entryTestCase{ds: ds.Where(dbv2.C("string").NotLike("0.1%")).Order(dbv2.C("id").Asc()), len: 9, check: func(entry entry, _ int) {
mt.NotEqual(entry.String, "0.100000")
}},
entryTestCase{ds: ds.Where(dbv2.C("string").IsNull()).Order(dbv2.C("id").Asc()), len: 0, check: func(entry entry, _ int) {
mt.Fail("Should not have returned any records")
}},
)
}
func (mt *mysqlTest) TestQuery_Prepared() {
ds := mt.db.From("entry").Prepared(true)
floatVal := float64(0)
baseDate, err := time.Parse(
"2006-01-02 15:04:05",
"2015-02-22 18:19:55",
)
mt.NoError(err)
mt.assertEntries(
entryTestCase{ds: ds.Order(dbv2.C("id").Asc()), len: 10, check: func(entry entry, index int) {
f := fmt.Sprintf("%f", floatVal)
mt.Equal(uint32(index+1), entry.ID)
mt.Equal(index, entry.Int)
mt.Equal(f, fmt.Sprintf("%f", entry.Float))
mt.Equal(f, entry.String)
mt.Equal([]byte(f), entry.Bytes)
mt.Equal(index%2 == 0, entry.Bool)
mt.Equal(baseDate.Add(time.Duration(index)*time.Hour).Unix(), entry.Time.Unix())
floatVal += float64(0.1)
}},
entryTestCase{ds: ds.Where(dbv2.C("bool").IsTrue()).Order(dbv2.C("id").Asc()), len: 5, check: func(entry entry, _ int) {
mt.True(entry.Bool)
}},
entryTestCase{ds: ds.Where(dbv2.C("int").Gt(4)).Order(dbv2.C("id").Asc()), len: 5, check: func(entry entry, _ int) {
mt.True(entry.Int > 4)
}},
entryTestCase{ds: ds.Where(dbv2.C("int").Gte(5)).Order(dbv2.C("id").Asc()), len: 5, check: func(entry entry, _ int) {
mt.True(entry.Int >= 5)
}},
entryTestCase{ds: ds.Where(dbv2.C("int").Lt(5)).Order(dbv2.C("id").Asc()), len: 5, check: func(entry entry, _ int) {
mt.True(entry.Int < 5)
}},
entryTestCase{ds: ds.Where(dbv2.C("int").Lte(4)).Order(dbv2.C("id").Asc()), len: 5, check: func(entry entry, _ int) {
mt.True(entry.Int <= 4)
}},
entryTestCase{ds: ds.Where(dbv2.C("int").Between(dbv2.Range(3, 6))).Order(dbv2.C("id").Asc()), len: 4, check: func(entry entry, _ int) {
mt.True(entry.Int >= 3)
mt.True(entry.Int <= 6)
}},
entryTestCase{ds: ds.Where(dbv2.C("string").Eq("0.100000")).Order(dbv2.C("id").Asc()), len: 1, check: func(entry entry, _ int) {
mt.Equal(entry.String, "0.100000")
}},
entryTestCase{ds: ds.Where(dbv2.C("string").Like("0.1%")).Order(dbv2.C("id").Asc()), len: 1, check: func(entry entry, _ int) {
mt.Equal(entry.String, "0.100000")
}},
entryTestCase{ds: ds.Where(dbv2.C("string").NotLike("0.1%")).Order(dbv2.C("id").Asc()), len: 9, check: func(entry entry, _ int) {
mt.NotEqual(entry.String, "0.100000")
}},
entryTestCase{ds: ds.Where(dbv2.C("string").IsNull()).Order(dbv2.C("id").Asc()), len: 0, check: func(entry entry, _ int) {
mt.Fail("Should not have returned any records")
}},
)
}
func (mt *mysqlTest) TestQuery_ValueExpressions() {
type wrappedEntry struct {
entry
BoolValue bool `db:"bool_value"`
}
expectedDate, err := time.Parse("2006-01-02 15:04:05", "2015-02-22 19:19:55")
mt.NoError(err)
ds := mt.db.From("entry").Select(dbv2.Star(), dbv2.V(true).As("bool_value")).Where(dbv2.Ex{"int": 1})
var we wrappedEntry
found, err := ds.ScanStruct(&we)
mt.NoError(err)
mt.True(found)
mt.Equal(wrappedEntry{
entry{2, 1, 0.100000, "0.100000", expectedDate, false, []byte("0.100000")},
true,
}, we)
}
func (mt *mysqlTest) TestCount() {
ds := mt.db.From("entry")
count, err := ds.Count()
mt.NoError(err)
mt.Equal(int64(10), count)
count, err = ds.Where(dbv2.C("int").Gt(4)).Count()
mt.NoError(err)
mt.Equal(int64(5), count)
count, err = ds.Where(dbv2.C("int").Gte(4)).Count()
mt.NoError(err)
mt.Equal(int64(6), count)
count, err = ds.Where(dbv2.C("string").Like("0.1%")).Count()
mt.NoError(err)
mt.Equal(int64(1), count)
count, err = ds.Where(dbv2.C("string").IsNull()).Count()
mt.NoError(err)
mt.Equal(int64(0), count)
}
func (mt *mysqlTest) TestInsert() {
ds := mt.db.From("entry")
now := time.Now()
e := entry{Int: 10, Float: 1.000000, String: "1.000000", Time: now, Bool: true, Bytes: []byte("1.000000")}
_, err := ds.Insert().Rows(e).Executor().Exec()
mt.NoError(err)
var insertedEntry entry
found, err := ds.Where(dbv2.C("int").Eq(10)).ScanStruct(&insertedEntry)
mt.NoError(err)
mt.True(found)
mt.True(insertedEntry.ID > 0)
entries := []entry{
{Int: 11, Float: 1.100000, String: "1.100000", Time: now, Bool: false, Bytes: []byte("1.100000")},
{Int: 12, Float: 1.200000, String: "1.200000", Time: now, Bool: true, Bytes: []byte("1.200000")},
{Int: 13, Float: 1.300000, String: "1.300000", Time: now, Bool: false, Bytes: []byte("1.300000")},
{Int: 14, Float: 1.400000, String: "1.400000", Time: now, Bool: true, Bytes: []byte("1.400000")},
}
_, err = ds.Insert().Rows(entries).Executor().Exec()
mt.NoError(err)
var newEntries []entry
mt.NoError(ds.Where(dbv2.C("int").In([]uint32{11, 12, 13, 14})).ScanStructs(&newEntries))
mt.Len(newEntries, 4)
for i, e := range newEntries {
mt.Equal(entries[i].Int, e.Int)
mt.Equal(entries[i].Float, e.Float)
mt.Equal(entries[i].String, e.String)
mt.Equal(entries[i].Time.UTC().Format(mysql.DialectOptions().TimeFormat), e.Time.Format(mysql.DialectOptions().TimeFormat))
mt.Equal(entries[i].Bool, e.Bool)
mt.Equal(entries[i].Bytes, e.Bytes)
}
_, err = ds.Insert().Rows(
entry{Int: 15, Float: 1.500000, String: "1.500000", Time: now, Bool: false, Bytes: []byte("1.500000")},
entry{Int: 16, Float: 1.600000, String: "1.600000", Time: now, Bool: true, Bytes: []byte("1.600000")},
entry{Int: 17, Float: 1.700000, String: "1.700000", Time: now, Bool: false, Bytes: []byte("1.700000")},
entry{Int: 18, Float: 1.800000, String: "1.800000", Time: now, Bool: true, Bytes: []byte("1.800000")},
).Executor().Exec()
mt.NoError(err)
newEntries = newEntries[0:0]
mt.NoError(ds.Where(dbv2.C("int").In([]uint32{15, 16, 17, 18})).ScanStructs(&newEntries))
mt.Len(newEntries, 4)
}
func (mt *mysqlTest) TestInsertReturning() {
ds := mt.db.From("entry")
now := time.Now()
e := entry{Int: 10, Float: 1.000000, String: "1.000000", Time: now, Bool: true, Bytes: []byte("1.000000")}
_, err := ds.Insert().Rows(e).Returning(dbv2.Star()).Executor().ScanStruct(&e)
mt.Error(err)
}
func (mt *mysqlTest) TestUpdate() {
ds := mt.db.From("entry")
var e entry
found, err := ds.Where(dbv2.C("int").Eq(9)).Select("id").ScanStruct(&e)
mt.NoError(err)
mt.True(found)
e.Int = 11
_, err = ds.Where(dbv2.C("id").Eq(e.ID)).Update().Set(e).Executor().Exec()
mt.NoError(err)
count, err := ds.Where(dbv2.C("int").Eq(11)).Count()
mt.NoError(err)
mt.Equal(int64(1), count)
}
func (mt *mysqlTest) TestUpdateReturning() {
ds := mt.db.From("entry")
var id uint32
_, err := ds.Where(dbv2.C("int").Eq(11)).
Update().
Set(dbv2.Record{"int": 9}).
Returning("id").
Executor().ScanVal(&id)
mt.Error(err)
mt.EqualError(err, "dbv2: dialect does not support RETURNING clause [dialect=mysql]")
}
func (mt *mysqlTest) TestDelete() {
ds := mt.db.From("entry")
var e entry
found, err := ds.Where(dbv2.C("int").Eq(9)).Select("id").ScanStruct(&e)
mt.NoError(err)
mt.True(found)
_, err = ds.Where(dbv2.C("id").Eq(e.ID)).Delete().Executor().Exec()
mt.NoError(err)
count, err := ds.Count()
mt.NoError(err)
mt.Equal(int64(9), count)
var id uint32
found, err = ds.Where(dbv2.C("id").Eq(e.ID)).ScanVal(&id)
mt.NoError(err)
mt.False(found)
e = entry{}
found, err = ds.Where(dbv2.C("int").Eq(8)).Select("id").ScanStruct(&e)
mt.NoError(err)
mt.True(found)
mt.NotEqual(0, e.ID)
id = 0
_, err = ds.Where(dbv2.C("id").Eq(e.ID)).Delete().Returning("id").Executor().ScanVal(&id)
mt.EqualError(err, "dbv2: dialect does not support RETURNING clause [dialect=mysql]")
}
func (mt *mysqlTest) TestInsertIgnore() {
ds := mt.db.From("entry")
now := time.Now()
// insert one
entries := []entry{
{Int: 8, Float: 6.100000, String: "6.100000", Time: now, Bytes: []byte("6.100000")},
{Int: 9, Float: 7.200000, String: "7.200000", Time: now, Bytes: []byte("7.200000")},
{Int: 10, Float: 7.200000, String: "7.200000", Time: now, Bytes: []byte("7.200000")},
}
_, err := ds.Insert().Rows(entries).OnConflict(dbv2.DoNothing()).Executor().Exec()
mt.NoError(err)
count, err := ds.Count()
mt.NoError(err)
mt.Equal(count, int64(11))
}
func (mt *mysqlTest) TestInsert_OnConflict() {
ds := mt.db.From("entry")
now := time.Now()
// insert
e := entry{Int: 10, Float: 1.100000, String: "1.100000", Time: now, Bool: false, Bytes: []byte("1.100000")}
_, err := ds.Insert().Rows(e).OnConflict(dbv2.DoNothing()).Executor().Exec()
mt.NoError(err)
// duplicate
e = entry{Int: 10, Float: 2.100000, String: "2.100000", Time: now.Add(time.Hour * 100), Bool: false, Bytes: []byte("2.100000")}
_, err = ds.Insert().Rows(e).OnConflict(dbv2.DoNothing()).Executor().Exec()
mt.NoError(err)
// update
var entryActual entry
e2 := entry{Int: 10, String: "2.000000"}
_, err = ds.Insert().
Rows(e2).
OnConflict(dbv2.DoUpdate("int", dbv2.Record{"string": "upsert"})).
Executor().Exec()
mt.NoError(err)
_, err = ds.Where(dbv2.C("int").Eq(10)).ScanStruct(&entryActual)
mt.NoError(err)
mt.Equal("upsert", entryActual.String)
// update where should error
entries := []entry{
{Int: 8, Float: 6.100000, String: "6.100000", Time: now, Bytes: []byte("6.100000")},
{Int: 9, Float: 7.200000, String: "7.200000", Time: now, Bytes: []byte("7.200000")},
}
_, err = ds.Insert().
Rows(entries).
OnConflict(dbv2.DoUpdate("int", dbv2.Record{"string": "upsert"}).Where(dbv2.C("int").Eq(9))).
Executor().Exec()
mt.EqualError(err, "dbv2: dialect does not support upsert with where clause [dialect=mysql]")
}
func (mt *mysqlTest) TestWindowFunction() {
var version string
ok, err := mt.db.Select(dbv2.Func("version")).ScanVal(&version)
mt.NoError(err)
mt.True(ok)
fields := strings.Split(version, ".")
mt.True(len(fields) > 0)
major, err := strconv.Atoi(fields[0])
mt.NoError(err)
if major < 8 {
//nolint:forbidigo
fmt.Printf("SKIPPING MYSQL WINDOW FUNCTION TEST BECAUSE VERSION IS < 8 [mysql_version:=%d]\n", major)
return
}
ds := mt.db.From("entry").
Select("int", dbv2.ROW_NUMBER().OverName(dbv2.I("w")).As("id")).
Window(dbv2.W("w").OrderBy(dbv2.I("int").Desc()))
var entries []entry
mt.NoError(ds.WithDialect("mysql8").ScanStructs(&entries))
mt.Equal([]entry{
{Int: 9, ID: 1},
{Int: 8, ID: 2},
{Int: 7, ID: 3},
{Int: 6, ID: 4},
{Int: 5, ID: 5},
{Int: 4, ID: 6},
{Int: 3, ID: 7},
{Int: 2, ID: 8},
{Int: 1, ID: 9},
{Int: 0, ID: 10},
}, entries)
mt.Error(ds.WithDialect("mysql").ScanStructs(&entries), "dbv2: adapter does not support window function clause")
}
func (mt *mysqlTest) TestInsertFromSelect() {
ds := mt.db.From("entry")
subquery := dbv2.Select(
dbv2.V(11),
dbv2.V(11),
dbv2.C("float"),
dbv2.C("string"),
dbv2.C("time"),
dbv2.C("bool"),
dbv2.C("bytes"),
).From(dbv2.T("entry")).Where(dbv2.C("int").Eq(9))
query := ds.Insert().Cols().FromQuery(subquery)
_, _, err := query.ToSQL()
mt.NoError(err)
_, err = query.Executor().Exec()
mt.NoError(err)
}
func TestMysqlSuite(t *testing.T) {
suite.Run(t, new(mysqlTest))
}