1 | var async = require('./async')
|
2 | var Row = require('./row')
|
3 |
|
4 |
|
5 | module.exports = function(params, cb) {
|
6 | var self = this
|
7 | var data = self
|
8 | var table = self._table
|
9 | var db = table.db
|
10 | var iq = db._platform.identifierQuoteChar
|
11 |
|
12 | params = params || {}
|
13 | if (typeof params === 'function') {
|
14 | cb = params
|
15 | params = {}
|
16 | }
|
17 | cb = cb || db._promiseResolver()
|
18 |
|
19 | var client = params.client
|
20 | var topLevel = false
|
21 |
|
22 |
|
23 | var properties = []
|
24 | table.columns.forEach(function(column) {
|
25 |
|
26 | if (typeof data[column.name] !== 'undefined') {
|
27 |
|
28 |
|
29 | if (data[column.name] !== self._data[column.name]) {
|
30 | properties.push(column.name)
|
31 | }
|
32 | }
|
33 | })
|
34 |
|
35 | var pk = self.getPrimaryKey()
|
36 | var pkWhere = false
|
37 | var isInsert = false
|
38 | if (Object.keys(pk).length === 0) {
|
39 | isInsert = true
|
40 | }
|
41 | Object.keys(pk).forEach(function(key) {
|
42 | if (!pk[key]) isInsert = true
|
43 | })
|
44 | if (!isInsert) {
|
45 | pkWhere = table.getPrimaryKeyWhereClause(pk)
|
46 | }
|
47 |
|
48 | var result
|
49 |
|
50 | async.series([
|
51 |
|
52 |
|
53 |
|
54 | function(next) {
|
55 | if (!client) {
|
56 | topLevel = true
|
57 | var conn = db._query.getConn({write: true})
|
58 | db._platform.getClient(conn, function(err, _client, _release) {
|
59 | client = _client
|
60 | client._release = _release
|
61 | client._nestedTransactionCount = 0
|
62 | next(err)
|
63 | })
|
64 | return
|
65 | }
|
66 | next(null)
|
67 | },
|
68 |
|
69 |
|
70 | function(next) {
|
71 | client._nestedTransactionCount++
|
72 | if (client._nestedTransactionCount === 1) {
|
73 | return db._platform.beginTransaction(client, next)
|
74 | }
|
75 | next(null)
|
76 | },
|
77 |
|
78 |
|
79 | function(next) {
|
80 | async.each(Object.keys(table.fk), function(name, nextFk) {
|
81 | if (data[name] && isObject(data[name])) {
|
82 | var foreignTable = db[table.fk[name].foreignTable]
|
83 | var foreignRow = new Row(data[name], foreignTable)
|
84 | foreignRow.save({client: client}, function(err, savedRow) {
|
85 | if (err) return nextFk(err)
|
86 |
|
87 | table.fk[name].columns.forEach(function(column, i) {
|
88 | data[column] = savedRow[table.fk[name].foreignColumns[i]]
|
89 | if (properties.indexOf(column) === -1) {
|
90 | properties.push(column)
|
91 | }
|
92 | })
|
93 | nextFk(null)
|
94 | })
|
95 | } else {
|
96 | nextFk(null)
|
97 | }
|
98 | }, next)
|
99 | },
|
100 |
|
101 |
|
102 | function(next) {
|
103 |
|
104 | var set = []
|
105 | properties.forEach(function(field) {
|
106 | set.push(iq + field + iq + ' = :' + field)
|
107 | })
|
108 | if (set.length === 0) return next()
|
109 |
|
110 | // insert or update
|
111 | db._platform.upsert(client, {
|
112 | table: table.name,
|
113 | set: set,
|
114 | properties: properties,
|
115 | pkWhere: pkWhere,
|
116 | data: data
|
117 | }, function(err, row) {
|
118 | if (err) {
|
119 | err.save = {
|
120 | table: table.name,
|
121 | data: data
|
122 | }
|
123 | return next(err)
|
124 | }
|
125 | result = row
|
126 | next(null)
|
127 | })
|
128 |
|
129 | },
|
130 |
|
131 |
|
132 | function(next) {
|
133 | async.each(Object.keys(data), function (field, done) {
|
134 | var foreignRowsName = self.getForeignRowsName(field)
|
135 | if (!foreignRowsName) return done(null)
|
136 | if (!isArray(data[field])) return done(new Error(table.name+'.'+field+' must be an array.'))
|
137 |
|
138 | var parts = foreignRowsName.split(':')
|
139 | var fkName = parts[0]
|
140 | var mTable = parts[1]
|
141 | var fk = db[mTable].fk[fkName]
|
142 | async.each(data[field], function (rowData, rowDone) {
|
143 |
|
144 | fk.columns.forEach(function (col, i) {
|
145 | var fkVal = result[fk.foreignColumns[i]]
|
146 | if (rowData[col] && rowData[col] !== fkVal) {
|
147 | return rowDone(new Error('Modifying foreign key "'+mTable+'.'+col+'" is not permitted.'))
|
148 | }
|
149 | rowData[col] = fkVal
|
150 | })
|
151 | var foreignRow = new Row(rowData, db[mTable])
|
152 | foreignRow.save({client: client}, function (err, savedRow) {
|
153 | rowDone(err)
|
154 | })
|
155 | }, done)
|
156 | }, function (err) {
|
157 | if (err) return next(err)
|
158 | next(null)
|
159 | })
|
160 | },
|
161 |
|
162 |
|
163 | function(next) {
|
164 | client._nestedTransactionCount--
|
165 | if (client._nestedTransactionCount === 0) {
|
166 | return db._platform.commitTransaction(client, function(err) {
|
167 | next(err)
|
168 | })
|
169 | }
|
170 | next(null)
|
171 | }
|
172 | ],
|
173 |
|
174 | function(err) {
|
175 | if (err) {
|
176 |
|
177 | if (!db._opts.silent) {
|
178 | console.log('\033[31m' + 'Save Error:', err.save)
|
179 | console.log('SQL Error:', err.message)
|
180 | console.log('Rollback Transaction' + '\033[0m')
|
181 | }
|
182 | client._nestedTransactionCount = 0
|
183 | if (topLevel) {
|
184 | db._platform.rollbackTransaction(client, function(rollbackError) {
|
185 | client._release()
|
186 | if (rollbackError) return cb(rollbackError)
|
187 | cb(err)
|
188 | })
|
189 | }
|
190 | return
|
191 | }
|
192 |
|
193 | if (topLevel) {
|
194 | client._release()
|
195 | }
|
196 | var savedRow = Row(result, db[table.name])
|
197 |
|
198 |
|
199 | savedRow._table.invalidateMemo(pk)
|
200 | var opts = self._table.db._opts
|
201 | if (!opts.cache || typeof opts.cache.set !== 'function') {
|
202 | return cb(null, savedRow)
|
203 | }
|
204 |
|
205 |
|
206 | var cacheKey = savedRow.getCacheKey()
|
207 | opts.cache.set(cacheKey, JSON.stringify(result), function(err) {
|
208 | if (err) return cb(err)
|
209 | cb(null, savedRow)
|
210 | })
|
211 | })
|
212 |
|
213 | return cb.promise ? cb.promise : this
|
214 | }
|
215 |
|
216 | function isArray(obj) {
|
217 | return Object.prototype.toString.call(obj) === '[object Array]'
|
218 | }
|
219 |
|
220 | function isObject(obj) {
|
221 | return toString.call(obj) === '[object Object]'
|
222 | }
|