-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathinsert.go
164 lines (154 loc) · 5.29 KB
/
insert.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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
package sqlinsert
import (
"context"
"database/sql"
"fmt"
"reflect"
"strings"
)
// InsertWith models functionality needed to execute a SQL INSERT statement with database/sql via sql.DB or sql.Tx.
// Note: sql.Conn is also supported, however, for PrepareContext and ExecContext only.
type InsertWith interface {
Prepare(query string) (*sql.Stmt, error)
PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
Exec(query string, args ...interface{}) (sql.Result, error)
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
}
// Inserter models functionality to produce a valid SQL INSERT statement with bind args.
type Inserter interface {
Tokenize(tokenType TokenType) string
Columns() string
Params() string
SQL() string
Args() []interface{}
Insert(with InsertWith) (*sql.Stmt, error)
InsertContext(ctx context.Context, with InsertWith) (*sql.Stmt, error)
}
// Insert models data used to produce a valid SQL INSERT statement with bind args.
// Table is the table name. Data is either a struct with column-name tagged fields and the data to be inserted or
// a slice struct (struct ptr works too).
type Insert struct {
Table string
Data interface{}
}
// Columns returns the comma-separated list of column names-as-tokens for the SQL INSERT statement.
// Multi Row Insert: Insert.Data is a slice; first item in slice is
func (ins *Insert) Columns() string {
v := reflect.ValueOf(ins.Data)
if v.Kind() == reflect.Slice {
if v.Index(0).Kind() == reflect.Pointer {
return Tokenize(v.Index(0).Elem().Type(), ColumnNameTokenType)
} else {
return Tokenize(v.Index(0).Type(), ColumnNameTokenType)
}
} else if v.Kind() == reflect.Pointer {
return Tokenize(v.Elem().Type(), ColumnNameTokenType)
} else {
return Tokenize(v.Type(), ColumnNameTokenType)
}
}
// Params returns the comma-separated list of bind param tokens for the SQL INSERT statement.
func (ins *Insert) Params() string {
v := reflect.ValueOf(ins.Data)
if v.Kind() == reflect.Slice {
var (
b strings.Builder
paramRow string
)
if v.Index(0).Kind() == reflect.Pointer {
paramRow = Tokenize(v.Index(0).Elem().Type(), UseTokenType)
} else {
paramRow = Tokenize(v.Index(0).Type(), UseTokenType)
}
b.WriteString(paramRow)
for i := 1; i < v.Len(); i++ {
b.WriteString(`,`)
b.WriteString(paramRow)
}
return b.String()
} else if v.Kind() == reflect.Pointer {
return Tokenize(v.Elem().Type(), UseTokenType)
} else {
return Tokenize(v.Type(), UseTokenType)
}
}
// SQL returns the full parameterized SQL INSERT statement.
func (ins *Insert) SQL() string {
var insertSQL strings.Builder
_, _ = fmt.Fprintf(&insertSQL, `INSERT INTO %s %s VALUES %s`,
ins.Table, ins.Columns(), ins.Params())
return insertSQL.String()
}
// Args returns the arguments to be bound in Insert() or the variadic Exec/ExecContext functions in database/sql.
func (ins *Insert) Args() []interface{} {
var (
data reflect.Value
rec reflect.Value
recType reflect.Type
args []interface{}
)
data = reflect.ValueOf(ins.Data)
if data.Kind() == reflect.Slice { // Multi row INSERT: Insert.Data is a slice-of-struct-pointer or slice-of-struct
argIndex := -1
if data.Index(0).Kind() == reflect.Pointer { // First slice element is struct pointers
recType = data.Index(0).Elem().Type()
} else { // First slice element is struct
recType = data.Index(0).Type()
}
numRecs := data.Len()
numFieldsPerRec := recType.NumField()
numBindArgs := numRecs * numFieldsPerRec
args = make([]interface{}, numBindArgs)
for rowIndex := 0; rowIndex < data.Len(); rowIndex++ {
if data.Index(0).Kind() == reflect.Pointer {
rec = data.Index(rowIndex).Elem() // Cur slice elem is struct pointer, get arg val from ref-element
} else {
rec = data.Index(rowIndex) // Cur slice elem is struct, can get arg val directly
}
for fieldIndex := 0; fieldIndex < numFieldsPerRec; fieldIndex++ {
argIndex += 1
args[argIndex] = rec.Field(fieldIndex).Interface()
}
}
return args
} else { // Single-row INSERT: Insert.Data must be a struct pointer or struct (otherwise reflect will panic)
if data.Kind() == reflect.Pointer { // Row information via struct pointer
recType = data.Elem().Type()
rec = data.Elem()
} else { // Row information via struct
recType = data.Type()
rec = data
}
args = make([]interface{}, recType.NumField())
for i := 0; i < recType.NumField(); i++ {
args[i] = rec.Field(i).Interface()
}
return args
}
}
// Insert prepares and executes a SQL INSERT statement on a *sql.DB, *sql.Tx,
// or other Inserter-compatible interface to Prepare and Exec.
func (ins *Insert) Insert(with InsertWith) (*sql.Stmt, error) {
stmt, err := with.Prepare(ins.SQL())
if err != nil {
return nil, err
}
defer func(stmt *sql.Stmt) {
_ = stmt.Close()
}(stmt)
_, err = stmt.Exec(ins.Args()...)
return stmt, err
}
// InsertContext prepares and executes a SQL INSERT statement on a *sql.DB, *sql.Tx, *sql.Conn,
// or other Inserter-compatible interface to PrepareContext and ExecContext.
func (ins *Insert) InsertContext(ctx context.Context, with InsertWith) (*sql.Stmt, error) {
stmt, err := with.Prepare(ins.SQL())
if err != nil {
return nil, err
}
defer func(stmt *sql.Stmt) {
_ = stmt.Close()
}(stmt)
_, err = stmt.ExecContext(ctx, ins.Args()...)
return stmt, err
}