Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,18 @@ func main() {
## Supported Database

- MySQL 5 and 8
- MariaDB 10

## Testing

### Start MariaDB server in Docker

```console
docker run -it --rm -p 3307:3306 -e "MARIADB_ROOT_PASSWORD=test" -e "MARIADB_DATABASE=rel_test" mariadb:10
```

### Run tests

```console
MYSQL_DATABASE="root:test@tcp(localhost:3307)/rel_test" go test ./...
```
12 changes: 10 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,16 @@ module github.com/go-rel/mysql
go 1.17

require (
github.com/go-rel/rel v0.24.0
github.com/go-rel/sql v0.1.0
github.com/go-rel/rel v0.25.1-0.20211011102656-a1b38f01d34a
github.com/go-rel/sql v0.1.1-0.20211011073646-a38034248e90
github.com/go-sql-driver/mysql v1.6.0
github.com/stretchr/testify v1.7.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
)
16 changes: 5 additions & 11 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/go-rel/rel v0.23.0 h1:xVte6QJhCLGQoTeHeUbIWTVDwllFCVzmJ8511XEnbtg=
github.com/go-rel/rel v0.23.0/go.mod h1:/VBLj1U4ZVb53aB3n22RQwX0V9DvckohseDJQibn2Rs=
github.com/go-rel/rel v0.24.0 h1:DXx8DjUwkkg9EcLfS/m5YGGjdV4fjs7dsrcDVS1Zk0w=
github.com/go-rel/rel v0.24.0/go.mod h1:/VBLj1U4ZVb53aB3n22RQwX0V9DvckohseDJQibn2Rs=
github.com/go-rel/sql v0.1.0 h1:fdkdENIgJzoqUfdbQYtPCl85jcu7Ajj8zh/A5hWS+SI=
github.com/go-rel/sql v0.1.0/go.mod h1:vnvQ9jFzTgFvOl5qcntoFXrs9sas39YcIJG30LFlc50=
github.com/go-rel/rel v0.25.1-0.20211007095335-eec7ac68c920/go.mod h1:/VBLj1U4ZVb53aB3n22RQwX0V9DvckohseDJQibn2Rs=
github.com/go-rel/rel v0.25.1-0.20211011102656-a1b38f01d34a h1:FAd8FrXgy+G/44OwXYOk3A0b4lhdoyKMgF9m3h6pTt4=
github.com/go-rel/rel v0.25.1-0.20211011102656-a1b38f01d34a/go.mod h1:/VBLj1U4ZVb53aB3n22RQwX0V9DvckohseDJQibn2Rs=
github.com/go-rel/sql v0.1.1-0.20211011073646-a38034248e90 h1:638QwWIymw8cizASdq594qlDM729vYEtJobdy8wv2Lo=
github.com/go-rel/sql v0.1.1-0.20211011073646-a38034248e90/go.mod h1:ukXvTr/zbEZLu+lJCXcKpMg1Vp7Ps9cfKD+Us4kJREo=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
Expand All @@ -18,11 +17,9 @@ github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:x
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
Expand Down Expand Up @@ -54,7 +51,6 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
Expand Down Expand Up @@ -86,14 +82,12 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
8 changes: 5 additions & 3 deletions mysql.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,17 @@ import (
// Existing connection needs to be created with `clientFoundRows=true` options for update and delete to works correctly.
func New(database *db.DB) rel.Adapter {
var (
bufferFactory = builder.BufferFactory{ArgumentPlaceholder: "?", EscapePrefix: "`", EscapeSuffix: "`"}
bufferFactory = builder.BufferFactory{ArgumentPlaceholder: "?", BoolTrueValue: "true", BoolFalseValue: "false", Quoter: Quote{}, ValueConverter: ValueConvert{}}
filterBuilder = builder.Filter{}
queryBuilder = builder.Query{BufferFactory: bufferFactory, Filter: filterBuilder}
InsertBuilder = builder.Insert{BufferFactory: bufferFactory, InsertDefaultValues: true}
insertAllBuilder = builder.InsertAll{BufferFactory: bufferFactory}
updateBuilder = builder.Update{BufferFactory: bufferFactory, Query: queryBuilder, Filter: filterBuilder}
deleteBuilder = builder.Delete{BufferFactory: bufferFactory, Query: queryBuilder, Filter: filterBuilder}
tableBuilder = builder.Table{BufferFactory: bufferFactory, ColumnMapper: sql.ColumnMapper}
indexBuilder = builder.Index{BufferFactory: bufferFactory, DropIndexOnTable: true}
ddlBufferFactory = builder.BufferFactory{InlineValues: true, BoolTrueValue: "true", BoolFalseValue: "false", Quoter: Quote{}, ValueConverter: ValueConvert{}}
ddlQueryBuilder = builder.Query{BufferFactory: ddlBufferFactory, Filter: filterBuilder}
tableBuilder = builder.Table{BufferFactory: ddlBufferFactory, ColumnMapper: sql.ColumnMapper}
indexBuilder = builder.Index{BufferFactory: ddlBufferFactory, Query: ddlQueryBuilder, Filter: filterBuilder, DropIndexOnTable: true}
)

return &sql.SQL{
Expand Down
84 changes: 84 additions & 0 deletions quote.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package mysql

import (
"database/sql/driver"
"strings"
"time"
)

// Quote MySQL identifiers and literals.
type Quote struct{}

func (q Quote) ID(name string) string {
end := strings.IndexRune(name, 0)
if end > -1 {
name = name[:end]
}
return "`" + strings.Replace(name, "`", "``", -1) + "`"
}

func (q Quote) Value(v interface{}) string {
switch v := v.(type) {
default:
panic("unsupported value")
case string:
// TODO: Need to check on connection for NO_BACKSLASH_ESCAPES
rv := []rune(v)
buf := make([]rune, len(rv)*2)
pos := 0
for i := 0; i < len(rv); i++ {
c := rv[i]
switch c {
case '\x00':
buf[pos] = '\\'
buf[pos+1] = '0'
pos += 2
case '\n':
buf[pos] = '\\'
buf[pos+1] = 'n'
pos += 2
case '\r':
buf[pos] = '\\'
buf[pos+1] = 'r'
pos += 2
case '\x1a':
buf[pos] = '\\'
buf[pos+1] = 'Z'
pos += 2
case '\'':
buf[pos] = '\\'
buf[pos+1] = '\''
pos += 2
case '"':
buf[pos] = '\\'
buf[pos+1] = '"'
pos += 2
case '\\':
buf[pos] = '\\'
buf[pos+1] = '\\'
pos += 2
default:
buf[pos] = c
pos++
}
}

return "'" + string(buf[:pos]) + "'"
}
}

// ValueConvert converts values to MySQL literals.
type ValueConvert struct{}

func (c ValueConvert) ConvertValue(v interface{}) (driver.Value, error) {
v, err := driver.DefaultParameterConverter.ConvertValue(v)
if err != nil {
return nil, err
}
switch v := v.(type) {
default:
return v, nil
case time.Time:
return v.Truncate(time.Microsecond).Format("2006-01-02 15:04:05.999999"), nil
}
}
76 changes: 76 additions & 0 deletions quote_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package mysql

import (
"database/sql/driver"
"testing"
"time"

"github.com/stretchr/testify/assert"
)

func TestQuote_Panic(t *testing.T) {
quoter := Quote{}
assert.PanicsWithValue(t, "unsupported value", func() {
quoter.Value(1)
})
}

func TestQuote_ID(t *testing.T) {
quoter := Quote{}

var cases = []struct {
input string
want string
}{
{`foo`, "`foo`"},
{`foo bar baz`, "`foo bar baz`"},
{"foo`bar", "`foo``bar`"},
{"foo\x00bar", "`foo`"},
{"\x00foo", "``"},
}

for _, test := range cases {
assert.Equal(t, test.want, quoter.ID(test.input))
}
}

func TestQuote_Value(t *testing.T) {
quoter := Quote{}

var cases = []struct {
input string
want string
}{
{"foo\x00bar", "'foo\\0bar'"},
{"foo\nbar", "'foo\\nbar'"},
{"foo\rbar", "'foo\\rbar'"},
{"foo\x1abar", "'foo\\Zbar'"},
{"foo\"bar", "'foo\\\"bar'"},
{"foo\\bar", "'foo\\\\bar'"},
{"foo'bar", "'foo\\'bar'"},
}

for _, test := range cases {
assert.Equal(t, test.want, quoter.Value(test.input))
}
}

type customType int

func (c customType) Value() (driver.Value, error) {
return int(c), nil
}

func TestValueConvert_CustomType(t *testing.T) {
valuer := ValueConvert{}
v, err := valuer.ConvertValue(customType(1))
assert.EqualError(t, err, "non-Value type int returned from Value")
assert.Nil(t, v)
}

func TestValueConvert_DateTime(t *testing.T) {
valuer := ValueConvert{}
v, err := valuer.ConvertValue(time.Unix(1633934368, 0).UTC())
assert.NoError(t, err)
assert.Equal(t, "2021-10-11 06:39:28", v)
}