UNPKG

6.31 kBJavaScriptView Raw
1var async = require('./async')
2var Row = require('./row')
3
4// TODO: if the primary key value changes, refresh old and new cache
5module.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 // get the properties that have data
23 var properties = []
24 table.columns.forEach(function(column) {
25 // TODO: check for unchanged jsonb data (equivalent objects)
26 if (typeof data[column.name] !== 'undefined') {
27 // don't save data that hasn't changed
28 // (self._data is a snapshot from the time of Row instatiation)
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 // get a db client - since we're using a transaction, we have to make
53 // sure we're not getting different connections from the db conn pool
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 // begin transaction
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 // save 1-to-1 nested objects
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 // get the newly inserted foreign key so it gets linked to this main row
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 // save this main row
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 // save 1-to-many arrays of nested objects
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 // we found a data field that is a valid 1-to-m array of foreign data rows
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 // set the foreign key value(s)
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 // commit transaction
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 // rollback transaction
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 // invalidate the memoization
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 // save to cache
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
216function isArray(obj) {
217 return Object.prototype.toString.call(obj) === '[object Array]'
218}
219
220function isObject(obj) {
221 return toString.call(obj) === '[object Object]'
222}