-
Notifications
You must be signed in to change notification settings - Fork 2.2k
/
generic.go
133 lines (106 loc) · 3.25 KB
/
generic.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package main
import (
"database/sql"
"log"
)
// =============================================================================
// The User and Customer types each have a specialized insert function. These
// functions use value semantic mutation by accepting a value of the specific
// type and returning a new value of the type modified with the id for the newly
// inserted data. The query and arguments required are bound inside the function.
type User struct {
ID int64
Name string
Email string
}
func InsertUser(db *sql.DB, u User) (User, error) {
const query = "insert into users (name, email) values ($1, $2)"
result, err := ExecuteQuery(query, u.Name, u.Email)
if err != nil {
return User{}, err
}
id, err := result.LastInsertId()
if err != nil {
return User{}, err
}
u.ID = id
return u, nil
}
type Customer struct {
ID int64
Name string
Email string
}
func InsertCustomer(db *sql.DB, c Customer) (Customer, error) {
const query = "insert into customers (name, email) values ($1, $2)"
result, err := ExecuteQuery(query, c.Name, c.Email)
if err != nil {
return Customer{}, err
}
id, err := result.LastInsertId()
if err != nil {
return Customer{}, err
}
c.ID = id
return c, nil
}
// =============================================================================
// This version of a generic insert function is cleaner. It accepts the query
// and args as parameters and still implements the value semantic mutation like
// the concrete version. There is no setter needed since the entities interface
// provides the compiler with the information that values of type T will contain
// an ID field. This is because both these types share this common field.
//
// NOTE: I am not sure I want anyone writing code like this.
// This is an experiment.
type entities interface {
User | Customer
}
func Insert[T entities](db *sql.DB, entity T, query string, args ...interface{}) (T, error) {
var zero T
result, err := ExecuteQuery(query, args...)
if err != nil {
return zero, err
}
id, err := result.LastInsertId()
if err != nil {
return zero, err
}
// The entities interface provides this support. This has not be implemented
// by the tooling though it is supported by the draft.
entity.ID = id
return entity, nil
}
func InsertUser2(db *sql.DB, u User) (User, error) {
const query = "insert into users (name, email) values ($1, $2)"
u, err := Insert(db, u, query, u.Name, u.Email)
if err != nil {
return User{}, err
}
return u, nil
}
// =============================================================================
type Result struct{}
func (r Result) LastInsertId() (int64, error) { return 1, nil }
func (r Result) RowsAffected() (int64, error) { return 1, nil }
func ExecuteQuery(query string, args ...interface{}) (sql.Result, error) {
return Result{}, nil
}
// =============================================================================
func main() {
var db *sql.DB
var u User
query := "insert into users (name, email) values ($1, $2)"
u, err := Insert(db, u, query, u.Name, u.Email)
if err != nil {
log.Fatal(err)
}
log.Println(u)
var c Customer
query = "insert into customers (name, email) values ($1, $2)"
c, err = Insert(db, c, query, u.Name, u.Email)
if err != nil {
log.Fatal(err)
}
log.Println(c)
}