Skip to content

Commit ca136a0

Browse files
author
Carlos Bolanos
committed
refactor to use instead of formatted string values
1 parent 0eeaa90 commit ca136a0

21 files changed

+1226
-1069
lines changed

README.md

Lines changed: 135 additions & 122 deletions
Large diffs are not rendered by default.

go.mod

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,11 @@ module github.com/bolanosdev/query-builder
22

33
go 1.25.1
44

5-
require github.com/mattn/go-sqlite3 v1.14.32 // indirect
5+
require github.com/stretchr/testify v1.11.1
6+
7+
require (
8+
github.com/davecgh/go-spew v1.1.1 // indirect
9+
github.com/mattn/go-sqlite3 v1.14.32 // indirect
10+
github.com/pmezard/go-difflib v1.0.0 // indirect
11+
gopkg.in/yaml.v3 v3.0.1 // indirect
12+
)

go.sum

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,11 @@
1+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
13
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
24
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
5+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
6+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
7+
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
8+
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
9+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
10+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
11+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

query_builder.go

Lines changed: 62 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,108 +1,86 @@
11
package querybuilder
22

33
import (
4-
"fmt"
5-
"strings"
4+
"fmt"
5+
"strings"
66
)
77

88
type QueryBuilder struct {
9-
baseQuery string
10-
conditions []string
11-
operators []string
12-
args []any
13-
values []any
14-
argCounter int
15-
limitValue int
16-
offsetValue int
17-
sortFields []SortField
9+
baseQuery string
10+
conditions []string
11+
operators []string
12+
args []any
13+
values []any
14+
argCounter int
15+
limitValue int
16+
offsetValue int
17+
sortFields []SortField
1818
}
1919

2020
type QueryCondition struct {
21-
condition string
22-
value any
23-
placeholder string
24-
isGroup bool
25-
groupConds []QueryCondition
26-
groupOp string
21+
condition string
22+
value any
23+
placeholder string
24+
isGroup bool
25+
groupConds []QueryCondition
26+
groupOp string
2727
}
2828

2929
type SortField struct {
30-
field string
31-
direction SortDirection
30+
field string
31+
direction SortDirection
3232
}
3333

3434
func NewQueryBuilder(query string) *QueryBuilder {
35-
return &QueryBuilder{
36-
baseQuery: strings.TrimSpace(query),
37-
conditions: []string{},
38-
operators: []string{},
39-
args: []any{},
40-
values: []any{},
41-
argCounter: 1,
42-
limitValue: -1,
43-
offsetValue: -1,
44-
sortFields: []SortField{},
45-
}
35+
return &QueryBuilder{
36+
baseQuery: strings.TrimSpace(query),
37+
conditions: []string{},
38+
operators: []string{},
39+
args: []any{},
40+
values: []any{},
41+
argCounter: 1,
42+
limitValue: -1,
43+
offsetValue: -1,
44+
sortFields: []SortField{},
45+
}
4646
}
4747

48-
func (qb *QueryBuilder) Apply() string {
49-
query := qb.baseQuery
48+
func (qb *QueryBuilder) Commit() (string, []any) {
49+
query := qb.baseQuery
5050

51-
if len(qb.conditions) > 0 {
52-
whereClause := " WHERE " + qb.conditions[0]
53-
for i := 1; i < len(qb.conditions); i++ {
54-
whereClause += " " + qb.operators[i] + " " + qb.conditions[i]
55-
}
56-
query += whereClause
51+
if len(qb.conditions) > 0 {
52+
whereClause := " WHERE " + qb.conditions[0]
53+
for i := 1; i < len(qb.conditions); i++ {
54+
whereClause += " " + qb.operators[i] + " " + qb.conditions[i]
55+
}
56+
query += whereClause
57+
}
5758

58-
for i, placeholder := range qb.args {
59-
tempPlaceholder := fmt.Sprintf("$%d", i+1)
60-
query = strings.Replace(query, tempPlaceholder, placeholder.(string), 1)
61-
}
62-
}
59+
if len(qb.sortFields) > 0 {
60+
var sortParts []string
61+
for _, field := range qb.sortFields {
62+
sortStr := field.field
63+
if field.direction == SortDesc {
64+
sortStr += " DESC"
65+
}
66+
sortParts = append(sortParts, sortStr)
67+
}
68+
query += " ORDER BY " + strings.Join(sortParts, ", ")
69+
}
6370

64-
if len(qb.sortFields) > 0 {
65-
var sortParts []string
66-
for _, field := range qb.sortFields {
67-
sortStr := field.field
68-
if field.direction == SortDesc {
69-
sortStr += " DESC"
70-
}
71-
sortParts = append(sortParts, sortStr)
72-
}
73-
query += " ORDER BY " + strings.Join(sortParts, ", ")
74-
}
71+
// Apply default limit of 10 if offset is set but limit is not
72+
limitToApply := qb.limitValue
73+
if qb.offsetValue >= 0 && qb.limitValue < 0 {
74+
limitToApply = 10
75+
}
7576

76-
if qb.limitValue >= 0 {
77-
query += fmt.Sprintf(" LIMIT %d", qb.limitValue)
78-
}
77+
if limitToApply >= 0 {
78+
query += fmt.Sprintf(" LIMIT %d", limitToApply)
79+
}
7980

80-
if qb.offsetValue >= 0 {
81-
query += fmt.Sprintf(" OFFSET %d", qb.offsetValue)
82-
}
81+
if qb.offsetValue >= 0 {
82+
query += fmt.Sprintf(" OFFSET %d", qb.offsetValue)
83+
}
8384

84-
return query + ";"
85-
}
86-
87-
func (qb *QueryBuilder) GetValues() []any {
88-
return qb.values
89-
}
90-
91-
func (qb *QueryBuilder) GetFormattedValues() []any {
92-
formatted := make([]any, len(qb.values))
93-
for i, val := range qb.values {
94-
switch v := val.(type) {
95-
case string:
96-
formatted[i] = fmt.Sprintf("'%s'", v)
97-
case []string:
98-
parts := make([]string, len(v))
99-
for j, str := range v {
100-
parts[j] = fmt.Sprintf("'%s'", str)
101-
}
102-
formatted[i] = fmt.Sprintf("(%s)", strings.Join(parts, ", "))
103-
default:
104-
formatted[i] = val
105-
}
106-
}
107-
return formatted
85+
return query + ";", qb.values
10886
}

query_builder_conditions.go

Lines changed: 108 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,119 @@ func (qb *QueryBuilder) Where(conditions ...QueryCondition) *QueryBuilder {
1010
if cond.isGroup {
1111
var groupParts []string
1212
for _, groupCond := range cond.groupConds {
13-
tempPlaceholder := fmt.Sprintf("$%d", qb.argCounter)
14-
condition := strings.Replace(groupCond.condition, "$1", tempPlaceholder, 1)
15-
groupParts = append(groupParts, condition)
16-
qb.args = append(qb.args, groupCond.placeholder)
17-
qb.values = append(qb.values, groupCond.value)
18-
qb.argCounter++
13+
// Expand slice values for IN clauses; otherwise single placeholder
14+
if strings.Contains(groupCond.condition, "IN $1") {
15+
switch v := groupCond.value.(type) {
16+
case []int:
17+
placeholders := make([]string, len(v))
18+
for i := range v {
19+
placeholders[i] = fmt.Sprintf("$%d", qb.argCounter+i)
20+
}
21+
expanded := strings.Replace(groupCond.condition, "IN $1", "IN ("+strings.Join(placeholders, ", ")+")", 1)
22+
groupParts = append(groupParts, expanded)
23+
for _, item := range v {
24+
qb.values = append(qb.values, item)
25+
}
26+
qb.argCounter += len(v)
27+
case []string:
28+
placeholders := make([]string, len(v))
29+
for i := range v {
30+
placeholders[i] = fmt.Sprintf("$%d", qb.argCounter+i)
31+
}
32+
expanded := strings.Replace(groupCond.condition, "IN $1", "IN ("+strings.Join(placeholders, ", ")+")", 1)
33+
groupParts = append(groupParts, expanded)
34+
for _, item := range v {
35+
qb.values = append(qb.values, item)
36+
}
37+
qb.argCounter += len(v)
38+
default:
39+
tempPlaceholder := fmt.Sprintf("$%d", qb.argCounter)
40+
condition := strings.Replace(groupCond.condition, "$1", tempPlaceholder, 1)
41+
groupParts = append(groupParts, condition)
42+
qb.values = append(qb.values, groupCond.value)
43+
qb.argCounter++
44+
}
45+
} else {
46+
// Handle date range queries with []string values (two placeholders)
47+
if v, ok := groupCond.value.([]string); ok && (strings.Contains(groupCond.condition, "$1") && strings.Contains(groupCond.condition, "$2")) {
48+
placeholder1 := fmt.Sprintf("$%d", qb.argCounter)
49+
placeholder2 := fmt.Sprintf("$%d", qb.argCounter+1)
50+
condition := strings.Replace(groupCond.condition, "$1", placeholder1, 1)
51+
condition = strings.Replace(condition, "$2", placeholder2, 1)
52+
groupParts = append(groupParts, condition)
53+
for _, item := range v {
54+
qb.values = append(qb.values, item)
55+
}
56+
qb.argCounter += len(v)
57+
} else {
58+
tempPlaceholder := fmt.Sprintf("$%d", qb.argCounter)
59+
condition := strings.Replace(groupCond.condition, "$1", tempPlaceholder, 1)
60+
groupParts = append(groupParts, condition)
61+
qb.values = append(qb.values, groupCond.value)
62+
qb.argCounter++
63+
}
64+
}
1965
}
2066
groupCondition := "(" + strings.Join(groupParts, " "+cond.groupOp+" ") + ")"
2167
qb.conditions = append(qb.conditions, groupCondition)
2268
qb.operators = append(qb.operators, "AND")
2369
} else {
24-
tempPlaceholder := fmt.Sprintf("$%d", qb.argCounter)
25-
condition := strings.Replace(cond.condition, "$1", tempPlaceholder, 1)
26-
qb.conditions = append(qb.conditions, condition)
27-
qb.operators = append(qb.operators, "AND")
28-
qb.args = append(qb.args, cond.placeholder)
29-
qb.values = append(qb.values, cond.value)
30-
qb.argCounter++
70+
if strings.Contains(cond.condition, "IN $1") {
71+
switch v := cond.value.(type) {
72+
case []int:
73+
placeholders := make([]string, len(v))
74+
for i := range v {
75+
placeholders[i] = fmt.Sprintf("$%d", qb.argCounter+i)
76+
}
77+
condition := strings.Replace(cond.condition, "IN $1", "IN ("+strings.Join(placeholders, ", ")+")", 1)
78+
qb.conditions = append(qb.conditions, condition)
79+
qb.operators = append(qb.operators, "AND")
80+
for _, item := range v {
81+
qb.values = append(qb.values, item)
82+
}
83+
qb.argCounter += len(v)
84+
case []string:
85+
placeholders := make([]string, len(v))
86+
for i := range v {
87+
placeholders[i] = fmt.Sprintf("$%d", qb.argCounter+i)
88+
}
89+
condition := strings.Replace(cond.condition, "IN $1", "IN ("+strings.Join(placeholders, ", ")+")", 1)
90+
qb.conditions = append(qb.conditions, condition)
91+
qb.operators = append(qb.operators, "AND")
92+
for _, item := range v {
93+
qb.values = append(qb.values, item)
94+
}
95+
qb.argCounter += len(v)
96+
default:
97+
tempPlaceholder := fmt.Sprintf("$%d", qb.argCounter)
98+
condition := strings.Replace(cond.condition, "$1", tempPlaceholder, 1)
99+
qb.conditions = append(qb.conditions, condition)
100+
qb.operators = append(qb.operators, "AND")
101+
qb.values = append(qb.values, cond.value)
102+
qb.argCounter++
103+
}
104+
} else {
105+
// Handle date range queries with []string values (two placeholders)
106+
if v, ok := cond.value.([]string); ok && (strings.Contains(cond.condition, "$1") && strings.Contains(cond.condition, "$2")) {
107+
placeholder1 := fmt.Sprintf("$%d", qb.argCounter)
108+
placeholder2 := fmt.Sprintf("$%d", qb.argCounter+1)
109+
condition := strings.Replace(cond.condition, "$1", placeholder1, 1)
110+
condition = strings.Replace(condition, "$2", placeholder2, 1)
111+
qb.conditions = append(qb.conditions, condition)
112+
qb.operators = append(qb.operators, "AND")
113+
for _, item := range v {
114+
qb.values = append(qb.values, item)
115+
}
116+
qb.argCounter += len(v)
117+
} else {
118+
tempPlaceholder := fmt.Sprintf("$%d", qb.argCounter)
119+
condition := strings.Replace(cond.condition, "$1", tempPlaceholder, 1)
120+
qb.conditions = append(qb.conditions, condition)
121+
qb.operators = append(qb.operators, "AND")
122+
qb.values = append(qb.values, cond.value)
123+
qb.argCounter++
124+
}
125+
}
31126
}
32127
}
33128
return qb

0 commit comments

Comments
 (0)