/
struct_insert.go
225 lines (199 loc) · 6.31 KB
/
struct_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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
package godb
import (
"database/sql"
"fmt"
"github.com/samonzeweb/godb/adapters"
"github.com/samonzeweb/godb/types"
)
// StructInsert builds an INSERT statement for the given object.
//
// Example (book is a struct instance, books a slice) :
//
// err := db.Insert(&book).Do()
//
// err = db.BulkInsert(&books).Do()
type StructInsert struct {
error error
insertStatement *InsertStatement
recordDescription *recordDescription
whiteList []string
blackList []string
}
// Insert initializes an INSERT sql statement for the given object.
func (db *DB) Insert(record interface{}) *StructInsert {
si := db.buildInsert(record)
if si.recordDescription.isSlice {
si.error = fmt.Errorf("Insert accepts only a single instance, got a slice")
}
return si
}
// BulkInsert initializes an INSERT sql statement for a slice.
//
// Warning : not all databases are able to update the auto columns in the
// case of insert with multiple rows. Only adapters implementing the
// InsertReturningSuffix interface will have auto columns updated.
func (db *DB) BulkInsert(record interface{}) *StructInsert {
si := db.buildInsert(record)
if !si.recordDescription.isSlice {
si.error = fmt.Errorf("BulkInsert accepts only a slice")
}
return si
}
// buildInsert initializes an insert sql statement for the given object, either
// a slice or a single instance.
// For internal use only.
func (db *DB) buildInsert(record interface{}) *StructInsert {
var err error
si := &StructInsert{}
si.recordDescription, err = buildRecordDescription(record)
if err != nil {
si.error = err
return si
}
quotedTableName := db.quote(db.defaultTableNamer(si.recordDescription.getTableName()))
si.insertStatement = db.InsertInto(quotedTableName)
return si
}
// Whitelist saves columns to be inserted from struct
// It adds columns to list each time it is called
// whitelist should not include auto key tagged columns
func (si *StructInsert) Whitelist(columns ...string) *StructInsert {
si.whiteList = append(si.whiteList, columns...)
return si
}
// WhitelistReset resets whiteList
func (si *StructInsert) WhitelistReset() *StructInsert {
si.whiteList = nil
return si
}
// Blacklist saves columns not to be inserted from struct
// It adds columns to list each time it is called. If a column defined in whitelist is
// also given in black list than that column will be blacklisted.
func (si *StructInsert) Blacklist(columns ...string) *StructInsert {
si.blackList = append(si.blackList, columns...)
return si
}
// BlacklistReset resets blacklist
func (si *StructInsert) BlacklistReset() *StructInsert {
si.blackList = nil
return si
}
// Do executes the insert statement.
//
// The behavior differs according to the adapter. If it implements the
// InsertReturningSuffixer interface it will use it and fill all auto fields
// of the given struct. Otherwise it only fills the key with LastInsertId.
//
// With BulkInsert the behavior changeq according to the adapter, see
// BulkInsert documentation for more information.
func (si *StructInsert) Do() error {
if si.error != nil {
return si.error
}
// Columns names
var columns []string
if len(si.whiteList) > 0 {
columns = si.whiteList
} else {
columns = si.recordDescription.structMapping.GetNonAutoColumnsNames()
}
// Filter black listed columns
i := 0
for _, c := range si.blackList {
i = 0
for _, a := range columns {
if a != c {
columns[i] = a
i++
}
}
columns = columns[:i]
}
hasWB := (len(si.whiteList) + len(si.blackList)) > 0
if !hasWB {
si.insertStatement = si.insertStatement.Columns(si.insertStatement.db.quoteAll(columns)...)
}
// Values
var values []interface{}
len := si.recordDescription.len()
wbColsSet := false
for i := 0; i < len; i++ {
currentRecord := si.recordDescription.index(i)
if hasWB {
if !wbColsSet { // order of old columns list and current values list may not be same so, set here:
columns, values = si.recordDescription.structMapping.GetNonAutoFieldsValuesFiltered(currentRecord, columns, false)
si.insertStatement = si.insertStatement.Columns(si.insertStatement.db.quoteAll(columns)...)
wbColsSet = true
} else {
// as columns are already ordered, just get values in same order
_, values = si.recordDescription.structMapping.GetNonAutoFieldsValuesFiltered(currentRecord, columns, true)
}
} else {
values = si.recordDescription.structMapping.GetNonAutoFieldsValues(currentRecord)
}
si.insertStatement.Values(values...)
}
// Use a RETURNING (or similar) clause ?
returningBuilder, ok := si.insertStatement.db.adapter.(adapters.ReturningBuilder)
if ok {
autoColumns := si.recordDescription.structMapping.GetAutoColumnsNames()
si.insertStatement.Returning(returningBuilder.FormatForNewValues(autoColumns)...)
}
// Run
if returningBuilder != nil {
// the function which will return the pointers according to the given columns
f := func(record interface{}, columns []string) ([]interface{}, error) {
pointers, err := si.recordDescription.structMapping.GetAutoFieldsPointers(record)
return pointers, err
}
_, err := si.insertStatement.doWithReturning(si.recordDescription, f)
return err
}
// Case for adapters not implenting ReturningSuffix(), we use the
// value given by LastInsertId() (through Do method)
insertedID, err := si.insertStatement.Do()
if err != nil {
return err
}
// Bulk insert don't update ids with this adater, the insert was done,
// without error, but the new ids are unknown.
if si.recordDescription.isSlice {
return nil
}
// Get the Id
pointerToID, err := si.recordDescription.structMapping.GetAutoKeyPointer(si.recordDescription.record)
if err != nil {
return err
}
if pointerToID != nil {
switch t := pointerToID.(type) {
default:
return fmt.Errorf("Not implemented type for key : %T", pointerToID)
case *int:
*t = int(insertedID)
case *int8:
*t = int8(insertedID)
case *int16:
*t = int16(insertedID)
case *int32:
*t = int32(insertedID)
case *int64:
*t = int64(insertedID)
case *uint:
*t = uint(insertedID)
case *uint8:
*t = uint8(insertedID)
case *uint16:
*t = uint16(insertedID)
case *uint32:
*t = uint32(insertedID)
case *uint64:
*t = uint64(insertedID)
case *types.NullInt64:
*t = types.ToNullInt64(insertedID)
case *sql.NullInt64:
*t = sql.NullInt64{Int64: insertedID, Valid: true}
}
}
return nil
}