@@ -30,18 +30,34 @@ type Table[T any, P Record[T, I], I ID] struct {
3030 Paginator Paginator [P ]
3131}
3232
33- // helpers for setting timestamp fields
34- type (
35- hasSetCreatedAt interface {
36- SetCreatedAt (time.Time )
37- }
38- hasSetUpdatedAt interface {
39- SetUpdatedAt (time.Time )
40- }
41- hasSetDeletedAt interface {
42- SetDeletedAt (time.Time )
43- }
44- )
33+ // HasSetCreatedAt is implemented by records that track creation time.
34+ // Insert will automatically call SetCreatedAt with the current UTC time.
35+ type HasSetCreatedAt interface {
36+ SetCreatedAt (time.Time )
37+ }
38+
39+ // HasSetUpdatedAt is implemented by records that track update time.
40+ // Insert, Update, and Save will automatically call SetUpdatedAt with the current UTC time.
41+ type HasSetUpdatedAt interface {
42+ SetUpdatedAt (time.Time )
43+ }
44+
45+ // HasSetDeletedAt is implemented by records that support soft delete.
46+ // DeleteByID will call SetDeletedAt with the current UTC time to soft-delete,
47+ // and RestoreByID will call SetDeletedAt with a zero time.Time{} to restore.
48+ //
49+ // Implementations should treat a zero time as a restore (clear the timestamp):
50+ //
51+ // func (r *MyRecord) SetDeletedAt(t time.Time) {
52+ // if t.IsZero() {
53+ // r.DeletedAt = nil // restore: clear the timestamp
54+ // return
55+ // }
56+ // r.DeletedAt = &t // soft delete: set the timestamp
57+ // }
58+ type HasSetDeletedAt interface {
59+ SetDeletedAt (time.Time )
60+ }
4561
4662// Insert inserts one or more records. Sets CreatedAt and UpdatedAt timestamps if available.
4763// Records are returned with their generated fields populated via RETURNING *.
@@ -66,10 +82,10 @@ func (t *Table[T, P, I]) insertOne(ctx context.Context, record P) error {
6682 }
6783
6884 now := time .Now ().UTC ()
69- if row , ok := any (record ).(hasSetCreatedAt ); ok {
85+ if row , ok := any (record ).(HasSetCreatedAt ); ok {
7086 row .SetCreatedAt (now )
7187 }
72- if row , ok := any (record ).(hasSetUpdatedAt ); ok {
88+ if row , ok := any (record ).(HasSetUpdatedAt ); ok {
7389 row .SetUpdatedAt (now )
7490 }
7591
@@ -97,10 +113,10 @@ func (t *Table[T, P, I]) insertAll(ctx context.Context, records []P) error {
97113 return fmt .Errorf ("validate record: %w" , err )
98114 }
99115
100- if row , ok := any (r ).(hasSetCreatedAt ); ok {
116+ if row , ok := any (r ).(HasSetCreatedAt ); ok {
101117 row .SetCreatedAt (now )
102118 }
103- if row , ok := any (r ).(hasSetUpdatedAt ); ok {
119+ if row , ok := any (r ).(HasSetUpdatedAt ); ok {
104120 row .SetUpdatedAt (now )
105121 }
106122 }
@@ -152,7 +168,7 @@ func (t *Table[T, P, I]) updateOne(ctx context.Context, record P) error {
152168 return fmt .Errorf ("validate record: %w" , err )
153169 }
154170
155- if row , ok := any (record ).(hasSetUpdatedAt ); ok {
171+ if row , ok := any (record ).(HasSetUpdatedAt ); ok {
156172 row .SetUpdatedAt (time .Now ().UTC ())
157173 }
158174
@@ -183,7 +199,7 @@ func (t *Table[T, P, I]) updateAll(ctx context.Context, records []P) error {
183199 return fmt .Errorf ("validate record: %w" , err )
184200 }
185201
186- if row , ok := any (r ).(hasSetUpdatedAt ); ok {
202+ if row , ok := any (r ).(HasSetUpdatedAt ); ok {
187203 row .SetUpdatedAt (now )
188204 }
189205
@@ -223,7 +239,7 @@ func (t *Table[T, P, I]) saveOne(ctx context.Context, record P) error {
223239 return fmt .Errorf ("validate record: %w" , err )
224240 }
225241
226- if row , ok := any (record ).(hasSetUpdatedAt ); ok {
242+ if row , ok := any (record ).(HasSetUpdatedAt ); ok {
227243 row .SetUpdatedAt (time .Now ().UTC ())
228244 }
229245
@@ -270,13 +286,13 @@ func (t *Table[T, P, I]) saveAll(ctx context.Context, records []P) error {
270286 return fmt .Errorf ("validate record: %w" , err )
271287 }
272288
273- if row , ok := any (r ).(hasSetUpdatedAt ); ok {
289+ if row , ok := any (r ).(HasSetUpdatedAt ); ok {
274290 row .SetUpdatedAt (now )
275291 }
276292
277293 var zero I
278294 if r .GetID () == zero {
279- if row , ok := any (r ).(hasSetCreatedAt ); ok {
295+ if row , ok := any (r ).(HasSetCreatedAt ); ok {
280296 row .SetCreatedAt (now )
281297 }
282298
@@ -432,7 +448,7 @@ func (t *Table[T, P, I]) DeleteByID(ctx context.Context, id I) error {
432448 }
433449
434450 // Soft delete.
435- if row , ok := any (record ).(hasSetDeletedAt ); ok {
451+ if row , ok := any (record ).(HasSetDeletedAt ); ok {
436452 row .SetDeletedAt (time .Now ().UTC ())
437453 if err := t .Save (ctx , record ); err != nil {
438454 return fmt .Errorf ("soft delete: %w" , err )
@@ -444,6 +460,27 @@ func (t *Table[T, P, I]) DeleteByID(ctx context.Context, id I) error {
444460 return t .HardDeleteByID (ctx , id )
445461}
446462
463+ // RestoreByID restores a soft-deleted record by ID by clearing its DeletedAt timestamp.
464+ // Returns an error if the record does not implement .SetDeletedAt().
465+ func (t * Table [T , P , I ]) RestoreByID (ctx context.Context , id I ) error {
466+ record , err := t .GetByID (ctx , id )
467+ if err != nil {
468+ return fmt .Errorf ("restore: %w" , err )
469+ }
470+
471+ row , ok := any (record ).(HasSetDeletedAt )
472+ if ! ok {
473+ return fmt .Errorf ("restore: record does not support soft delete" )
474+ }
475+
476+ row .SetDeletedAt (time.Time {})
477+ if err := t .Save (ctx , record ); err != nil {
478+ return fmt .Errorf ("restore: %w" , err )
479+ }
480+
481+ return nil
482+ }
483+
447484// HardDeleteByID permanently deletes a record by ID.
448485func (t * Table [T , P , I ]) HardDeleteByID (ctx context.Context , id I ) error {
449486 q := t .SQL .Delete (t .Name ).Where (sq.Eq {t .IDColumn : id })
0 commit comments