-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsql_insert.go
172 lines (160 loc) · 4.36 KB
/
sql_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
package psql
import (
"encoding/json"
"fmt"
"strings"
)
type (
// InsertSQL can be created with Model.NewSQL().AsInsert()
InsertSQL struct {
*SQL
fields []string
outputExpression string
conflictTargets []string
conflictActions []string
}
)
// Convert SQL to InsertSQL. The optional fields will be used in DoUpdateAll().
func (s SQL) AsInsert(fields ...string) *InsertSQL {
i := &InsertSQL{
SQL: &s,
fields: fields,
}
i.SQL.main = i
return i
}
// Insert builds an INSERT INTO statement with fields and values in the
// changes.
//
// var id int
// m.Insert(changes...).Returning("id").MustQueryRow(&id)
//
// Changes can be a list of field name and value pairs and can also be obtained
// from methods like Changes(), FieldChanges(), Assign(), Bind(), Filter().
//
// m.Insert("FieldA", 123, "FieldB", "other").MustExecute()
func (m Model) Insert(lotsOfChanges ...interface{}) *InsertSQL {
fields := []string{}
fieldsIndex := map[string]int{}
numbers := []string{}
values := []interface{}{}
jsonbFields := map[string]Changes{}
i := 1
for _, changes := range m.getChanges(lotsOfChanges) {
for field, value := range changes {
if field.Jsonb != "" {
if _, ok := jsonbFields[field.Jsonb]; !ok {
jsonbFields[field.Jsonb] = Changes{}
}
jsonbFields[field.Jsonb][field] = value
continue
}
if idx, ok := fieldsIndex[field.Name]; ok { // prevent duplication
values[idx] = value
continue
}
fields = append(fields, field.ColumnName)
fieldsIndex[field.Name] = i - 1
numbers = append(numbers, fmt.Sprintf("$%d", i))
values = append(values, value)
i += 1
}
}
for jsonbField, changes := range jsonbFields {
fields = append(fields, jsonbField)
numbers = append(numbers, fmt.Sprintf("$%d", i))
out := map[string]interface{}{}
for field, value := range changes {
out[field.ColumnName] = value
}
j, _ := json.Marshal(out)
values = append(values, string(j))
i += 1
}
var sql string
if len(fields) > 0 {
sql = "INSERT INTO " + m.tableName + " (" + strings.Join(fields, ", ") + ") VALUES (" + strings.Join(numbers, ", ") + ")"
}
return m.NewSQL(sql, values...).AsInsert(fields...)
}
// Adds RETURNING clause to INSERT INTO statement.
func (s *InsertSQL) Returning(expressions ...string) *InsertSQL {
s.outputExpression = strings.Join(expressions, ", ")
return s
}
// Used with DoNothing(), DoUpdate() or DoUpdateAll().
func (s *InsertSQL) OnConflict(targets ...string) *InsertSQL {
s.conflictTargets = append([]string{}, targets...)
return s
}
// Used with OnConflict(), adds ON CONFLICT DO NOTHING clause to INSERT INTO
// statement.
func (s *InsertSQL) DoNothing() *InsertSQL {
s.conflictActions = []string{}
return s
}
// Used with OnConflict(), adds custom expressions ON CONFLICT ... DO UPDATE
// SET ... clause to INSERT INTO statement.
func (s *InsertSQL) DoUpdate(expressions ...string) *InsertSQL {
for _, expr := range expressions {
s.conflictActions = append(s.conflictActions, expr)
}
return s
}
// DoUpdateAll is like DoUpdate but update every field.
func (s *InsertSQL) DoUpdateAll() *InsertSQL {
for _, field := range s.fields {
s.conflictActions = append(s.conflictActions, field+" = EXCLUDED."+field)
}
return s
}
// DoUpdateAllExcept is like DoUpdateAll but except some field names.
func (s *InsertSQL) DoUpdateAllExcept(fields ...string) *InsertSQL {
outer:
for _, field := range s.fields {
for _, f := range fields {
if f == field {
continue outer
}
}
s.conflictActions = append(s.conflictActions, field+" = EXCLUDED."+field)
}
return s
}
// Perform operations on the chain.
func (s *InsertSQL) Tap(funcs ...func(*InsertSQL) *InsertSQL) *InsertSQL {
for i := range funcs {
s = funcs[i](s)
}
return s
}
func (s InsertSQL) String() string {
sql := s.sql
if s.conflictTargets != nil && s.conflictActions != nil {
action := strings.Join(s.conflictActions, ", ")
if action == "" {
action = "DO NOTHING"
} else {
action = "DO UPDATE SET " + action
}
target := strings.Join(s.conflictTargets, ", ")
if target != "" && !strings.HasPrefix(target, "(") {
target = "(" + target + ")"
}
if sql == "" {
return sql
}
if target == "" {
sql += " ON CONFLICT " + action
} else {
sql += " ON CONFLICT " + target + " " + action
}
}
if s.outputExpression != "" {
if sql == "" {
return sql
}
sql += " RETURNING " + s.outputExpression
}
return sql
}