Skip to content

Commit 43536c7

Browse files
committed
Specialize escape functions for string
benchmark old ns/op new ns/op delta BenchmarkInterpolation 2463 2118 -14.01% benchmark old allocs new allocs delta BenchmarkInterpolation 3 2 -33.33% benchmark old bytes new bytes delta BenchmarkInterpolation 496 448 -9.68%
1 parent 88aeb98 commit 43536c7

File tree

3 files changed

+96
-9
lines changed

3 files changed

+96
-9
lines changed

connection.go

+13-3
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,19 @@ func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
169169
func (mc *mysqlConn) escapeBytes(buf, v []byte) []byte {
170170
buf = append(buf, '\'')
171171
if mc.status&statusNoBackslashEscapes == 0 {
172-
buf = escapeBackslash(buf, v)
172+
buf = escapeBytesBackslash(buf, v)
173173
} else {
174-
buf = escapeQuotes(buf, v)
174+
buf = escapeBytesQuotes(buf, v)
175+
}
176+
return append(buf, '\'')
177+
}
178+
179+
func (mc *mysqlConn) escapeString(buf []byte, v string) []byte {
180+
buf = append(buf, '\'')
181+
if mc.status&statusNoBackslashEscapes == 0 {
182+
buf = escapeStringBackslash(buf, v)
183+
} else {
184+
buf = escapeStringQuotes(buf, v)
175185
}
176186
return append(buf, '\'')
177187
}
@@ -293,7 +303,7 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
293303
buf = mc.escapeBytes(buf, v)
294304
}
295305
case string:
296-
buf = mc.escapeBytes(buf, []byte(v))
306+
buf = mc.escapeString(buf, v)
297307
default:
298308
return "", driver.ErrSkip
299309
}

utils.go

+81-4
Original file line numberDiff line numberDiff line change
@@ -807,12 +807,12 @@ func appendLengthEncodedInteger(b []byte, n uint64) []byte {
807807
byte(n>>32), byte(n>>40), byte(n>>48), byte(n>>56))
808808
}
809809

810-
// Escape string with backslashes (\)
810+
// escapeBytesBackslash escapes []byte with backslashes (\)
811811
// This escapes the contents of a string (provided as []byte) by adding backslashes before special
812812
// characters, and turning others into specific escape sequences, such as
813813
// turning newlines into \n and null bytes into \0.
814814
// https://github.com/mysql/mysql-server/blob/mysql-5.7.5/mysys/charset.c#L823-L932
815-
func escapeBackslash(buf, v []byte) []byte {
815+
func escapeBytesBackslash(buf, v []byte) []byte {
816816
pos := len(buf)
817817
end := pos + len(v)*2
818818
if cap(buf) < end {
@@ -861,12 +861,63 @@ func escapeBackslash(buf, v []byte) []byte {
861861
return buf[:pos]
862862
}
863863

864-
// Escape apostrophes by doubling them up
864+
// escapeStringBackslash is similar to escapeBytesBackslash but for string.
865+
func escapeStringBackslash(buf []byte, v string) []byte {
866+
pos := len(buf)
867+
end := pos + len(v)*2
868+
if cap(buf) < end {
869+
n := make([]byte, pos+end)
870+
copy(n, buf)
871+
buf = n
872+
}
873+
buf = buf[0:end]
874+
875+
for i := 0; i < len(v); i++ {
876+
c := v[i]
877+
switch c {
878+
case '\x00':
879+
buf[pos] = '\\'
880+
buf[pos+1] = '0'
881+
pos += 2
882+
case '\n':
883+
buf[pos] = '\\'
884+
buf[pos+1] = 'n'
885+
pos += 2
886+
case '\r':
887+
buf[pos] = '\\'
888+
buf[pos+1] = 'r'
889+
pos += 2
890+
case '\x1a':
891+
buf[pos] = '\\'
892+
buf[pos+1] = 'Z'
893+
pos += 2
894+
case '\'':
895+
buf[pos] = '\\'
896+
buf[pos+1] = '\''
897+
pos += 2
898+
case '"':
899+
buf[pos] = '\\'
900+
buf[pos+1] = '"'
901+
pos += 2
902+
case '\\':
903+
buf[pos] = '\\'
904+
buf[pos+1] = '\\'
905+
pos += 2
906+
default:
907+
buf[pos] = c
908+
pos += 1
909+
}
910+
}
911+
912+
return buf[:pos]
913+
}
914+
915+
// escapeBytesQuotes escapes apostrophes in []byte by doubling them up.
865916
// This escapes the contents of a string by doubling up any apostrophes that
866917
// it contains. This is used when the NO_BACKSLASH_ESCAPES SQL_MODE is in
867918
// effect on the server.
868919
// https://github.com/mysql/mysql-server/blob/mysql-5.7.5/mysys/charset.c#L963-L1038
869-
func escapeQuotes(buf, v []byte) []byte {
920+
func escapeBytesQuotes(buf, v []byte) []byte {
870921
pos := len(buf)
871922
end := pos + len(v)*2
872923
if cap(buf) < end {
@@ -889,3 +940,29 @@ func escapeQuotes(buf, v []byte) []byte {
889940

890941
return buf[:pos]
891942
}
943+
944+
// escapeStringQuotes is similar to escapeBytesQuotes but for string.
945+
func escapeStringQuotes(buf []byte, v string) []byte {
946+
pos := len(buf)
947+
end := pos + len(v)*2
948+
if cap(buf) < end {
949+
n := make([]byte, pos+end)
950+
copy(n, buf)
951+
buf = n
952+
}
953+
buf = buf[0:end]
954+
955+
for i := 0; i < len(v); i++ {
956+
c := v[i]
957+
if c == '\'' {
958+
buf[pos] = '\''
959+
buf[pos+1] = '\''
960+
pos += 2
961+
} else {
962+
buf[pos] = c
963+
pos++
964+
}
965+
}
966+
967+
return buf[:pos]
968+
}

utils_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ func TestFormatBinaryDateTime(t *testing.T) {
255255

256256
func TestEscapeBackslash(t *testing.T) {
257257
expect := func(expected, value string) {
258-
actual := string(escapeBackslash([]byte{}, []byte(value)))
258+
actual := string(escapeBytesBackslash([]byte{}, []byte(value)))
259259
if actual != expected {
260260
t.Errorf(
261261
"expected %s, got %s",
@@ -275,7 +275,7 @@ func TestEscapeBackslash(t *testing.T) {
275275

276276
func TestEscapeQuotes(t *testing.T) {
277277
expect := func(expected, value string) {
278-
actual := string(escapeQuotes([]byte{}, []byte(value)))
278+
actual := string(escapeBytesQuotes([]byte{}, []byte(value)))
279279
if actual != expected {
280280
t.Errorf(
281281
"expected %s, got %s",

0 commit comments

Comments
 (0)