+2
-2
expr.go
+2
-2
expr.go
···
117
117
func Lt(left string, right any) Expr { return buildBinExpr(IdentExpr(left), "<", right) }
118
118
func Lte(left string, right any) Expr { return buildBinExpr(IdentExpr(left), "<=", right) }
119
119
120
-
func (l Expr) And(r Expr) Expr { return BinExpr{l, "and", r}.AsExpr() }
121
-
func (l Expr) Or(r Expr) Expr { return BinExpr{l, "or", r}.AsExpr() }
120
+
func (l Expr) And(r Expr) Expr { return BinExpr{l, "AND", r}.AsExpr() }
121
+
func (l Expr) Or(r Expr) Expr { return BinExpr{l, "OR", r}.AsExpr() }
+3
-3
expr_test.go
+3
-3
expr_test.go
···
74
74
right := Eq("status", "active")
75
75
76
76
andExpr := left.And(right)
77
-
expectedAnd := "((age) = (?)) and ((status) = (?))"
77
+
expectedAnd := "((age) = (?)) AND ((status) = (?))"
78
78
if andExpr.String() != expectedAnd {
79
79
t.Errorf("Expected '%s', got '%s'", expectedAnd, andExpr.String())
80
80
}
81
81
82
82
orExpr := left.Or(right)
83
-
expectedOr := "((age) = (?)) or ((status) = (?))"
83
+
expectedOr := "((age) = (?)) OR ((status) = (?))"
84
84
if orExpr.String() != expectedOr {
85
85
t.Errorf("Expected '%s', got '%s'", expectedOr, orExpr.String())
86
86
}
···
94
94
complex := age.
95
95
And(status).
96
96
Or(score)
97
-
expected := "(((age) = (?)) and ((status) = (?))) or ((score) > (?))"
97
+
expected := "(((age) = (?)) AND ((status) = (?))) OR ((score) > (?))"
98
98
99
99
if complex.String() != expected {
100
100
t.Errorf("Expected '%s', got '%s'", expected, complex.String())
+2
-2
select_test.go
+2
-2
select_test.go
···
67
67
GroupBy("department").
68
68
OrderBy("name", Ascending).
69
69
Limit(10),
70
-
expectedSql: "SELECT name, age, department FROM users WHERE ((active) = (?)) and ((age) > (?)) GROUP BY department ORDER BY name asc LIMIT 10",
70
+
expectedSql: "SELECT name, age, department FROM users WHERE ((active) = (?)) AND ((age) > (?)) GROUP BY department ORDER BY name asc LIMIT 10",
71
71
expectedArgs: []any{true, 18},
72
72
},
73
73
}
···
184
184
expectedRows: 2,
185
185
},
186
186
{
187
-
name: "Select users with salary between 70000 and 80000",
187
+
name: "Select users with salary between 70000 AND 80000",
188
188
stmt: Select("name", "salary").
189
189
From("users").
190
190
Where(Gte("salary", 70000.0).And(Lte("salary", 80000.0))),
+143
update.go
+143
update.go
···
1
+
package norm
2
+
3
+
import (
4
+
"context"
5
+
"database/sql"
6
+
"fmt"
7
+
"strings"
8
+
)
9
+
10
+
type update struct {
11
+
table string
12
+
or UpdateOr
13
+
sets []struct {
14
+
col string
15
+
val any
16
+
}
17
+
where *Expr
18
+
}
19
+
20
+
type UpdateOr int
21
+
22
+
const (
23
+
UpdateNone UpdateOr = iota
24
+
UpdateAbort
25
+
UpdateFail
26
+
UpdateIgnore
27
+
UpdateReplace
28
+
UpdateRollback
29
+
)
30
+
31
+
func (u UpdateOr) String() string {
32
+
switch u {
33
+
case UpdateAbort:
34
+
return "ABORT"
35
+
case UpdateFail:
36
+
return "FAIL"
37
+
case UpdateIgnore:
38
+
return "IGNORE"
39
+
case UpdateReplace:
40
+
return "REPLACE"
41
+
case UpdateRollback:
42
+
return "ROLLBACK"
43
+
default:
44
+
return ""
45
+
}
46
+
}
47
+
48
+
func Update(table string) update {
49
+
return update{table: table}
50
+
}
51
+
52
+
type UpdateOpt func(s *update)
53
+
54
+
func (u update) Or(option UpdateOr) update {
55
+
u.or = option
56
+
return u
57
+
}
58
+
59
+
func (u update) Set(column string, value any) update {
60
+
u.sets = append(u.sets, struct {
61
+
col string
62
+
val any
63
+
}{
64
+
column, value,
65
+
})
66
+
return u
67
+
}
68
+
69
+
func (u update) Sets(values map[string]any) update {
70
+
for column, value := range values {
71
+
u = u.Set(column, value)
72
+
}
73
+
return u
74
+
}
75
+
76
+
func (u update) Where(expr Expr) update {
77
+
u.where = &expr
78
+
return u
79
+
}
80
+
81
+
func (u update) Compile() (string, []any, error) {
82
+
var sql strings.Builder
83
+
var args []any
84
+
85
+
sql.WriteString("UPDATE ")
86
+
87
+
orKw := u.or.String()
88
+
if orKw != "" {
89
+
sql.WriteString("OR ")
90
+
sql.WriteString(u.or.String())
91
+
sql.WriteString(" ")
92
+
}
93
+
94
+
if u.table == "" {
95
+
return "", nil, fmt.Errorf("table name is required")
96
+
}
97
+
sql.WriteString(u.table)
98
+
99
+
if len(u.sets) == 0 {
100
+
return "", nil, fmt.Errorf("no SET clauses supplied")
101
+
}
102
+
103
+
sql.WriteString(" SET ")
104
+
105
+
for i, set := range u.sets {
106
+
if i != 0 {
107
+
sql.WriteString(", ")
108
+
}
109
+
sql.WriteString(set.col)
110
+
sql.WriteString(" = ?")
111
+
args = append(args, set.val)
112
+
}
113
+
114
+
if u.where != nil {
115
+
sql.WriteString(" WHERE ")
116
+
sql.WriteString(u.where.String())
117
+
118
+
args = append(args, u.where.Binds()...)
119
+
}
120
+
121
+
return sql.String(), args, nil
122
+
}
123
+
124
+
func (u update) MustCompile() (string, []any) {
125
+
sql, args, err := u.Compile()
126
+
if err != nil {
127
+
panic(err)
128
+
}
129
+
130
+
return sql, args
131
+
}
132
+
133
+
func (u update) Build(p Database) (*sql.Stmt, []any, error) { return Build(u, p) }
134
+
func (u update) MustBuild(p Database) (*sql.Stmt, []any) { return MustBuild(u, p) }
135
+
136
+
func (u update) Exec(p Database) (sql.Result, error) { return Exec(u, p) }
137
+
func (u update) ExecContext(ctx context.Context, p Database) (sql.Result, error) {
138
+
return ExecContext(ctx, u, p)
139
+
}
140
+
func (u update) MustExec(p Database) sql.Result { return MustExec(u, p) }
141
+
func (u update) MustExecContext(ctx context.Context, p Database) sql.Result {
142
+
return MustExecContext(ctx, u, p)
143
+
}
+301
update_test.go
+301
update_test.go
···
1
+
package norm
2
+
3
+
import (
4
+
"slices"
5
+
"testing"
6
+
7
+
_ "github.com/mattn/go-sqlite3"
8
+
)
9
+
10
+
func TestUpdateBuild_Success(t *testing.T) {
11
+
tests := []struct {
12
+
name string
13
+
stmt Compiler
14
+
expectedSql string
15
+
expectedArgs []any
16
+
}{
17
+
{
18
+
name: "Simple update",
19
+
stmt: Update("users").Set("name", "John"),
20
+
expectedSql: "UPDATE users SET name = ?",
21
+
expectedArgs: []any{"John"},
22
+
},
23
+
{
24
+
name: "Update with WHERE",
25
+
stmt: Update("users").Set("name", "John").Where(Eq("id", 1)),
26
+
expectedSql: "UPDATE users SET name = ? WHERE (id) = (?)",
27
+
expectedArgs: []any{"John", 1},
28
+
},
29
+
{
30
+
name: "Abort clause",
31
+
stmt: Update("users").Or(UpdateAbort).Set("name", "John"),
32
+
expectedSql: "UPDATE OR ABORT users SET name = ?",
33
+
expectedArgs: []any{"John"},
34
+
},
35
+
{
36
+
name: "Ignore clause",
37
+
stmt: Update("users").Or(UpdateIgnore).Set("name", "John"),
38
+
expectedSql: "UPDATE OR IGNORE users SET name = ?",
39
+
expectedArgs: []any{"John"},
40
+
},
41
+
{
42
+
name: "Fail clause",
43
+
stmt: Update("users").Or(UpdateFail).Set("name", "John"),
44
+
expectedSql: "UPDATE OR FAIL users SET name = ?",
45
+
expectedArgs: []any{"John"},
46
+
},
47
+
{
48
+
name: "Replace clause",
49
+
stmt: Update("users").Or(UpdateReplace).Set("name", "John"),
50
+
expectedSql: "UPDATE OR REPLACE users SET name = ?",
51
+
expectedArgs: []any{"John"},
52
+
},
53
+
{
54
+
name: "Rollback clause",
55
+
stmt: Update("users").Or(UpdateRollback).Set("name", "John"),
56
+
expectedSql: "UPDATE OR ROLLBACK users SET name = ?",
57
+
expectedArgs: []any{"John"},
58
+
},
59
+
{
60
+
name: "Default clause",
61
+
stmt: Update("users").Or(UpdateOr(10)).Set("name", "John"),
62
+
expectedSql: "UPDATE users SET name = ?",
63
+
expectedArgs: []any{"John"},
64
+
},
65
+
{
66
+
name: "Multiple sets",
67
+
stmt: Update("users").Set("name", "John").Set("age", 35),
68
+
expectedSql: "UPDATE users SET name = ?, age = ?",
69
+
expectedArgs: []any{"John", 35},
70
+
},
71
+
{
72
+
name: "Multiple WHERE conditions",
73
+
stmt: Update("users").Set("salary", 90000.0).Where(Eq("department", "Engineering").And(Eq("active", true))),
74
+
expectedSql: "UPDATE users SET salary = ? WHERE ((department) = (?)) AND ((active) = (?))",
75
+
expectedArgs: []any{90000.0, "Engineering", true},
76
+
},
77
+
{
78
+
name: "Complex update",
79
+
stmt: Update("users").Or(UpdateIgnore).Set("name", "Updated").Set("salary", 100000.0).Where(Gt("age", 30).And(Eq("active", true))),
80
+
expectedSql: "UPDATE OR IGNORE users SET name = ?, salary = ? WHERE ((age) > (?)) AND ((active) = (?))",
81
+
expectedArgs: []any{"Updated", 100000.0, 30, true},
82
+
},
83
+
}
84
+
85
+
for _, test := range tests {
86
+
t.Run(test.name, func(t *testing.T) {
87
+
sql, args := test.stmt.MustCompile()
88
+
89
+
if sql != test.expectedSql {
90
+
t.Errorf("Expected '%s', got '%s'", test.expectedSql, sql)
91
+
}
92
+
93
+
if len(args) != len(test.expectedArgs) {
94
+
t.Errorf("Expected '%d' args, got '%d' args", len(test.expectedArgs), len(args))
95
+
}
96
+
97
+
for i := range len(args) {
98
+
if args[i] != test.expectedArgs[i] {
99
+
t.Errorf("Expected '%v', got '%v' at index %d", test.expectedArgs[i], args[i], i)
100
+
}
101
+
}
102
+
})
103
+
}
104
+
}
105
+
106
+
func TestUpdateSetMap_Build(t *testing.T) {
107
+
tests := []struct {
108
+
name string
109
+
stmt Compiler
110
+
expectedConfig []struct {
111
+
sql string
112
+
args []any
113
+
}
114
+
}{
115
+
{
116
+
name: "Sets with map",
117
+
stmt: Update("users").Sets(map[string]any{
118
+
"name": "John",
119
+
"age": 25,
120
+
}).Where(Eq("id", 1)),
121
+
expectedConfig: []struct {
122
+
sql string
123
+
args []any
124
+
}{
125
+
{
126
+
sql: "UPDATE users SET name = ?, age = ? WHERE (id) = (?)",
127
+
args: []any{"John", 25, 1},
128
+
},
129
+
{
130
+
sql: "UPDATE users SET age = ?, name = ? WHERE (id) = (?)",
131
+
args: []any{25, "John", 1},
132
+
},
133
+
},
134
+
},
135
+
{
136
+
name: "Mixed individual and map sets",
137
+
stmt: Update("users").Set("active", false).Sets(map[string]any{
138
+
"name": "Updated User",
139
+
"salary": 95000.0,
140
+
}),
141
+
expectedConfig: []struct {
142
+
sql string
143
+
args []any
144
+
}{
145
+
{
146
+
sql: "UPDATE users SET active = ?, name = ?, salary = ?",
147
+
args: []any{false, "Updated User", 95000.0},
148
+
},
149
+
{
150
+
sql: "UPDATE users SET active = ?, salary = ?, name = ?",
151
+
args: []any{false, 95000.0, "Updated User"},
152
+
},
153
+
},
154
+
},
155
+
}
156
+
157
+
for _, test := range tests {
158
+
t.Run(test.name, func(t *testing.T) {
159
+
sql, args := test.stmt.MustCompile()
160
+
161
+
any := false
162
+
idx := 0
163
+
for i, config := range test.expectedConfig {
164
+
idx = i
165
+
equalSql := config.sql == sql
166
+
equalArgs := slices.Equal(config.args, args)
167
+
if equalSql && equalArgs {
168
+
any = true
169
+
}
170
+
}
171
+
172
+
if !any {
173
+
t.Errorf("Config did not match: %d: %q; got %q, %q", idx, test.expectedConfig[idx], sql, args)
174
+
}
175
+
176
+
})
177
+
}
178
+
}
179
+
180
+
func TestUpdateCompileFail(t *testing.T) {
181
+
tests := []struct {
182
+
name string
183
+
stmt Compiler
184
+
expectedError string
185
+
}{
186
+
{
187
+
name: "No SET clauses",
188
+
stmt: Update("users"),
189
+
expectedError: "no SET clauses supplied",
190
+
},
191
+
}
192
+
193
+
for _, test := range tests {
194
+
t.Run(test.name, func(t *testing.T) {
195
+
sql, args, err := test.stmt.Compile()
196
+
if err == nil {
197
+
t.Error("Expected error, got nil")
198
+
}
199
+
200
+
if err.Error() != test.expectedError {
201
+
t.Errorf("Expected error '%s', got '%s'", test.expectedError, err.Error())
202
+
}
203
+
204
+
if sql != "" {
205
+
t.Errorf("Expected empty SQL on error, got '%s'", sql)
206
+
}
207
+
208
+
if args != nil {
209
+
t.Errorf("Expected empty args on error, got '%q'", args)
210
+
}
211
+
})
212
+
}
213
+
}
214
+
215
+
func TestUpdateIntegration(t *testing.T) {
216
+
tests := []struct {
217
+
name string
218
+
stmt Execer
219
+
expectedRows int64
220
+
}{
221
+
{
222
+
name: "Update all users salary",
223
+
stmt: Update("users").Set("salary", 100000.0),
224
+
expectedRows: 6,
225
+
},
226
+
{
227
+
name: "Update active users only",
228
+
stmt: Update("users").
229
+
Set("department", "Updated Department").
230
+
Where(Eq("active", true)),
231
+
expectedRows: 4,
232
+
},
233
+
{
234
+
name: "Update users in Engineering",
235
+
stmt: Update("users").
236
+
Set("salary", 95000.0).
237
+
Where(Eq("department", "Engineering")),
238
+
expectedRows: 3,
239
+
},
240
+
{
241
+
name: "Update users with age > 30",
242
+
stmt: Update("users").
243
+
Set("active", false).
244
+
Where(Gt("age", 30)),
245
+
expectedRows: 2,
246
+
},
247
+
{
248
+
name: "Update users with multiple conditions",
249
+
stmt: Update("users").
250
+
Set("salary", 120000.0).
251
+
Where(Eq("department", "Engineering").And(Eq("active", true))),
252
+
expectedRows: 2,
253
+
},
254
+
{
255
+
name: "Update with OR IGNORE (no conflict expected)",
256
+
stmt: Update("users").
257
+
Or(UpdateIgnore).
258
+
Set("name", "Updated Name").
259
+
Where(Eq("id", 1)),
260
+
expectedRows: 1,
261
+
},
262
+
{
263
+
name: "Update multiple fields",
264
+
stmt: Update("users").
265
+
Sets(map[string]any{
266
+
"active": true,
267
+
"salary": 110000.0,
268
+
}).
269
+
Where(Eq("department", "Marketing")),
270
+
expectedRows: 2,
271
+
},
272
+
{
273
+
name: "Update with no matching rows",
274
+
stmt: Update("users").
275
+
Set("name", "Non-existent").
276
+
Where(Eq("id", 999)),
277
+
expectedRows: 0,
278
+
},
279
+
}
280
+
281
+
for _, test := range tests {
282
+
t.Run(test.name, func(t *testing.T) {
283
+
db := setupTestDB(t)
284
+
defer db.Close()
285
+
286
+
res, err := test.stmt.Exec(db)
287
+
if err != nil {
288
+
t.Fatalf("Failed to execute query: %v", err)
289
+
}
290
+
291
+
count, err := res.RowsAffected()
292
+
if err != nil {
293
+
t.Fatalf("Failed to get rows affected: %v", err)
294
+
}
295
+
296
+
if count != test.expectedRows {
297
+
t.Errorf("Expected %d rows, got %d", test.expectedRows, count)
298
+
}
299
+
})
300
+
}
301
+
}