Skip to content

Commit 43ef7aa

Browse files
committed
added noupdate field tag to exclude column from updates
1 parent 487c04f commit 43ef7aa

File tree

4 files changed

+52
-28
lines changed

4 files changed

+52
-28
lines changed

db.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ func makeUpdatePlan(m *Model) updatePlan {
245245

246246
var setColumns []string
247247
for _, col := range m.mappedColumns {
248-
if col.AutoIncr || primaryKey[col.Name] {
248+
if col.AutoIncr || primaryKey[col.Name] || m.fields[col.Name].noupdate {
249249
continue
250250
}
251251
setColumns = append(setColumns, col.Name)

db_test.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1827,13 +1827,15 @@ const versionedUsersDDL = `
18271827
CREATE TABLE users (
18281828
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
18291829
name VARCHAR(255) NOT NULL,
1830-
ver INT NOT NULL
1830+
ver INT NOT NULL,
1831+
created_at BIGINT NOT NULL
18311832
)`
18321833

18331834
type VersionedUser struct {
1834-
ID uint64 `db:"id"`
1835-
Name string `db:"name"`
1836-
Version int `db:"ver,optlock"`
1835+
ID uint64 `db:"id"`
1836+
Name string `db:"name"`
1837+
Version int `db:"ver,optlock"`
1838+
CreatedAt int64 `db:"created_at,noupdate"`
18371839
}
18381840

18391841
func TestVersionedUser_base(t *testing.T) {
@@ -1844,16 +1846,19 @@ func TestVersionedUser_base(t *testing.T) {
18441846
}
18451847

18461848
// Insert
1849+
createdAt := time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC)
18471850
u := &VersionedUser{
1848-
Name: "foo",
1849-
Version: 1,
1851+
Name: "foo",
1852+
Version: 1,
1853+
CreatedAt: createdAt.UnixMilli(),
18501854
}
18511855
if err := db.Insert(u); err != nil {
18521856
t.Fatal(err)
18531857
}
18541858

18551859
// Update
18561860
u.Name = "bar"
1861+
u.CreatedAt = createdAt.Add(time.Hour).UnixMilli()
18571862
if count, err := db.Update(u); err != nil {
18581863
t.Fatal(err)
18591864
} else if count != 1 {
@@ -1874,6 +1879,9 @@ func TestVersionedUser_base(t *testing.T) {
18741879
if stored.Version != 2 {
18751880
t.Fatalf("expected stored version %d, was at version %d", 2, stored.Version)
18761881
}
1882+
if stored.CreatedAt != createdAt.UnixMilli() {
1883+
t.Fatalf("expected stored created_at %d, was at created_at %d", createdAt.UnixMilli(), stored.CreatedAt)
1884+
}
18771885
}
18781886

18791887
func TestVersionedUser_concurrentModification(t *testing.T) {

reflect.go

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,16 @@ import (
2222

2323
type fieldDesc struct {
2424
reflect.StructField
25-
optlock bool
25+
optlock bool
26+
noupdate bool
2627
}
2728

2829
type fieldMap map[string]fieldDesc
2930

3031
const (
31-
tagName = "db"
32-
tagOptlock = "optlock"
32+
tagName = "db"
33+
tagSubOptlock = "optlock"
34+
tagSubNoUpdate = "noupdate"
3335
)
3436

3537
// getMapping returns a mapping for the type t, using the tagName and
@@ -52,7 +54,7 @@ func getMapping(t reflect.Type, mapFunc func(string) string) fieldMap {
5254
for fieldPos := 0; fieldPos < tq.t.NumField(); fieldPos++ {
5355
f := tq.t.Field(fieldPos)
5456

55-
name, optlock := readTag(f.Tag.Get(tagName))
57+
name, optlock, noupdate := readTag(f.Tag.Get(tagName))
5658

5759
// Breadth first search of untagged anonymous embedded structs.
5860
if f.Anonymous && f.Type.Kind() == reflect.Struct && name == "" {
@@ -86,18 +88,26 @@ func getMapping(t reflect.Type, mapFunc func(string) string) fieldMap {
8688
// Add it to the map at the current position.
8789
sf := f
8890
sf.Index = appendIndex(tq.p, fieldPos)
89-
m[name] = fieldDesc{sf, optlock}
91+
m[name] = fieldDesc{sf, optlock, noupdate}
9092
}
9193
}
9294
return m
9395
}
9496

95-
func readTag(tag string) (string, bool) {
96-
if comma := strings.Index(tag, ","); comma != -1 {
97-
return tag[0:comma], tag[comma+1:len(tag)] == tagOptlock
98-
} else {
99-
return tag, false
97+
func readTag(tag string) (name string, optLock bool, noUpdate bool) {
98+
values := strings.Split(tag, ",")
99+
name = values[0]
100+
if len(values) > 1 {
101+
for _, s := range values[1:] {
102+
switch s {
103+
case tagSubOptlock:
104+
optLock = true
105+
case tagSubNoUpdate:
106+
noUpdate = true
107+
}
108+
}
100109
}
110+
return name, optLock, noUpdate
101111
}
102112

103113
func getDBFields(t reflect.Type) fieldMap {

reflect_test.go

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -158,26 +158,32 @@ func TestMapping(t *testing.T) {
158158

159159
func TestReadTag(t *testing.T) {
160160
testCases := []struct {
161-
tag string
162-
name string
163-
optlock bool
161+
tag string
162+
name string
163+
optlock bool
164+
noupdate bool
164165
}{
165-
{"-", "-", false},
166-
{"foo", "foo", false},
167-
{"foo,", "foo", false},
168-
{"foo,optlock", "foo", true},
169-
{",optlock", "", true},
170-
{",wrong", "", false},
171-
{",", "", false},
166+
{"-", "-", false, false},
167+
{"foo", "foo", false, false},
168+
{"foo,", "foo", false, false},
169+
{"foo,optlock", "foo", true, false},
170+
{",optlock", "", true, false},
171+
{",wrong", "", false, false},
172+
{",noupdate", "", false, true},
173+
{",optlock,noupdate", "", true, true},
174+
{",", "", false, false},
172175
}
173176
for _, c := range testCases {
174-
name, optlock := readTag(c.tag)
177+
name, optlock, noupdate := readTag(c.tag)
175178
if name != c.name {
176179
t.Errorf("%s: expected name '%s', actual '%s'", c.tag, c.name, name)
177180
}
178181
if optlock != c.optlock {
179182
t.Errorf("%s: expected optlock '%t', actual '%t'", c.tag, c.optlock, optlock)
180183
}
184+
if noupdate != c.noupdate {
185+
t.Errorf("%s: expected optlock '%t', actual '%t'", c.tag, c.noupdate, noupdate)
186+
}
181187
}
182188
}
183189

0 commit comments

Comments
 (0)