Skip to content

Commit a927dec

Browse files
authored
Use a string builder for queries instead of scanner (#25)
* Use a string builder for queries instead of scanner * Update coverage.svg and coverage.out --------- Co-authored-by: nullism <[email protected]>
1 parent 04c2cbe commit a927dec

File tree

4 files changed

+125
-328
lines changed

4 files changed

+125
-328
lines changed

coverage.out

Lines changed: 73 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -50,95 +50,76 @@ github.com/nullism/bqb/query.go:176.2,176.28 1 1
5050
github.com/nullism/bqb/query.go:176.28,180.23 3 1
5151
github.com/nullism/bqb/query.go:180.23,182.4 1 1
5252
github.com/nullism/bqb/query.go:185.2,185.44 1 1
53-
github.com/nullism/bqb/utils.go:13.80,20.17 2 1
54-
github.com/nullism/bqb/utils.go:21.11,23.32 2 1
55-
github.com/nullism/bqb/utils.go:23.32,25.18 2 1
56-
github.com/nullism/bqb/utils.go:25.18,27.5 1 1
57-
github.com/nullism/bqb/utils.go:28.4,28.15 1 1
58-
github.com/nullism/bqb/utils.go:31.3,33.63 1 1
59-
github.com/nullism/bqb/utils.go:33.63,33.81 1 1
60-
github.com/nullism/bqb/utils.go:35.18,38.61 1 1
61-
github.com/nullism/bqb/utils.go:38.61,38.84 1 1
62-
github.com/nullism/bqb/utils.go:40.13,43.68 1 1
63-
github.com/nullism/bqb/utils.go:43.68,43.91 1 1
64-
github.com/nullism/bqb/utils.go:44.63,44.97 1 1
65-
github.com/nullism/bqb/utils.go:46.10,48.18 1 1
66-
github.com/nullism/bqb/utils.go:61.62,63.23 2 1
67-
github.com/nullism/bqb/utils.go:63.23,67.3 3 1
68-
github.com/nullism/bqb/utils.go:68.2,68.33 1 1
69-
github.com/nullism/bqb/utils.go:71.77,75.85 3 1
70-
github.com/nullism/bqb/utils.go:75.85,77.30 1 1
71-
github.com/nullism/bqb/utils.go:77.30,79.4 1 1
72-
github.com/nullism/bqb/utils.go:81.3,81.38 1 1
73-
github.com/nullism/bqb/utils.go:82.15,83.40 1 1
74-
github.com/nullism/bqb/utils.go:84.14,85.28 1 1
75-
github.com/nullism/bqb/utils.go:90.3,90.12 1 1
76-
github.com/nullism/bqb/utils.go:90.12,92.4 1 1
77-
github.com/nullism/bqb/utils.go:94.3,94.9 1 1
78-
github.com/nullism/bqb/utils.go:97.2,101.21 3 1
79-
github.com/nullism/bqb/utils.go:101.21,102.37 1 1
80-
github.com/nullism/bqb/utils.go:103.16,107.7 2 1
81-
github.com/nullism/bqb/utils.go:109.11,112.30 1 1
82-
github.com/nullism/bqb/utils.go:116.2,116.35 1 1
83-
github.com/nullism/bqb/utils.go:119.64,123.25 3 1
84-
github.com/nullism/bqb/utils.go:125.16,126.53 1 1
85-
github.com/nullism/bqb/utils.go:128.21,131.17 3 1
86-
github.com/nullism/bqb/utils.go:131.17,133.4 1 1
87-
github.com/nullism/bqb/utils.go:133.9,135.4 1 1
88-
github.com/nullism/bqb/utils.go:136.13,138.23 2 1
89-
github.com/nullism/bqb/utils.go:138.23,141.4 2 1
90-
github.com/nullism/bqb/utils.go:142.3,142.65 1 1
91-
github.com/nullism/bqb/utils.go:144.14,146.23 2 1
92-
github.com/nullism/bqb/utils.go:146.23,149.4 2 1
93-
github.com/nullism/bqb/utils.go:150.3,150.21 1 1
94-
github.com/nullism/bqb/utils.go:150.21,152.4 1 1
95-
github.com/nullism/bqb/utils.go:152.9,155.4 2 1
96-
github.com/nullism/bqb/utils.go:157.16,159.23 2 1
97-
github.com/nullism/bqb/utils.go:159.23,162.4 2 1
98-
github.com/nullism/bqb/utils.go:163.3,163.65 1 1
99-
github.com/nullism/bqb/utils.go:165.17,167.23 2 1
100-
github.com/nullism/bqb/utils.go:167.23,170.4 2 1
101-
github.com/nullism/bqb/utils.go:171.3,171.21 1 1
102-
github.com/nullism/bqb/utils.go:171.21,173.4 1 1
103-
github.com/nullism/bqb/utils.go:173.9,176.4 2 1
104-
github.com/nullism/bqb/utils.go:178.13,180.23 2 1
105-
github.com/nullism/bqb/utils.go:180.23,183.4 2 1
106-
github.com/nullism/bqb/utils.go:184.3,184.65 1 1
107-
github.com/nullism/bqb/utils.go:186.14,187.15 1 1
108-
github.com/nullism/bqb/utils.go:187.15,191.4 3 1
109-
github.com/nullism/bqb/utils.go:192.3,194.17 3 1
110-
github.com/nullism/bqb/utils.go:194.17,196.4 1 1
111-
github.com/nullism/bqb/utils.go:196.9,198.4 1 1
112-
github.com/nullism/bqb/utils.go:200.25,203.17 3 1
113-
github.com/nullism/bqb/utils.go:203.17,205.4 1 1
114-
github.com/nullism/bqb/utils.go:205.9,207.4 1 1
115-
github.com/nullism/bqb/utils.go:209.27,212.17 3 1
116-
github.com/nullism/bqb/utils.go:212.17,214.4 1 1
117-
github.com/nullism/bqb/utils.go:214.9,216.4 1 1
118-
github.com/nullism/bqb/utils.go:218.16,219.50 1 1
119-
github.com/nullism/bqb/utils.go:221.10,223.31 2 1
120-
github.com/nullism/bqb/utils.go:226.2,226.28 1 1
121-
github.com/nullism/bqb/utils.go:229.64,231.20 2 1
122-
github.com/nullism/bqb/utils.go:231.20,233.3 1 1
123-
github.com/nullism/bqb/utils.go:235.2,236.28 2 1
124-
github.com/nullism/bqb/utils.go:236.28,238.3 1 1
125-
github.com/nullism/bqb/utils.go:239.2,239.12 1 1
126-
github.com/nullism/bqb/utils.go:242.51,250.27 6 1
127-
github.com/nullism/bqb/utils.go:250.27,252.23 2 1
128-
github.com/nullism/bqb/utils.go:252.23,254.4 1 1
129-
github.com/nullism/bqb/utils.go:255.3,256.17 2 1
130-
github.com/nullism/bqb/utils.go:259.2,259.70 1 1
131-
github.com/nullism/bqb/utils.go:259.70,261.3 1 1
132-
github.com/nullism/bqb/utils.go:263.2,269.3 2 1
133-
github.com/nullism/bqb/utils.go:272.44,273.27 1 1
134-
github.com/nullism/bqb/utils.go:274.12,275.35 1 1
135-
github.com/nullism/bqb/utils.go:277.33,278.35 1 1
136-
github.com/nullism/bqb/utils.go:279.12,280.15 1 1
137-
github.com/nullism/bqb/utils.go:280.15,282.4 1 1
138-
github.com/nullism/bqb/utils.go:283.3,283.36 1 1
139-
github.com/nullism/bqb/utils.go:284.14,285.37 1 1
140-
github.com/nullism/bqb/utils.go:286.15,287.15 1 1
141-
github.com/nullism/bqb/utils.go:287.15,289.4 1 1
142-
github.com/nullism/bqb/utils.go:290.3,290.38 1 1
143-
github.com/nullism/bqb/utils.go:291.11,292.21 1 1
144-
github.com/nullism/bqb/utils.go:293.10,294.65 1 1
53+
github.com/nullism/bqb/utils.go:11.80,18.17 2 1
54+
github.com/nullism/bqb/utils.go:19.11,20.32 1 1
55+
github.com/nullism/bqb/utils.go:20.32,22.18 2 1
56+
github.com/nullism/bqb/utils.go:22.18,24.5 1 1
57+
github.com/nullism/bqb/utils.go:25.4,25.45 1 1
58+
github.com/nullism/bqb/utils.go:27.3,27.18 1 1
59+
github.com/nullism/bqb/utils.go:28.18,29.61 1 1
60+
github.com/nullism/bqb/utils.go:30.13,34.25 4 1
61+
github.com/nullism/bqb/utils.go:34.25,36.4 1 1
62+
github.com/nullism/bqb/utils.go:37.3,38.31 2 1
63+
github.com/nullism/bqb/utils.go:39.10,41.18 1 1
64+
github.com/nullism/bqb/utils.go:45.64,49.25 3 1
65+
github.com/nullism/bqb/utils.go:51.16,52.53 1 1
66+
github.com/nullism/bqb/utils.go:54.21,57.17 3 1
67+
github.com/nullism/bqb/utils.go:57.17,59.4 1 1
68+
github.com/nullism/bqb/utils.go:59.9,61.4 1 1
69+
github.com/nullism/bqb/utils.go:62.13,64.23 2 1
70+
github.com/nullism/bqb/utils.go:64.23,67.4 2 1
71+
github.com/nullism/bqb/utils.go:68.3,68.65 1 1
72+
github.com/nullism/bqb/utils.go:70.14,72.23 2 1
73+
github.com/nullism/bqb/utils.go:72.23,75.4 2 1
74+
github.com/nullism/bqb/utils.go:76.3,76.21 1 1
75+
github.com/nullism/bqb/utils.go:76.21,78.4 1 1
76+
github.com/nullism/bqb/utils.go:78.9,81.4 2 1
77+
github.com/nullism/bqb/utils.go:83.16,85.23 2 1
78+
github.com/nullism/bqb/utils.go:85.23,88.4 2 1
79+
github.com/nullism/bqb/utils.go:89.3,89.65 1 1
80+
github.com/nullism/bqb/utils.go:91.17,93.23 2 1
81+
github.com/nullism/bqb/utils.go:93.23,96.4 2 1
82+
github.com/nullism/bqb/utils.go:97.3,97.21 1 1
83+
github.com/nullism/bqb/utils.go:97.21,99.4 1 1
84+
github.com/nullism/bqb/utils.go:99.9,102.4 2 1
85+
github.com/nullism/bqb/utils.go:104.13,106.23 2 1
86+
github.com/nullism/bqb/utils.go:106.23,109.4 2 1
87+
github.com/nullism/bqb/utils.go:110.3,110.65 1 1
88+
github.com/nullism/bqb/utils.go:112.14,113.15 1 1
89+
github.com/nullism/bqb/utils.go:113.15,117.4 3 1
90+
github.com/nullism/bqb/utils.go:118.3,120.17 3 1
91+
github.com/nullism/bqb/utils.go:120.17,122.4 1 1
92+
github.com/nullism/bqb/utils.go:122.9,124.4 1 1
93+
github.com/nullism/bqb/utils.go:126.25,129.17 3 1
94+
github.com/nullism/bqb/utils.go:129.17,131.4 1 1
95+
github.com/nullism/bqb/utils.go:131.9,133.4 1 1
96+
github.com/nullism/bqb/utils.go:135.27,138.17 3 1
97+
github.com/nullism/bqb/utils.go:138.17,140.4 1 1
98+
github.com/nullism/bqb/utils.go:140.9,142.4 1 1
99+
github.com/nullism/bqb/utils.go:144.16,145.50 1 1
100+
github.com/nullism/bqb/utils.go:147.10,149.31 2 1
101+
github.com/nullism/bqb/utils.go:152.2,152.28 1 1
102+
github.com/nullism/bqb/utils.go:155.64,157.20 2 1
103+
github.com/nullism/bqb/utils.go:157.20,159.3 1 1
104+
github.com/nullism/bqb/utils.go:161.2,162.28 2 1
105+
github.com/nullism/bqb/utils.go:162.28,164.3 1 1
106+
github.com/nullism/bqb/utils.go:165.2,165.12 1 1
107+
github.com/nullism/bqb/utils.go:168.51,176.27 6 1
108+
github.com/nullism/bqb/utils.go:176.27,178.23 2 1
109+
github.com/nullism/bqb/utils.go:178.23,180.4 1 1
110+
github.com/nullism/bqb/utils.go:181.3,182.17 2 1
111+
github.com/nullism/bqb/utils.go:185.2,185.70 1 1
112+
github.com/nullism/bqb/utils.go:185.70,187.3 1 1
113+
github.com/nullism/bqb/utils.go:189.2,195.3 2 1
114+
github.com/nullism/bqb/utils.go:198.44,199.27 1 1
115+
github.com/nullism/bqb/utils.go:200.12,201.35 1 1
116+
github.com/nullism/bqb/utils.go:203.33,204.35 1 1
117+
github.com/nullism/bqb/utils.go:205.12,206.15 1 1
118+
github.com/nullism/bqb/utils.go:206.15,208.4 1 1
119+
github.com/nullism/bqb/utils.go:209.3,209.36 1 1
120+
github.com/nullism/bqb/utils.go:210.14,211.37 1 1
121+
github.com/nullism/bqb/utils.go:212.15,213.15 1 1
122+
github.com/nullism/bqb/utils.go:213.15,215.4 1 1
123+
github.com/nullism/bqb/utils.go:216.3,216.38 1 1
124+
github.com/nullism/bqb/utils.go:217.11,218.21 1 1
125+
github.com/nullism/bqb/utils.go:219.10,220.65 1 1

query_test.go

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,23 @@ func TestQuery_ToPgsql(t *testing.T) {
452452
if sql != want {
453453
t.Errorf("got: %q, want: %q", sql, want)
454454
}
455+
456+
ql := New("? ?? ? ? ? ?", "a", "b", "c", "d", "e")
457+
sql, _, _ = ql.ToPgsql()
458+
want = "$1 ? $2 $3 $4 $5"
459+
if sql != want {
460+
t.Errorf("got: %q, want: %q", sql, want)
461+
}
462+
463+
want = "SELECT * FROM foo"
464+
qno := New(want)
465+
sql, params, _ = qno.ToPgsql()
466+
if len(params) > 0 {
467+
t.Errorf("expected no params, got: %v", params)
468+
}
469+
if sql != want {
470+
t.Errorf("got: %q, want: %q", sql, want)
471+
}
455472
}
456473

457474
func TestQuery_ToRaw(t *testing.T) {
@@ -674,18 +691,38 @@ func Benchmark_ToPgsql_Params(b *testing.B) {
674691
parts := []string{}
675692
args := []any{}
676693
for j := 0; j < 10; j++ {
677-
for j := 0; j < 1000; j++ {
694+
for j := 0; j < 400; j++ {
678695
parts = append(parts, "?")
679696
args = append(args, 1)
680697
}
681698
}
682699

683700
query := fmt.Sprintf("(%s)", strings.Join(parts, ","))
684-
701+
b.ResetTimer()
685702
for i := 0; i < b.N; i++ {
686703
_, _, err := New(query, args...).ToPgsql()
687704
if err != nil {
688705
b.Fatalf("failed to make benchmark sql: %v", err)
689706
}
690707
}
691708
}
709+
710+
func Benchmark_ToMysql_Params(b *testing.B) {
711+
parts := []string{}
712+
args := []any{}
713+
for j := 0; j < 10; j++ {
714+
for j := 0; j < 1000; j++ {
715+
parts = append(parts, "?")
716+
args = append(args, 1)
717+
}
718+
}
719+
720+
query := fmt.Sprintf("(%s)", strings.Join(parts, ","))
721+
b.ResetTimer()
722+
for i := 0; i < b.N; i++ {
723+
_, _, err := New(query, args...).ToMysql()
724+
if err != nil {
725+
b.Fatalf("failed to make benchmark sql: %v", err)
726+
}
727+
}
728+
}

utils.go

Lines changed: 13 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
package bqb
22

33
import (
4-
"bufio"
5-
"bytes"
64
"database/sql/driver"
75
"encoding/json"
8-
"errors"
96
"fmt"
7+
"strconv"
108
"strings"
119
)
1210

@@ -19,103 +17,31 @@ func dialectReplace(dialect Dialect, sql string, params []any) (string, error) {
1917

2018
switch dialect {
2119
case RAW:
22-
raws := make([]string, len(params))
23-
for i, param := range params {
20+
for _, param := range params {
2421
p, err := paramToRaw(param)
2522
if err != nil {
2623
return "", err
2724
}
28-
raws[i] = p
25+
sql = strings.Replace(sql, paramPh, p, 1)
2926
}
30-
31-
return replaceWithScans(
32-
sql,
33-
scan{pattern: parameterPlaceholder, fn: func(i int) string { return raws[i] }},
34-
)
27+
return sql, nil
3528
case MYSQL, SQL:
36-
return replaceWithScans(
37-
sql,
38-
scan{pattern: parameterPlaceholder, fn: func(int) string { return questionMark }},
39-
)
29+
return strings.ReplaceAll(sql, paramPh, questionMark), nil
4030
case PGSQL:
41-
return replaceWithScans(
42-
sql,
43-
scan{pattern: doubleQuestionMarkDelimiter, fn: func(int) string { return questionMark }},
44-
scan{pattern: parameterPlaceholder, fn: func(i int) string { return fmt.Sprintf("$%d", i+1) }},
45-
)
31+
sql = strings.ReplaceAll(sql, doubleQuestionMarkDelimiter, questionMark)
32+
parts := strings.Split(sql, paramPh)
33+
var builder strings.Builder
34+
for i := range params {
35+
_, _ = builder.WriteString(parts[i] + "$" + strconv.Itoa(i+1))
36+
}
37+
builder.WriteString(parts[len(parts)-1])
38+
return builder.String(), nil
4639
default:
4740
// No replacement defined for dialect
4841
return sql, nil
4942
}
5043
}
5144

52-
type replaceFn func(int) string
53-
54-
type scan struct {
55-
pattern string
56-
fn replaceFn
57-
}
58-
59-
// replaceWithScans applies the given set of scanning arguments and joins their
60-
// errors together.
61-
func replaceWithScans(in string, ss ...scan) (string, error) {
62-
errs := []error{}
63-
for _, s := range ss {
64-
out, err := scanReplace(in, s.pattern, s.fn)
65-
errs = append(errs, err)
66-
in = out
67-
}
68-
return in, errors.Join(errs...)
69-
}
70-
71-
func scanReplace(stmt string, replace string, fn replaceFn) (string, error) {
72-
// Build a scanner that will iterate based on the replace token
73-
ph := []byte(replace)
74-
scanner := bufio.NewScanner(bytes.NewBufferString(stmt))
75-
scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
76-
// Return nothing if at end of file and no data passed
77-
if atEOF && len(data) == 0 {
78-
return 0, nil, nil
79-
}
80-
81-
switch i := bytes.Index(data, ph); {
82-
case i == 0:
83-
return len(ph), data[0:len(ph)], nil
84-
case i > 0:
85-
return i, data[0:i], nil
86-
87-
}
88-
89-
// If at end of file with data return the data
90-
if atEOF {
91-
return len(data), data, nil
92-
}
93-
94-
return
95-
})
96-
97-
i := 0
98-
99-
// Scan replacing tokens with the value returned from delegate
100-
sb := strings.Builder{}
101-
for scanner.Scan() {
102-
switch txt := scanner.Text(); txt {
103-
case replace:
104-
// String builder will always return nil for an err so it is thrown
105-
// away.
106-
_, _ = sb.WriteString(fn(i))
107-
i++
108-
109-
default:
110-
// String builder will always return nil for an err so it is thrown
111-
// away.
112-
_, _ = sb.WriteString(txt)
113-
}
114-
}
115-
116-
return sb.String(), scanner.Err()
117-
}
118-
11945
func convertArg(text string, arg any) (string, []any, []error) {
12046
var newArgs []any
12147
var errs []error

0 commit comments

Comments
 (0)