@sap/cds 7.0.2 → 7.1.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.
Files changed (77) hide show
  1. package/CHANGELOG.md +60 -3
  2. package/_i18n/i18n_ar.properties +3 -0
  3. package/_i18n/i18n_cs.properties +4 -1
  4. package/_i18n/i18n_da.properties +3 -0
  5. package/_i18n/i18n_de.properties +3 -0
  6. package/_i18n/i18n_en.properties +3 -0
  7. package/_i18n/i18n_es.properties +3 -0
  8. package/_i18n/i18n_fi.properties +3 -0
  9. package/_i18n/i18n_fr.properties +3 -0
  10. package/_i18n/i18n_it.properties +3 -0
  11. package/_i18n/i18n_ja.properties +3 -0
  12. package/_i18n/i18n_ko.properties +3 -0
  13. package/_i18n/i18n_ms.properties +3 -0
  14. package/_i18n/i18n_nl.properties +3 -0
  15. package/_i18n/i18n_no.properties +3 -0
  16. package/_i18n/i18n_pl.properties +3 -0
  17. package/_i18n/i18n_pt.properties +3 -0
  18. package/_i18n/i18n_ro.properties +3 -0
  19. package/_i18n/i18n_ru.properties +3 -0
  20. package/_i18n/i18n_sv.properties +3 -0
  21. package/_i18n/i18n_th.properties +3 -0
  22. package/_i18n/i18n_zh_CN.properties +3 -0
  23. package/_i18n/i18n_zh_TW.properties +3 -0
  24. package/apis/core.d.ts +26 -30
  25. package/apis/cqn.d.ts +1 -0
  26. package/apis/ql.d.ts +2 -0
  27. package/apis/serve.d.ts +9 -0
  28. package/apis/services.d.ts +3 -2
  29. package/lib/compile/for/lean_drafts.js +22 -19
  30. package/lib/compile/to/srvinfo.js +7 -19
  31. package/lib/dbs/cds-deploy.js +11 -6
  32. package/lib/env/cds-env.js +3 -4
  33. package/lib/env/presets.js +14 -9
  34. package/lib/env/schemas/cds-package.json +3 -1
  35. package/lib/env/schemas/cds-rc.json +0 -4
  36. package/lib/linked/classes.js +112 -12
  37. package/lib/linked/entities.js +3 -0
  38. package/lib/linked/models.js +2 -1
  39. package/lib/ql/SELECT.js +1 -0
  40. package/lib/ql/Whereable.js +1 -0
  41. package/lib/srv/cds-serve.js +2 -1
  42. package/lib/srv/protocols/_legacy.js +7 -6
  43. package/lib/srv/protocols/index.js +30 -55
  44. package/lib/utils/tar.js +2 -2
  45. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +2 -1
  46. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -4
  47. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/format/ResponseContentNegotiator.js +12 -0
  48. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/SerializerFactory.js +3 -1
  49. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/MetadataCache.js +1 -1
  50. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +1 -4
  51. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +10 -4
  52. package/libx/_runtime/cds-services/services/utils/columns.js +8 -2
  53. package/libx/_runtime/cds-services/services/utils/differ.js +1 -1
  54. package/libx/_runtime/common/composition/data.js +49 -29
  55. package/libx/_runtime/common/composition/update.js +0 -1
  56. package/libx/_runtime/common/composition/utils.js +1 -1
  57. package/libx/_runtime/common/generic/crud.js +1 -1
  58. package/libx/_runtime/common/generic/input.js +18 -13
  59. package/libx/_runtime/common/utils/cqn2cqn4sql.js +1 -1
  60. package/libx/_runtime/common/utils/resolveView.js +115 -35
  61. package/libx/_runtime/common/utils/rewriteAsterisks.js +21 -0
  62. package/libx/_runtime/common/utils/search2cqn4sql.js +1 -1
  63. package/libx/_runtime/db/generic/rewrite.js +5 -4
  64. package/libx/_runtime/db/query/read.js +10 -9
  65. package/libx/_runtime/db/query/update.js +9 -18
  66. package/libx/_runtime/db/utils/deep.js +6 -5
  67. package/libx/_runtime/db/utils/normalizeTimeData.js +1 -1
  68. package/libx/_runtime/fiori/generic/activate.js +14 -19
  69. package/libx/_runtime/fiori/generic/edit.js +2 -5
  70. package/libx/_runtime/hana/streaming.js +3 -4
  71. package/libx/_runtime/remote/utils/client.js +9 -5
  72. package/libx/odata/afterburner.js +5 -2
  73. package/libx/rest/RestAdapter.js +2 -2
  74. package/libx/rest/middleware/error.js +4 -1
  75. package/libx/rest/middleware/operation.js +1 -1
  76. package/package.json +3 -3
  77. package/lib/srv/protocols/graphql.js +0 -30
@@ -36,6 +36,7 @@ const _inverseTransition = transition => {
36
36
  const nested = inverseTransition.mapping.get(ref0) || {}
37
37
  if (!nested.transition) nested.transition = { mapping: new Map() }
38
38
  let current = nested.transition.mapping
39
+
39
40
  for (let i = 1; i < value.ref.length; i++) {
40
41
  const last = i === value.ref.length - 1
41
42
  const obj = last ? { ref: [key] } : { transition: { mapping: new Map() } }
@@ -63,16 +64,19 @@ const revertData = (data, transition, service) => {
63
64
 
64
65
  const _newSubData = (newData, key, transition, el, inverse, service) => {
65
66
  const val = newData[key]
67
+
66
68
  if ((!Array.isArray(val) && typeof val === 'object') || (Array.isArray(val) && val.length !== 0)) {
67
69
  let mapped = transition.mapping.get(key)
68
70
  if (!mapped) {
69
71
  mapped = {}
70
72
  transition.mapping.set(key, mapped)
71
73
  }
74
+
72
75
  if (!mapped.transition) {
73
76
  const subTransition = getTransition(el._target, service)
74
77
  mapped.transition = inverse ? _inverseTransition(subTransition) : subTransition
75
78
  }
79
+
76
80
  if (Array.isArray(val)) {
77
81
  newData[key] = val.map(singleVal => _newData(singleVal, mapped.transition, inverse, service))
78
82
  } else {
@@ -85,6 +89,7 @@ const _newNestedData = (queryTarget, newData, ref, value) => {
85
89
  const parent = queryTarget.query && queryTarget.query._target
86
90
  let currentEntity = parent
87
91
  let currentData = newData
92
+
88
93
  for (let i = 0; i < ref.length; i++) {
89
94
  currentEntity = currentEntity.elements[ref[i]]
90
95
  if (currentEntity.isAssociation) {
@@ -101,6 +106,7 @@ const _newNestedData = (queryTarget, newData, ref, value) => {
101
106
  // eslint-disable-next-line complexity
102
107
  const _newData = (data, transition, inverse, service) => {
103
108
  if (data === null) return null
109
+
104
110
  // no transition -> nothing to do
105
111
  if (transition.target && transition.target.name === transition.queryTarget.name) return data
106
112
 
@@ -109,8 +115,9 @@ const _newData = (data, transition, inverse, service) => {
109
115
  const queryTarget = transition.queryTarget
110
116
 
111
117
  for (const key in newData) {
112
- const el = queryTarget && queryTarget.elements && queryTarget.elements[key]
118
+ const el = queryTarget && queryTarget?.elements[key]
113
119
  const isAssoc = el && el.isAssociation
120
+
114
121
  if (isAssoc) {
115
122
  if (newData[key] || (newData[key] === null && service.name === 'db')) {
116
123
  _newSubData(newData, key, transition, el, inverse, service)
@@ -136,6 +143,7 @@ const _newData = (data, transition, inverse, service) => {
136
143
  const value = newData[key]
137
144
  delete newData[key]
138
145
  const { ref } = mapped
146
+
139
147
  if (ref.length === 1) {
140
148
  newData[ref[0]] = value
141
149
  if (mapped.alternatives) mapped.alternatives.forEach(({ ref }) => (newData[ref[0]] = value))
@@ -159,6 +167,7 @@ const _newColumns = (columns = [], transition, service, withAlias = false) => {
159
167
  newColumns.push(newColumn)
160
168
  return newColumns
161
169
  }
170
+
162
171
  const mapped = column.ref && transition.mapping.get(column.ref[0])
163
172
 
164
173
  if (mapped && mapped.ref) {
@@ -193,24 +202,31 @@ const _newColumns = (columns = [], transition, service, withAlias = false) => {
193
202
  const expandTarget = def._target
194
203
  const subtransition = getTransition(expandTarget, service)
195
204
  mapped.transition = subtransition
196
-
197
205
  newColumn.expand = _newColumns(column.expand, subtransition, service, withAlias)
198
206
  }
207
+
199
208
  newColumns.push(newColumn)
200
209
  })
201
210
 
202
211
  return newColumns
203
212
  }
204
213
 
214
+ const _resolveColumn = (column, transition) => {
215
+ const mapped = transition.mapping.get(column)
216
+ if (mapped && mapped.ref) {
217
+ return mapped.ref[0]
218
+ } else if (!mapped) {
219
+ return column
220
+ }
221
+ }
222
+
205
223
  const _newInsertColumns = (columns = [], transition) => {
206
224
  const newColumns = []
207
225
 
208
226
  columns.forEach(column => {
209
- const mapped = transition.mapping.get(column)
210
- if (mapped && mapped.ref) {
211
- newColumns.push(mapped.ref[0])
212
- } else if (!mapped) {
213
- newColumns.push(column)
227
+ const resolvedColumn = _resolveColumn(column, transition)
228
+ if (resolvedColumn) {
229
+ newColumns.push(resolvedColumn)
214
230
  }
215
231
  })
216
232
 
@@ -220,6 +236,7 @@ const _newInsertColumns = (columns = [], transition) => {
220
236
  // REVISIT: this hard-coding on ref indexes does not support path expressions
221
237
  const _newWhereRef = (newWhereElement, transition, alias, tableName, isSubSelect) => {
222
238
  const newRef = Array.isArray(newWhereElement.ref) ? [...newWhereElement.ref] : [newWhereElement.ref]
239
+
223
240
  if (newRef[0] === alias) {
224
241
  const mapped = transition.mapping.get(newRef[1])
225
242
  if (mapped) newRef[1] = mapped.ref[0]
@@ -236,6 +253,7 @@ const _newWhereRef = (newWhereElement, transition, alias, tableName, isSubSelect
236
253
  if (mapped) newRef[0] = mapped.ref[0]
237
254
  }
238
255
  }
256
+
239
257
  newWhereElement.ref = newRef
240
258
  }
241
259
 
@@ -250,20 +268,23 @@ const _newWhere = (where = [], transition, tableName, alias, isSubselect = false
250
268
 
251
269
  const newWhereElement = { ...whereElement }
252
270
  if (!whereElement.ref && !whereElement.SELECT && !whereElement.func) return whereElement
271
+
253
272
  if (whereElement.SELECT && whereElement.SELECT.where && !whereElement._doNotResolve) {
254
273
  newWhereElement.SELECT.where = _newWhere(whereElement.SELECT.where, transition, tableName, alias, true)
255
274
  return newWhereElement
256
- } else {
257
- if (newWhereElement.ref) {
258
- _newWhereRef(newWhereElement, transition, alias, tableName, isSubselect)
259
- return newWhereElement
260
- } else if (newWhereElement.func) {
261
- newWhereElement.args = _newWhere(newWhereElement.args, transition, tableName, alias)
262
- return newWhereElement
263
- } else {
264
- return whereElement
265
- }
266
275
  }
276
+
277
+ if (newWhereElement.ref) {
278
+ _newWhereRef(newWhereElement, transition, alias, tableName, isSubselect)
279
+ return newWhereElement
280
+ }
281
+
282
+ if (newWhereElement.func) {
283
+ newWhereElement.args = _newWhere(newWhereElement.args, transition, tableName, alias)
284
+ return newWhereElement
285
+ }
286
+
287
+ return whereElement
267
288
  })
268
289
 
269
290
  return newWhere
@@ -277,6 +298,7 @@ const _initialColumns = transition => {
277
298
  if (!transition.queryTarget.elements[transitionEl] || transition.queryTarget.elements[transitionEl].isAssociation) {
278
299
  continue
279
300
  }
301
+
280
302
  columns.push({ ref: [transitionEl] })
281
303
  }
282
304
 
@@ -318,9 +340,10 @@ const _rewriteQueryPath = (path, transitions) => {
318
340
  }
319
341
 
320
342
  const _newUpdate = (query, transitions, service) => {
321
- const targetTransition = transitions[transitions.length - 1]
343
+ const targetTransition = transitions.at(-1)
322
344
  const targetName = targetTransition.target.name
323
345
  const newUpdate = Object.create(query.UPDATE)
346
+
324
347
  newUpdate.entity = newUpdate.entity.ref
325
348
  ? {
326
349
  ...newUpdate.entity,
@@ -341,16 +364,18 @@ const _newUpdate = (query, transitions, service) => {
341
364
  enumerable: false,
342
365
  value: transitions
343
366
  })
367
+
344
368
  return newUpdate
345
369
  }
346
370
 
347
371
  const _newSelect = (query, transitions, service) => {
348
- const targetTransition = transitions[transitions.length - 1]
372
+ const targetTransition = transitions.at(-1)
349
373
  const newSelect = Object.create(query.SELECT)
350
374
  newSelect.from = {
351
375
  ...newSelect.from,
352
376
  ref: _rewriteQueryPath(query.SELECT.from, transitions)
353
377
  }
378
+
354
379
  if (!newSelect.columns && targetTransition.mapping.size) newSelect.columns = _initialColumns(targetTransition)
355
380
  if (newSelect.columns) {
356
381
  rewriteAsterisks({ SELECT: query.SELECT }, service.model, {
@@ -359,6 +384,7 @@ const _newSelect = (query, transitions, service) => {
359
384
  })
360
385
  newSelect.columns = _newColumns(newSelect.columns, targetTransition, service, service.kind !== 'app-service')
361
386
  }
387
+
362
388
  if (newSelect.having) newSelect.having = _newColumns(newSelect.having, targetTransition)
363
389
  if (newSelect.groupBy) newSelect.groupBy = _newColumns(newSelect.groupBy, targetTransition)
364
390
  if (newSelect.orderBy) newSelect.orderBy = _newColumns(newSelect.orderBy, targetTransition)
@@ -370,17 +396,42 @@ const _newSelect = (query, transitions, service) => {
370
396
  query.SELECT.from && query.SELECT.from.as
371
397
  )
372
398
  }
399
+
373
400
  Object.defineProperty(newSelect, '_transitions', {
374
401
  enumerable: false,
375
402
  value: transitions
376
403
  })
404
+
377
405
  return newSelect
378
406
  }
379
407
 
408
+ const _newStream = (query, transitions) => {
409
+ const targetTransition = transitions[transitions.length - 1]
410
+ const targetName = targetTransition.target.name
411
+ const newStream = Object.create(query.STREAM)
412
+ if (newStream.into) {
413
+ const refObject = newStream.into.ref ? newStream.into : { ref: [query.STREAM.into] }
414
+ newStream.into = {
415
+ ...refObject,
416
+ ref: _rewriteQueryPath(refObject, transitions)
417
+ }
418
+ if (!query.STREAM.into.ref) newStream.into = newStream.into.ref[0] // leave as string
419
+ } else {
420
+ newStream.into = targetName
421
+ }
422
+ if (newStream.column) newStream.column = _resolveColumn([newStream.column], targetTransition)
423
+ Object.defineProperty(newStream, '_transitions', {
424
+ enumerable: false,
425
+ value: transitions
426
+ })
427
+ return newStream
428
+ }
429
+
380
430
  const _newInsert = (query, transitions, service) => {
381
431
  const targetTransition = transitions[transitions.length - 1]
382
432
  const targetName = targetTransition.target.name
383
433
  const newInsert = Object.create(query.INSERT)
434
+
384
435
  if (newInsert.into) {
385
436
  const refObject = newInsert.into.ref ? newInsert.into : { ref: [query.INSERT.into] }
386
437
  newInsert.into = {
@@ -391,12 +442,14 @@ const _newInsert = (query, transitions, service) => {
391
442
  } else {
392
443
  newInsert.into = targetName
393
444
  }
445
+
394
446
  if (newInsert.columns) newInsert.columns = _newInsertColumns(newInsert.columns, targetTransition)
395
447
  if (newInsert.entries) newInsert.entries = _newEntries(newInsert.entries, targetTransition, service)
396
448
  Object.defineProperty(newInsert, '_transitions', {
397
449
  enumerable: false,
398
450
  value: transitions
399
451
  })
452
+
400
453
  return newInsert
401
454
  }
402
455
 
@@ -404,6 +457,7 @@ const _newUpsert = (query, transitions, service) => {
404
457
  const targetTransition = transitions[transitions.length - 1]
405
458
  const targetName = targetTransition.target.name
406
459
  const newUpsert = Object.create(query.UPSERT)
460
+
407
461
  newUpsert.into = newUpsert.into.ref
408
462
  ? {
409
463
  ...newUpsert.into,
@@ -416,6 +470,7 @@ const _newUpsert = (query, transitions, service) => {
416
470
  enumerable: false,
417
471
  value: transitions
418
472
  })
473
+
419
474
  return newUpsert
420
475
  }
421
476
 
@@ -423,12 +478,14 @@ const _newDelete = (query, transitions) => {
423
478
  const targetTransition = transitions[transitions.length - 1]
424
479
  const targetName = targetTransition.target.name
425
480
  const newDelete = Object.create(query.DELETE)
481
+
426
482
  newDelete.from = newDelete.from.ref
427
483
  ? {
428
484
  ...newDelete.from,
429
485
  ref: _rewriteQueryPath(query.DELETE.from, transitions)
430
486
  }
431
487
  : targetName
488
+
432
489
  if (newDelete.where) {
433
490
  newDelete.where = _newWhere(
434
491
  newDelete.where,
@@ -437,10 +494,12 @@ const _newDelete = (query, transitions) => {
437
494
  query.DELETE.from.as
438
495
  )
439
496
  }
497
+
440
498
  Object.defineProperty(newDelete, '_transitions', {
441
499
  enumerable: false,
442
500
  value: transitions
443
501
  })
502
+
444
503
  return newDelete
445
504
  }
446
505
 
@@ -451,32 +510,41 @@ const _findRenamed = (cqnColumns, column) =>
451
510
  cqnColumns.find(
452
511
  cqnColumn =>
453
512
  cqnColumn.as &&
454
- ((column.ref && column.ref[column.ref.length - 1] === cqnColumn.as) ||
513
+ (column?.ref?.at(-1) === cqnColumn.as ||
455
514
  (column.as === cqnColumn.as && Object.prototype.hasOwnProperty.call(cqnColumn, 'val')))
456
515
  )
457
516
 
458
517
  const _queryColumns = (target, columns = [], persistenceTable = false, force = false) => {
459
518
  if (!(target && target.query && target.query.SELECT)) return columns
519
+
460
520
  const cqnColumns = target.query.SELECT.columns || []
461
521
  const from = target.query.SELECT.from
462
- const isTargetAliased = from.as && cqnColumns.some(c => c.ref && c.ref[0] === from.as)
522
+ const isTargetAliased = from.as && cqnColumns.some(c => c.ref?.[0] === from.as)
523
+
463
524
  if (!columns.length) columns = Object.keys(target.elements).map(e => ({ ref: [e], as: e }))
464
- return columns.reduce((res, column) => {
525
+
526
+ const queryColumns = columns.reduce((res, column) => {
465
527
  const renamed = _findRenamed(cqnColumns, column)
528
+
466
529
  if (renamed) {
467
530
  if (renamed.val) return res.concat({ as: renamed.as, val: renamed.val })
531
+
468
532
  // There could be some `where` clause inside `ref` which we don't support yet
469
533
  if (!renamed.ref || renamed.ref.some(e => typeof e !== 'string') || renamed.xpr) return res
470
534
  if (isTargetAliased) renamed.ref.shift()
535
+
471
536
  // If the entity is annotated with the annotation `@cds.persistence.table`
472
537
  // and elements aliases exist, the aliases must be used as column references.
473
538
  // The reason is that in this scenario, the cds compiler generate a table
474
539
  // instead of a view. If forced, skip this.
475
540
  column.ref = !force && persistenceTable ? [renamed.as] : [...renamed.ref]
476
541
  }
542
+
477
543
  res.push(column)
478
544
  return _appendForeignKeys(res, target, columns, column)
479
545
  }, [])
546
+
547
+ return queryColumns
480
548
  }
481
549
 
482
550
  const _mappedValue = (col, alias) => {
@@ -498,7 +566,8 @@ const getDBTable = target => {
498
566
  }
499
567
 
500
568
  const _appendForeignKeys = (newColumns, target, columns, { as, ref = [] }) => {
501
- const el = target.elements[as] || target.query._target.elements[ref[ref.length - 1]]
569
+ const el = target.elements[as] || target.query._target.elements[ref.at(-1)]
570
+
502
571
  if (el && el.isAssociation && el.keys) {
503
572
  for (const key of el.keys) {
504
573
  // .as and .ref has a different meaning here
@@ -506,6 +575,7 @@ const _appendForeignKeys = (newColumns, target, columns, { as, ref = [] }) => {
506
575
  const keyName = key.as || key.ref[0]
507
576
  const keyAlias = key.ref[0]
508
577
  const found = columns.find(col => col.as === `${as}_${keyAlias}`)
578
+
509
579
  if (found) {
510
580
  found.ref = [`${ref.join('_')}_${keyName}`]
511
581
  } else {
@@ -516,11 +586,13 @@ const _appendForeignKeys = (newColumns, target, columns, { as, ref = [] }) => {
516
586
  }
517
587
  }
518
588
  }
589
+
519
590
  return newColumns
520
591
  }
521
592
 
522
593
  const _checkForForbiddenViews = queryTarget => {
523
594
  const select = queryTarget && queryTarget.query && queryTarget.query.SELECT
595
+
524
596
  if (select) {
525
597
  if (!select.from || select.from.join || select.from.length > 1) {
526
598
  throw getError({
@@ -529,6 +601,7 @@ const _checkForForbiddenViews = queryTarget => {
529
601
  target: queryTarget.name
530
602
  })
531
603
  }
604
+
532
605
  if (select.where) {
533
606
  LOG._debug &&
534
607
  LOG.debug(`Ignoring where clause during ${_event || 'INSERT|UPDATE|DELETE'} on view "${queryTarget.name}".`)
@@ -543,22 +616,28 @@ const _getTransitionData = (target, columns, service, skipForbiddenViewCheck) =>
543
616
  const persistenceTable = _isPersistenceTable(target)
544
617
  const isDatabaseService = service.isDatabaseService
545
618
  columns = _queryColumns(target, columns, persistenceTable, !isDatabaseService && !targetStartsWithSrvName)
619
+
546
620
  // REVISIT: Change once we expose database service
547
621
  if (persistenceTable && isDatabaseService) {
548
622
  return { target, transitionColumns: columns }
549
623
  }
624
+
550
625
  // stop projection resolving if it starts with the service name prefix
551
626
  if (!isDatabaseService && targetStartsWithSrvName) {
552
627
  return { target, transitionColumns: columns }
553
628
  }
629
+
554
630
  // continue projection resolving if the target is a projection
555
631
  if (target.query && target.query._target) {
556
632
  const newTarget = target.query._target
633
+
557
634
  if (isDatabaseService || !(service.namespace && newTarget.name.startsWith(`${service.namespace}.`))) {
558
635
  return _getTransitionData(newTarget, columns, service, skipForbiddenViewCheck)
559
636
  }
637
+
560
638
  return { target: newTarget, transitionColumns: columns }
561
639
  }
640
+
562
641
  return { target, transitionColumns: columns }
563
642
  }
564
643
 
@@ -607,14 +686,14 @@ const _entityTransitionsForTarget = (from, model, service) => {
607
686
  // > assoc
608
687
  previousEntity = entity
609
688
  return getTransition(entity, service)
610
- } else {
611
- // > struct
612
- previousEntity = previousEntity.elements[element]
613
- return {
614
- target: previousEntity,
615
- queryTarget: previousEntity,
616
- mapping: new Map()
617
- }
689
+ }
690
+
691
+ // > struct
692
+ previousEntity = previousEntity.elements[element]
693
+ return {
694
+ target: previousEntity,
695
+ queryTarget: previousEntity,
696
+ mapping: new Map()
618
697
  }
619
698
  }
620
699
  })
@@ -626,7 +705,8 @@ const _newQuery = (query, event, model, service) => {
626
705
  INSERT: ['into', _newInsert],
627
706
  UPSERT: ['into', _newUpsert],
628
707
  UPDATE: ['entity', _newUpdate],
629
- DELETE: ['from', _newDelete]
708
+ DELETE: ['from', _newDelete],
709
+ STREAM: ['into', _newStream]
630
710
  }[event]
631
711
  const newQuery = Object.create(query)
632
712
  const transitions = _entityTransitionsForTarget(query[event][_prop], model, service)
@@ -647,13 +727,13 @@ const resolveView = (query, model, service) => {
647
727
  else if (query.UPSERT) _event = 'UPSERT'
648
728
  else if (query.UPDATE) _event = 'UPDATE'
649
729
  else if (query.DELETE) _event = 'DELETE'
730
+ else if (query.STREAM) _event = 'STREAM'
650
731
 
651
732
  const newQuery = _newQuery(query, _event, model, service)
652
733
 
653
734
  // restore logger and clear _event
654
735
  LOG = _LOG
655
736
  _event = undefined
656
-
657
737
  return newQuery
658
738
  }
659
739
 
@@ -664,10 +744,10 @@ const resolveView = (query, model, service) => {
664
744
  * @param {*} req
665
745
  */
666
746
  const restoreLink = req => {
667
- if (req.query.INSERT && req.query.INSERT.entries) {
747
+ if (req.query.INSERT?.entries) {
668
748
  if (Array.isArray(req.query.INSERT.entries)) req.data = req.query.INSERT.entries[0]
669
749
  else req.data = req.query.INSERT.entries
670
- } else if (req.query.UPDATE && req.query.UPDATE.data) {
750
+ } else if (req.query.UPDATE?.data) {
671
751
  req.data = req.query.UPDATE.data
672
752
  }
673
753
  }
@@ -27,7 +27,28 @@ const _expandColumn = (column, target, _4db) => {
27
27
  return column
28
28
  }
29
29
 
30
+ const _resolveTarget = (ref, target) => {
31
+ if (ref.length > 1) {
32
+ if (target.elements[ref[0]]) {
33
+ // structured
34
+ return _resolveTarget(ref.slice(1), target.elements[ref[0]])
35
+ } else {
36
+ // in case there is an alias, try with the next entry
37
+ return _resolveTarget(ref.slice(1), target)
38
+ }
39
+ } else {
40
+ return target.elements[ref[0].id || ref[0]]._target
41
+ }
42
+ }
43
+
30
44
  const rewriteExpandAsterisk = (columns, target) => {
45
+ // check all nested expands to resolve nested expand asterisks first
46
+ for (const column of columns) {
47
+ if (column.expand && column.ref) {
48
+ rewriteExpandAsterisk(column.expand, _resolveTarget(column.ref, target))
49
+ }
50
+ }
51
+
31
52
  const expandAllColIdx = columns.findIndex(col => {
32
53
  if (col.ref || !col.expand) return
33
54
  return col.expand.includes('*')
@@ -20,7 +20,7 @@ const search2cqn4sql = (query, model, options = {}) => {
20
20
 
21
21
  // Call custom (optimized search to cqn for sql implementation) that tries
22
22
  // to optimize the search behavior for a specific database service.
23
- if (typeof search2cqn4sql === 'function' && entity.associations?.localized && !aggregated) {
23
+ if (typeof search2cqn4sql === 'function' && entity?.associations?.localized && !aggregated) {
24
24
  const search2cqnOptions = { columns: computeColumnsToBeSearched(query, entity), locale: options.locale }
25
25
  return search2cqn4sql(query, entity, search2cqnOptions)
26
26
  }
@@ -3,10 +3,12 @@ const { generateAliases } = require('../utils/generateAliases')
3
3
  const { restoreLink } = require('../../common/utils/resolveView')
4
4
 
5
5
  const _isLinked = req => {
6
- if (req.query.INSERT && req.query.INSERT.entries) {
6
+ if (req.query.INSERT?.entries) {
7
7
  if (Array.isArray(req.query.INSERT.entries)) return req.data === req.query.INSERT.entries[0]
8
- else return req.data === req.query.INSERT.entries
9
- } else if (req.query.UPDATE && req.query.UPDATE.data) {
8
+ return req.data === req.query.INSERT.entries
9
+ }
10
+
11
+ if (req.query.UPDATE?.data) {
10
12
  return req.data === req.query.UPDATE.data
11
13
  }
12
14
  }
@@ -36,7 +38,6 @@ function handler(req) {
36
38
  if (linked) restoreLink(req)
37
39
 
38
40
  if (streaming) req.query._streaming = streaming
39
-
40
41
  generateAliases(req.query)
41
42
  }
42
43
 
@@ -45,6 +45,7 @@ const read = (executeSelectCQN, executeStreamCQN) => (model, dbc, query, req) =>
45
45
  if (!query.SELECT || (query.SELECT && !query.SELECT.columns)) {
46
46
  throw getError(500, 'Invalid SELECT statement for streaming')
47
47
  }
48
+
48
49
  return executeStreamCQN(model, dbc, query, user, locale, isoTs)
49
50
  }
50
51
 
@@ -55,20 +56,20 @@ const read = (executeSelectCQN, executeStreamCQN) => (model, dbc, query, req) =>
55
56
  if (query.SELECT.limit) {
56
57
  const countQuery = _createCountQuery(query)
57
58
  const countResultPromise = executeSelectCQN(model, dbc, countQuery, user, locale, isoTs)
58
- if (query.SELECT.limit.rows && query.SELECT.limit.rows.val === 0) {
59
+ if (query.SELECT.limit?.rows?.val === 0) {
59
60
  // We don't need to perform our result query
60
61
  return countResultPromise.then(countResults => _arrayWithCount([], countValue(countResults)))
61
- } else {
62
- const resultPromise = executeSelectCQN(model, dbc, query, user, locale, isoTs)
63
- return Promise.all([countResultPromise, resultPromise]).then(([countResults, result]) =>
64
- _arrayWithCount(result, countValue(countResults))
65
- )
66
62
  }
67
- } else {
68
- return executeSelectCQN(model, dbc, query, user, locale, isoTs).then(result =>
69
- _arrayWithCount(result, result.length)
63
+
64
+ const resultPromise = executeSelectCQN(model, dbc, query, user, locale, isoTs)
65
+ return Promise.all([countResultPromise, resultPromise]).then(([countResults, result]) =>
66
+ _arrayWithCount(result, countValue(countResults))
70
67
  )
71
68
  }
69
+
70
+ return executeSelectCQN(model, dbc, query, user, locale, isoTs).then(result =>
71
+ _arrayWithCount(result, result.length)
72
+ )
72
73
  }
73
74
 
74
75
  return executeSelectCQN(model, dbc, query, user, locale, isoTs)
@@ -19,14 +19,13 @@ const _getFilteredCqns = (cqns, model) => {
19
19
  const entity = model && cqn.UPDATE && model.definitions[cqn.UPDATE.entity]
20
20
  if (!entity) continue
21
21
 
22
- /*
23
- * do not filter if:
24
- * - there is a propterty that is not managed or managed but filled by custom handler (i.e., its value doesn't start with $)
25
- * - a composition target is updated as well
26
- */
22
+ // do not filter if:
23
+ // - there is a property that is not managed or managed but filled by custom handler (i.e., its value doesn't start
24
+ // with $) a composition target is updated as well
27
25
  let moreThanManaged = Object.keys(cqn.UPDATE.data).some(
28
- k => entity.elements[k]['@cds.on.update'] === undefined || !cqn.UPDATE.data[k].startsWith('$')
26
+ key => entity.elements[key]['@cds.on.update'] === undefined || !cqn.UPDATE.data[key]?.startsWith('$')
29
27
  )
28
+
30
29
  if (moreThanManaged) continue
31
30
 
32
31
  const comps = Object.values(entity.associations || {}).filter(assoc => assoc.isComposition)
@@ -36,6 +35,7 @@ const _getFilteredCqns = (cqns, model) => {
36
35
  break
37
36
  }
38
37
  }
38
+
39
39
  if (moreThanManaged) continue
40
40
 
41
41
  // remove current cqn
@@ -50,16 +50,8 @@ const update = executeUpdateCQN => async (model, dbc, req) => {
50
50
  const isoTs = normalizeTimestamp(timestamp)
51
51
 
52
52
  if (model && hasDeepUpdate(model, query)) {
53
- // REVISIT: _activeData gets set in case of draftActivate for performance, but this is a layer violation
54
- let selectData = req._ && req._.query && req._.query._activeData
55
-
56
- if (selectData) {
57
- selectData = [selectData]
58
- } else {
59
- // REVISIT: avoid additional read
60
- selectData = await selectDeepUpdateData(cds.db, model, req)
61
- }
62
-
53
+ // REVISIT: avoid additional read
54
+ const selectData = await selectDeepUpdateData(cds.db, model, req)
63
55
  let cqns = await getDeepUpdateCQNs(model, req, selectData)
64
56
 
65
57
  // the delete chunks, i.e., how many deletes can be processed in parallel
@@ -70,11 +62,10 @@ const update = executeUpdateCQN => async (model, dbc, req) => {
70
62
  cqns = _getFilteredCqns(getFlatArray(cqns), model)
71
63
 
72
64
  if (cqns.length === 0) return 0
73
-
74
65
  const results = await processCQNs(executeUpdateCQN, cqns, model, dbc, user, locale, isoTs, chunks)
66
+
75
67
  // return number of affected rows of "root cqn", if an update, 1 otherwise (as not update of root but its children)
76
68
  if (cqns[0].UPDATE) return results[0]
77
-
78
69
  return 1
79
70
  }
80
71
 
@@ -3,9 +3,11 @@ const _flattenDeep = (arr, res) => {
3
3
  res.push(arr)
4
4
  return res
5
5
  }
6
+
6
7
  for (const a of arr) {
7
8
  _flattenDeep(a, res)
8
9
  }
10
+
9
11
  return res
10
12
  }
11
13
 
@@ -20,20 +22,18 @@ async function _processChunk(processFn, model, dbc, cqns, user, locale, ts, inde
20
22
  const promisesResults = await Promise.allSettled(promises)
21
23
  const firstRejected = promisesResults.find(r => r.status === 'rejected')
22
24
  if (firstRejected) throw firstRejected.reason
25
+
23
26
  // put results of queries into correct place of return results
24
27
  for (let i = 0; i < promisesResults.length; i++) results[indexes[i]] = promisesResults[i].value
25
28
  }
26
29
 
30
+ // execute deletes first, but keep results in order of cqns
27
31
  async function processCQNs(processFn, cqns, model, dbc, user, locale, ts, chunks) {
28
- /*
29
- * execute deletes first, but keep results in order of cqns
30
- */
31
-
32
32
  const results = new Array(cqns.length)
33
-
34
33
  const deletes = []
35
34
  const updatesForDeletes = []
36
35
  const others = []
36
+
37
37
  for (let i = 0; i < cqns.length; i++) {
38
38
  if (cqns[i].DELETE) deletes.push(i)
39
39
  else if (cqns[i].__4delete) updatesForDeletes.push(i)
@@ -43,6 +43,7 @@ async function processCQNs(processFn, cqns, model, dbc, user, locale, ts, chunks
43
43
  if (deletes.length > 0) {
44
44
  if (chunks) {
45
45
  let offset = 0
46
+
46
47
  for (const amount of chunks) {
47
48
  const indexes = deletes.slice(offset, offset + amount)
48
49
  await _processChunk(processFn, model, dbc, cqns, user, locale, ts, indexes, results)
@@ -16,7 +16,7 @@ const _isToConvert = type => type === 'cds.DateTime' || type === 'cds.Timestamp'
16
16
 
17
17
  const _convertDateTimeEntry = (entry, element, model) => {
18
18
  const { name, type, _target } = element
19
- if (!(entry[name] === undefined || entry[name] === null)) {
19
+ if (!(entry[name] == null)) {
20
20
  if (_isToConvert(type) && entry[name] !== '$now') {
21
21
  entry[name] = _convertDateTimeElement(entry[name], element)
22
22
  }