UNPKG

8.77 kBJavaScriptView Raw
1var Big = require('big.js'),
2 db = require('../db')
3
4module.exports = function updateItem(store, data, cb) {
5
6 store.getTable(data.TableName, function(err, table) {
7 if (err) return cb(err)
8
9 if ((err = db.validateKey(data.Key, table)) != null) return cb(err)
10
11 if ((err = db.validateUpdates(data.AttributeUpdates, data._updates, table)) != null) return cb(err)
12
13 var itemDb = store.getItemDb(data.TableName), key = db.createKey(data.Key, table)
14
15 itemDb.lock(key, function(release) {
16 cb = release(cb)
17
18 itemDb.get(key, function(err, oldItem) {
19 if (err && err.name != 'NotFoundError') return cb(err)
20
21 if ((err = db.checkConditional(data, oldItem)) != null) return cb(err)
22
23 var returnObj = {}, item = data.Key,
24 paths = data._updates ? data._updates.paths : Object.keys(data.AttributeUpdates || {})
25
26 if (oldItem) {
27 item = deepClone(oldItem)
28 if (data.ReturnValues == 'ALL_OLD') {
29 returnObj.Attributes = oldItem
30 } else if (data.ReturnValues == 'UPDATED_OLD') {
31 returnObj.Attributes = db.mapPaths(paths, oldItem)
32 }
33 }
34
35 err = data._updates ? applyUpdateExpression(data._updates.sections, table, item) :
36 applyAttributeUpdates(data.AttributeUpdates, table, item)
37 if (err) return cb(err)
38
39 if (db.itemSize(item) > store.options.maxItemSize)
40 return cb(db.validationError('Item size to update has exceeded the maximum allowed size'))
41
42 if (data.ReturnValues == 'ALL_NEW') {
43 returnObj.Attributes = item
44 } else if (data.ReturnValues == 'UPDATED_NEW') {
45 returnObj.Attributes = db.mapPaths(paths, item)
46 }
47
48 returnObj.ConsumedCapacity = db.addConsumedCapacity(data, false, oldItem, item)
49
50 db.updateIndexes(store, table, oldItem, item, function(err) {
51 if (err) return cb(err)
52
53 itemDb.put(key, item, function(err) {
54 if (err) return cb(err)
55 cb(null, returnObj)
56 })
57 })
58 })
59 })
60 })
61}
62
63// Relatively fast deep clone of simple objects/arrays
64function deepClone(obj) {
65 if (typeof obj != 'object' || obj == null) return obj
66 var result
67 if (Array.isArray(obj)) {
68 result = new Array(obj.length)
69 for (var i = 0; i < obj.length; i++) {
70 result[i] = deepClone(obj[i])
71 }
72 } else {
73 result = Object.create(null)
74 for (var attr in obj) {
75 result[attr] = deepClone(obj[attr])
76 }
77 }
78 return result
79}
80
81function applyAttributeUpdates(updates, table, item) {
82 for (var attr in updates) {
83 var update = updates[attr]
84 if (update.Action == 'PUT' || update.Action == null) {
85 item[attr] = update.Value
86 } else if (update.Action == 'ADD') {
87 if (update.Value.N) {
88 if (item[attr] && !item[attr].N)
89 return db.validationError('Type mismatch for attribute to update')
90 if (!item[attr]) item[attr] = {N: '0'}
91 item[attr].N = new Big(item[attr].N).plus(update.Value.N).toFixed()
92 } else {
93 var type = Object.keys(update.Value)[0]
94 if (item[attr] && !item[attr][type])
95 return db.validationError('Type mismatch for attribute to update')
96 if (!item[attr]) item[attr] = {}
97 if (!item[attr][type]) item[attr][type] = []
98 var val = type == 'L' ? update.Value[type] : update.Value[type].filter(function(a) { // eslint-disable-line no-loop-func
99 return !~item[attr][type].indexOf(a)
100 })
101 item[attr][type] = item[attr][type].concat(val)
102 }
103 } else if (update.Action == 'DELETE') {
104 if (update.Value) {
105 type = Object.keys(update.Value)[0]
106 if (item[attr] && !item[attr][type])
107 return db.validationError('Type mismatch for attribute to update')
108 if (item[attr] && item[attr][type]) {
109 item[attr][type] = item[attr][type].filter(function(val) { // eslint-disable-line no-loop-func
110 return !~update.Value[type].indexOf(val)
111 })
112 if (!item[attr][type].length) delete item[attr]
113 }
114 } else {
115 delete item[attr]
116 }
117 }
118 }
119}
120
121function applyUpdateExpression(sections, table, item) {
122 var toSquash = []
123 for (var i = 0; i < sections.length; i++) {
124 var section = sections[i]
125 if (section.type == 'set') {
126 section.val = resolveValue(section.val, item)
127 if (typeof section.val == 'string') {
128 return db.validationError(section.val)
129 }
130 }
131 }
132 for (i = 0; i < sections.length; i++) {
133 section = sections[i]
134 var parent = db.mapPath(section.path.slice(0, -1), item)
135 var attr = section.path[section.path.length - 1]
136 if (parent == null || (typeof attr == 'number' ? parent.L : parent.M) == null) {
137 return db.validationError('The document path provided in the update expression is invalid for update')
138 }
139 var existing = parent.M ? parent.M[attr] : parent.L[attr]
140 var alreadyExists = existing != null
141 if (section.type == 'remove') {
142 deleteFromParent(parent, attr)
143 } else if (section.type == 'delete') {
144 if (alreadyExists && Object.keys(existing)[0] != section.attrType) {
145 return db.validationError('An operand in the update expression has an incorrect data type')
146 }
147 if (alreadyExists) {
148 existing[section.attrType] = existing[section.attrType].filter(function(val) { // eslint-disable-line no-loop-func
149 return !~section.val[section.attrType].indexOf(val)
150 })
151 if (!existing[section.attrType].length) {
152 deleteFromParent(parent, attr)
153 }
154 }
155 } else if (section.type == 'add') {
156 if (alreadyExists && Object.keys(existing)[0] != section.attrType) {
157 return db.validationError('An operand in the update expression has an incorrect data type')
158 }
159 if (section.attrType == 'N') {
160 if (!existing) existing = {N: '0'}
161 existing.N = new Big(existing.N).plus(section.val.N).toFixed()
162 } else {
163 if (!existing) existing = {}
164 if (!existing[section.attrType]) existing[section.attrType] = []
165 existing[section.attrType] = existing[section.attrType].concat(section.val[section.attrType].filter(function(a) { // eslint-disable-line no-loop-func
166 return !~existing[section.attrType].indexOf(a)
167 }))
168 }
169 if (!alreadyExists) {
170 addToParent(parent, attr, existing, toSquash)
171 }
172 } else if (section.type == 'set') {
173 if (section.path.length == 1) {
174 var err = db.traverseIndexes(table, function(attr, type) {
175 if (section.path[0] == attr && section.val[type] == null) {
176 return db.validationError('The update expression attempted to update the secondary index key to unsupported type')
177 }
178 })
179 if (err) return err
180 }
181 addToParent(parent, attr, section.val, toSquash)
182 }
183 }
184 toSquash.forEach(function(obj) { obj.L = obj.L.filter(Boolean) })
185}
186
187function resolveValue(val, item) {
188 if (Array.isArray(val)) {
189 val = db.mapPath(val, item)
190 } else if (val.type == 'add' || val.type == 'subtract') {
191 var val1 = resolveValue(val.args[0], item)
192 if (typeof val1 == 'string') return val1
193 if (val1.N == null) {
194 return 'An operand in the update expression has an incorrect data type'
195 }
196 var val2 = resolveValue(val.args[1], item)
197 if (typeof val2 == 'string') return val2
198 if (val2.N == null) {
199 return 'An operand in the update expression has an incorrect data type'
200 }
201 val = {N: new Big(val1.N)[val.type == 'add' ? 'plus' : 'minus'](val2.N).toFixed()}
202 } else if (val.type == 'function' && val.name == 'if_not_exists') {
203 val = db.mapPath(val.args[0], item) || resolveValue(val.args[1], item)
204 } else if (val.type == 'function' && val.name == 'list_append') {
205 val1 = resolveValue(val.args[0], item)
206 if (typeof val1 == 'string') return val1
207 if (val1.L == null) {
208 return 'An operand in the update expression has an incorrect data type'
209 }
210 val2 = resolveValue(val.args[1], item)
211 if (typeof val2 == 'string') return val2
212 if (val2.L == null) {
213 return 'An operand in the update expression has an incorrect data type'
214 }
215 return {L: val1.L.concat(val2.L)}
216 }
217 return val || 'The provided expression refers to an attribute that does not exist in the item'
218}
219
220function deleteFromParent(parent, attr) {
221 if (parent.M) {
222 delete parent.M[attr]
223 } else if (parent.L) {
224 parent.L.splice(attr, 1)
225 }
226}
227
228function addToParent(parent, attr, val, toSquash) {
229 if (parent.M) {
230 parent.M[attr] = val
231 } else if (parent.L) {
232 if (attr > parent.L.length && !~toSquash.indexOf(parent)) {
233 toSquash.push(parent)
234 }
235 parent.L[attr] = val
236 }
237}