@@ -10,18 +10,94 @@ import (
1010 "github.com/jackc/pgx/v5"
1111)
1212
13+ // NewTable creates a new Table instance for a specific record type.
14+ //
15+ // T: the concrete struct type representing a single record (e.g., Account).
16+ // PT: the pointer type to T (e.g., *Account), which must implement tableP[T, IDT].
17+ // IDT: the type of the primary key (e.g., int64), which must be comparable.
18+ //
19+ // db: a pointer to the DB instance
20+ //
21+ // The function introspects a zero value of T to extract metadata if it
22+ // implements the Base[IDT], specifically:
23+ //
24+ // - Validate() error -> validation function, which si called everytime on saving
25+ // - DBTableName() string -> returns the database table name.
26+ // - GetID() IDT -> returns primary key ID
27+ // - GetIDColumn() string -> returns the name of the primary key column, which is then used for default sorting
28+ //
29+ // Additionally, records may optionally implement the following interfaces to
30+ // allow automatic timestamp management:
31+ //
32+ // - hasSetCreatedAt -> SetCreatedAt(time.Time), called automatically on record creation
33+ // - hasSetUpdatedAt -> SetUpdatedAt(time.Time), called automatically on record creation and update
34+ // - hasSetDeletedAt -> SetDeletedAt(time.Time), called automatically on soft deletion
35+ //
36+ // Example usage:
37+ //
38+ // type accountsTable struct {
39+ // *pgkit.Table[Account, *Account, int64]
40+ // }
41+ //
42+ // at := accountsTable{
43+ // Table: pgkit.NewTable[Account, *Account, int64](db),
44+ // }
45+ //
46+ // type Account struct {
47+ // ID int64 `db:"id,omitempty"`
48+ // Name string `db:"name"`
49+ // CreatedAt time.Time `db:"created_at,omitempty"` // ,omitempty will rely on Postgres DEFAULT
50+ // UpdatedAt time.Time `db:"updated_at,omitempty"` // ,omitempty will rely on Postgres DEFAULT
51+ // }
52+ //
53+ // func (a *Account) DBTableName() string { return "accounts" }
54+ // func (a *Account) GetIDColumn() string { return "id" }
55+ // func (a *Account) GetID() int64 { return a.ID }
56+ // func (a *Account) SetUpdatedAt(t time.Time) { a.UpdatedAt = t }
57+ //
58+ // func (a *Account) Validate() error {
59+ // if a.Name == "" {
60+ // return fmt.Errorf("name is required")
61+ // }
62+ // return nil
63+ // }
64+ func NewTable [T any , PT TableP [T , IDT ], IDT comparable ](db * DB , name string ) * Table [T , PT , IDT ] {
65+ var t T
66+
67+ idColumn := ""
68+ if v , ok := any (& t ).(Base [IDT ]); ok {
69+ idColumn = v .GetIDColumn ()
70+ }
71+
72+ return & Table [T , PT , IDT ]{
73+ DB : db ,
74+ Name : name ,
75+ IDColumn : idColumn ,
76+ }
77+ }
78+
1379// Table provides basic CRUD operations for database records.
1480// Records must implement GetID() and Validate() methods.
15- type Table [T any , PT interface {
16- * T // Enforce T is a pointer.
17- GetID () IDT
18- Validate () error
19- }, IDT comparable ] struct {
81+ type Table [T any , PT TableP [T , IDT ], IDT comparable ] struct {
2082 * DB
2183 Name string
2284 IDColumn string
2385}
2486
87+ type TableP [T any , IDT comparable ] interface {
88+ * T // Enforce that T is a pointer.
89+ Base [IDT ]
90+ }
91+
92+ type Base [IDT comparable ] interface {
93+ Validate () error
94+
95+ GetID () IDT
96+ GetIDColumn () string
97+ }
98+
99+ func (t * Table [T , PT , IDT ]) DBTableName () string { return t .Name }
100+
25101type hasSetCreatedAt interface {
26102 SetCreatedAt (time.Time )
27103}
@@ -68,7 +144,7 @@ func (t *Table[T, PT, IDT]) saveOne(ctx context.Context, record PT) error {
68144 Suffix ("RETURNING *" )
69145
70146 if err := t .Query .GetOne (ctx , q , record ); err != nil {
71- return fmt .Errorf ("save: insert record: %w" , err )
147+ return fmt .Errorf ("insert record: %w" , err )
72148 }
73149
74150 return nil
@@ -77,7 +153,7 @@ func (t *Table[T, PT, IDT]) saveOne(ctx context.Context, record PT) error {
77153 // Update
78154 q := t .SQL .UpdateRecord (record , sq.Eq {t .IDColumn : record .GetID ()}, t .Name )
79155 if _ , err := t .Query .Exec (ctx , q ); err != nil {
80- return fmt .Errorf ("save: update record: %w" , err )
156+ return fmt .Errorf ("update record: %w" , err )
81157 }
82158
83159 return nil
0 commit comments