Skip to content

Commit 153aee0

Browse files
authored
Merge pull request #66 from huandu/flavor-sqlserver
Fix #64 Add SQLServer flavor
2 parents d2aadb4 + b826691 commit 153aee0

File tree

9 files changed

+437
-172
lines changed

9 files changed

+437
-172
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
- [Usage](#usage)
1010
- [Basic usage](#basic-usage)
1111
- [Pre-defined SQL builders](#pre-defined-sql-builders)
12-
- [Build SQL for MySQL, PostgreSQL or SQLite](#build-sql-for-mysql--postgresql-or-sqlite)
12+
- [Build SQL for MySQL, PostgreSQL, SQLServer or SQLite](#build-sql-for-mysql--postgresql-sqlserver-or-sqlite)
1313
- [Using `Struct` as a light weight ORM](#using--struct--as-a-light-weight-orm)
1414
- [Nested SQL](#nested-sql)
1515
- [Use `sql.Named` in a builder](#use--sqlnamed--in-a-builder)
@@ -104,9 +104,9 @@ fmt.Println(sql)
104104

105105
To learn how to use builders, check out [examples on GoDoc](https://pkg.go.dev/github.com/huandu/go-sqlbuilder#pkg-examples).
106106

107-
### Build SQL for MySQL, PostgreSQL or SQLite
107+
### Build SQL for MySQL, PostgreSQL, SQLServer or SQLite
108108

109-
Parameter markers are different in MySQL, PostgreSQL and SQLite. This package provides some methods to set the type of markers (we call it "flavor") in all builders.
109+
Parameter markers are different in MySQL, PostgreSQL, SQLServer and SQLite. This package provides some methods to set the type of markers (we call it "flavor") in all builders.
110110

111111
By default, all builders uses `DefaultFlavor` to build SQL. The default value is `MySQL`.
112112

@@ -116,7 +116,7 @@ We can wrap any `Builder` with a default flavor through `WithFlavor`.
116116

117117
To be more verbose, we can use `PostgreSQL.NewSelectBuilder()` to create a `SelectBuilder` with the `PostgreSQL` flavor. All builders can be created in this way.
118118

119-
Right now, there are only three flavors, `MySQL`, `PostgreSQL` and `SQLite`. Open new issue to me to ask for a new flavor if you find it necessary.
119+
Right now, there are only three flavors, `MySQL`, `PostgreSQL`, `SQLServer` and `SQLite`. Open new issue to me to ask for a new flavor if you find it necessary.
120120

121121
### Using `Struct` as a light weight ORM
122122

args.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,9 @@ func (args *Args) compileArg(buf *bytes.Buffer, flavor Flavor, values []interfac
236236
case MySQL, SQLite:
237237
buf.WriteRune('?')
238238
case PostgreSQL:
239-
fmt.Fprintf(buf, "$%v", len(values)+1)
239+
fmt.Fprintf(buf, "$%d", len(values)+1)
240+
case SQLServer:
241+
fmt.Fprintf(buf, "@p%d", len(values)+1)
240242
default:
241243
panic(fmt.Errorf("Args.CompileWithFlavor: invalid flavor %v (%v)", flavor, int(flavor)))
242244
}

args_test.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,12 @@ func TestArgs(t *testing.T) {
4040
}
4141

4242
old := DefaultFlavor
43-
DefaultFlavor = PostgreSQL
4443
defer func() {
4544
DefaultFlavor = old
4645
}()
4746

47+
DefaultFlavor = PostgreSQL
48+
4849
// PostgreSQL flavor compiled sql.
4950
for expected, c := range cases {
5051
args := new(Args)
@@ -59,6 +60,23 @@ func TestArgs(t *testing.T) {
5960

6061
a.Equal(actual, expected)
6162
}
63+
64+
DefaultFlavor = SQLServer
65+
66+
// SQLServer flavor compiled sql.
67+
for expected, c := range cases {
68+
args := new(Args)
69+
70+
for i := 1; i < len(c); i++ {
71+
args.Add(c[i])
72+
}
73+
74+
sql, values := args.Compile(c[0].(string))
75+
actual := fmt.Sprintf("%v\n%v", sql, values)
76+
expected = toSQLServerSQL(expected)
77+
78+
a.Equal(actual, expected)
79+
}
6280
}
6381

6482
func toPostgreSQL(sql string) string {
@@ -74,6 +92,19 @@ func toPostgreSQL(sql string) string {
7492
return buf.String()
7593
}
7694

95+
func toSQLServerSQL(sql string) string {
96+
parts := strings.Split(sql, "?")
97+
buf := &bytes.Buffer{}
98+
buf.WriteString(parts[0])
99+
100+
for i, p := range parts[1:] {
101+
fmt.Fprintf(buf, "@p%v", i+1)
102+
buf.WriteString(p)
103+
}
104+
105+
return buf.String()
106+
}
107+
77108
func TestArgsAdd(t *testing.T) {
78109
a := assert.New(t)
79110
args := &Args{}

flavor.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const (
1515
MySQL
1616
PostgreSQL
1717
SQLite
18+
SQLServer
1819
)
1920

2021
var (
@@ -46,6 +47,8 @@ func (f Flavor) String() string {
4647
return "PostgreSQL"
4748
case SQLite:
4849
return "SQLite"
50+
case SQLServer:
51+
return "SQLServer"
4952
}
5053

5154
return "<invalid>"
@@ -64,6 +67,8 @@ func (f Flavor) Interpolate(sql string, args []interface{}) (string, error) {
6467
return postgresqlInterpolate(sql, args...)
6568
case SQLite:
6669
return sqliteInterpolate(sql, args...)
70+
case SQLServer:
71+
return sqlserverInterpolate(sql, args...)
6772
}
6873

6974
return "", ErrInterpolateNotImplemented
@@ -114,13 +119,13 @@ func (f Flavor) NewUnionBuilder() *UnionBuilder {
114119
// Quote adds quote for name to make sure the name can be used safely
115120
// as table name or field name.
116121
//
117-
// * For MySQL, use back quote (`) to quote name;
118-
// * For PostgreSQL and SQLite, use double quote (") to quote name.
122+
// * For MySQL, use back quote (`) to quote name;
123+
// * For PostgreSQL, SQL Server and SQLite, use double quote (") to quote name.
119124
func (f Flavor) Quote(name string) string {
120125
switch f {
121126
case MySQL:
122127
return fmt.Sprintf("`%s`", name)
123-
case PostgreSQL, SQLite:
128+
case PostgreSQL, SQLServer, SQLite:
124129
return fmt.Sprintf(`"%s"`, name)
125130
}
126131

flavor_test.go

Lines changed: 19 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ package sqlbuilder
55

66
import (
77
"fmt"
8-
"strconv"
98
"testing"
10-
"time"
119

1210
"github.com/huandu/go-assert"
1311
)
@@ -19,6 +17,7 @@ func TestFlavor(t *testing.T) {
1917
MySQL: "MySQL",
2018
PostgreSQL: "PostgreSQL",
2119
SQLite: "SQLite",
20+
SQLServer: "SQLServer",
2221
}
2322

2423
for f, expected := range cases {
@@ -27,120 +26,6 @@ func TestFlavor(t *testing.T) {
2726
}
2827
}
2928

30-
func TestFlavorInterpolate(t *testing.T) {
31-
a := assert.New(t)
32-
dt := time.Date(2019, 4, 24, 12, 23, 34, 123456789, time.FixedZone("CST", 8*60*60)) // 2019-04-24 12:23:34.987654321 CST
33-
_, errOutOfRange := strconv.ParseInt("12345678901234567890", 10, 32)
34-
cases := []struct {
35-
flavor Flavor
36-
sql string
37-
args []interface{}
38-
query string
39-
err error
40-
}{
41-
{
42-
MySQL,
43-
"SELECT * FROM a WHERE name = ? AND state IN (?, ?, ?, ?, ?)", []interface{}{"I'm fine", 42, int8(8), int16(-16), int32(32), int64(64)},
44-
"SELECT * FROM a WHERE name = 'I\\'m fine' AND state IN (42, 8, -16, 32, 64)", nil,
45-
},
46-
{
47-
MySQL,
48-
"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"},
49-
"SELECT * FROM `a?` WHERE name = \"?\" AND state IN ('\\r\\n\\b\\t\\Z\\0\\\\\\\"\\'', '?', 42, 8, 16, 32, 64)", nil,
50-
},
51-
{
52-
MySQL,
53-
"SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?", []interface{}{true, false, float32(1.234567), float64(9.87654321), []byte(nil), []byte("I'm bytes"), dt, time.Time{}, nil},
54-
"SELECT TRUE, FALSE, 1.234567, 9.87654321, NULL, _binary'I\\'m bytes', '2019-04-24 12:23:34.123457', '0000-00-00', NULL", nil,
55-
},
56-
{
57-
MySQL,
58-
"SELECT '\\'?', \"\\\"?\", `\\`?`, \\?", []interface{}{MySQL},
59-
"SELECT '\\'?', \"\\\"?\", `\\`?`, \\'MySQL'", nil,
60-
},
61-
{
62-
MySQL,
63-
"SELECT ?", nil,
64-
"", ErrInterpolateMissingArgs,
65-
},
66-
{
67-
MySQL,
68-
"SELECT ?", []interface{}{complex(1, 2)},
69-
"", ErrInterpolateUnsupportedArgs,
70-
},
71-
72-
{
73-
PostgreSQL,
74-
"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)},
75-
"SELECT * FROM a WHERE name = 8 AND state IN (42, -16, E'I\\'m fine', 64, 32)", nil,
76-
},
77-
{
78-
PostgreSQL,
79-
"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"},
80-
"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,
81-
},
82-
{
83-
PostgreSQL,
84-
"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},
85-
"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,
86-
},
87-
{
88-
PostgreSQL,
89-
"SELECT '\\'$1', \"\\\"$1\", `$1`, \\$1a, $$1$$, $a $b$ $a $ $1$b$1$1 $a$ $", []interface{}{MySQL},
90-
"SELECT '\\'$1', \"\\\"$1\", `E'MySQL'`, \\E'MySQL'a, $$1$$, $a $b$ $a $ $1$b$1E'MySQL' $a$ $", nil,
91-
},
92-
{
93-
PostgreSQL,
94-
"SELECT * FROM a WHERE name = 'Huan''Du''$1' AND desc = $1", []interface{}{"c'mon"},
95-
"SELECT * FROM a WHERE name = 'Huan''Du''$1' AND desc = E'c\\'mon'", nil,
96-
},
97-
{
98-
PostgreSQL,
99-
"SELECT $1", nil,
100-
"", ErrInterpolateMissingArgs,
101-
},
102-
{
103-
PostgreSQL,
104-
"SELECT $1", []interface{}{complex(1, 2)},
105-
"", ErrInterpolateUnsupportedArgs,
106-
},
107-
{
108-
PostgreSQL,
109-
"SELECT $12345678901234567890", nil,
110-
"", errOutOfRange,
111-
},
112-
113-
{
114-
SQLite,
115-
"SELECT * FROM a WHERE name = ? AND state IN (?, ?, ?, ?, ?)", []interface{}{"I'm fine", 42, int8(8), int16(-16), int32(32), int64(64)},
116-
"SELECT * FROM a WHERE name = 'I\\'m fine' AND state IN (42, 8, -16, 32, 64)", nil,
117-
},
118-
{
119-
SQLite,
120-
"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"},
121-
"SELECT * FROM `a?` WHERE name = \"?\" AND state IN ('\\r\\n\\b\\t\\Z\\0\\\\\\\"\\'', '?', 42, 8, 16, 32, 64)", nil,
122-
},
123-
{
124-
SQLite,
125-
"SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?", []interface{}{true, false, float32(1.234567), float64(9.87654321), []byte(nil), []byte("I'm bytes"), dt, time.Time{}, nil},
126-
"SELECT TRUE, FALSE, 1.234567, 9.87654321, NULL, X'49276D206279746573', '2019-04-24 12:23:34.123', '0000-00-00', NULL", nil,
127-
},
128-
{
129-
SQLite,
130-
"SELECT '\\'?', \"\\\"?\", `\\`?`, \\?", []interface{}{SQLite},
131-
"SELECT '\\'?', \"\\\"?\", `\\`?`, \\'SQLite'", nil,
132-
},
133-
}
134-
135-
for idx, c := range cases {
136-
a.Use(&idx, &c)
137-
query, err := c.flavor.Interpolate(c.sql, c.args)
138-
139-
a.Equal(query, c.query)
140-
a.Assert(err == c.err || err.Error() == c.err.Error())
141-
}
142-
}
143-
14429
func ExampleFlavor() {
14530
// Create a flavored builder.
14631
sb := PostgreSQL.NewSelectBuilder()
@@ -218,3 +103,21 @@ func ExampleFlavor_Interpolate_sqlite() {
218103
// SELECT name FROM user WHERE id <> 1234 AND name = 'Charmy Liu' AND desc LIKE '%mother\'s day%'
219104
// <nil>
220105
}
106+
107+
func ExampleFlavor_Interpolate_sqlServer() {
108+
sb := SQLServer.NewSelectBuilder()
109+
sb.Select("name").From("user").Where(
110+
sb.NE("id", 1234),
111+
sb.E("name", "Charmy Liu"),
112+
sb.Like("desc", "%mother's day%"),
113+
)
114+
sql, args := sb.Build()
115+
query, err := SQLServer.Interpolate(sql, args)
116+
117+
fmt.Println(query)
118+
fmt.Println(err)
119+
120+
// Output:
121+
// SELECT name FROM user WHERE id <> 1234 AND name = N'Charmy Liu' AND desc LIKE N'%mother\'s day%'
122+
// <nil>
123+
}

0 commit comments

Comments
 (0)