refs #64 add SQLServer flavor

This commit is contained in:
Huan Du
2021-07-25 03:28:33 +08:00
parent c4c083ed73
commit 3e007c306f
8 changed files with 427 additions and 168 deletions
+3 -1
View File
@@ -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
View File
@@ -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{}
+8 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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, '\'')
+149
View File
@@ -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())
}
}
+33 -5
View File
@@ -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
View File
@@ -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() {