mirror of
https://github.com/hanzoai/sqlbuilder.git
synced 2026-10-01 00:37:42 +00:00
refs #64 add SQLServer flavor
This commit is contained in:
@@ -236,7 +236,9 @@ func (args *Args) compileArg(buf *bytes.Buffer, flavor Flavor, values []interfac
|
||||
case MySQL, SQLite:
|
||||
buf.WriteRune('?')
|
||||
case PostgreSQL:
|
||||
fmt.Fprintf(buf, "$%v", len(values)+1)
|
||||
fmt.Fprintf(buf, "$%d", len(values)+1)
|
||||
case SQLServer:
|
||||
fmt.Fprintf(buf, "@p%d", len(values)+1)
|
||||
default:
|
||||
panic(fmt.Errorf("Args.CompileWithFlavor: invalid flavor %v (%v)", flavor, int(flavor)))
|
||||
}
|
||||
|
||||
+32
-1
@@ -40,11 +40,12 @@ func TestArgs(t *testing.T) {
|
||||
}
|
||||
|
||||
old := DefaultFlavor
|
||||
DefaultFlavor = PostgreSQL
|
||||
defer func() {
|
||||
DefaultFlavor = old
|
||||
}()
|
||||
|
||||
DefaultFlavor = PostgreSQL
|
||||
|
||||
// PostgreSQL flavor compiled sql.
|
||||
for expected, c := range cases {
|
||||
args := new(Args)
|
||||
@@ -59,6 +60,23 @@ func TestArgs(t *testing.T) {
|
||||
|
||||
a.Equal(actual, expected)
|
||||
}
|
||||
|
||||
DefaultFlavor = SQLServer
|
||||
|
||||
// SQLServer flavor compiled sql.
|
||||
for expected, c := range cases {
|
||||
args := new(Args)
|
||||
|
||||
for i := 1; i < len(c); i++ {
|
||||
args.Add(c[i])
|
||||
}
|
||||
|
||||
sql, values := args.Compile(c[0].(string))
|
||||
actual := fmt.Sprintf("%v\n%v", sql, values)
|
||||
expected = toSQLServerSQL(expected)
|
||||
|
||||
a.Equal(actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func toPostgreSQL(sql string) string {
|
||||
@@ -74,6 +92,19 @@ func toPostgreSQL(sql string) string {
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func toSQLServerSQL(sql string) string {
|
||||
parts := strings.Split(sql, "?")
|
||||
buf := &bytes.Buffer{}
|
||||
buf.WriteString(parts[0])
|
||||
|
||||
for i, p := range parts[1:] {
|
||||
fmt.Fprintf(buf, "@p%v", i+1)
|
||||
buf.WriteString(p)
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func TestArgsAdd(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
args := &Args{}
|
||||
|
||||
@@ -15,6 +15,7 @@ const (
|
||||
MySQL
|
||||
PostgreSQL
|
||||
SQLite
|
||||
SQLServer
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -46,6 +47,8 @@ func (f Flavor) String() string {
|
||||
return "PostgreSQL"
|
||||
case SQLite:
|
||||
return "SQLite"
|
||||
case SQLServer:
|
||||
return "SQLServer"
|
||||
}
|
||||
|
||||
return "<invalid>"
|
||||
@@ -64,6 +67,8 @@ func (f Flavor) Interpolate(sql string, args []interface{}) (string, error) {
|
||||
return postgresqlInterpolate(sql, args...)
|
||||
case SQLite:
|
||||
return sqliteInterpolate(sql, args...)
|
||||
case SQLServer:
|
||||
return sqlserverInterpolate(sql, args...)
|
||||
}
|
||||
|
||||
return "", ErrInterpolateNotImplemented
|
||||
@@ -114,13 +119,13 @@ func (f Flavor) NewUnionBuilder() *UnionBuilder {
|
||||
// Quote adds quote for name to make sure the name can be used safely
|
||||
// as table name or field name.
|
||||
//
|
||||
// * For MySQL, use back quote (`) to quote name;
|
||||
// * For PostgreSQL and SQLite, use double quote (") to quote name.
|
||||
// * For MySQL, use back quote (`) to quote name;
|
||||
// * For PostgreSQL, SQL Server and SQLite, use double quote (") to quote name.
|
||||
func (f Flavor) Quote(name string) string {
|
||||
switch f {
|
||||
case MySQL:
|
||||
return fmt.Sprintf("`%s`", name)
|
||||
case PostgreSQL, SQLite:
|
||||
case PostgreSQL, SQLServer, SQLite:
|
||||
return fmt.Sprintf(`"%s"`, name)
|
||||
}
|
||||
|
||||
|
||||
+19
-116
@@ -5,9 +5,7 @@ package sqlbuilder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/huandu/go-assert"
|
||||
)
|
||||
@@ -19,6 +17,7 @@ func TestFlavor(t *testing.T) {
|
||||
MySQL: "MySQL",
|
||||
PostgreSQL: "PostgreSQL",
|
||||
SQLite: "SQLite",
|
||||
SQLServer: "SQLServer",
|
||||
}
|
||||
|
||||
for f, expected := range cases {
|
||||
@@ -27,120 +26,6 @@ func TestFlavor(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlavorInterpolate(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
dt := time.Date(2019, 4, 24, 12, 23, 34, 123456789, time.FixedZone("CST", 8*60*60)) // 2019-04-24 12:23:34.987654321 CST
|
||||
_, errOutOfRange := strconv.ParseInt("12345678901234567890", 10, 32)
|
||||
cases := []struct {
|
||||
flavor Flavor
|
||||
sql string
|
||||
args []interface{}
|
||||
query string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
MySQL,
|
||||
"SELECT * FROM a WHERE name = ? AND state IN (?, ?, ?, ?, ?)", []interface{}{"I'm fine", 42, int8(8), int16(-16), int32(32), int64(64)},
|
||||
"SELECT * FROM a WHERE name = 'I\\'m fine' AND state IN (42, 8, -16, 32, 64)", nil,
|
||||
},
|
||||
{
|
||||
MySQL,
|
||||
"SELECT * FROM `a?` WHERE name = \"?\" AND state IN (?, '?', ?, ?, ?, ?, ?)", []interface{}{"\r\n\b\t\x1a\x00\\\"'", uint(42), uint8(8), uint16(16), uint32(32), uint64(64), "useless"},
|
||||
"SELECT * FROM `a?` WHERE name = \"?\" AND state IN ('\\r\\n\\b\\t\\Z\\0\\\\\\\"\\'', '?', 42, 8, 16, 32, 64)", nil,
|
||||
},
|
||||
{
|
||||
MySQL,
|
||||
"SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?", []interface{}{true, false, float32(1.234567), float64(9.87654321), []byte(nil), []byte("I'm bytes"), dt, time.Time{}, nil},
|
||||
"SELECT TRUE, FALSE, 1.234567, 9.87654321, NULL, _binary'I\\'m bytes', '2019-04-24 12:23:34.123457', '0000-00-00', NULL", nil,
|
||||
},
|
||||
{
|
||||
MySQL,
|
||||
"SELECT '\\'?', \"\\\"?\", `\\`?`, \\?", []interface{}{MySQL},
|
||||
"SELECT '\\'?', \"\\\"?\", `\\`?`, \\'MySQL'", nil,
|
||||
},
|
||||
{
|
||||
MySQL,
|
||||
"SELECT ?", nil,
|
||||
"", ErrInterpolateMissingArgs,
|
||||
},
|
||||
{
|
||||
MySQL,
|
||||
"SELECT ?", []interface{}{complex(1, 2)},
|
||||
"", ErrInterpolateUnsupportedArgs,
|
||||
},
|
||||
|
||||
{
|
||||
PostgreSQL,
|
||||
"SELECT * FROM a WHERE name = $3 AND state IN ($2, $4, $1, $6, $5)", []interface{}{"I'm fine", 42, int8(8), int16(-16), int32(32), int64(64)},
|
||||
"SELECT * FROM a WHERE name = 8 AND state IN (42, -16, E'I\\'m fine', 64, 32)", nil,
|
||||
},
|
||||
{
|
||||
PostgreSQL,
|
||||
"SELECT * FROM $abc$$1$abc$1$1 WHERE name = \"$1\" AND state IN ($2, '$1', $3, $6, $5, $4, $2) $3", []interface{}{"\r\n\b\t\x1a\x00\\\"'", uint(42), uint8(8), uint16(16), uint32(32), uint64(64), "useless"},
|
||||
"SELECT * FROM $abc$$1$abc$1E'\\r\\n\\b\\t\\Z\\0\\\\\\\"\\'' WHERE name = \"$1\" AND state IN (42, '$1', 8, 64, 32, 16, 42) 8", nil,
|
||||
},
|
||||
{
|
||||
PostgreSQL,
|
||||
"SELECT $1, $2, $3, $4, $5, $6, $7, $8, $9, $11, $a", []interface{}{true, false, float32(1.234567), float64(9.87654321), []byte(nil), []byte("I'm bytes"), dt, time.Time{}, nil, 10, 11, 12},
|
||||
"SELECT TRUE, FALSE, 1.234567, 9.87654321, NULL, E'\\\\x49276D206279746573'::bytea, '2019-04-24 12:23:34.123457 CST', '0000-00-00', NULL, 11, $a", nil,
|
||||
},
|
||||
{
|
||||
PostgreSQL,
|
||||
"SELECT '\\'$1', \"\\\"$1\", `$1`, \\$1a, $$1$$, $a $b$ $a $ $1$b$1$1 $a$ $", []interface{}{MySQL},
|
||||
"SELECT '\\'$1', \"\\\"$1\", `E'MySQL'`, \\E'MySQL'a, $$1$$, $a $b$ $a $ $1$b$1E'MySQL' $a$ $", nil,
|
||||
},
|
||||
{
|
||||
PostgreSQL,
|
||||
"SELECT * FROM a WHERE name = 'Huan''Du''$1' AND desc = $1", []interface{}{"c'mon"},
|
||||
"SELECT * FROM a WHERE name = 'Huan''Du''$1' AND desc = E'c\\'mon'", nil,
|
||||
},
|
||||
{
|
||||
PostgreSQL,
|
||||
"SELECT $1", nil,
|
||||
"", ErrInterpolateMissingArgs,
|
||||
},
|
||||
{
|
||||
PostgreSQL,
|
||||
"SELECT $1", []interface{}{complex(1, 2)},
|
||||
"", ErrInterpolateUnsupportedArgs,
|
||||
},
|
||||
{
|
||||
PostgreSQL,
|
||||
"SELECT $12345678901234567890", nil,
|
||||
"", errOutOfRange,
|
||||
},
|
||||
|
||||
{
|
||||
SQLite,
|
||||
"SELECT * FROM a WHERE name = ? AND state IN (?, ?, ?, ?, ?)", []interface{}{"I'm fine", 42, int8(8), int16(-16), int32(32), int64(64)},
|
||||
"SELECT * FROM a WHERE name = 'I\\'m fine' AND state IN (42, 8, -16, 32, 64)", nil,
|
||||
},
|
||||
{
|
||||
SQLite,
|
||||
"SELECT * FROM `a?` WHERE name = \"?\" AND state IN (?, '?', ?, ?, ?, ?, ?)", []interface{}{"\r\n\b\t\x1a\x00\\\"'", uint(42), uint8(8), uint16(16), uint32(32), uint64(64), "useless"},
|
||||
"SELECT * FROM `a?` WHERE name = \"?\" AND state IN ('\\r\\n\\b\\t\\Z\\0\\\\\\\"\\'', '?', 42, 8, 16, 32, 64)", nil,
|
||||
},
|
||||
{
|
||||
SQLite,
|
||||
"SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?", []interface{}{true, false, float32(1.234567), float64(9.87654321), []byte(nil), []byte("I'm bytes"), dt, time.Time{}, nil},
|
||||
"SELECT TRUE, FALSE, 1.234567, 9.87654321, NULL, X'49276D206279746573', '2019-04-24 12:23:34.123', '0000-00-00', NULL", nil,
|
||||
},
|
||||
{
|
||||
SQLite,
|
||||
"SELECT '\\'?', \"\\\"?\", `\\`?`, \\?", []interface{}{SQLite},
|
||||
"SELECT '\\'?', \"\\\"?\", `\\`?`, \\'SQLite'", nil,
|
||||
},
|
||||
}
|
||||
|
||||
for idx, c := range cases {
|
||||
a.Use(&idx, &c)
|
||||
query, err := c.flavor.Interpolate(c.sql, c.args)
|
||||
|
||||
a.Equal(query, c.query)
|
||||
a.Assert(err == c.err || err.Error() == c.err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleFlavor() {
|
||||
// Create a flavored builder.
|
||||
sb := PostgreSQL.NewSelectBuilder()
|
||||
@@ -218,3 +103,21 @@ func ExampleFlavor_Interpolate_sqlite() {
|
||||
// SELECT name FROM user WHERE id <> 1234 AND name = 'Charmy Liu' AND desc LIKE '%mother\'s day%'
|
||||
// <nil>
|
||||
}
|
||||
|
||||
func ExampleFlavor_Interpolate_sqlServer() {
|
||||
sb := SQLServer.NewSelectBuilder()
|
||||
sb.Select("name").From("user").Where(
|
||||
sb.NE("id", 1234),
|
||||
sb.E("name", "Charmy Liu"),
|
||||
sb.Like("desc", "%mother's day%"),
|
||||
)
|
||||
sql, args := sb.Build()
|
||||
query, err := SQLServer.Interpolate(sql, args)
|
||||
|
||||
fmt.Println(query)
|
||||
fmt.Println(err)
|
||||
|
||||
// Output:
|
||||
// SELECT name FROM user WHERE id <> 1234 AND name = N'Charmy Liu' AND desc LIKE N'%mother\'s day%'
|
||||
// <nil>
|
||||
}
|
||||
|
||||
+123
-1
@@ -267,6 +267,118 @@ func postgresqlInterpolate(query string, args ...interface{}) (string, error) {
|
||||
return *(*string)(unsafe.Pointer(&buf)), nil
|
||||
}
|
||||
|
||||
// sqlserverInterpolate parses query and replace all "@p*" with encoded args.
|
||||
// If there are more "@p*" than len(args), returns ErrMissingArgs.
|
||||
// Otherwise, if there are less "@p*" than len(args), the redundant args are omitted.
|
||||
func sqlserverInterpolate(query string, args ...interface{}) (string, error) {
|
||||
// Roughly estimate the size to avoid useless memory allocation and copy.
|
||||
buf := make([]byte, 0, len(query)+len(args)*20)
|
||||
|
||||
var quote rune
|
||||
var err error
|
||||
var idx int64
|
||||
max := len(args)
|
||||
escaping := false
|
||||
offset := 0
|
||||
target := query
|
||||
r, sz := utf8.DecodeRuneInString(target)
|
||||
|
||||
for ; sz != 0; r, sz = utf8.DecodeRuneInString(target) {
|
||||
offset += sz
|
||||
target = query[offset:]
|
||||
|
||||
if escaping {
|
||||
escaping = false
|
||||
continue
|
||||
}
|
||||
|
||||
switch r {
|
||||
case '@':
|
||||
if quote != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
oldSz := sz
|
||||
pos := offset
|
||||
r, sz = utf8.DecodeRuneInString(target)
|
||||
|
||||
// Only parameters starting with @p or @P are interpolated.
|
||||
if r != 'p' && r != 'P' {
|
||||
continue
|
||||
}
|
||||
|
||||
pos += sz
|
||||
target = query[pos:]
|
||||
r, sz = utf8.DecodeRuneInString(target)
|
||||
|
||||
if '1' <= r && r <= '9' {
|
||||
// A placeholder is found.
|
||||
pos += sz
|
||||
target = query[pos:]
|
||||
|
||||
for r, sz = utf8.DecodeRuneInString(target); sz != 0 && '0' <= r && r <= '9'; r, sz = utf8.DecodeRuneInString(target) {
|
||||
pos += sz
|
||||
target = query[pos:]
|
||||
}
|
||||
|
||||
idx, err = strconv.ParseInt(query[offset+1:pos], 10, strconv.IntSize)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if int(idx) >= max+1 {
|
||||
return "", ErrInterpolateMissingArgs
|
||||
}
|
||||
|
||||
buf = append(buf, query[:offset-oldSz]...)
|
||||
buf, err = encodeValue(buf, args[idx-1], SQLServer)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
query = target
|
||||
offset = 0
|
||||
|
||||
if sz == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
case '\'':
|
||||
if quote == '\'' {
|
||||
quote = 0
|
||||
continue
|
||||
}
|
||||
|
||||
if quote == 0 {
|
||||
quote = '\''
|
||||
}
|
||||
|
||||
case '"':
|
||||
if quote == '"' {
|
||||
quote = 0
|
||||
continue
|
||||
}
|
||||
|
||||
if quote == 0 {
|
||||
quote = '"'
|
||||
}
|
||||
|
||||
case '\\':
|
||||
if quote != 0 {
|
||||
escaping = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buf = append(buf, query...)
|
||||
return *(*string)(unsafe.Pointer(&buf)), nil
|
||||
}
|
||||
|
||||
// mysqlInterpolate works the same as MySQL interpolating.
|
||||
func sqliteInterpolate(query string, args ...interface{}) (string, error) {
|
||||
return mysqlLikeInterpolate(SQLite, query, args...)
|
||||
@@ -340,6 +452,10 @@ func encodeValue(buf []byte, arg interface{}, flavor Flavor) ([]byte, error) {
|
||||
buf = append(buf, "X'"...)
|
||||
buf = appendHex(buf, v)
|
||||
buf = append(buf, '\'')
|
||||
|
||||
case SQLServer:
|
||||
buf = append(buf, "0x"...)
|
||||
buf = appendHex(buf, v)
|
||||
}
|
||||
|
||||
case string:
|
||||
@@ -365,6 +481,9 @@ func encodeValue(buf []byte, arg interface{}, flavor Flavor) ([]byte, error) {
|
||||
|
||||
case SQLite:
|
||||
buf = append(buf, v.Format("2006-01-02 15:04:05.000")...)
|
||||
|
||||
case SQLServer:
|
||||
buf = append(buf, v.Format("2006-01-02 15:04:05.999999 Z07:00")...)
|
||||
}
|
||||
|
||||
buf = append(buf, '\'')
|
||||
@@ -390,8 +509,11 @@ func appendHex(buf, v []byte) []byte {
|
||||
}
|
||||
|
||||
func quoteStringValue(buf []byte, s string, flavor Flavor) []byte {
|
||||
if flavor == PostgreSQL {
|
||||
switch flavor {
|
||||
case PostgreSQL:
|
||||
buf = append(buf, 'E')
|
||||
case SQLServer:
|
||||
buf = append(buf, 'N')
|
||||
}
|
||||
|
||||
buf = append(buf, '\'')
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
package sqlbuilder
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/huandu/go-assert"
|
||||
)
|
||||
|
||||
func TestFlavorInterpolate(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
dt := time.Date(2019, 4, 24, 12, 23, 34, 123456789, time.FixedZone("CST", 8*60*60)) // 2019-04-24 12:23:34.987654321 CST
|
||||
_, errOutOfRange := strconv.ParseInt("12345678901234567890", 10, 32)
|
||||
cases := []struct {
|
||||
flavor Flavor
|
||||
sql string
|
||||
args []interface{}
|
||||
query string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
MySQL,
|
||||
"SELECT * FROM a WHERE name = ? AND state IN (?, ?, ?, ?, ?)", []interface{}{"I'm fine", 42, int8(8), int16(-16), int32(32), int64(64)},
|
||||
"SELECT * FROM a WHERE name = 'I\\'m fine' AND state IN (42, 8, -16, 32, 64)", nil,
|
||||
},
|
||||
{
|
||||
MySQL,
|
||||
"SELECT * FROM `a?` WHERE name = \"?\" AND state IN (?, '?', ?, ?, ?, ?, ?)", []interface{}{"\r\n\b\t\x1a\x00\\\"'", uint(42), uint8(8), uint16(16), uint32(32), uint64(64), "useless"},
|
||||
"SELECT * FROM `a?` WHERE name = \"?\" AND state IN ('\\r\\n\\b\\t\\Z\\0\\\\\\\"\\'', '?', 42, 8, 16, 32, 64)", nil,
|
||||
},
|
||||
{
|
||||
MySQL,
|
||||
"SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?", []interface{}{true, false, float32(1.234567), float64(9.87654321), []byte(nil), []byte("I'm bytes"), dt, time.Time{}, nil},
|
||||
"SELECT TRUE, FALSE, 1.234567, 9.87654321, NULL, _binary'I\\'m bytes', '2019-04-24 12:23:34.123457', '0000-00-00', NULL", nil,
|
||||
},
|
||||
{
|
||||
MySQL,
|
||||
"SELECT '\\'?', \"\\\"?\", `\\`?`, \\?", []interface{}{MySQL},
|
||||
"SELECT '\\'?', \"\\\"?\", `\\`?`, \\'MySQL'", nil,
|
||||
},
|
||||
{
|
||||
MySQL,
|
||||
"SELECT ?", nil,
|
||||
"", ErrInterpolateMissingArgs,
|
||||
},
|
||||
{
|
||||
MySQL,
|
||||
"SELECT ?", []interface{}{complex(1, 2)},
|
||||
"", ErrInterpolateUnsupportedArgs,
|
||||
},
|
||||
|
||||
{
|
||||
PostgreSQL,
|
||||
"SELECT * FROM a WHERE name = $3 AND state IN ($2, $4, $1, $6, $5)", []interface{}{"I'm fine", 42, int8(8), int16(-16), int32(32), int64(64)},
|
||||
"SELECT * FROM a WHERE name = 8 AND state IN (42, -16, E'I\\'m fine', 64, 32)", nil,
|
||||
},
|
||||
{
|
||||
PostgreSQL,
|
||||
"SELECT * FROM $abc$$1$abc$1$1 WHERE name = \"$1\" AND state IN ($2, '$1', $3, $6, $5, $4, $2) $3", []interface{}{"\r\n\b\t\x1a\x00\\\"'", uint(42), uint8(8), uint16(16), uint32(32), uint64(64), "useless"},
|
||||
"SELECT * FROM $abc$$1$abc$1E'\\r\\n\\b\\t\\Z\\0\\\\\\\"\\'' WHERE name = \"$1\" AND state IN (42, '$1', 8, 64, 32, 16, 42) 8", nil,
|
||||
},
|
||||
{
|
||||
PostgreSQL,
|
||||
"SELECT $1, $2, $3, $4, $5, $6, $7, $8, $9, $11, $a", []interface{}{true, false, float32(1.234567), float64(9.87654321), []byte(nil), []byte("I'm bytes"), dt, time.Time{}, nil, 10, 11, 12},
|
||||
"SELECT TRUE, FALSE, 1.234567, 9.87654321, NULL, E'\\\\x49276D206279746573'::bytea, '2019-04-24 12:23:34.123457 CST', '0000-00-00', NULL, 11, $a", nil,
|
||||
},
|
||||
{
|
||||
PostgreSQL,
|
||||
"SELECT '\\'$1', \"\\\"$1\", `$1`, \\$1a, $$1$$, $a $b$ $a $ $1$b$1$1 $a$ $", []interface{}{MySQL},
|
||||
"SELECT '\\'$1', \"\\\"$1\", `E'MySQL'`, \\E'MySQL'a, $$1$$, $a $b$ $a $ $1$b$1E'MySQL' $a$ $", nil,
|
||||
},
|
||||
{
|
||||
PostgreSQL,
|
||||
"SELECT * FROM a WHERE name = 'Huan''Du''$1' AND desc = $1", []interface{}{"c'mon"},
|
||||
"SELECT * FROM a WHERE name = 'Huan''Du''$1' AND desc = E'c\\'mon'", nil,
|
||||
},
|
||||
{
|
||||
PostgreSQL,
|
||||
"SELECT $1", nil,
|
||||
"", ErrInterpolateMissingArgs,
|
||||
},
|
||||
{
|
||||
PostgreSQL,
|
||||
"SELECT $1", []interface{}{complex(1, 2)},
|
||||
"", ErrInterpolateUnsupportedArgs,
|
||||
},
|
||||
{
|
||||
PostgreSQL,
|
||||
"SELECT $12345678901234567890", nil,
|
||||
"", errOutOfRange,
|
||||
},
|
||||
|
||||
{
|
||||
SQLite,
|
||||
"SELECT * FROM a WHERE name = ? AND state IN (?, ?, ?, ?, ?)", []interface{}{"I'm fine", 42, int8(8), int16(-16), int32(32), int64(64)},
|
||||
"SELECT * FROM a WHERE name = 'I\\'m fine' AND state IN (42, 8, -16, 32, 64)", nil,
|
||||
},
|
||||
{
|
||||
SQLite,
|
||||
"SELECT * FROM `a?` WHERE name = \"?\" AND state IN (?, '?', ?, ?, ?, ?, ?)", []interface{}{"\r\n\b\t\x1a\x00\\\"'", uint(42), uint8(8), uint16(16), uint32(32), uint64(64), "useless"},
|
||||
"SELECT * FROM `a?` WHERE name = \"?\" AND state IN ('\\r\\n\\b\\t\\Z\\0\\\\\\\"\\'', '?', 42, 8, 16, 32, 64)", nil,
|
||||
},
|
||||
{
|
||||
SQLite,
|
||||
"SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?", []interface{}{true, false, float32(1.234567), float64(9.87654321), []byte(nil), []byte("I'm bytes"), dt, time.Time{}, nil},
|
||||
"SELECT TRUE, FALSE, 1.234567, 9.87654321, NULL, X'49276D206279746573', '2019-04-24 12:23:34.123', '0000-00-00', NULL", nil,
|
||||
},
|
||||
{
|
||||
SQLite,
|
||||
"SELECT '\\'?', \"\\\"?\", `\\`?`, \\?", []interface{}{SQLite},
|
||||
"SELECT '\\'?', \"\\\"?\", `\\`?`, \\'SQLite'", nil,
|
||||
},
|
||||
|
||||
{
|
||||
SQLServer,
|
||||
"SELECT * FROM a WHERE name = @p1 AND state IN (@p3, @P2, @p4, @P6, @p5)", []interface{}{"I'm fine", 42, int8(8), int16(-16), int32(32), int64(64)},
|
||||
"SELECT * FROM a WHERE name = N'I\\'m fine' AND state IN (8, 42, -16, 64, 32)", nil,
|
||||
},
|
||||
{
|
||||
SQLServer,
|
||||
"SELECT * FROM \"a@p1\" WHERE name = '@p1' AND state IN (@p2, '@p1', @p1, @p3, @p4, @p5, @p6)", []interface{}{"\r\n\b\t\x1a\x00\\\"'", uint(42), uint8(8), uint16(16), uint32(32), uint64(64), "useless"},
|
||||
"SELECT * FROM \"a@p1\" WHERE name = '@p1' AND state IN (42, '@p1', N'\\r\\n\\b\\t\\Z\\0\\\\\\\"\\'', 8, 16, 32, 64)", nil,
|
||||
},
|
||||
{
|
||||
SQLServer,
|
||||
"SELECT @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9", []interface{}{true, false, float32(1.234567), float64(9.87654321), []byte(nil), []byte("I'm bytes"), dt, time.Time{}, nil},
|
||||
"SELECT TRUE, FALSE, 1.234567, 9.87654321, NULL, 0x49276D206279746573, '2019-04-24 12:23:34.123457 +08:00', '0000-00-00', NULL", nil,
|
||||
},
|
||||
{
|
||||
SQLServer,
|
||||
"SELECT '\\'@p1', \"\\\"@p1\", \\@p1, @abc", []interface{}{SQLServer},
|
||||
"SELECT '\\'@p1', \"\\\"@p1\", \\N'SQLServer', @abc", nil,
|
||||
},
|
||||
{
|
||||
SQLServer,
|
||||
"SELECT @p1", nil,
|
||||
"", ErrInterpolateMissingArgs,
|
||||
},
|
||||
}
|
||||
|
||||
for idx, c := range cases {
|
||||
a.Use(&idx, &c)
|
||||
query, err := c.flavor.Interpolate(c.sql, c.args)
|
||||
|
||||
a.Equal(query, c.query)
|
||||
a.Assert(err == c.err || err.Error() == c.err.Error())
|
||||
}
|
||||
}
|
||||
@@ -299,16 +299,44 @@ func (sb *SelectBuilder) BuildWithFlavor(flavor Flavor, initialArg ...interface{
|
||||
sb.injection.WriteTo(buf, selectMarkerAfterOrderBy)
|
||||
}
|
||||
|
||||
if sb.limit >= 0 {
|
||||
buf.WriteString(" LIMIT ")
|
||||
buf.WriteString(strconv.Itoa(sb.limit))
|
||||
}
|
||||
switch flavor {
|
||||
case MySQL, SQLite:
|
||||
if sb.limit >= 0 {
|
||||
buf.WriteString(" LIMIT ")
|
||||
buf.WriteString(strconv.Itoa(sb.limit))
|
||||
|
||||
if sb.offset >= 0 {
|
||||
buf.WriteString(" OFFSET ")
|
||||
buf.WriteString(strconv.Itoa(sb.offset))
|
||||
}
|
||||
}
|
||||
case PostgreSQL:
|
||||
if sb.limit >= 0 {
|
||||
buf.WriteString(" LIMIT ")
|
||||
buf.WriteString(strconv.Itoa(sb.limit))
|
||||
}
|
||||
|
||||
if MySQL == flavor && sb.limit >= 0 || PostgreSQL == flavor {
|
||||
if sb.offset >= 0 {
|
||||
buf.WriteString(" OFFSET ")
|
||||
buf.WriteString(strconv.Itoa(sb.offset))
|
||||
}
|
||||
|
||||
case SQLServer:
|
||||
if sb.offset >= 0 {
|
||||
buf.WriteString(" OFFSET ")
|
||||
buf.WriteString(strconv.Itoa(sb.offset))
|
||||
buf.WriteString(" ROWS")
|
||||
}
|
||||
|
||||
if sb.limit >= 0 {
|
||||
if sb.offset < 0 {
|
||||
buf.WriteString(" OFFSET 0 ROWS")
|
||||
}
|
||||
|
||||
buf.WriteString(" FETCH NEXT ")
|
||||
buf.WriteString(strconv.Itoa(sb.limit))
|
||||
buf.WriteString(" ROWS ONLY")
|
||||
}
|
||||
}
|
||||
|
||||
if sb.limit >= 0 {
|
||||
|
||||
+60
-41
@@ -112,64 +112,83 @@ func ExampleSelectBuilder_join() {
|
||||
}
|
||||
|
||||
func ExampleSelectBuilder_limit_offset() {
|
||||
flavors := []Flavor{MySQL, PostgreSQL, SQLite, SQLServer}
|
||||
results := make([][]string, len(flavors))
|
||||
sb := NewSelectBuilder()
|
||||
saveResults := func() {
|
||||
for i, f := range flavors {
|
||||
sql, _ := sb.BuildWithFlavor(f)
|
||||
results[i] = append(results[i], sql)
|
||||
}
|
||||
}
|
||||
|
||||
sb.Select("*")
|
||||
sb.From("user")
|
||||
|
||||
// first test case: limit and offset < 0
|
||||
// will not add LIMIT and OFFSET to either query for MySQL or PostgresSQL
|
||||
// Case #1: limit < 0 and offset < 0
|
||||
//
|
||||
// All: No limit or offset in query.
|
||||
sb.Limit(-1)
|
||||
sb.Offset(-1)
|
||||
saveResults()
|
||||
|
||||
pgSQL, _ := sb.BuildWithFlavor(PostgreSQL)
|
||||
fmt.Println(pgSQL)
|
||||
|
||||
mySQL, _ := sb.BuildWithFlavor(MySQL)
|
||||
fmt.Println(mySQL)
|
||||
|
||||
// second test case: limit <= 0 and offset >= 0
|
||||
// doesn't add offset for MySQL as limit <= 0
|
||||
// just adds offset to PostgresSQL with no limit as it is not specified
|
||||
// Case #2: limit < 0 and offset >= 0
|
||||
//
|
||||
// MySQL and SQLite: Ignore offset if the limit is not set.
|
||||
// PostgreSQL: Offset can be set without limit.
|
||||
// SQLServer: Offset can be set without limit.
|
||||
sb.Limit(-1)
|
||||
sb.Offset(0)
|
||||
saveResults()
|
||||
|
||||
pgSQL, _ = sb.BuildWithFlavor(PostgreSQL)
|
||||
fmt.Println(pgSQL)
|
||||
|
||||
mySQL, _ = sb.BuildWithFlavor(MySQL)
|
||||
fmt.Println(mySQL)
|
||||
|
||||
// third test case: limit >= 0 and offset >= 0
|
||||
// adds offset and limit for MySQL and PostgresSQL, the query will not return a row (hint: can be used to check if table exists)
|
||||
sb.Limit(0)
|
||||
// Case #3: limit >= 0 and offset >= 0
|
||||
//
|
||||
// All: Set both limit and offset.
|
||||
sb.Limit(1)
|
||||
sb.Offset(0)
|
||||
saveResults()
|
||||
|
||||
pgSQL, _ = sb.BuildWithFlavor(PostgreSQL)
|
||||
fmt.Println(pgSQL)
|
||||
|
||||
mySQL, _ = sb.BuildWithFlavor(MySQL)
|
||||
fmt.Println(mySQL)
|
||||
|
||||
// forth test case: limit >= 0 and offset <= 0
|
||||
// adds limit for MySQL and PostgresSQL and omits offset
|
||||
sb.Limit(0)
|
||||
// Case #4: limit >= 0 and offset < 0
|
||||
//
|
||||
// All: Set limit in query.
|
||||
sb.Limit(1)
|
||||
sb.Offset(-1)
|
||||
saveResults()
|
||||
|
||||
pgSQL, _ = sb.BuildWithFlavor(PostgreSQL)
|
||||
fmt.Println(pgSQL)
|
||||
for i, result := range results {
|
||||
fmt.Println()
|
||||
fmt.Println(flavors[i])
|
||||
|
||||
mySQL, _ = sb.BuildWithFlavor(MySQL)
|
||||
fmt.Println(mySQL)
|
||||
for n, sql := range result {
|
||||
fmt.Printf("#%d: %s\n", n+1, sql)
|
||||
}
|
||||
}
|
||||
|
||||
// Output:
|
||||
// SELECT * FROM user
|
||||
// SELECT * FROM user
|
||||
// SELECT * FROM user OFFSET 0
|
||||
// SELECT * FROM user
|
||||
// SELECT * FROM user LIMIT 0 OFFSET 0
|
||||
// SELECT * FROM user LIMIT 0 OFFSET 0
|
||||
// SELECT * FROM user LIMIT 0
|
||||
// SELECT * FROM user LIMIT 0
|
||||
//
|
||||
// MySQL
|
||||
// #1: SELECT * FROM user
|
||||
// #2: SELECT * FROM user
|
||||
// #3: SELECT * FROM user LIMIT 1 OFFSET 0
|
||||
// #4: SELECT * FROM user LIMIT 1
|
||||
//
|
||||
// PostgreSQL
|
||||
// #1: SELECT * FROM user
|
||||
// #2: SELECT * FROM user OFFSET 0
|
||||
// #3: SELECT * FROM user LIMIT 1 OFFSET 0
|
||||
// #4: SELECT * FROM user LIMIT 1
|
||||
//
|
||||
// SQLite
|
||||
// #1: SELECT * FROM user
|
||||
// #2: SELECT * FROM user
|
||||
// #3: SELECT * FROM user LIMIT 1 OFFSET 0
|
||||
// #4: SELECT * FROM user LIMIT 1
|
||||
//
|
||||
// SQLServer
|
||||
// #1: SELECT * FROM user
|
||||
// #2: SELECT * FROM user OFFSET 0 ROWS
|
||||
// #3: SELECT * FROM user OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY
|
||||
// #4: SELECT * FROM user OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY
|
||||
}
|
||||
|
||||
func ExampleSelectBuilder_ForUpdate() {
|
||||
|
||||
Reference in New Issue
Block a user