Skip to content

Commit 203d3b8

Browse files
authored
Oneline builder (#23)
1 parent 1df949a commit 203d3b8

File tree

4 files changed

+87
-50
lines changed

4 files changed

+87
-50
lines changed

builq.go

+34-13
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@ package builq
33
import (
44
"errors"
55
"fmt"
6+
"io"
67
"reflect"
78
"regexp"
89
"strings"
910
)
1011

12+
// used to enforce const strings in API.
13+
type constString string
14+
1115
// Columns is a convenience wrapper for table columns.
1216
type Columns []string
1317

@@ -30,11 +34,24 @@ type Builder struct {
3034
placeholder rune // a placeholder used to build the query.
3135
}
3236

33-
type constString string
37+
// OnelineBuilder behaves like Builder but result is 1 line.
38+
type OnelineBuilder struct {
39+
Builder
40+
}
41+
42+
// Addf formats according to a format specifier, writes to query and appends args.
43+
// Format param must be a constant string.
44+
func (b *OnelineBuilder) Addf(format constString, args ...any) *Builder {
45+
return b.addf(' ', format, args...)
46+
}
3447

3548
// Addf formats according to a format specifier, writes to query and appends args.
3649
// Format param must be a constant string.
3750
func (b *Builder) Addf(format constString, args ...any) *Builder {
51+
return b.addf('\n', format, args...)
52+
}
53+
54+
func (b *Builder) addf(sep byte, format constString, args ...any) *Builder {
3855
if b.err != nil {
3956
return b
4057
}
@@ -54,16 +71,16 @@ func (b *Builder) Addf(format constString, args ...any) *Builder {
5471
b.err = err
5572
}
5673

57-
b.query.WriteByte('\n')
74+
b.query.WriteByte(sep)
5875
return b
5976
}
6077

6178
var missingRE = regexp.MustCompile(`%!.\(MISSING\)`)
6279

63-
func (b *Builder) Build() (string, []any, error) {
80+
func (b *Builder) Build() (query string, args []any, err error) {
6481
// prioritize the errors from the query string,
6582
// otherwise Build might return a wrong error.
66-
query := b.query.String()
83+
query = b.query.String()
6784

6885
switch {
6986
case missingRE.MatchString(query):
@@ -79,23 +96,23 @@ func (b *Builder) Build() (string, []any, error) {
7996
return query, b.args, b.err
8097
}
8198

82-
func (b *Builder) writeArgs(s fmt.State, verb rune, arg any, isMulti bool) {
99+
func (b *Builder) writeArgs(w io.Writer, verb rune, arg any, isMulti bool) {
83100
args := []any{arg}
84101
if isMulti {
85102
args = b.asSlice(arg)
86103
}
87104

88105
for i, arg := range args {
89106
if i > 0 {
90-
fmt.Fprint(s, ", ")
107+
fmt.Fprint(w, ", ")
91108
}
92109

93110
switch verb {
94111
case '$': // PostgreSQL
95112
b.counter++
96-
fmt.Fprintf(s, "$%d", b.counter)
113+
fmt.Fprintf(w, "$%d", b.counter)
97114
case '?': // MySQL/SQLite
98-
fmt.Fprint(s, "?")
115+
fmt.Fprint(w, "?")
99116
default:
100117
panic("unreachable")
101118
}
@@ -106,15 +123,15 @@ func (b *Builder) writeArgs(s fmt.State, verb rune, arg any, isMulti bool) {
106123
b.placeholder = verb
107124
}
108125
if b.placeholder != verb {
109-
b.err = errMixedPlaceholders
126+
b.setErr(errMixedPlaceholders)
110127
return
111128
}
112129

113130
b.args = append(b.args, arg)
114131
}
115132
}
116133

117-
func (b *Builder) writeBatchArgs(s fmt.State, verb rune, arg any) {
134+
func (b *Builder) writeBatchArgs(s io.Writer, verb rune, arg any) {
118135
args := b.asSlice(arg)
119136
for i, arg := range args {
120137
if i > 0 {
@@ -130,9 +147,7 @@ func (b *Builder) asSlice(v any) []any {
130147
value := reflect.ValueOf(v)
131148

132149
if value.Kind() != reflect.Slice {
133-
if b.err == nil {
134-
b.err = errNonSliceArgument
135-
}
150+
b.setErr(errNonSliceArgument)
136151
return nil
137152
}
138153

@@ -189,3 +204,9 @@ func (a *argument) Format(s fmt.State, verb rune) {
189204
a.builder.err = fmt.Errorf("%w %c", errUnsupportedVerb, verb)
190205
}
191206
}
207+
208+
func (b *Builder) setErr(err error) {
209+
if b.err == nil {
210+
b.err = err
211+
}
212+
}

builq_bench_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
"github.com/cristalhq/builq"
77
)
88

9-
func BenchmarkBuildNaive(b *testing.B) {
9+
func BenchmarkBuildSimple(b *testing.B) {
1010
for i := 0; i < b.N; i++ {
1111
var bb builq.Builder
1212
bb.Addf("SELECT %s FROM %s", "foo,bar", "table")
@@ -16,7 +16,7 @@ func BenchmarkBuildNaive(b *testing.B) {
1616
switch {
1717
case err != nil:
1818
b.Fatal(err)
19-
case len(query) == 0:
19+
case query == "":
2020
b.Fatal("empty query")
2121
case len(args) == 0:
2222
b.Fatal("empty args")
@@ -40,7 +40,7 @@ func BenchmarkBuildManyArgs(b *testing.B) {
4040
switch {
4141
case err != nil:
4242
b.Fatal(err)
43-
case len(query) == 0:
43+
case query == "":
4444
b.Fatal("empty query")
4545
case len(args) == 0:
4646
b.Fatal("empty args")
@@ -64,7 +64,7 @@ func BenchmarkBuildCached(b *testing.B) {
6464
switch {
6565
case err != nil:
6666
b.Fatal(err)
67-
case len(query) == 0:
67+
case query == "":
6868
b.Fatal("empty query")
6969
case len(args) == 0:
7070
b.Fatal("empty args")

builq_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ func FuzzBuilder(f *testing.F) {
6262

6363
var b Builder
6464
b.Addf(constString(format), arg1, arg2)
65-
_, args, err := b.Build()
6665

66+
_, args, err := b.Build()
6767
if err != nil {
6868
// those errors are expected, we're looking for something new.
6969
if errors.Is(err, errTooFewArguments) ||

example_test.go

+48-32
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,45 @@ func ExampleBuilder() {
3636
// [123 42]
3737
}
3838

39-
func ExampleQuery1() {
39+
func ExampleOnelineBuilder() {
40+
cols := builq.Columns{"foo", "bar"}
41+
42+
var b builq.OnelineBuilder
43+
b.Addf("SELECT %s FROM %s", cols, "table")
44+
b.Addf("WHERE id = %$", 123)
45+
46+
query, _, err := b.Build()
47+
panicIf(err)
48+
49+
fmt.Print(query)
50+
51+
// Output:
52+
// SELECT foo, bar FROM table WHERE id = $1
53+
}
54+
55+
func ExampleColumns() {
56+
columns := builq.Columns{"id", "created_at", "value"}
57+
params := []any{42, "right now", "just testing"}
58+
59+
var b builq.Builder
60+
b.Addf("INSERT INTO %s (%s)", "table", columns)
61+
b.Addf("VALUES (%?, %?, %?);", params...)
62+
63+
query, args, err := b.Build()
64+
panicIf(err)
65+
66+
fmt.Printf("query:\n%v", query)
67+
fmt.Printf("args:\n%v", args)
68+
69+
// Output:
70+
// query:
71+
// INSERT INTO table (id, created_at, value)
72+
// VALUES (?, ?, ?);
73+
// args:
74+
// [42 right now just testing]
75+
}
76+
77+
func Example_query1() {
4078
cols := builq.Columns{"foo, bar"}
4179

4280
var b builq.Builder
@@ -59,7 +97,7 @@ func ExampleQuery1() {
5997
// [42 root]
6098
}
6199

62-
func ExampleQuery2() {
100+
func Example_query2() {
63101
var b builq.Builder
64102
b.Addf("SELECT %s FROM %s", "foo, bar", "users")
65103
b.Addf("WHERE")
@@ -86,7 +124,7 @@ func ExampleQuery2() {
86124
// [true 42]
87125
}
88126

89-
func ExampleQuery3() {
127+
func Example_query3() {
90128
var b builq.Builder
91129
b.Addf("SELECT * FROM foo").
92130
Addf("WHERE active IS TRUE").
@@ -109,7 +147,7 @@ func ExampleQuery3() {
109147
// [42]
110148
}
111149

112-
func ExampleQueryWhere() {
150+
func Example_queryWhere() {
113151
filter := map[string]any{
114152
"name": "the best",
115153
"category": []int{1, 2, 3},
@@ -152,29 +190,7 @@ func ExampleQueryWhere() {
152190
// [the best 1 2 3 42 100]
153191
}
154192

155-
func ExampleColumns() {
156-
columns := builq.Columns{"id", "created_at", "value"}
157-
params := []any{42, "right now", "just testing"}
158-
159-
var b builq.Builder
160-
b.Addf("INSERT INTO %s (%s)", "table", columns)
161-
b.Addf("VALUES (%?, %?, %?);", params...)
162-
163-
query, args, err := b.Build()
164-
panicIf(err)
165-
166-
fmt.Printf("query:\n%v", query)
167-
fmt.Printf("args:\n%v", args)
168-
169-
// Output:
170-
// query:
171-
// INSERT INTO table (id, created_at, value)
172-
// VALUES (?, ?, ?);
173-
// args:
174-
// [42 right now just testing]
175-
}
176-
177-
func ExampleSlicePostgres() {
193+
func Example_slicePostgres() {
178194
params := []any{42, true, "str"}
179195

180196
var b builq.Builder
@@ -195,7 +211,7 @@ func ExampleSlicePostgres() {
195211
// [42 true str]
196212
}
197213

198-
func ExampleSliceMySQL() {
214+
func Example_sliceMySQL() {
199215
params := []any{42, true, "str"}
200216

201217
var b builq.Builder
@@ -216,7 +232,7 @@ func ExampleSliceMySQL() {
216232
// [42 true str]
217233
}
218234

219-
func ExampleInsertReturn() {
235+
func Example_insertReturn() {
220236
cols := builq.Columns{"id", "is_active", "name"}
221237
params := []any{true, "str"}
222238

@@ -240,7 +256,7 @@ func ExampleInsertReturn() {
240256
// [true str]
241257
}
242258

243-
func ExampleBatchPostgres() {
259+
func Example_batchPostgres() {
244260
params := [][]any{
245261
{42, true, "str"},
246262
{69, true, "noice"},
@@ -264,7 +280,7 @@ func ExampleBatchPostgres() {
264280
// [42 true str 69 true noice]
265281
}
266282

267-
func ExampleBatchMySQL() {
283+
func Example_batchMySQL() {
268284
params := [][]any{
269285
{42, true, "str"},
270286
{69, true, "noice"},
@@ -288,7 +304,7 @@ func ExampleBatchMySQL() {
288304
// [42 true str 69 true noice]
289305
}
290306

291-
func ExampleSliceInBatch() {
307+
func Example_sliceInBatch() {
292308
params := [][]any{
293309
{42, []any{1, 2, 3}},
294310
{69, []any{4, 5, 6}},

0 commit comments

Comments
 (0)