Skip to content

Commit dc8bf48

Browse files
committed
add StringBuilder to improve performance and add ConvertPlaceHolder function
1 parent 92cb06f commit dc8bf48

File tree

4 files changed

+169
-5
lines changed

4 files changed

+169
-5
lines changed

builder.go

+31
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
package builder
66

7+
import (
8+
"fmt"
9+
)
10+
711
type optype byte
812

913
const (
@@ -186,6 +190,33 @@ func (b *Builder) ToSQL() (string, []interface{}, error) {
186190
return w.writer.String(), w.args, nil
187191
}
188192

193+
// ConvertPlaceholder replaces ? to $1, $2 ... or :1, :2 ... according prefix
194+
func ConvertPlaceholder(sql, prefix string) (string, error) {
195+
buf := StringBuilder{}
196+
var j, start = 0, 0
197+
for i := 0; i < len(sql); i++ {
198+
if sql[i] == '?' {
199+
_, err := buf.WriteString(sql[start:i])
200+
if err != nil {
201+
return "", err
202+
}
203+
start = i + 1
204+
205+
_, err = buf.WriteString(prefix)
206+
if err != nil {
207+
return "", err
208+
}
209+
210+
j = j + 1
211+
_, err = buf.WriteString(fmt.Sprintf("%d", j))
212+
if err != nil {
213+
return "", err
214+
}
215+
}
216+
}
217+
return buf.String(), nil
218+
}
219+
189220
// ToSQL convert a builder or condtions to SQL and args
190221
func ToSQL(cond interface{}) (string, []interface{}, error) {
191222
switch cond.(type) {

builder_test.go

+15
Original file line numberDiff line numberDiff line change
@@ -460,3 +460,18 @@ func TestExprCond(t *testing.T) {
460460
assert.EqualValues(t, "SELECT id FROM table1 WHERE (a=? OR b=?) AND (c=? OR d=?)", sql)
461461
assert.EqualValues(t, []interface{}{1, 2, 3, 4}, args)
462462
}
463+
464+
const placeholderConverterSQL = "SELECT a, b FROM table_a WHERE b_id=(SELECT id FROM table_b WHERE b=?) AND id=? AND c=? AND d=? AND e=? AND f=?"
465+
const placeholderConvertedSQL = "SELECT a, b FROM table_a WHERE b_id=(SELECT id FROM table_b WHERE b=$1) AND id=$2 AND c=$3 AND d=$4 AND e=$5 AND f=$6"
466+
467+
func TestPlaceholderConverter(t *testing.T) {
468+
newSQL, err := ConvertPlaceholder(placeholderConverterSQL, "$")
469+
assert.NoError(t, err)
470+
assert.EqualValues(t, placeholderConvertedSQL, newSQL)
471+
}
472+
473+
func BenchmarkPlaceholderConverter(b *testing.B) {
474+
for i := 0; i < b.N; i++ {
475+
ConvertPlaceholder(placeholderConverterSQL, "$")
476+
}
477+
}

cond.go

+4-5
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
package builder
66

77
import (
8-
"bytes"
98
"io"
109
)
1110

@@ -19,15 +18,15 @@ var _ Writer = NewWriter()
1918

2019
// BytesWriter implments Writer and save SQL in bytes.Buffer
2120
type BytesWriter struct {
22-
writer *bytes.Buffer
23-
buffer []byte
21+
writer *StringBuilder
2422
args []interface{}
2523
}
2624

2725
// NewWriter creates a new string writer
2826
func NewWriter() *BytesWriter {
29-
w := &BytesWriter{}
30-
w.writer = bytes.NewBuffer(w.buffer)
27+
w := &BytesWriter{
28+
writer: &StringBuilder{},
29+
}
3130
return w
3231
}
3332

strings_builder.go

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Copyright 2017 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package builder
6+
7+
import (
8+
"unicode/utf8"
9+
"unsafe"
10+
)
11+
12+
// A StringBuilder is used to efficiently build a string using Write methods.
13+
// It minimizes memory copying. The zero value is ready to use.
14+
// Do not copy a non-zero Builder.
15+
type StringBuilder struct {
16+
addr *StringBuilder // of receiver, to detect copies by value
17+
buf []byte
18+
}
19+
20+
// noescape hides a pointer from escape analysis. noescape is
21+
// the identity function but escape analysis doesn't think the
22+
// output depends on the input. noescape is inlined and currently
23+
// compiles down to zero instructions.
24+
// USE CAREFULLY!
25+
// This was copied from the runtime; see issues 23382 and 7921.
26+
//go:nosplit
27+
func noescape(p unsafe.Pointer) unsafe.Pointer {
28+
x := uintptr(p)
29+
return unsafe.Pointer(x ^ 0)
30+
}
31+
32+
func (b *StringBuilder) copyCheck() {
33+
if b.addr == nil {
34+
// This hack works around a failing of Go's escape analysis
35+
// that was causing b to escape and be heap allocated.
36+
// See issue 23382.
37+
// TODO: once issue 7921 is fixed, this should be reverted to
38+
// just "b.addr = b".
39+
b.addr = (*StringBuilder)(noescape(unsafe.Pointer(b)))
40+
} else if b.addr != b {
41+
panic("strings: illegal use of non-zero Builder copied by value")
42+
}
43+
}
44+
45+
// String returns the accumulated string.
46+
func (b *StringBuilder) String() string {
47+
return *(*string)(unsafe.Pointer(&b.buf))
48+
}
49+
50+
// Len returns the number of accumulated bytes; b.Len() == len(b.String()).
51+
func (b *StringBuilder) Len() int { return len(b.buf) }
52+
53+
// Reset resets the Builder to be empty.
54+
func (b *StringBuilder) Reset() {
55+
b.addr = nil
56+
b.buf = nil
57+
}
58+
59+
// grow copies the buffer to a new, larger buffer so that there are at least n
60+
// bytes of capacity beyond len(b.buf).
61+
func (b *StringBuilder) grow(n int) {
62+
buf := make([]byte, len(b.buf), 2*cap(b.buf)+n)
63+
copy(buf, b.buf)
64+
b.buf = buf
65+
}
66+
67+
// Grow grows b's capacity, if necessary, to guarantee space for
68+
// another n bytes. After Grow(n), at least n bytes can be written to b
69+
// without another allocation. If n is negative, Grow panics.
70+
func (b *StringBuilder) Grow(n int) {
71+
b.copyCheck()
72+
if n < 0 {
73+
panic("strings.Builder.Grow: negative count")
74+
}
75+
if cap(b.buf)-len(b.buf) < n {
76+
b.grow(n)
77+
}
78+
}
79+
80+
// Write appends the contents of p to b's buffer.
81+
// Write always returns len(p), nil.
82+
func (b *StringBuilder) Write(p []byte) (int, error) {
83+
b.copyCheck()
84+
b.buf = append(b.buf, p...)
85+
return len(p), nil
86+
}
87+
88+
// WriteByte appends the byte c to b's buffer.
89+
// The returned error is always nil.
90+
func (b *StringBuilder) WriteByte(c byte) error {
91+
b.copyCheck()
92+
b.buf = append(b.buf, c)
93+
return nil
94+
}
95+
96+
// WriteRune appends the UTF-8 encoding of Unicode code point r to b's buffer.
97+
// It returns the length of r and a nil error.
98+
func (b *StringBuilder) WriteRune(r rune) (int, error) {
99+
b.copyCheck()
100+
if r < utf8.RuneSelf {
101+
b.buf = append(b.buf, byte(r))
102+
return 1, nil
103+
}
104+
l := len(b.buf)
105+
if cap(b.buf)-l < utf8.UTFMax {
106+
b.grow(utf8.UTFMax)
107+
}
108+
n := utf8.EncodeRune(b.buf[l:l+utf8.UTFMax], r)
109+
b.buf = b.buf[:l+n]
110+
return n, nil
111+
}
112+
113+
// WriteString appends the contents of s to b's buffer.
114+
// It returns the length of s and a nil error.
115+
func (b *StringBuilder) WriteString(s string) (int, error) {
116+
b.copyCheck()
117+
b.buf = append(b.buf, s...)
118+
return len(s), nil
119+
}

0 commit comments

Comments
 (0)