@sap/cds 1.15.0 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/manager.js CHANGED
@@ -1,30 +1,29 @@
1
- var async = require("async");
1
+ const async = require('async');
2
2
 
3
- var cds = require('./cds');
4
- var metadata = require('./metadata');
5
- var exprs = require('./exprs');
6
- var utils = require('./utils');
7
- var Queue = require('./util/Queue');
8
- var ppp = utils.ppp; // @DEBUG
9
- var transaction = require('./transaction');
3
+ const metadata = require('./metadata');
4
+ const exprs = require('./exprs');
5
+ const utils = require('./utils');
6
+ const Queue = require('./util/Queue');
7
+ const ppp = utils.ppp; // @DEBUG
8
+ const transaction = require('./transaction');
10
9
 
11
- var logger = utils.logger;
10
+ const logger = utils.logger;
12
11
 
13
12
 
14
- /// CRUD operations ////////////////////////////////////////////////////////////
13
+ // / CRUD operations ////////////////////////////////////////////////////////////
15
14
 
16
15
  // cache for fully built entity instances; indexed by entityName x cacheId
17
- var instanceCache = {};
18
- var nextInstanceId = 1; // keeping track of objects @DEBUG
16
+ const instanceCache = {};
17
+ let nextInstanceId = 1; // keeping track of objects @DEBUG
19
18
 
20
19
  exports.extensionPoints = {
21
- instanceMethods: function(im){
22
- return im;
23
- }
20
+ instanceMethods: function(im) {
21
+ return im;
22
+ },
24
23
  }; // hook for extenders
25
24
 
26
25
 
27
- /// retrieve ///////////////////////////////////////////////////////////////////
26
+ // / retrieve ///////////////////////////////////////////////////////////////////
28
27
 
29
28
  /*
30
29
  * fetchInstance, _find: public $get, $findAll functions
@@ -45,50 +44,56 @@ exports.extensionPoints = {
45
44
  */
46
45
 
47
46
  // central request queue for serializing CRUD operations
48
- var queue = new Queue.Queue();
47
+ const queue = new Queue.Queue();
49
48
 
50
49
  exports._get = _get;
51
- exports._find = function (client, entity, condition, callback) {
52
- _get(client, entity, condition, callback, true);
50
+ exports._find = function(client, entity, condition, callback) {
51
+ _get(client, entity, condition, callback, true);
53
52
  };
54
53
 
55
54
  // retrieve instance(s) from database
56
55
  function _get(client, entity, key, callback, isFind) {
57
- if (!("$_metadata" in entity))
58
- return callback("not an entity");
59
-
60
- var entityName = entity.$_metadata.entityName;
61
- var pool = {}; // work in progress
62
- logger.debug("node-cds: getting " + entityName + " with key " +
63
- JSON.stringify(projectOnKeys(entity, key)));
64
-
65
- if (entity.$_metadata.isUnmanaged)
66
- return callback("Cannot get instance of unmanaged entity " + entityName);
56
+ if (!('$_metadata' in entity)) {
57
+ return callback('not an entity');
58
+ }
59
+
60
+ const entityName = entity.$_metadata.entityName;
61
+ const pool = {}; // work in progress
62
+ logger.debug(`node-cds: getting ${ entityName } with key ${
63
+ JSON.stringify(projectOnKeys(entity, key))}`);
64
+
65
+ if (entity.$_metadata.isUnmanaged) {
66
+ return callback(`Cannot get instance of unmanaged entity ${ entityName}`);
67
+ }
68
+
69
+ // convert to instances and update cache
70
+ function done(err) {
71
+ logger.debug(`node-cds: ${ entityName
72
+ }${JSON.stringify(projectOnKeys(entity, key)) } retrieved`);
73
+ if (err) {
74
+ return callback(err);
75
+ }
67
76
 
68
- // convert to instances and update cache
69
- function done(err) {
70
- logger.debug("node-cds: " + entityName +
71
- JSON.stringify(projectOnKeys(entity, key)) + " retrieved");
72
- if (err)
73
- return callback(err);
74
-
75
- // add XS interface and wire up associations in wip skeletons, add to cache
76
- for (var i in pool)
77
- if (pool[i]) {
78
- makeInstance(client, pool[i], pool, false);
79
- }
77
+ // add XS interface and wire up associations in wip skeletons, add to cache
78
+ for (const i in pool) {
79
+ if (pool[i]) {
80
+ makeInstance(client, pool[i], pool, false);
81
+ }
82
+ }
80
83
 
81
- // return result via cache
82
- var result = isFind ?
84
+ // return result via cache
85
+ const result = isFind ?
83
86
  findCached(client, entity, key) :
84
- getCached(client, entity, key, true); // could be null
85
- if (typeof result === "string") // indicates error
86
- return callback(result);
87
- else
88
- return callback(null, result);
87
+ getCached(client, entity, key, true); // could be null
88
+ // indicates error
89
+ if (typeof result === 'string') {
90
+ return callback(result);
91
+ } else {
92
+ return callback(null, result);
89
93
  }
94
+ }
90
95
 
91
- fetchInstance(client, entity, key, pool, isFind, done);
96
+ fetchInstance(client, entity, key, pool, isFind, done);
92
97
  }
93
98
 
94
99
 
@@ -96,698 +101,775 @@ function _get(client, entity, key, callback, isFind) {
96
101
 
97
102
  // retrieve instance plus associations by JOIN
98
103
  function fetchInstance(client, entity, key, pool, isFind, callback) {
99
- var entityName = entity.$_metadata.entityName;
100
-
101
- // request queue for fetching root and potential target instances
102
- var todo = [];
103
-
104
- // initiate actual request for root instance
105
- var debugId = entityName;
106
- if (isFind) {
107
- todo.push({entity: entity, key: key, isFind: true});
108
- } else {
109
- todo.push({entity: entity, key: projectOnKeys(entity, key)});
104
+ const entityName = entity.$_metadata.entityName;
105
+
106
+ // request queue for fetching root and potential target instances
107
+ const todo = [];
108
+
109
+ // initiate actual request for root instance
110
+ const debugId = entityName;
111
+ if (isFind) {
112
+ todo.push({entity: entity, key: key, isFind: true});
113
+ } else {
114
+ todo.push({entity: entity, key: projectOnKeys(entity, key)});
115
+ }
116
+ queue.push(start, callback, debugId);
117
+
118
+ // main queue
119
+
120
+ function start(callback) {
121
+ async.whilst(pending, loop, callback);
122
+ }
123
+
124
+ function pending() {
125
+ return todo.length > 0;
126
+ }
127
+
128
+ // main query loop
129
+ function loop(callback) {
130
+ const item = todo.shift();
131
+ if (!item) {
132
+ throw new Error('*** ASSERT FAIL *** empty todo list');
110
133
  }
111
- queue.push(start, callback, debugId);
112
-
113
- // main queue
114
-
115
- function start(callback) {
116
- async.whilst(pending, loop, callback);
134
+ if (item.itemId && item.itemId in pool) {
135
+ return callback(null);
136
+ } // already retrieved
137
+ logger.debug(`node-cds fetch loop: processing ${ item.itemId}`);
138
+
139
+ // check if cached
140
+ if (!item.isFind) {
141
+ const instance = getCached(client, entity, item.key);
142
+ if (instance && !instance.$_reload) {
143
+ return callback(null);
144
+ }
117
145
  }
118
146
 
119
- function pending() {
120
- return todo.length > 0;
121
- }
147
+ // optimize: pre-compute at import
148
+ const relations = metadata.computeRelations(item.entity);
149
+ query(item.entity, item.key, relations, callback);
150
+ }
122
151
 
123
- // main query loop
124
- function loop(callback) {
125
- var item = todo.shift();
126
- if (!item)
127
- throw new Error("*** ASSERT FAIL *** empty todo list");
128
- if (item.itemId && item.itemId in pool)
129
- return callback(null); // already retrieved
130
- logger.debug("node-cds fetch loop: processing " + item.itemId);
131
-
132
- // check if cached
133
- if (!item.isFind) {
134
- var instance = getCached(client, entity, item.key);
135
- if (instance && !instance.$_reload)
136
- return callback(null);
137
- }
152
+ // queue items
138
153
 
139
- // optimize: pre-compute at import
140
- var relations = metadata.computeRelations(item.entity);
141
- query(item.entity, item.key, relations, callback);
154
+ // query database for key
155
+ function query(entity, condition, relations, callback) {
156
+ if (condition.$_id) {
157
+ throw new Error(
158
+ `*** ASSERT FAIL *** query condition is instance: ${ ppp(condition)}`,
159
+ );
142
160
  }
143
161
 
144
- // queue items
145
-
146
- // query database for key
147
- function query(entity, condition, relations, callback) {
148
- if (condition.$_id)
149
- throw new Error("*** ASSERT FAIL *** query condition is instance: " + ppp(condition));
162
+ const q = entity.$query().$matching(condition).$project(relations.projection);
163
+ // logger.debug("node-cds SQL: " + q.$sql());
164
+ q.$execute(client, {$factorized: true, $noCommit: true}, function(err, skeletons) {
165
+ if (!err) {
166
+ logger.debug(`node-cds: query returns ${ skeletons.length } results`);
167
+ addSkeletonsToWipPool(entity, skeletons, relations.associations);
168
+ fetchRecursiveAssocs(skeletons, relations.cycles);
169
+ }
170
+ callback(err);
171
+ });
172
+ }
150
173
 
151
- var q = entity.$query().$matching(condition).$project(relations.projection);
152
- //logger.debug("node-cds SQL: " + q.$sql());
153
- q.$execute(client, {$factorized: true, $noCommit: true}, function(err, skeletons) {
154
- if (!err) {
155
- logger.debug("node-cds: query returns " + skeletons.length + " results");
156
- addSkeletonsToWipPool(entity, skeletons, relations.associations);
157
- fetchRecursiveAssocs(skeletons, relations.cycles);
158
- }
159
- callback(err);
160
- });
161
- }
174
+ // add skeletons to work-in-progress pool
175
+ function addSkeletonsToWipPool(entity, skeletons, associations) {
176
+ const _add = function(entity, skeletons, index) {
177
+ const skeleton = skeletons[index];
178
+ if (skeleton === null) {
179
+ return;
180
+ }
162
181
 
163
- // add skeletons to work-in-progress pool
164
- function addSkeletonsToWipPool(entity, skeletons, associations) {
165
-
166
- var _add = function (entity, skeletons, index) {
167
- var skeleton = skeletons[index];
168
- if (skeleton === null)
169
- return;
170
-
171
- var itemId = computeItemId(entity, skeleton);
172
- if (itemId === null)
173
- // NOTE: this may happen when (1) an association is key and
174
- // (2) the foreign-key does not point to a valid instance
175
- return;
176
-
177
- var instance = getCached(client, entity, skeleton);
178
- if (instance && !instance.$_reload) {
179
- if (!(itemId in pool))
180
- pool[itemId] = instance; // add to wip pool to choke recursive gets
181
- logger.debug("node-cds: added cached instance to pool: " + itemId);
182
- } else {
183
- if (itemId in pool) {
184
- var existingSkeleton = pool[itemId];
185
- skeletons[index] = existingSkeleton; // replace newcomer
186
- logger.debug("node-cds: replaced skeleton in pool: " + itemId);
187
- } else {
188
- skeleton.$__entity = entity;
189
- pool[itemId] = skeleton;
190
- logger.debug("node-cds: added skeleton to pool: " + itemId);
191
- }
192
- }
193
- };
182
+ const itemId = computeItemId(entity, skeleton);
183
+ // NOTE: this may happen when (1) an association is key and
184
+ // (2) the foreign-key does not point to a valid instance
185
+ if (itemId === null) {
186
+ return;
187
+ }
188
+
189
+ const instance = getCached(client, entity, skeleton);
190
+ if (instance && !instance.$_reload) {
191
+ if (!(itemId in pool)) {
192
+ pool[itemId] = instance;
193
+ } // add to wip pool to choke recursive gets
194
+ logger.debug(`node-cds: added cached instance to pool: ${ itemId}`);
195
+ } else {
196
+ if (itemId in pool) {
197
+ const existingSkeleton = pool[itemId];
198
+ skeletons[index] = existingSkeleton; // replace newcomer
199
+ logger.debug(`node-cds: replaced skeleton in pool: ${ itemId}`);
200
+ } else {
201
+ skeleton.$__entity = entity;
202
+ pool[itemId] = skeleton;
203
+ logger.debug(`node-cds: added skeleton to pool: ${ itemId}`);
204
+ }
205
+ }
206
+ };
194
207
 
195
- for (var i in skeletons) {
196
- _add(entity, skeletons, i);
197
- for (var a in associations) {
198
- var assoc = associations[a];
199
- var targetEntity = assoc.target;
200
- var targetSkeletons = utils.getPropPathSet(skeletons[i], assoc.field);
201
- if (!assoc.lazy) {
202
- var hasTarget = false;
203
- for (var j in targetSkeletons) {
204
- _add(targetEntity, targetSkeletons, j);
205
- hasTarget = true;
206
- }
207
- if (!hasTarget)
208
- utils.setPropPath(skeletons[i], assoc.field, assoc.toMany ? [] : null);
209
- }
210
- }
208
+ for (const i in skeletons) {
209
+ _add(entity, skeletons, i);
210
+ for (const a in associations) {
211
+ const assoc = associations[a];
212
+ const targetEntity = assoc.target;
213
+ const targetSkeletons = utils.getPropPathSet(skeletons[i], assoc.field);
214
+ if (!assoc.lazy) {
215
+ let hasTarget = false;
216
+ for (const j in targetSkeletons) {
217
+ _add(targetEntity, targetSkeletons, j);
218
+ hasTarget = true;
219
+ }
220
+ if (!hasTarget) {
221
+ utils.setPropPath(skeletons[i], assoc.field, assoc.toMany ? [] : null);
222
+ }
211
223
  }
224
+ }
212
225
  }
213
-
214
- // fetch targets of recursive associations
215
- function fetchRecursiveAssocs(skeletons, cycles) {
216
- for (var i in skeletons) {
217
- for (var c in cycles) {
218
- var entity = cycles[c].assoc.$class;
219
- var links = utils.getPropPathSet(skeletons[i], cycles[c].field);
220
- for (var f in links) {
221
- var link = links[f];
222
- if (link && !link.$_id) {
223
- var itemId = computeItemId(entity, link);
224
- todo.push({itemId: itemId, entity: entity, key: link});
225
- logger.debug("node-cds: requesting recursive assoc " +
226
- entity.$_metadata.entityName + JSON.stringify(link));
227
- }
228
- }
229
- }
226
+ }
227
+
228
+ // fetch targets of recursive associations
229
+ function fetchRecursiveAssocs(skeletons, cycles) {
230
+ for (const i in skeletons) {
231
+ for (const c in cycles) {
232
+ const entity = cycles[c].assoc.$class;
233
+ const links = utils.getPropPathSet(skeletons[i], cycles[c].field);
234
+ for (const f in links) {
235
+ const link = links[f];
236
+ if (link && !link.$_id) {
237
+ const itemId = computeItemId(entity, link);
238
+ todo.push({itemId: itemId, entity: entity, key: link});
239
+ logger.debug(`node-cds: requesting recursive assoc ${
240
+ entity.$_metadata.entityName }${JSON.stringify(link)}`);
241
+ }
230
242
  }
243
+ }
231
244
  }
232
-
245
+ }
233
246
  }
234
247
 
235
248
  // build instance from property skeleton, swap in cached instances,
236
249
  // lock key properties, and add XS-specific interface components
237
250
  function makeInstance(client, skeleton, pool, needsDeepCopy) {
238
- var entity = skeleton.$__entity || skeleton.$_entity; // || skeleton.$
239
- var cacheId = computeKeyId(entity, skeleton);
240
-
241
- // check for cached instance
242
- var instance = getCached(client, entity, skeleton);
243
- if (instance)
244
- return;
245
-
246
- // build instance from property skeleton
247
- if (needsDeepCopy) {
248
- instance = utils.deepCopy(skeleton);
249
- } else {
250
- instance = skeleton;
251
- }
252
- delete instance.$__entity;
253
-
254
- logger.debug("node-cds: making instance for " + entity + " : " + cacheId);
255
- makeAssociations(client, entity, instance, pool);
256
- lockKeys(entity, instance);
257
- addXSInterface(client, entity, instance);
258
-
259
- addCache(client, instance, cacheId);
251
+ const entity = skeleton.$__entity || skeleton.$_entity; // || skeleton.$
252
+ const cacheId = computeKeyId(entity, skeleton);
253
+
254
+ // check for cached instance
255
+ let instance = getCached(client, entity, skeleton);
256
+ if (instance) {
257
+ return;
258
+ }
259
+
260
+ // build instance from property skeleton
261
+ if (needsDeepCopy) {
262
+ instance = utils.deepCopy(skeleton);
263
+ } else {
264
+ instance = skeleton;
265
+ }
266
+ delete instance.$__entity;
267
+
268
+ logger.debug(`node-cds: making instance for ${ entity } : ${ cacheId}`);
269
+ makeAssociations(client, entity, instance, pool);
270
+ lockKeys(entity, instance);
271
+ addXSInterface(client, entity, instance);
272
+
273
+ addCache(client, instance, cacheId);
260
274
  }
261
275
 
262
276
  // build associations from skeletons in wip pool
263
277
  function makeAssociations(client, entity, instance, pool) {
264
- // load function for lazy associations
265
- var load = function (entity, field, value) {
266
- return function (cb) {
267
- // load based on skeleton key information
268
- client.$get(entity, value[field], function (err, target) {
269
- value[field] = target;
270
- cb(err, target);
271
- });
272
- };
278
+ // load function for lazy associations
279
+ const load = function(entity, field, value) {
280
+ return function(cb) {
281
+ // load based on skeleton key information
282
+ client.$get(entity, value[field], function(err, target) {
283
+ value[field] = target;
284
+ cb(err, target);
285
+ });
273
286
  };
274
-
275
- // find existing instance or wip item for skeleton
276
- var getFromWip = function (entity, skeleton) {
277
- var itemId = computeItemId(entity, skeleton);
278
- if (!itemId)
279
- return null; // missing target
280
- return getCached(client, entity, skeleton) || pool[itemId] || null;
281
- };
282
-
283
- // process all associations
284
- utils.forInstance(instance, entity.$_mapping, {
285
- $association: function (p, f, v, m) {
286
- var a = m[f].$association;
287
- var targetEntity = a.$class;
288
- if (typeof v[f] === "undefined") {
289
- var isToMany = utils.isToMany(a);
290
- v[f] = isToMany ? [] : null; // @DESIGN
291
- }
292
- if (a.$lazy) {
293
- utils.addInternalProp(v[f], "$load", load(targetEntity, f, v));
294
- return;
295
- }
296
- if (utils.isArray(v[f])) {
297
- var is = [];
298
- for (var j in v[f])
299
- is.push(getFromWip(targetEntity, v[f][j]));
300
- v[f] = is;
301
- } else {
302
- v[f] = getFromWip(targetEntity, v[f]);
303
- }
304
- if (a.$viaBacklink || a.$on) {
305
- utils.addInternalProp(v[f], "$reload", reloadInstance(client, entity, instance, p + f));
306
- }
287
+ };
288
+
289
+ // find existing instance or wip item for skeleton
290
+ const getFromWip = function(entity, skeleton) {
291
+ const itemId = computeItemId(entity, skeleton);
292
+ if (!itemId) {
293
+ return null;
294
+ } // missing target
295
+ return getCached(client, entity, skeleton) || pool[itemId] || null;
296
+ };
297
+
298
+ // process all associations
299
+ utils.forInstance(instance, entity.$_mapping, {
300
+ $association: function(p, f, v, m) {
301
+ const a = m[f].$association;
302
+ const targetEntity = a.$class;
303
+ if (typeof v[f] === 'undefined') {
304
+ const isToMany = utils.isToMany(a);
305
+ v[f] = isToMany ? [] : null; // @DESIGN
306
+ }
307
+ if (a.$lazy) {
308
+ utils.addInternalProp(v[f], '$load', load(targetEntity, f, v));
309
+ return;
310
+ }
311
+ if (utils.isArray(v[f])) {
312
+ const is = [];
313
+ for (const j in v[f]) {
314
+ is.push(getFromWip(targetEntity, v[f][j]));
307
315
  }
308
- });
316
+ v[f] = is;
317
+ } else {
318
+ v[f] = getFromWip(targetEntity, v[f]);
319
+ }
320
+ if (a.$viaBacklink || a.$on) {
321
+ utils.addInternalProp(
322
+ v[f],
323
+ '$reload',
324
+ reloadInstance(client, entity, instance, p + f),
325
+ );
326
+ }
327
+ },
328
+ });
309
329
  }
310
330
 
311
331
  // reload instance function, e.g., for unmanaged assocs
312
- var reloadInstance = function (client, entity, instance, path) {
313
- return function (cb) {
314
- var reloadPool = {};
315
- var key = projectOnKeys(entity, instance);
316
- ++instance.$_reload; // instance.$_reload = true;
317
-
318
- function done(err) {
319
- if (err)
320
- cb(err);
321
- var itemId = computeItemId(entity, instance);
322
- var reloadedInstance = reloadPool[itemId];
323
- if (!reloadedInstance)
324
- throw new Error("*** ASSERT FAIL *** missing reload instance");
325
- makeAssociations(client, entity, reloadedInstance, reloadPool);
326
- var targets = utils.getPropPath(reloadedInstance, path) || [];
327
- utils.setPropPath(instance, path, targets);
328
- utils.addInternalProp(targets, "$reload",
329
- reloadInstance(client, entity, instance, path)); // rearm reloader
330
- --instance.$_reload; //delete(instance.$_reload);
331
-
332
- for (var i in reloadPool)
333
- if (i !== itemId && reloadPool[i]) {
334
- makeInstance(client, reloadPool[i], reloadPool, false);
335
- }
336
- cb(null, targets); // pass only reloaded assoc target to callback
337
- };
338
- fetchInstance(client, entity, key, reloadPool, false, done);
332
+ const reloadInstance = function(client, entity, instance, path) {
333
+ return function(cb) {
334
+ const reloadPool = {};
335
+ const key = projectOnKeys(entity, instance);
336
+ ++instance.$_reload; // instance.$_reload = true;
337
+
338
+ function done(err) {
339
+ if (err) {
340
+ cb(err);
341
+ }
342
+ const itemId = computeItemId(entity, instance);
343
+ const reloadedInstance = reloadPool[itemId];
344
+ if (!reloadedInstance) {
345
+ throw new Error('*** ASSERT FAIL *** missing reload instance');
346
+ }
347
+ makeAssociations(client, entity, reloadedInstance, reloadPool);
348
+ const targets = utils.getPropPath(reloadedInstance, path) || [];
349
+ utils.setPropPath(instance, path, targets);
350
+ utils.addInternalProp(targets, '$reload',
351
+ reloadInstance(client, entity, instance, path)); // rearm reloader
352
+ --instance.$_reload; // delete(instance.$_reload);
353
+
354
+ for (const i in reloadPool) {
355
+ if (i !== itemId && reloadPool[i]) {
356
+ makeInstance(client, reloadPool[i], reloadPool, false);
357
+ }
358
+ }
359
+ cb(null, targets); // pass only reloaded assoc target to callback
339
360
  };
361
+ fetchInstance(client, entity, key, reloadPool, false, done);
362
+ };
340
363
  };
341
364
 
342
365
  // add XS1-style interface
343
366
  function addXSInterface(client, entity, instance) {
344
- Object.defineProperties(instance, exports.extensionPoints.instanceMethods({
345
- $_id: {value: nextInstanceId++},
346
- $_client: client,
347
- $_entity: {value: entity},
348
- $_tx: {value: 0, writable: true},
349
- $_reload: {value: 0, writable: true},
350
- }));
367
+ Object.defineProperties(instance, exports.extensionPoints.instanceMethods({
368
+ $_id: {value: nextInstanceId++},
369
+ $_client: client,
370
+ $_entity: {value: entity},
371
+ $_tx: {value: 0, writable: true},
372
+ $_reload: {value: 0, writable: true},
373
+ }));
351
374
  }
352
375
 
353
376
 
354
- /// create/update /////////////////////////////////////////////////////////
377
+ // / create/update /////////////////////////////////////////////////////////
355
378
 
356
379
  exports._save = function(client, instance, callback) {
357
- _save(client, instance.$_entity || instance.$entity, instance, callback);
380
+ _save(client, instance.$_entity || instance.$entity, instance, callback);
358
381
  };
359
382
 
360
383
  // persist instance to database
361
384
  function _save(client, entity, instance, callback) {
362
- _tx = Date.now();
363
- var keys = [], saves = [], links = [], reloads = [];
364
- collectInstancesToSave(client, entity, instance, _tx, keys, saves, links, reloads);
365
- logger.debug("node-cds: saving instance " + instance.$_id + ", #keys=" +
366
- keys.length + " #saves=" + saves.length + " #links=" + links.length +
367
- " #reloads=" + reloads.length);
368
- async.series([].concat(keys, saves, links, reloads), function(err) {
369
- logger.debug("node-cds: saving instance " + instance.$_id + " done");
370
- if (err)
371
- return callback(err);
372
- if (client.$_autoCommit)
373
- client.$commit(function (err) { callback(err, instance); });
374
- else
375
- callback(err, instance);
376
- });
385
+ const _tx = Date.now();
386
+ const keys = []; const saves = []; const links = []; const reloads = [];
387
+ collectInstancesToSave(client, entity, instance, _tx, keys, saves, links, reloads);
388
+ logger.debug(`node-cds: saving instance ${ instance.$_id }, #keys=${
389
+ keys.length } #saves=${ saves.length } #links=${ links.length
390
+ } #reloads=${ reloads.length}`);
391
+ async.series([].concat(keys, saves, links, reloads), function(err) {
392
+ logger.debug(`node-cds: saving instance ${ instance.$_id } done`);
393
+ if (err) {
394
+ return callback(err);
395
+ }
396
+ if (client.$_autoCommit) {
397
+ client.$commit(function(err) {
398
+ callback(err, instance);
399
+ });
400
+ } else {
401
+ callback(err, instance);
402
+ }
403
+ });
377
404
  }
378
405
 
379
406
  // (sync) recursively collect all instances that need to be updated
380
- function collectInstancesToSave(client, entity, instance, _tx, keys, saves, links, reloads) {
381
- if (instance.$_tx && instance.$_tx >= _tx)
382
- return; // already saved or to be saved
383
- if (instance.$load)
384
- return; // don't attempt to save unresolved lazy associations
385
-
386
- // (async) insert or update single instance
387
- function upsert(entity, instance, isCreate, callback) {
388
- var cloneForSave = createInstance(client, entity, instance, isCreate);
389
- var key = projectOnKeys(instance.$_entity, instance);
390
- var query = entity.$query().$matching(key).$values(cloneForSave);
391
- query = isCreate ? query.$insert() : query.$update();
392
- query.$execute(client, {$noCommit: true}, callback);
407
+ function collectInstancesToSave(
408
+ client, entity, instance, _tx, keys, saves, links, reloads,
409
+ ) {
410
+ if (instance.$_tx && instance.$_tx >= _tx) {
411
+ return;
412
+ } // already saved or to be saved
413
+ if (instance.$load) {
414
+ return;
415
+ } // don't attempt to save unresolved lazy associations
416
+
417
+ // (async) insert or update single instance
418
+ function upsert(entity, instance, isCreate, callback) {
419
+ const cloneForSave = createInstance(client, entity, instance);
420
+ const key = projectOnKeys(instance.$_entity, instance);
421
+ let query = entity.$query().$matching(key).$values(cloneForSave);
422
+ query = isCreate ? query.$insert() : query.$update();
423
+ query.$execute(client, {$noCommit: true}, callback);
424
+ }
425
+
426
+ // update via entity links
427
+ const updateLinks = function(entity, instance, assoc, targets, linkEntity, callback) {
428
+ const qs = [];
429
+ const sourceKey = projectOnKeys(entity, instance);
430
+ // delete old links
431
+ qs.push(function(cb) {
432
+ const link = {};
433
+ link[assoc.$source] = sourceKey;
434
+ linkEntity
435
+ .$query()
436
+ .$matching(link)
437
+ .$discard()
438
+ .$execute(client, {$noCommit: true}, cb);
439
+ });
440
+ // create new links
441
+ for (let i = 0; i < targets.length; ++i) {
442
+ qs.push((function(t) {
443
+ return function(cb) {
444
+ const link = {}; // must create unique object in each loop iteration
445
+ link[assoc.$source] = sourceKey;
446
+ link[assoc.$target] = t;
447
+ linkEntity
448
+ .$query()
449
+ .$values(link)
450
+ .$insert()
451
+ .$execute(client, {$noCommit: true}, cb);
452
+ };
453
+ })(targets[i]));
393
454
  }
394
-
395
- // update via entity links
396
- var updateLinks = function (entity, instance, assoc, targets, linkEntity, callback) {
397
- var qs = [];
398
- var sourceKey = projectOnKeys(entity, instance);
399
- // delete old links
400
- qs.push(function (cb) {
401
- var link = {};
402
- link[assoc.$source] = sourceKey;
403
- linkEntity.$query().$matching(link).$discard().$execute(client, {$noCommit: true}, cb);
455
+ async.series(qs, callback);
456
+ };
457
+
458
+ const isCreate = typeof instance.$_id === 'undefined';
459
+ const isUnmanaged = entity.$_metadata.isUnmanaged;
460
+ if (isCreate) {
461
+ Object.defineProperty(instance, '$_tx', {value: 0, writable: true});
462
+ }
463
+ instance.$_tx = _tx;
464
+
465
+ // collect associated instances
466
+ utils.forInstance(instance, entity.$_mapping, {
467
+ $column: function(p, f, v, m) {
468
+ // generate missing keys
469
+ if (typeof m[f].$key === 'string' && v && typeof v[f] === 'undefined') {
470
+ // v[f] = { $key: utils.quoteTable(m[f].$key };
471
+ // @NOTE: The current architecture doesn't support the creation of keys
472
+ // during INSERT, as the application wouldn't know about the
473
+ // key values. We thus have to retrieve the keys manually from
474
+ // the database sequence.
475
+ keys.push(function(cb) {
476
+ querySequence(m[f].$key, function(err, seq) {
477
+ v[f] = seq;
478
+ cb(err);
479
+ });
480
+ });
481
+ }
482
+ },
483
+ $association: function(p, f, v, m, e) {
484
+ if (isUnmanaged || v[f] === null) {
485
+ return;
486
+ }
487
+ const assoc = m[f].$association;
488
+ // fix missing target property
489
+ if (typeof v[f] === 'undefined') {
490
+ v[f] = utils.isToMany(assoc) ? [] : null;
491
+ }
492
+
493
+ const targets = utils.isArray(v[f]) ? v[f] : [v[f]];
494
+ if (assoc.$viaEntity) {
495
+ const linkEntity = assoc.$viaClass;
496
+ links.push(function(cb) {
497
+ updateLinks(entity, instance, assoc, targets, linkEntity, cb);
404
498
  });
405
- // create new links
406
- for (i = 0; i < targets.length; ++i) {
407
- qs.push((function(t) { return function (cb) {
408
- var link = {}; // must create unique object in each loop iteration
409
- link[assoc.$source] = sourceKey;
410
- link[assoc.$target] = t;
411
- linkEntity.$query().$values(link).$insert().$execute(client, {$noCommit: true}, cb);
412
- }; })(targets[i]));
499
+ } else if (assoc.$viaBacklink) {
500
+ // update backlink in children (thus adding children to association)
501
+ // only if instance is newly created -- updating backlinks unconditionally
502
+ // would pose potential conflict when updating multiple children in
503
+ // parallel (see spec text case "save backlinks/parallel child update")
504
+ if (isCreate) {
505
+ logger.debug(`node-cds: updating target backlinks for ${ instance.$_id}`);
506
+ const backlink = assoc.$viaBacklink;
507
+ for (const i in targets) {
508
+ utils.setPropPath(targets[i], backlink, instance);
509
+ }
413
510
  }
414
- async.series(qs, callback);
415
- };
416
-
417
- var isCreate = typeof instance.$_id === "undefined";
418
- var isUnmanaged = entity.$_metadata.isUnmanaged;
419
- if (isCreate)
420
- Object.defineProperty(instance, "$_tx", {value: 0, writable: true});
421
- instance.$_tx = _tx;
422
-
423
- // collect associated instances
424
- utils.forInstance(instance, entity.$_mapping, {
425
- $column: function (p, f, v, m) {
426
- // generate missing keys
427
- if (typeof m[f].$key === "string" && v && typeof v[f] === "undefined") {
428
- //v[f] = { $key: utils.quoteTable(m[f].$key };
429
- //@NOTE: The current architecture doesn't support the creation of keys
430
- // during INSERT, as the application wouldn't know about the
431
- // key values. We thus have to retrieve the keys manually from
432
- // the database sequence.
433
- keys.push(function (cb) {
434
- querySequence(m[f].$key, function (err, seq) {
435
- v[f] = seq;
436
- cb(err);
437
- });
438
- });
439
- }
440
- },
441
- $association: function(p, f, v, m, e) {
442
- if (isUnmanaged || v[f] === null)
443
- return;
444
- var assoc = m[f].$association;
445
- // fix missing target property
446
- if (typeof v[f] === "undefined")
447
- v[f] = utils.isToMany(assoc) ? [] : null;
448
-
449
- var targets = utils.isArray(v[f]) ? v[f] : [v[f]];
450
- if (assoc.$viaEntity) {
451
- var linkEntity = assoc.$viaClass;
452
- links.push(function(cb) {
453
- updateLinks(entity, instance, assoc, targets, linkEntity, cb);
454
- });
455
- } else if (assoc.$viaBacklink) {
456
- // update backlink in children (thus adding children to association)
457
- // only if instance is newly created -- updating backlinks unconditionally
458
- // would pose potential conflict when updating multiple children in
459
- // parallel (see spec text case "save backlinks/parallel child update")
460
- if (isCreate) {
461
- logger.debug("node-cds: updating target backlinks for " + instance.$_id);
462
- var backlink = assoc.$viaBacklink;
463
- for (var i in targets)
464
- utils.setPropPath(targets[i], backlink, instance);
465
- }
466
- }
467
-
468
- // add reload functions
469
- if (assoc.$viaBacklink || assoc.$on) {
470
- if (!v[f].$reload)
471
- utils.addInternalProp(v[f], "$reload", reloadInstance(client, entity, instance, p + f));
472
- // trigger reload of unmanaged associations
473
- reloads.push(function (cb) {
474
- if (v[f].$reload)
475
- v[f].$reload(cb);
476
- else
477
- logger.warn("node-cds: missing $reload function"); // removed by user
478
- });
479
- }
480
-
481
- // save target instances
482
- for (var i in targets)
483
- collectInstancesToSave(client, assoc.$class, targets[i], _tx, keys, saves, links, reloads);
511
+ }
512
+
513
+ // add reload functions
514
+ if (assoc.$viaBacklink || assoc.$on) {
515
+ if (!v[f].$reload) {
516
+ utils.addInternalProp(
517
+ v[f], '$reload', reloadInstance(client, entity, instance, p + f),
518
+ );
484
519
  }
485
- });
486
-
487
- // register upsert of root instance
488
- saves.push(function(cb) {
489
- upsert(entity, instance, isCreate, cb);
490
- });
520
+ // trigger reload of unmanaged associations
521
+ reloads.push(function(cb) {
522
+ if (v[f].$reload) {
523
+ v[f].$reload(cb);
524
+ } else {
525
+ logger.warn('node-cds: missing $reload function');
526
+ } // removed by user
527
+ });
528
+ }
529
+
530
+ // save target instances
531
+ for (const i in targets) {
532
+ collectInstancesToSave(
533
+ client, assoc.$class, targets[i], _tx, keys, saves, links, reloads,
534
+ );
535
+ }
536
+ },
537
+ });
538
+
539
+ // register upsert of root instance
540
+ saves.push(function(cb) {
541
+ upsert(entity, instance, isCreate, cb);
542
+ });
491
543
  }
492
544
 
493
545
  // like makeInstance but specifically for save
494
546
  function createInstance(client, entity, skeleton) {
495
- // missing target instance?
496
- if (skeleton === undefined) // e.g., missing assoc target from get
497
- return;
498
-
499
- // clone instance for $query, which might get confused
500
- var colValueClone = {};
501
- utils.forInstance(skeleton, entity.$_mapping, {
502
- $column: function (p, f, v, m) {
503
- // clone column values
504
- var x = v && f in v ? v[f] : undefined;
505
- utils.setPropPath(colValueClone, p + f, x);
506
- // lock keys
507
- if (m[f].$key)
508
- Object.defineProperty(v, f, {writable: false});
509
- }
510
- });
547
+ // missing target instance?
548
+ if (skeleton === undefined) {
549
+ // e.g., missing assoc target from get
550
+ return;
551
+ }
552
+
553
+
554
+ // clone instance for $query, which might get confused
555
+ const colValueClone = {};
556
+ utils.forInstance(skeleton, entity.$_mapping, {
557
+ $column: function(p, f, v, m) {
558
+ // clone column values
559
+ const x = v && f in v ? v[f] : undefined;
560
+ utils.setPropPath(colValueClone, p + f, x);
561
+ // lock keys
562
+ if (m[f].$key) {
563
+ Object.defineProperty(v, f, {writable: false});
564
+ }
565
+ },
566
+ });
567
+
568
+ if (!skeleton.$_id) {
569
+ // add XS-style interface}
570
+ Object.defineProperties(skeleton, exports.extensionPoints.instanceMethods({
571
+ $_id: {value: nextInstanceId++},
572
+ $_client: client,
573
+ $_entity: {value: entity},
574
+ $_reload: {value: 0, writable: true},
575
+ }));
511
576
 
512
- if (!skeleton.$_id) {
513
- // add XS-style interface}
514
- Object.defineProperties(skeleton, exports.extensionPoints.instanceMethods({
515
- $_id: {value: nextInstanceId++},
516
- $_client: client,
517
- $_entity: {value: entity},
518
- $_reload: {value: 0, writable: true},
519
- }));
520
-
521
- // add to cache
522
- var cacheId = computeKeyId(entity, skeleton);
523
- addCache(client, skeleton, cacheId);
524
- }
577
+ // add to cache
578
+ const cacheId = computeKeyId(entity, skeleton);
579
+ addCache(client, skeleton, cacheId);
580
+ }
525
581
 
526
- return colValueClone;
582
+ return colValueClone;
527
583
  }
528
584
 
529
585
 
530
- /// discard /////////////////////////////////////////////////////////
586
+ // / discard /////////////////////////////////////////////////////////
531
587
 
532
- exports._discard = function (client, instance, callback) {
533
- _discard(client, instance.$_entity || instance.$entity, instance, callback);
588
+ exports._discard = function(client, instance, callback) {
589
+ _discard(client, instance.$_entity || instance.$entity, instance, callback);
534
590
  };
535
591
 
536
592
  // discard instance(s) from database
537
593
  function _discard(client, entity, instance, callback) {
538
- _tx = Date.now();
539
- var deletes = [];
540
- var id = instance.$_id;
541
- collectInstancesToDiscard(client, entity, instance, _tx, deletes);
542
- logger.debug("node-cds: discarding instance " + id + ", #deletes=" + deletes.length);
543
- async.series(deletes, function(err) {
544
- logger.debug("node-cds: discarding of instance " + id + " complete");
545
- if (!err && client.$_autoCommit)
546
- client.$commit(callback);
547
- else
548
- callback(err); // strip result of series call
549
- });
594
+ const _tx = Date.now();
595
+ const deletes = [];
596
+ const id = instance.$_id;
597
+ collectInstancesToDiscard(client, entity, instance, _tx, deletes);
598
+ logger.debug(`node-cds: discarding instance ${ id }, #deletes=${ deletes.length}`);
599
+ async.series(deletes, function(err) {
600
+ logger.debug(`node-cds: discarding of instance ${ id } complete`);
601
+ if (!err && client.$_autoCommit) {
602
+ client.$commit(callback);
603
+ } else {
604
+ callback(err);
605
+ } // strip result of series call
606
+ });
550
607
  }
551
608
 
552
609
  function collectInstancesToDiscard(client, entity, instance, _tx, deletes) {
553
- if (instance.$_tx && instance.$_tx >= _tx)
554
- return; // already marked for deletion
555
- instance.$_tx = _tx;
610
+ if (instance.$_tx && instance.$_tx >= _tx) {
611
+ return;
612
+ } // already marked for deletion
613
+ instance.$_tx = _tx;
614
+
615
+ function discard(entity, key) {
616
+ deletes.push(function(cb) {
617
+ const query = entity.$query().$matching(key).$discard();
618
+ query.$execute(client, {$noCommit: true}, cb);
619
+ });
620
+ }
621
+
622
+ // discard root instance
623
+ removeCache(client, instance);
624
+ const key = projectOnKeys(entity, instance);
625
+ discard(entity, key);
626
+
627
+ // cascade discard to associated instances
628
+ utils.forInstance(instance, entity.$_mapping, {
629
+ $association: function(p, f, v, m, e) {
630
+ // delete via entity links
631
+ let linkEntity;
632
+ let link;
633
+ if (m[f].$association.$viaEntity) {
634
+ linkEntity = m[f].$association.$viaClass;
635
+ link = {};
636
+ link[m[f].$association.$source] = key;
637
+ discard(linkEntity, link);
638
+ }
639
+
640
+ if (!m[f].$association.$cascadeDiscard) {
641
+ return;
642
+ }
556
643
 
557
- function discard(entity, key) {
644
+ // discard unresolved lazy associations
645
+ if (m[f].$association.$lazy && v[f].$load) {
558
646
  deletes.push(function(cb) {
559
- var query = entity.$query().$matching(key).$discard();
560
- query.$execute(client, {$noCommit: true}, cb);
561
- });
562
- }
563
-
564
- // discard root instance
565
- removeCache(client, instance);
566
- var key = projectOnKeys(entity, instance);
567
- discard(entity, key);
568
-
569
- // cascade discard to associated instances
570
- utils.forInstance(instance, entity.$_mapping, {
571
- $association: function (p, f, v, m, e) {
572
- // delete via entity links
573
- if (m[f].$association.$viaEntity) {
574
- var linkEntity = m[f].$association.$viaClass;
575
- var link = {};
576
- link[m[f].$association.$source] = key;
577
- discard(linkEntity, link);
647
+ v[f].$load(function(err, targets) {
648
+ if (err) {
649
+ return cb(err);
578
650
  }
579
-
580
- if (!m[f].$association.$cascadeDiscard)
581
- return;
582
-
583
- // discard unresolved lazy associations
584
- if (m[f].$association.$lazy && v[f].$load) {
585
- deletes.push(function(cb) {
586
- v[f].$load(function (err, targets) {
587
- if (err)
588
- return cb(err);
589
- var ts = utils.isArray(targets) ? targets : [targets];
590
- client.$discardAll(ts, cb);
591
- });
592
- });
593
- return;
594
- }
595
-
596
- // discard target instances
597
- var targetEntity = m[f].$association.$class;
598
- var targets = utils.isArray(v[f]) ? v[f] : [v[f]];
599
- for (var i in targets)
600
- if (targets[i]) {
601
- collectInstancesToDiscard(client, targetEntity, targets[i], _tx, deletes);
602
-
603
- // delete backlinking via entity entries for discarded targets
604
- if (m[f].$association.$viaEntity) {
605
- link = {};
606
- var targetKey = projectOnKeys(targetEntity, targets[i]);
607
- link[m[f].$association.$target] = targetKey;
608
- discard(linkEntity, link);
609
- }
610
- }
651
+ const ts = utils.isArray(targets) ? targets : [targets];
652
+ client.$discardAll(ts, cb);
653
+ });
654
+ });
655
+ return;
656
+ }
657
+
658
+ // discard target instances
659
+ const targetEntity = m[f].$association.$class;
660
+ const targets = utils.isArray(v[f]) ? v[f] : [v[f]];
661
+ for (const i in targets) {
662
+ if (targets[i]) {
663
+ collectInstancesToDiscard(client, targetEntity, targets[i], _tx, deletes);
664
+
665
+ // delete backlinking via entity entries for discarded targets
666
+ if (m[f].$association.$viaEntity) {
667
+ link = {};
668
+ const targetKey = projectOnKeys(targetEntity, targets[i]);
669
+ link[m[f].$association.$target] = targetKey;
670
+ discard(linkEntity, link);
671
+ }
611
672
  }
612
- });
673
+ }
674
+ },
675
+ });
613
676
  }
614
677
 
615
678
 
616
- /// unmanaged operations ///////////////////////////////////////////////////////
679
+ // / unmanaged operations ///////////////////////////////////////////////////////
617
680
 
618
681
  // unmanaged delete: delete without associations, ignoring cache
619
- exports._delete = function (client, entity, condition, callback) {
620
- entity.$query().$matching(condition).$discard()
621
- .$execute(client, function(err) { callback(err) });
682
+ exports._delete = function(client, entity, condition, callback) {
683
+ entity.$query().$matching(condition).$discard()
684
+ .$execute(client, function(err) {
685
+ callback(err);
686
+ });
622
687
  };
623
688
 
624
689
 
625
- /// internal functions /////////////////////////////////////////////////////////
690
+ // / internal functions /////////////////////////////////////////////////////////
626
691
 
627
692
  // install new cache for given transaction
628
693
  exports.openCache = function(tx) {
629
- instanceCache[tx.$_tx_id] = {};
630
- exports._clearCaches(tx);
694
+ instanceCache[tx.$_tx_id] = {};
695
+ exports._clearCaches(tx);
631
696
  };
632
697
 
633
698
  // purge and close cache for given transaction
634
699
  exports.closeCache = function(tx) {
635
- delete(instanceCache[tx.$_tx_id]);
700
+ delete(instanceCache[tx.$_tx_id]);
636
701
  };
637
702
 
638
703
  // clear all caches (for testing)
639
704
  exports._clearCaches = function(tx) {
640
- var entities = metadata.getKnownEntities();
641
- for (var name in entities)
642
- instanceCache[tx.$_tx_id][name] = {};
705
+ const entities = metadata.getKnownEntities();
706
+ for (const name in entities) {
707
+ instanceCache[tx.$_tx_id][name] = {};
708
+ }
643
709
  };
644
710
 
645
711
  // (sync) check for cached instance with given key properties
646
712
  function getCached(tx, entity, props, reportErrors) {
647
- var cacheId = computeKeyId(entity, props);
648
- if (!cacheId)
649
- return reportErrors ? "invalid key" : null;
650
- var name = entity.$_metadata.entityName;
651
- if (!(tx.$_tx_id in instanceCache))
652
- throw new Error("*** ASSERT FAIL *** missing cache for tx " + tx.$_tx_id);
653
- if (!(name in instanceCache[tx.$_tx_id]))
654
- throw new Error("*** ASSERT FAIL *** unregistered entity " + name);
655
- return instanceCache[tx.$_tx_id][name][cacheId] || null;
713
+ const cacheId = computeKeyId(entity, props);
714
+ if (!cacheId) {
715
+ return reportErrors ? 'invalid key' : null;
716
+ }
717
+ const name = entity.$_metadata.entityName;
718
+ if (!(tx.$_tx_id in instanceCache)) {
719
+ throw new Error(`*** ASSERT FAIL *** missing cache for tx ${ tx.$_tx_id}`);
720
+ }
721
+ if (!(name in instanceCache[tx.$_tx_id])) {
722
+ throw new Error(`*** ASSERT FAIL *** unregistered entity ${ name}`);
723
+ }
724
+ return instanceCache[tx.$_tx_id][name][cacheId] || null;
656
725
  }
657
726
 
658
727
  function findCached(tx, entity, condition) {
659
- var name = entity.$_metadata.entityName;
660
- var txid = tx.$_tx_id;
661
- if (!(name in instanceCache[txid]))
662
- throw new Error("*** ASSERT FAIL *** unregistered entity: " + name);
663
-
664
- // build filter function
665
- var matches = exprs.buildInstanceFilter(entity, condition);
666
-
667
- // apply filter to cache (synchronous)
668
- var result = [];
669
- try {
670
- for (var i in instanceCache[txid][name])
671
- if (matches(instanceCache[txid][name][i]))
672
- result.push(instanceCache[txid][name][i]);
673
- } catch (e) {
674
- return "Error: " + e; // return type string -> error
728
+ const name = entity.$_metadata.entityName;
729
+ const txid = tx.$_tx_id;
730
+ if (!(name in instanceCache[txid])) {
731
+ throw new Error(`*** ASSERT FAIL *** unregistered entity: ${ name}`);
732
+ }
733
+
734
+ // build filter function
735
+ const matches = exprs.buildInstanceFilter(entity, condition);
736
+
737
+ // apply filter to cache (synchronous)
738
+ const result = [];
739
+ try {
740
+ for (const i in instanceCache[txid][name]) {
741
+ if (matches(instanceCache[txid][name][i])) {
742
+ result.push(instanceCache[txid][name][i]);
743
+ }
675
744
  }
745
+ } catch (e) {
746
+ return `Error: ${ e}`; // return type string -> error
747
+ }
676
748
 
677
- return result;
749
+ return result;
678
750
  }
679
751
 
680
752
  function addCache(tx, instance, cacheId) {
681
- var entity = instance.$_entity;
682
- if (entity === undefined)
683
- throw new Error("*** ASSERT FAIL *** not an entity instance");
684
- var name = entity.$_metadata.entityName;
685
- if (!cacheId)
686
- logger.warn("node-cds: invalid key for instance of " + name);
687
- else
688
- instanceCache[tx.$_tx_id][name][cacheId] = instance;
753
+ const entity = instance.$_entity;
754
+ if (entity === undefined) {
755
+ throw new Error('*** ASSERT FAIL *** not an entity instance');
756
+ }
757
+ const name = entity.$_metadata.entityName;
758
+ if (!cacheId) {
759
+ logger.warn(`node-cds: invalid key for instance of ${ name}`);
760
+ } else {
761
+ instanceCache[tx.$_tx_id][name][cacheId] = instance;
762
+ }
689
763
  }
690
764
 
691
765
  function removeCache(tx, instance) {
692
- var entity = instance.$_entity;
693
- if (entity === undefined)
694
- return; // not an instance, but discard by key
695
-
696
- var name = entity.$_metadata.entityName;
697
- var cacheId = computeKeyId(entity, instance);
698
- if (!cacheId)
699
- throw new Error("*** ASSERT FAIL *** invalid cache id");
700
- delete instanceCache[tx.$_tx_id][name][cacheId];
766
+ const entity = instance.$_entity;
767
+ if (entity === undefined) {
768
+ return;
769
+ } // not an instance, but discard by key
770
+
771
+ const name = entity.$_metadata.entityName;
772
+ const cacheId = computeKeyId(entity, instance);
773
+ if (!cacheId) {
774
+ throw new Error('*** ASSERT FAIL *** invalid cache id');
775
+ }
776
+ delete instanceCache[tx.$_tx_id][name][cacheId];
701
777
  }
702
778
 
703
779
  function computeItemId(entity, properties) {
704
- var cacheId = computeKeyId(entity, properties);
705
- return cacheId ? entity.$_metadata.entityName + "##" + cacheId : null;
780
+ const cacheId = computeKeyId(entity, properties);
781
+ return cacheId ? `${entity.$_metadata.entityName }##${ cacheId}` : null;
706
782
  }
707
783
 
708
784
  function computeKeyId(entity, properties) {
709
- var keyValues = getKeyValues(entity, properties);
710
- if (!keyValues)
711
- return null;
712
- var id = "#";
713
- for (var i in keyValues)
714
- id += hash(keyValues[i]);
715
- return id;
785
+ const keyValues = getKeyValues(entity, properties);
786
+ if (!keyValues) {
787
+ return null;
788
+ }
789
+ let id = '#';
790
+ for (const i in keyValues) {
791
+ id += hash(keyValues[i]);
792
+ }
793
+ return id;
716
794
  }
717
795
 
718
796
  function getKeyValues(entity, properties, relaxed) {
719
- var keyValues = [];
720
- var keyFields = entity.$_metadata.keyFields;
721
- for (var k in keyFields) {
722
- var v = utils.getPropPath(properties, k);
723
- if (typeof v === "undefined" && !relaxed)
724
- return null; // incomplete key yields null if not in relaxed mode
725
- keyValues.push(v);
726
- }
727
- return keyValues;
797
+ const keyValues = [];
798
+ const keyFields = entity.$_metadata.keyFields;
799
+ for (const k in keyFields) {
800
+ const v = utils.getPropPath(properties, k);
801
+ if (typeof v === 'undefined' && !relaxed) {
802
+ return null;
803
+ } // incomplete key yields null if not in relaxed mode
804
+ keyValues.push(v);
805
+ }
806
+ return keyValues;
728
807
  }
729
808
 
730
809
  // remove non-(secondary-)key values from properties
731
810
  function projectOnKeys(entity, instance) {
732
- var key = {};
733
- utils.forInstance(instance, entity.$_mapping, {
734
- $column: function(p, f, v, m) {
735
- if (m[f].$key)
736
- utils.setPropPath(key, p + f, v[f]);
737
- }
738
- });
739
- return key;
811
+ const key = {};
812
+ utils.forInstance(instance, entity.$_mapping, {
813
+ $column: function(p, f, v, m) {
814
+ if (m[f].$key) {
815
+ utils.setPropPath(key, p + f, v[f]);
816
+ }
817
+ },
818
+ });
819
+ return key;
740
820
  }
741
821
 
742
822
  // lock keys
743
823
  function lockKeys(entity, skeleton) {
744
- utils.forInstance(skeleton, entity.$_mapping, {
745
- $column: function (p, f, v, m) {
746
- if (m[f].$key)
747
- Object.defineProperty(v, f, {writable: false});
748
- }
749
- });
824
+ utils.forInstance(skeleton, entity.$_mapping, {
825
+ $column: function(p, f, v, m) {
826
+ if (m[f].$key) {
827
+ Object.defineProperty(v, f, {writable: false});
828
+ }
829
+ },
830
+ });
750
831
  }
751
832
 
752
833
  function querySequence(key, callback) {
753
- logger.debug("node-cds: query sequence " + key);
754
- transaction.getClient(null, function(err, client) {
755
- if (err)
756
- return callback(err);
757
- client.exec("SELECT " + key + ".NEXTVAL AS V FROM DUMMY", function(err, result) {
758
- transaction.releaseClient(client);
759
- logger.debug("node-cds: received sequence value " + result[0].V);
760
- callback(err, result[0].V);
761
- });
834
+ logger.debug(`node-cds: query sequence ${ key}`);
835
+ transaction.getClient(null, function(err, client) {
836
+ if (err) {
837
+ return callback(err);
838
+ }
839
+ client.exec(`SELECT ${ key }.NEXTVAL AS V FROM DUMMY`, function(err, result) {
840
+ transaction.releaseClient(client);
841
+ logger.debug(`node-cds: received sequence value ${ result[0].V}`);
842
+ callback(err, result[0].V);
762
843
  });
844
+ });
763
845
  }
764
846
 
765
847
  // simple hash function
766
848
  function hash(data) {
767
- var dataView = new DataView(new ArrayBuffer(8));
768
- switch (typeof data) {
769
- case "undefined":
770
- return "U";
771
- case "number":
772
- // see http://www.ecma-international.org/ecma-262/5.1/#sec-9.8.1
773
- return "N" + data;
774
- case "boolean":
775
- return data ? "B1" : "B0";
776
- case "string":
777
- var str = data.toString(),
778
- hash = 0;
779
- for (var i = 0; i < str.length; i++) {
780
- var c = str.charCodeAt(i);
781
- hash = c + (hash << 6) + (hash << 16) - hash;
782
- }
783
- return "S" + hash;
784
- case "object":
785
- if (data === null)
786
- return "Q";
787
- if (data instanceof Date)
788
- return "D" + data.getTime();
789
- /* fall-through */
790
- default:
791
- }
792
- throw new Error("*** ASSERT FAIL *** hash: invalid type: " + typeof data);
849
+ switch (typeof data) {
850
+ case 'undefined':
851
+ return 'U';
852
+ case 'number':
853
+ return `N${ data}`;
854
+ case 'boolean':
855
+ return data ? 'B1' : 'B0';
856
+ case 'string':
857
+ const str = data.toString();
858
+ let hash = 0;
859
+ for (let i = 0; i < str.length; i++) {
860
+ const c = str.charCodeAt(i);
861
+ hash = c + (hash << 6) + (hash << 16) - hash;
862
+ }
863
+ return `S${ hash}`;
864
+ case 'object':
865
+ if (data === null) {
866
+ return 'Q';
867
+ }
868
+ if (data instanceof Date) {
869
+ return `D${ data.getTime()}`;
870
+ }
871
+ /* fall-through */
872
+ default:
873
+ }
874
+ throw new Error(`*** ASSERT FAIL *** hash: invalid type: ${ typeof data}`);
793
875
  }