@sap/cds 5.6.2 → 5.7.2

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 (183) hide show
  1. package/CHANGELOG.md +133 -0
  2. package/_i18n/i18n_fr.properties +4 -4
  3. package/apis/cds.d.ts +7 -10
  4. package/apis/connect.d.ts +3 -3
  5. package/apis/core.d.ts +2 -4
  6. package/apis/models.d.ts +2 -3
  7. package/apis/ql.d.ts +0 -1
  8. package/apis/services.d.ts +7 -3
  9. package/bin/build/buildTaskFactory.js +16 -10
  10. package/bin/build/buildTaskProviderFactory.js +3 -3
  11. package/bin/build/constants.js +2 -1
  12. package/bin/build/provider/buildTaskProviderInternal.js +14 -14
  13. package/bin/build/provider/hana/2migration.js +2 -3
  14. package/bin/build/provider/hana/index.js +34 -0
  15. package/bin/build/provider/hana/migrationtable.js +90 -22
  16. package/bin/build/provider/hana/template/undeploy.json +5 -0
  17. package/bin/build/provider/node-cf/index.js +9 -2
  18. package/bin/serve.js +16 -18
  19. package/lib/compile/cdsc.js +15 -5
  20. package/lib/compile/etc/_localized.js +4 -4
  21. package/lib/compile/extend.js +8 -0
  22. package/lib/compile/index.js +3 -1
  23. package/lib/compile/minify.js +61 -0
  24. package/lib/compile/resolve.js +4 -1
  25. package/lib/compile/to/gql.js +9 -0
  26. package/lib/compile/to/sql.js +26 -30
  27. package/lib/connect/index.js +1 -1
  28. package/lib/core/entities.js +0 -3
  29. package/lib/core/infer.js +1 -0
  30. package/lib/core/reflect.js +0 -34
  31. package/lib/deploy.js +25 -17
  32. package/lib/env/defaults.js +3 -1
  33. package/lib/env/index.js +13 -4
  34. package/lib/env/presets.js +38 -0
  35. package/lib/env/requires.js +16 -11
  36. package/lib/index.js +13 -11
  37. package/lib/log/format/kibana.js +4 -2
  38. package/lib/log/index.js +2 -2
  39. package/lib/ql/Whereable.js +1 -0
  40. package/lib/req/cds-context.js +79 -0
  41. package/lib/req/context.js +5 -77
  42. package/lib/req/request.js +1 -1
  43. package/lib/serve/Service-api.js +8 -4
  44. package/lib/serve/Service-dispatch.js +0 -7
  45. package/lib/serve/Service-methods.js +6 -8
  46. package/lib/serve/Transaction.js +35 -30
  47. package/lib/serve/adapters.js +1 -4
  48. package/lib/utils/axios.js +1 -1
  49. package/libx/_runtime/audit/Service.js +44 -20
  50. package/libx/_runtime/audit/generic/personal/access.js +16 -11
  51. package/libx/_runtime/audit/generic/personal/modification.js +5 -5
  52. package/libx/_runtime/audit/generic/personal/utils.js +46 -37
  53. package/libx/_runtime/{common/auth → auth}/index.js +21 -7
  54. package/libx/_runtime/{common/auth → auth}/strategies/JWT.js +2 -2
  55. package/libx/_runtime/{common/auth → auth}/strategies/basic.js +2 -2
  56. package/libx/_runtime/{common/auth → auth}/strategies/dummy.js +1 -1
  57. package/libx/_runtime/{common/auth → auth}/strategies/mock.js +2 -2
  58. package/libx/_runtime/{common/auth → auth}/strategies/utils/uaa.js +1 -1
  59. package/libx/_runtime/{common/auth → auth}/strategies/utils/xssec.js +0 -0
  60. package/libx/_runtime/{common/auth → auth}/strategies/xsuaa.js +2 -2
  61. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +7 -2
  62. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +0 -7
  63. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +0 -8
  64. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +3 -4
  65. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +6 -7
  66. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +3 -4
  67. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +2 -11
  68. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +16 -6
  69. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +26 -65
  70. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +0 -7
  71. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +3 -66
  72. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +26 -0
  73. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +5 -5
  74. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +2 -2
  75. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +13 -10
  76. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -7
  77. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +0 -8
  78. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +54 -76
  79. package/libx/_runtime/cds-services/adapter/rest/RestRequest.js +0 -7
  80. package/libx/_runtime/cds-services/adapter/rest/handlers/create.js +3 -6
  81. package/libx/_runtime/cds-services/adapter/rest/handlers/delete.js +3 -6
  82. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +3 -6
  83. package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +3 -6
  84. package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +3 -6
  85. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +2 -2
  86. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +8 -4
  87. package/libx/_runtime/cds-services/services/Service.js +0 -6
  88. package/libx/_runtime/cds-services/services/utils/columns.js +10 -3
  89. package/libx/_runtime/cds-services/services/utils/compareJson.js +4 -7
  90. package/libx/_runtime/cds-services/services/utils/differ.js +4 -1
  91. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +1 -41
  92. package/libx/_runtime/cds-services/util/assert.js +1 -262
  93. package/libx/_runtime/cds.js +6 -9
  94. package/libx/_runtime/common/aspects/entity.js +1 -1
  95. package/libx/_runtime/common/composition/delete.js +4 -2
  96. package/libx/_runtime/common/composition/update.js +22 -35
  97. package/libx/_runtime/common/composition/utils.js +3 -7
  98. package/libx/_runtime/common/error/standardError.js +11 -0
  99. package/libx/_runtime/common/generic/auth.js +63 -33
  100. package/libx/_runtime/common/generic/crud.js +11 -23
  101. package/libx/_runtime/common/generic/input.js +20 -0
  102. package/libx/_runtime/common/generic/paging.js +2 -2
  103. package/libx/_runtime/common/generic/put.js +4 -10
  104. package/libx/_runtime/common/generic/sorting.js +12 -30
  105. package/libx/_runtime/common/perf/index.js +24 -0
  106. package/libx/_runtime/common/utils/cqn.js +58 -1
  107. package/libx/_runtime/common/utils/cqn2cqn4sql.js +297 -121
  108. package/libx/_runtime/common/utils/csn.js +38 -56
  109. package/libx/_runtime/common/utils/entityFromCqn.js +6 -6
  110. package/libx/_runtime/common/utils/resolveView.js +4 -5
  111. package/libx/_runtime/common/utils/rewriteAsterisks.js +46 -5
  112. package/libx/_runtime/common/utils/search2cqn4sql.js +21 -9
  113. package/libx/_runtime/common/utils/structured.js +35 -25
  114. package/libx/_runtime/db/Service.js +0 -6
  115. package/libx/_runtime/db/expand/expand-v2.js +130 -0
  116. package/libx/_runtime/db/expand/expandCQNToJoin.js +38 -52
  117. package/libx/_runtime/db/expand/index.js +3 -1
  118. package/libx/_runtime/db/generic/arrayed.js +3 -1
  119. package/libx/_runtime/db/generic/input.js +52 -10
  120. package/libx/_runtime/db/generic/integrity.js +367 -26
  121. package/libx/_runtime/db/generic/virtual.js +51 -13
  122. package/libx/_runtime/db/query/update.js +9 -3
  123. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +8 -9
  124. package/libx/_runtime/{common → db}/utils/propagateForeignKeys.js +11 -14
  125. package/libx/_runtime/fiori/generic/activate.js +1 -0
  126. package/libx/_runtime/fiori/generic/before.js +2 -1
  127. package/libx/_runtime/fiori/generic/edit.js +1 -0
  128. package/libx/_runtime/fiori/generic/patch.js +1 -1
  129. package/libx/_runtime/fiori/generic/read.js +155 -57
  130. package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +0 -4
  131. package/libx/_runtime/fiori/uiflex/index.js +1 -1
  132. package/libx/_runtime/fiori/uiflex/{extensibility/index.js → service.js} +6 -4
  133. package/libx/_runtime/fiori/utils/delete.js +7 -1
  134. package/libx/_runtime/hana/Service.js +1 -8
  135. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -14
  136. package/libx/_runtime/hana/execute.js +10 -4
  137. package/libx/_runtime/hana/pool.js +55 -45
  138. package/libx/_runtime/hana/search.js +7 -6
  139. package/libx/_runtime/hana/search2cqn4sql.js +8 -5
  140. package/libx/_runtime/hana/searchToContains.js +3 -1
  141. package/libx/_runtime/index.js +5 -5
  142. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +3 -3
  143. package/libx/_runtime/messaging/Outbox.js +53 -0
  144. package/libx/_runtime/messaging/common-utils/AMQPClient.js +17 -10
  145. package/libx/_runtime/messaging/common-utils/connections.js +14 -9
  146. package/libx/_runtime/messaging/common-utils/waitingTime.js +2 -0
  147. package/libx/_runtime/messaging/enterprise-messaging-shared.js +2 -3
  148. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -2
  149. package/libx/_runtime/messaging/enterprise-messaging.js +21 -15
  150. package/libx/_runtime/messaging/file-based.js +5 -5
  151. package/libx/_runtime/messaging/message-queuing.js +2 -3
  152. package/libx/_runtime/messaging/outbox/OutboxRunner.js +75 -0
  153. package/libx/_runtime/messaging/outbox/utils.js +192 -0
  154. package/libx/_runtime/messaging/service.js +16 -30
  155. package/libx/_runtime/remote/Service.js +15 -0
  156. package/libx/_runtime/remote/utils/client.js +15 -3
  157. package/libx/_runtime/remote/utils/{dataConversion.js → data.js} +12 -2
  158. package/libx/_runtime/sqlite/Service.js +7 -10
  159. package/libx/_runtime/sqlite/customBuilder/CustomExpressionBuilder.js +19 -0
  160. package/libx/_runtime/sqlite/execute.js +18 -12
  161. package/libx/_runtime/types/api.js +2 -1
  162. package/libx/gql/resolvers/parse/ast2cqn/columns.js +1 -1
  163. package/libx/odata/{odata2cqn/afterburner.js → afterburner.js} +28 -16
  164. package/libx/odata/{cqn2odata/index.js → cqn2odata.js} +1 -1
  165. package/libx/odata/{odata2cqn/grammar.pegjs → grammar.pegjs} +182 -118
  166. package/libx/odata/index.js +18 -15
  167. package/libx/odata/parser.js +1 -0
  168. package/libx/odata/utils.js +57 -0
  169. package/libx/rest/RestAdapter.js +2 -6
  170. package/libx/rest/utils/data.js +1 -6
  171. package/package.json +4 -3
  172. package/server.js +4 -5
  173. package/srv/audit-log.cds +87 -0
  174. package/{libx/_runtime/fiori/uiflex/extensibility/index.cds → srv/flex.cds} +0 -0
  175. package/srv/flex.js +1 -0
  176. package/srv/outbox.cds +11 -0
  177. package/srv/outbox.js +0 -0
  178. package/libx/_runtime/cds-services/adapter/perf/performance.js +0 -104
  179. package/libx/_runtime/cds-services/adapter/perf/performanceMeasurement.js +0 -33
  180. package/libx/odata/odata2cqn/index.js +0 -3
  181. package/libx/odata/odata2cqn/parser.js +0 -1
  182. package/libx/odata/readme.md +0 -1
  183. package/libx/odata/utils/index.js +0 -64
@@ -1,10 +1,5 @@
1
- const cds = require('../../cds')
2
- const { all, resolve } = require('../../common/utils/thenable')
3
- const { getDependents } = require('../../common/utils/csn')
4
-
5
1
  // REVISIT: replace with cds.Request
6
2
  const getEntry = require('../../common/error/entry')
7
- const crypto = require('crypto')
8
3
 
9
4
  const ISO_DATE_PART1 =
10
5
  '[1-9]\\d{3}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)'
@@ -27,11 +22,8 @@ const ASSERT_FORMAT = 'ASSERT_FORMAT'
27
22
  const ASSERT_DATA_TYPE = 'ASSERT_DATA_TYPE'
28
23
  const ASSERT_ENUM = 'ASSERT_ENUM'
29
24
  const ASSERT_NOT_NULL = 'ASSERT_NOT_NULL'
30
- const ASSERT_REFERENCE_INTEGRITY = 'ASSERT_REFERENCE_INTEGRITY'
31
25
  const ASSERT_DEEP_ASSOCIATION = 'ASSERT_DEEP_ASSOCIATION'
32
26
 
33
- const ASSERT_INTEGRITY_ANNOTATION = '@assert.integrity'
34
-
35
27
  const _enumValues = element => {
36
28
  return Object.keys(element).map(enumKey => {
37
29
  const enum_ = element[enumKey]
@@ -178,6 +170,7 @@ const checkComplexType = ([key, value], elements, ignoreNonModelledData) => {
178
170
 
179
171
  const _checkStaticElementByKey = (definition, key, value, result, ignoreNonModelledData) => {
180
172
  const elementsOrParameters = definition.elements || definition.params
173
+ if (!elementsOrParameters) return result
181
174
  const elementOrParameter = elementsOrParameters[key]
182
175
 
183
176
  if (!elementOrParameter) {
@@ -309,259 +302,6 @@ const checkStatic = (definition, data, ignoreNonModelledData = false) => {
309
302
  }, [])
310
303
  }
311
304
 
312
- const _checkExistsWhere = (entity, whereList, run) => {
313
- const checks = whereList.map(where => {
314
- if (where.length === 0) {
315
- return true
316
- }
317
-
318
- const cqn = {
319
- SELECT: {
320
- columns: [{ val: 1, as: '_exists' }],
321
- from: { ref: [entity.name || entity] },
322
- where: where
323
- }
324
- }
325
-
326
- if (cds.context) {
327
- const hash = crypto.createHash('sha1').update(JSON.stringify(cqn)).digest('base64') // fastest hash
328
- if (!cds.context.__alreadyExecutedIntegrityChecks) cds.context.__alreadyExecutedIntegrityChecks = new Map()
329
- if (cds.context.__alreadyExecutedIntegrityChecks.has(hash)) {
330
- return cds.context.__alreadyExecutedIntegrityChecks.get(hash)
331
- } else {
332
- const promise = run(cqn).then(exists => {
333
- return exists.length !== 0
334
- })
335
- // we store the promise object in the map, it won't get executed twice when calling await Promise.all([promise, promise])
336
- cds.context.__alreadyExecutedIntegrityChecks.set(hash, promise)
337
- return promise
338
- }
339
- }
340
- return run(cqn).then(exists => {
341
- return exists.length !== 0
342
- })
343
- })
344
-
345
- return all(checks)
346
- }
347
-
348
- const _checkExists = (entity, data, req, run) => {
349
- if (!Array.isArray(data)) {
350
- return _checkExists(entity, [data], req, run).then(result => {
351
- return result[0]
352
- })
353
- }
354
-
355
- const where = data.map(row => {
356
- return Object.keys(entity.keys).reduce((where, name) => {
357
- if (row[name] !== undefined && row[name] !== null) {
358
- if (where.length > 0) {
359
- where.push('and')
360
- }
361
- where.push({ ref: [name] }, '=', { val: row[name] })
362
- }
363
-
364
- return where
365
- }, [])
366
- })
367
- return _checkExistsWhere(entity, where, run)
368
- }
369
-
370
- const _getFullForeignKeyName = (elementName, foreignKeyName) => `${elementName}_${foreignKeyName}`
371
-
372
- const _foreignKeyReducer = (key, foreignKeyName, row, element, ref) => {
373
- const fullForeignKeyName = _getFullForeignKeyName(element.name, foreignKeyName)
374
-
375
- if (ref.length > 1) {
376
- // ref includes assoc name, so we need to replace it by foreign key name
377
- const refWithFlatForeignKey = [...ref.slice(0, ref.length - 1), fullForeignKeyName]
378
- key[foreignKeyName] = _getDataFromRef(row, refWithFlatForeignKey)
379
- } else {
380
- key[foreignKeyName] = Object.prototype.hasOwnProperty.call(row, fullForeignKeyName) ? row[fullForeignKeyName] : null
381
- }
382
-
383
- return key
384
- }
385
-
386
- const _buildForeignKey = (element, row, ref) => {
387
- let foreignKey
388
-
389
- if (element.keys) {
390
- foreignKey = element.keys
391
- .map(obj => obj.ref[obj.ref.length - 1])
392
- .reduce((key, foreignKeyName) => {
393
- return _foreignKeyReducer(key, foreignKeyName, row, element, ref)
394
- }, {})
395
- }
396
-
397
- return foreignKey
398
- }
399
-
400
- const _getDataFromRef = (row, ref) => {
401
- if (row === undefined) return
402
-
403
- if (ref.length > 1) {
404
- return _getDataFromRef(row[ref[0]], ref.slice(1))
405
- }
406
-
407
- return row[ref[0]]
408
- }
409
-
410
- const _getElement = (entity, ref) => {
411
- if (ref.length > 1) {
412
- // structured
413
- return _getElement(entity.elements[ref[0]], ref.slice(1))
414
- }
415
-
416
- return entity.elements[ref[0]]
417
- }
418
-
419
- const _checkCreateUpdate = (result, ref, rootEntity, checks, data, req, run) => {
420
- const resolvedElement = _getElement(rootEntity, ref)
421
-
422
- return data.reduce((result, row) => {
423
- if (resolvedElement.on) return result
424
-
425
- const foreignKey = _buildForeignKey(resolvedElement, row, ref)
426
- if (foreignKey === undefined) return result
427
-
428
- checks.push(
429
- _checkExists(resolvedElement._target, foreignKey, req, run).then(exists => {
430
- if (!exists) {
431
- result.push(assertError(ASSERT_REFERENCE_INTEGRITY, resolvedElement, foreignKey))
432
- }
433
- })
434
- )
435
-
436
- return result
437
- }, result)
438
- }
439
-
440
- const _buildWhereDelete = (result, key, element, data) => {
441
- return data
442
- .map(d => {
443
- return Object.keys(d).reduce((result, name) => {
444
- if (key.ref[0] === name) {
445
- if (result.length > 0) {
446
- result.push('and')
447
- }
448
- result.push({ ref: [_getFullForeignKeyName(element.name, key.ref[0])] }, '=', { val: d[name] })
449
- }
450
-
451
- return result
452
- }, result)
453
- })
454
- .reduce((accumulatedWhere, currentWhere, i) => {
455
- if (i > 0) accumulatedWhere.push('or')
456
- accumulatedWhere.push(...currentWhere)
457
- return accumulatedWhere
458
- }, [])
459
- }
460
-
461
- const _checkDelete = (result, key, entity, checks, req, csn, run, data) => {
462
- const elements = csn.definitions[key].elements
463
- const source = csn.definitions[key].name
464
-
465
- const dependents = getDependents(req.target, csn) || []
466
- const sourceDependent = dependents.find(dep => dep.parent.name === source)
467
- if (!sourceDependent) return result
468
-
469
- return Object.keys(elements).reduce((result, assoc) => {
470
- if (!elements[assoc].target || !elements[assoc].keys) return result
471
-
472
- const targetDependent = dependents.find(dep => dep.target.name === elements[assoc].target)
473
- if (!targetDependent) return result
474
-
475
- const where = elements[assoc].keys.reduce((buildWhere, key) => {
476
- return _buildWhereDelete(buildWhere, key, elements[assoc], data)
477
- }, [])
478
- checks.push(
479
- _checkExistsWhere(source, [where], run).then(exists => {
480
- if (exists.includes(true)) {
481
- result.push(assertError(ASSERT_REFERENCE_INTEGRITY, elements[assoc], req.data))
482
- }
483
- })
484
- )
485
- return result
486
- }, result)
487
- }
488
-
489
- function _filterStructured(element, structuredAssocs, prefix) {
490
- const elements = element.elements
491
- for (const subElement in elements) {
492
- if (_filterAssocs(elements[subElement], structuredAssocs, prefix)) {
493
- structuredAssocs.push([...prefix, elements[subElement].name])
494
- }
495
- }
496
- }
497
-
498
- const _filterAssocs = (element, structuredAssocs, prefix = []) => {
499
- if (element.elements) {
500
- _filterStructured(element, structuredAssocs, [...prefix, element.name])
501
- }
502
-
503
- return (
504
- element._isAssociationStrict &&
505
- !element.virtual &&
506
- !element.abstract &&
507
- element[ASSERT_INTEGRITY_ANNOTATION] !== false &&
508
- !element['@odata.contained'] &&
509
- !element._target._hasPersistenceSkip
510
- )
511
- }
512
-
513
- // can be removed ones we switch to db integrity check
514
- const checkReferenceIntegrity = (entity, data, req, csn, run) => {
515
- const service = entity._service
516
- if (entity[ASSERT_INTEGRITY_ANNOTATION] === false || (service && service[ASSERT_INTEGRITY_ANNOTATION] === false)) {
517
- return
518
- }
519
-
520
- if (!Array.isArray(data)) data = [data]
521
-
522
- const checks = []
523
- let result
524
- if (req.event === 'CREATE' || req.event === 'UPDATE') {
525
- const structuredAssocRefs = []
526
- const associationRefs = Object.keys(entity.elements)
527
- .filter(elementName => _filterAssocs(entity.elements[elementName], structuredAssocRefs))
528
- .map(name => [name])
529
- result = [...associationRefs, ...structuredAssocRefs].reduce((createUpdateResult, ref) => {
530
- return _checkCreateUpdate(createUpdateResult, ref, entity, checks, data, req, run)
531
- }, [])
532
- }
533
- if (req.event === 'DELETE') {
534
- // we are only interested in table-level references not all derived ones on view levels
535
- // TODO: why?
536
- while (entity.query && entity.query._target) {
537
- entity = csn.definitions[entity.query._target.name]
538
- }
539
-
540
- result = Object.keys(csn.definitions)
541
- .filter(
542
- key =>
543
- !csn.definitions[key]['@cds.persistence.skip'] &&
544
- csn.definitions[key].elements !== undefined &&
545
- // skip check for events, aspects and localized tables
546
- csn.definitions[key].kind !== 'event' &&
547
- csn.definitions[key].kind !== 'aspect' &&
548
- csn.definitions[key].kind !== 'type' &&
549
- !csn.definitions[key].name.startsWith('localized.')
550
- )
551
- .reduce((deleteResult, key) => {
552
- return _checkDelete(deleteResult, key, entity, checks, req, csn, run, data)
553
- }, [])
554
- }
555
-
556
- if (checks.length) {
557
- return Promise.all(checks).then(() => {
558
- return result
559
- })
560
- }
561
-
562
- return resolve(result || [])
563
- }
564
-
565
305
  const checkKeys = (entity, data) => {
566
306
  if (!Array.isArray(data)) {
567
307
  return checkKeys(entity, [data])
@@ -583,7 +323,6 @@ module.exports = {
583
323
  checkStatic,
584
324
  checkInputConstraints,
585
325
  checkKeys,
586
- checkReferenceIntegrity,
587
326
  assertError,
588
327
  checkIfAssocDeep
589
328
  }
@@ -22,15 +22,12 @@ Object.defineProperty(cds, '_mtxEnabled', {
22
22
  * (lazy) feature flags
23
23
  */
24
24
  // referential integrity
25
- Object.defineProperty(cds.env.features, '_foreign_key_constraints', {
26
- get: () => cds.env.cdsc.beta && cds.env.cdsc.beta.foreignKeyConstraints,
27
- configurable: true
28
- })
29
- let assertIntegrity = cds.env.features.assert_integrity
30
- Object.defineProperty(cds.env.features, 'assert_integrity', {
31
- get: () => (assertIntegrity != null ? assertIntegrity : !cds.env.features._foreign_key_constraints),
32
- set: val => {
33
- assertIntegrity = val
25
+ // REVISIT: why is _db_foreign_key_constraints necessary?
26
+ Object.defineProperty(cds.env.features, '_db_foreign_key_constraints', {
27
+ get: () => {
28
+ const { assert_integrity: ai, assert_integrity_type: ait } = cds.env.features
29
+ if ((typeof ai === 'string' && ai.match(/individual/i)) || (ait && ait.match(/db/i))) return true
30
+ return false
34
31
  },
35
32
  configurable: true
36
33
  })
@@ -35,7 +35,7 @@ module.exports = class {
35
35
  '__isDraftEnabled',
36
36
  (this.associations && this.associations.DraftAdministrativeData) ||
37
37
  this.name.match(/\.DraftAdministrativeData$/) ||
38
- this.own('@odata.draft.enabled') // > case: entity not in service (tests only?)
38
+ (this.own('@odata.draft.enabled') && this.own('@Common.DraftRoot.ActivationAction'))
39
39
  )
40
40
  )
41
41
  }
@@ -140,7 +140,8 @@ const _addToCQNs = (cqns, subCQN, element, definitions, level) => {
140
140
  // Since `>2.5.2` compiler generates constraints for compositions of one like for annotations
141
141
  // Thus only single 2one case (`$self`-managed composition) has DELETE CASCADE
142
142
  // Here it's ignored to simplify i.e. handle all "2ones" in a same manner
143
- if (!cds.env.features._foreign_key_constraints || _is2oneComposition(element, definitions)) {
143
+ // REVISIT: why is _db_foreign_key_constraints necessary?
144
+ if (!cds.env.features._db_foreign_key_constraints || _is2oneComposition(element, definitions)) {
144
145
  cqns[level].push(subCQN)
145
146
  }
146
147
  }
@@ -251,7 +252,8 @@ const getDeepDeleteCQNs = (definitions, cqn) => {
251
252
  _recursivelyAliasRefs(parentWhere, 'ALIAS0', parentAlias)
252
253
  }
253
254
  const setNullUpdates = []
254
- if (cds.env.features._foreign_key_constraints && definitions[entityName].own('__oneCompositionParents')) {
255
+ // REVISIT: why is _db_foreign_key_constraints necessary?
256
+ if (cds.env.features._db_foreign_key_constraints && definitions[entityName].own('__oneCompositionParents')) {
255
257
  setNullUpdates.push(..._getSetNullParentForeignKeyCQNs(definitions, entityName, parentWhere, draft))
256
258
  }
257
259
  const subCascadeDeletes = _addSubCascadeDeleteCQN(definitions, compositionTree, parentWhere, 0, [], draft)
@@ -76,10 +76,13 @@ const _diffData = (newData, oldData, entity, newEntry, oldEntry, definitions) =>
76
76
  const oldVal = ctUtils.val(oldData[key])
77
77
 
78
78
  if (newVal !== undefined && newVal !== oldVal) {
79
+ if (!entity.elements[key]) continue
80
+
79
81
  if (entity.elements[key]._isStructured && Object.keys(newData[key]).length === 0) {
80
82
  // empty structured -> skip
81
83
  continue
82
84
  }
85
+
83
86
  result[key] = newData[key]
84
87
  continue
85
88
  }
@@ -135,36 +138,12 @@ function _addSubDeepUpdateCQNForUpdateInsert({
135
138
  return deepUpdateData
136
139
  }
137
140
 
138
- function _addSubDeepUpdateCQNCollectDelete(deleteCQNs, cqns, index) {
139
- deleteCQNs.forEach(deleteCQN => {
140
- if (
141
- !cqns.find((subCQNs, subIndex) => {
142
- if (subIndex > 0) {
143
- const deleteIndex = subCQNs.findIndex(cqn => {
144
- return cqn.DELETE && cqn.DELETE.from === deleteCQN.DELETE.from
145
- })
146
- if (deleteIndex > -1) {
147
- if (subIndex < index) {
148
- subCQNs.splice(deleteIndex, 1)
149
- } else {
150
- return true
151
- }
152
- }
153
- }
154
- return false
155
- })
156
- ) {
157
- cqns[index] = cqns[index] || []
158
- cqns[index].push(deleteCQN)
159
- }
160
- })
161
- }
162
-
163
141
  function _addSubDeepUpdateCQNCollect(definitions, cqns, updateCQNs, insertCQN, deleteCQN) {
164
142
  if (updateCQNs.length > 0) {
165
143
  cqns[0] = cqns[0] || []
166
144
  cqns[0].push(...updateCQNs)
167
145
  }
146
+
168
147
  if (insertCQN.INSERT.entries.length > 0) {
169
148
  cqns[0] = cqns[0] || []
170
149
  const deepInsertCQNs = getDeepInsertCQNs(definitions, insertCQN)
@@ -184,7 +163,10 @@ function _addSubDeepUpdateCQNCollect(definitions, cqns, updateCQNs, insertCQN, d
184
163
  cqns[0] = cqns[0] || []
185
164
  const deepDeleteCQNs = getDeepDeleteCQNs(definitions, deleteCQN)
186
165
  deepDeleteCQNs.forEach((deleteCQNs, index) => {
187
- _addSubDeepUpdateCQNCollectDelete(deleteCQNs, cqns, index)
166
+ deleteCQNs.forEach(el => {
167
+ cqns[index] = cqns[index] || []
168
+ cqns[index].push(el)
169
+ })
188
170
  })
189
171
  }
190
172
  }
@@ -200,15 +182,17 @@ const _addToData = (subData, entity, element, entry) => {
200
182
 
201
183
  function _addSubDeepUpdateCQNRecursion({ definitions, compositionTree, entity, data, selectData, cqns, draft }) {
202
184
  const selectDataByKey = _dataByKey(entity, selectData)
185
+
203
186
  for (const element of compositionTree.compositionElements) {
204
187
  const subData = []
205
188
  const selectSubData = []
189
+
206
190
  for (const entry of data) {
207
191
  if (element.name in entry) {
208
192
  const selectEntry = selectDataByKey.get(_serializedKey(entity, entry))
209
193
 
210
194
  if (selectEntry && element.name in selectEntry) {
211
- if (selectEntry[element.name] === null && Object.keys(entry[element.name]).length === 0) {
195
+ if (selectEntry[element.name] === null && entry[element.name] === null) {
212
196
  continue
213
197
  }
214
198
  _addToData(selectSubData, entity, element, selectEntry)
@@ -217,6 +201,7 @@ function _addSubDeepUpdateCQNRecursion({ definitions, compositionTree, entity, d
217
201
  _addToData(subData, entity, element, entry)
218
202
  }
219
203
  }
204
+
220
205
  _addSubDeepUpdateCQN({
221
206
  definitions,
222
207
  compositionTree: element,
@@ -226,6 +211,7 @@ function _addSubDeepUpdateCQNRecursion({ definitions, compositionTree, entity, d
226
211
  draft
227
212
  })
228
213
  }
214
+
229
215
  return cqns
230
216
  }
231
217
 
@@ -250,11 +236,11 @@ const _addSubDeepUpdateCQN = ({ definitions, compositionTree, data, selectData,
250
236
  insertCQN,
251
237
  definitions
252
238
  })
239
+
253
240
  _addSubDeepUpdateCQNCollect(definitions, cqns, updateCQNs, insertCQN, deleteCQN)
254
241
 
255
- if (deepUpdateData.length === 0) {
256
- return Promise.resolve()
257
- }
242
+ if (deepUpdateData.length === 0) return Promise.resolve()
243
+
258
244
  return _addSubDeepUpdateCQNRecursion({
259
245
  definitions,
260
246
  compositionTree,
@@ -272,15 +258,16 @@ const _addSubDeepUpdateCQN = ({ definitions, compositionTree, data, selectData,
272
258
 
273
259
  const hasDeepUpdate = (definitions, cqn) => {
274
260
  if (cqn && cqn.UPDATE && cqn.UPDATE.entity && (cqn.UPDATE.data || cqn.UPDATE.with)) {
275
- const entityName =
276
- (cqn.UPDATE.entity.ref && cqn.UPDATE.entity.ref[0]) || cqn.UPDATE.entity.name || cqn.UPDATE.entity
261
+ const updateEntity = cqn.UPDATE.entity
262
+ const entityName = (updateEntity.ref && updateEntity.ref[0]) || updateEntity.name || updateEntity
277
263
  const entity = definitions && definitions[ensureNoDraftsSuffix(entityName)]
264
+
278
265
  if (entity) {
279
- return !!Object.keys(Object.assign({}, cqn.UPDATE.data || {}, cqn.UPDATE.with || {})).find(k => {
280
- return ctUtils.isCompOrAssoc(entity, k)
281
- })
266
+ const keys = Object.keys(Object.assign({}, cqn.UPDATE.data || {}, cqn.UPDATE.with || {}))
267
+ return !!keys.find(k => ctUtils.isCompOrAssoc(entity, k))
282
268
  }
283
269
  }
270
+
284
271
  return false
285
272
  }
286
273
 
@@ -32,13 +32,9 @@ const val = element => (element && element.val) || element
32
32
 
33
33
  const array = x => (Array.isArray(x) ? x : [x])
34
34
 
35
- const isCompOrAssoc = (entity, k, onlyToOne) => {
36
- return (
37
- entity.elements &&
38
- entity.elements[k] &&
39
- entity.elements[k].isAssociation &&
40
- ((onlyToOne && entity.elements[k].is2one) || !onlyToOne)
41
- )
35
+ const isCompOrAssoc = (entity, elementName, onlyToOne) => {
36
+ const element = entity.elements && entity.elements[elementName]
37
+ return !!(element && element.isAssociation && ((onlyToOne && element.is2one) || !onlyToOne))
42
38
  }
43
39
 
44
40
  const cleanDeepData = (entity, data, onlyToOne = false) => {
@@ -0,0 +1,11 @@
1
+ const isStandardError = err => {
2
+ return (
3
+ err instanceof TypeError ||
4
+ err instanceof ReferenceError ||
5
+ err instanceof SyntaxError ||
6
+ err instanceof RangeError ||
7
+ err instanceof URIError
8
+ )
9
+ }
10
+
11
+ module.exports = { isStandardError }
@@ -8,7 +8,8 @@ const { SELECT } = cds.ql
8
8
  const { getRequiresAsArray } = require('../utils/auth')
9
9
  const { cqn2cqn4sql } = require('../utils/cqn2cqn4sql')
10
10
  const { isActiveEntityRequested, removeIsActiveEntityRecursively } = require('../../fiori/utils/where')
11
- const { ensureDraftsSuffix } = require('../../fiori/utils/handler')
11
+ const { ensureNoDraftsSuffix } = require('../../fiori/utils/handler')
12
+ const { rewriteExpandAsterisk } = require('../../common/utils/rewriteAsterisks')
12
13
 
13
14
  const WRITE = ['CREATE', 'UPDATE', 'DELETE']
14
15
  const MOD = { UPDATE: 1, DELETE: 1, EDIT: 1 }
@@ -41,6 +42,48 @@ const _reject = req => {
41
42
  }
42
43
  }
43
44
 
45
+ const _getTarget = (ref, target, definitions) => {
46
+ if (cds.env.effective.odata.proxies) {
47
+ const target_ = target.elements[ref[0]]
48
+
49
+ if (ref.length === 1) {
50
+ return definitions[ensureNoDraftsSuffix(target_.target)]
51
+ }
52
+
53
+ return _getTarget(ref.slice(1), target_, definitions)
54
+ }
55
+
56
+ const target_ = target.elements[ref.join('_')]
57
+ return definitions[ensureNoDraftsSuffix(target_.target)]
58
+ }
59
+
60
+ const _getRestrictedExpand = (columns, target, definitions) => {
61
+ if (!columns || !target || columns === '*') return
62
+
63
+ const annotation = target['@Capabilities.ExpandRestrictions.NonExpandableProperties']
64
+ const restrictions = annotation && annotation.map(element => element['='])
65
+
66
+ rewriteExpandAsterisk(columns, target)
67
+
68
+ for (const col of columns) {
69
+ if (col.expand) {
70
+ if (restrictions && restrictions.length !== 0) {
71
+ const ref = col.ref.join('_')
72
+ const ref_ = restrictions.find(element => element.replace(/\./g, '_') === ref)
73
+ if (ref_) return ref_
74
+ }
75
+
76
+ // expand: '**' or '*3' is only possible within custom handler, no check needed
77
+ if (typeof col.expand === 'string' && /^\*{1}[\d|*]+/.test(col.expand)) {
78
+ continue
79
+ } else {
80
+ const restricted = _getRestrictedExpand(col.expand, _getTarget(col.ref, target, definitions), definitions)
81
+ if (restricted) return restricted
82
+ }
83
+ }
84
+ }
85
+ }
86
+
44
87
  const _getCurrentSubClause = (next, restrict) => {
45
88
  const escaped = next[0].replace(/\$/g, '\\$').replace(/\./g, '\\.')
46
89
  const re1 = new RegExp(`([\\w\\.']*)\\s*=\\s*(${escaped})|(${escaped})\\s*=\\s*([\\w\\.']*)`)
@@ -367,7 +410,7 @@ const _getRestrictionForTarget = (resolvedApplicables, target) => {
367
410
 
368
411
  const _addRestrictionsToRead = async (req, model, resolvedApplicables) => {
369
412
  if (req.target._isDraftEnabled) {
370
- req.query._draftRestrictions = resolvedApplicables.map(ra => ra._xpr)
413
+ req.query._draftRestrictions = resolvedApplicables
371
414
  return
372
415
  }
373
416
 
@@ -402,12 +445,11 @@ const _getUnrestrictedCount = async req => {
402
445
  const _getRestrictedCount = async (req, model, resolvedApplicables) => {
403
446
  const dbtx = cds.tx(req)
404
447
 
405
- let target =
448
+ const target =
406
449
  (req.query.UPDATE && req.query.UPDATE.entity) ||
407
450
  (req.query.DELETE && req.query.DELETE.from) ||
408
451
  (req.query.SELECT && req.query.SELECT.from)
409
- // REVISIT: req._ gets set in onDraftActivate to original req
410
- if (req._ && req._.event === 'draftActivate') target = ensureDraftsSuffix(target)
452
+
411
453
  const selectRestricted = SELECT.one(['count(*) as n']).from(target)
412
454
 
413
455
  const whereRestricted = (req.query.UPDATE && req.query.UPDATE.where) || (req.query.DELETE && req.query.DELETE.where)
@@ -541,36 +583,9 @@ const _addNormalizedRestrictPerGrant = (grant, where, restrict, restricts, defin
541
583
  }
542
584
 
543
585
  const _addNormalizedRestrict = (restrict, restricts, definition, definitions) => {
544
- let where = restrict.where
586
+ const where = restrict.where
545
587
  ? restrict.where.replace(/\$user/g, '$user.id').replace(/\$user\.id\./g, '$user.')
546
588
  : undefined
547
-
548
- // NOTE: "exists toMany.toOne[prop = $user]" -> "exists toMany[exists toOne[prop = $user]]"
549
- try {
550
- if (where) {
551
- // operate on a copy
552
- let _where = where
553
- // find all path expressions in order to normalize shorthand (i.e., inject "[exists ...]")
554
- const paths = (where.match(/ (\w\.*)*/g) || []).filter(m => m.match(/\./) && m !== ' ')
555
- for (let i = 0; i < paths.length; i++) {
556
- const parts = paths[i].trim().split('.')
557
- let current = definition
558
- while (parts.length) {
559
- current = current.elements[parts.shift()]
560
- if (current.isAssociation && _where.includes(current.name + '.')) {
561
- const matches = _where.match(new RegExp(`(${current.name}).(.*)]`))
562
- _where = _where.replace(`${matches[1]}.`, `${current.name}[exists `)
563
- _where = _where.replace(matches[2], `${matches[2]}]`)
564
- }
565
- if (current.target) current = definitions[current.target]
566
- }
567
- }
568
- where = _where
569
- }
570
- } catch (e) {
571
- // ignore
572
- }
573
-
574
589
  restrict.grant = Array.isArray(restrict.grant) ? restrict.grant : [restrict.grant || '*']
575
590
  restrict.grant.forEach(grant => _addNormalizedRestrictPerGrant(grant, where, restrict, restricts, definition))
576
591
  }
@@ -888,9 +903,24 @@ const _secureDependentEntities = srv => {
888
903
  }
889
904
  }
890
905
 
906
+ const _restrictExpand = service => {
907
+ service.on('READ', '*', (req, next) => {
908
+ const restricted = _getRestrictedExpand(
909
+ req.query.SELECT && req.query.SELECT.columns,
910
+ req.target,
911
+ service.model.definitions
912
+ )
913
+ if (restricted) {
914
+ return req.reject(400, 'EXPAND_IS_RESTRICTED', [restricted])
915
+ }
916
+ return next()
917
+ })
918
+ }
919
+
891
920
  module.exports = cds.service.impl(function () {
892
921
  // @restrict, @requires, @readonly, @insertonly, and @Capabilities for entities
893
922
  _secureDependentEntities(this)
923
+ _restrictExpand(this)
894
924
  for (const k in this.entities) {
895
925
  const entity = this.entities[k]
896
926
  if (!_authDependsOnParents(entity)) _registerAuthHandlers(entity, this)