Skip to content

Commit ba0464b

Browse files
Added support for non-serial pField mutation (#39)
* Added support for non-serial pField mutation Yet this implememnation gidoes not protect from accidental mutation of pk * Added test to cover changes, Removed unnecessary break * Prevented setting mutable PK from Convert method * Added pk set prevention for change method * Allowed Change to mut PK and updated desc for Convert * Updated tests * Simplified PK update logic * Fixed naming, added annotations to tests
1 parent 1c208e7 commit ba0464b

File tree

3 files changed

+130
-9
lines changed

3 files changed

+130
-9
lines changed

changeset.go

+9-8
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@ import (
1212

1313
// Changeset used to cast and validate data before saving it to the database.
1414
type Changeset struct {
15-
errors []error
16-
params params.Params
17-
changes map[string]interface{}
18-
values map[string]interface{}
19-
types map[string]reflect.Type
20-
constraints Constraints
21-
zero bool
15+
errors []error
16+
params params.Params
17+
changes map[string]interface{}
18+
values map[string]interface{}
19+
types map[string]reflect.Type
20+
constraints Constraints
21+
zero bool
22+
ignorePrimary bool
2223
}
2324

2425
// Errors of changeset.
@@ -87,7 +88,7 @@ func (c *Changeset) Apply(doc *rel.Document, mut *rel.Mutation) {
8788
c.applyAssocMany(doc, field, mut, v)
8889
}
8990
default:
90-
if field != pField && scannable(c.types[field]) {
91+
if (pField != field || pField == field && !c.ignorePrimary) && scannable(c.types[field]) {
9192
c.set(doc, mut, field, v)
9293
}
9394
}

changeset_test.go

+119
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ type User struct {
2222
DeletedAt *time.Time
2323
}
2424

25+
type UUIDUser struct {
26+
UUID string `db:"uuid,primary"`
27+
Name string
28+
Age int
29+
CreatedAt time.Time
30+
UpdatedAt time.Time
31+
DeletedAt *time.Time
32+
}
33+
2534
type Transaction struct {
2635
ID int
2736
Item string
@@ -206,3 +215,113 @@ func TestChangesetApply_constraint(t *testing.T) {
206215
UpdatedAt: now,
207216
}, user)
208217
}
218+
219+
//If PK is explicitly caseted then it should be updated
220+
func TestChangesetApply_updatePK(t *testing.T) {
221+
var (
222+
user UUIDUser
223+
now = time.Now().Truncate(time.Second)
224+
doc = rel.NewDocument(&user)
225+
input = params.Map{
226+
"uuid": "3a90fc96-6cff-4914-9ce8-01c9e607b28b",
227+
"name": "Luffy",
228+
"age": 20,
229+
}
230+
userMutation = rel.Apply(rel.NewDocument(&UUIDUser{}),
231+
rel.Set("uuid", "3a90fc96-6cff-4914-9ce8-01c9e607b28b"),
232+
rel.Set("name", "Luffy"),
233+
rel.Set("age", 20),
234+
rel.Set("created_at", now),
235+
rel.Set("updated_at", now),
236+
)
237+
)
238+
239+
ch := Cast(user, input, []string{"name", "age", "uuid"})
240+
UniqueConstraint(ch, "name")
241+
mut := rel.Apply(doc, ch)
242+
243+
assert.Nil(t, ch.Error())
244+
assert.Equal(t, userMutation.Mutates, mut.Mutates)
245+
assert.NotNil(t, mut.ErrorFunc)
246+
assert.Equal(t, UUIDUser{
247+
UUID: "3a90fc96-6cff-4914-9ce8-01c9e607b28b",
248+
Name: "Luffy",
249+
Age: 20,
250+
CreatedAt: now,
251+
UpdatedAt: now,
252+
}, user)
253+
}
254+
255+
//Covert function should ignore the PK updates for safety reasons
256+
func TestChangesetApply_updatePK_fromConvert(t *testing.T) {
257+
var (
258+
user UUIDUser
259+
now = time.Now().Truncate(time.Second)
260+
doc = rel.NewDocument(&user)
261+
input = struct {
262+
UUID string
263+
Name string
264+
Age int
265+
}{
266+
UUID: "3a90fc96-6cff-4914-9ce8-01c9e607b28b",
267+
Name: "Luffy",
268+
Age: 20,
269+
}
270+
userMutation = rel.Apply(rel.NewDocument(&UUIDUser{}),
271+
rel.Set("name", "Luffy"),
272+
rel.Set("age", 20),
273+
rel.Set("created_at", now),
274+
rel.Set("updated_at", now),
275+
)
276+
)
277+
278+
ch := Convert(input)
279+
UniqueConstraint(ch, "name")
280+
mut := rel.Apply(doc, ch)
281+
282+
assert.Nil(t, ch.Error())
283+
assert.Equal(t, userMutation.Mutates, mut.Mutates)
284+
assert.NotNil(t, mut.ErrorFunc)
285+
assert.Equal(t, UUIDUser{
286+
Name: "Luffy",
287+
Age: 20,
288+
CreatedAt: now,
289+
UpdatedAt: now,
290+
}, user)
291+
}
292+
293+
//If PK is explicitly set in the Change function it should be updated
294+
func TestChangesetApply_updatePK_fromChange(t *testing.T) {
295+
var (
296+
user UUIDUser
297+
now = time.Now().Truncate(time.Second)
298+
doc = rel.NewDocument(&user)
299+
input = map[string]interface{}{
300+
"uuid": "3a90fc96-6cff-4914-9ce8-01c9e607b28b",
301+
"name": "Luffy",
302+
"age": 20,
303+
}
304+
userMutation = rel.Apply(rel.NewDocument(&UUIDUser{}),
305+
rel.Set("uuid", "3a90fc96-6cff-4914-9ce8-01c9e607b28b"),
306+
rel.Set("name", "Luffy"),
307+
rel.Set("age", 20),
308+
rel.Set("created_at", now),
309+
rel.Set("updated_at", now),
310+
)
311+
)
312+
313+
ch := Change(user, input)
314+
UniqueConstraint(ch, "name")
315+
mut := rel.Apply(doc, ch)
316+
317+
assert.Nil(t, ch.Error())
318+
assert.Equal(t, userMutation.Mutates, mut.Mutates)
319+
assert.NotNil(t, mut.ErrorFunc)
320+
assert.Equal(t, UUIDUser{
321+
UUID: "3a90fc96-6cff-4914-9ce8-01c9e607b28b",
322+
Name: "Luffy",
323+
Age: 20,
324+
CreatedAt: now,
325+
UpdatedAt: now,
326+
}, user)
327+
}

convert.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
package changeset
22

33
// Convert a struct as changeset, every field's value will be treated as changes. Returns a new changeset.
4+
// PK changes in the changeset created with this function will be ignored
45
func Convert(data interface{}) *Changeset {
56
ch := &Changeset{}
67
ch.values = make(map[string]interface{})
78
ch.changes, ch.types, _ = mapSchema(data, false)
8-
9+
ch.ignorePrimary = true // set ignore primary to prevent implicit PK change
910
return ch
1011
}

0 commit comments

Comments
 (0)