@sap/cds 5.6.4 → 5.7.1

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 (176) hide show
  1. package/CHANGELOG.md +102 -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 +3 -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 +8 -3
  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 +3 -1
  38. package/lib/log/index.js +2 -2
  39. package/lib/req/cds-context.js +79 -0
  40. package/lib/req/context.js +5 -77
  41. package/lib/req/request.js +1 -1
  42. package/lib/serve/Service-api.js +8 -4
  43. package/lib/serve/Service-dispatch.js +0 -7
  44. package/lib/serve/Service-methods.js +6 -8
  45. package/lib/serve/Transaction.js +35 -30
  46. package/lib/serve/adapters.js +1 -4
  47. package/lib/utils/axios.js +1 -1
  48. package/libx/_runtime/audit/Service.js +44 -20
  49. package/libx/_runtime/audit/generic/personal/access.js +16 -11
  50. package/libx/_runtime/audit/generic/personal/modification.js +5 -5
  51. package/libx/_runtime/audit/generic/personal/utils.js +46 -37
  52. package/libx/_runtime/{common/auth → auth}/index.js +21 -7
  53. package/libx/_runtime/{common/auth → auth}/strategies/JWT.js +2 -2
  54. package/libx/_runtime/{common/auth → auth}/strategies/basic.js +2 -2
  55. package/libx/_runtime/{common/auth → auth}/strategies/dummy.js +1 -1
  56. package/libx/_runtime/{common/auth → auth}/strategies/mock.js +2 -2
  57. package/libx/_runtime/{common/auth → auth}/strategies/utils/uaa.js +1 -1
  58. package/libx/_runtime/{common/auth → auth}/strategies/utils/xssec.js +0 -0
  59. package/libx/_runtime/{common/auth → auth}/strategies/xsuaa.js +2 -2
  60. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +7 -2
  61. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +0 -7
  62. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +0 -8
  63. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +3 -4
  64. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +6 -7
  65. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +3 -4
  66. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +2 -11
  67. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +16 -6
  68. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +26 -65
  69. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +0 -7
  70. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +3 -66
  71. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +24 -0
  72. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +2 -2
  73. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +13 -10
  74. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -7
  75. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +0 -8
  76. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +54 -76
  77. package/libx/_runtime/cds-services/adapter/rest/RestRequest.js +0 -7
  78. package/libx/_runtime/cds-services/adapter/rest/handlers/create.js +3 -6
  79. package/libx/_runtime/cds-services/adapter/rest/handlers/delete.js +3 -6
  80. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +3 -6
  81. package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +3 -6
  82. package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +3 -6
  83. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +8 -4
  84. package/libx/_runtime/cds-services/services/Service.js +0 -6
  85. package/libx/_runtime/cds-services/services/utils/columns.js +10 -3
  86. package/libx/_runtime/cds-services/services/utils/compareJson.js +3 -6
  87. package/libx/_runtime/cds-services/services/utils/differ.js +4 -1
  88. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +1 -41
  89. package/libx/_runtime/cds-services/util/assert.js +1 -262
  90. package/libx/_runtime/cds.js +6 -9
  91. package/libx/_runtime/common/aspects/entity.js +1 -1
  92. package/libx/_runtime/common/composition/delete.js +4 -2
  93. package/libx/_runtime/common/composition/update.js +22 -38
  94. package/libx/_runtime/common/composition/utils.js +3 -7
  95. package/libx/_runtime/common/error/standardError.js +11 -0
  96. package/libx/_runtime/common/generic/auth.js +61 -30
  97. package/libx/_runtime/common/generic/crud.js +11 -23
  98. package/libx/_runtime/common/generic/input.js +20 -0
  99. package/libx/_runtime/common/generic/put.js +4 -10
  100. package/libx/_runtime/common/generic/sorting.js +12 -30
  101. package/libx/_runtime/common/perf/index.js +24 -0
  102. package/libx/_runtime/common/utils/cqn.js +58 -1
  103. package/libx/_runtime/common/utils/cqn2cqn4sql.js +289 -114
  104. package/libx/_runtime/common/utils/csn.js +38 -56
  105. package/libx/_runtime/common/utils/entityFromCqn.js +6 -6
  106. package/libx/_runtime/common/utils/resolveView.js +4 -5
  107. package/libx/_runtime/common/utils/rewriteAsterisks.js +46 -5
  108. package/libx/_runtime/common/utils/search2cqn4sql.js +21 -9
  109. package/libx/_runtime/common/utils/structured.js +35 -25
  110. package/libx/_runtime/db/Service.js +0 -6
  111. package/libx/_runtime/db/expand/expand-v2.js +130 -0
  112. package/libx/_runtime/db/expand/expandCQNToJoin.js +38 -52
  113. package/libx/_runtime/db/expand/index.js +3 -1
  114. package/libx/_runtime/db/generic/input.js +52 -10
  115. package/libx/_runtime/db/generic/integrity.js +367 -26
  116. package/libx/_runtime/db/generic/virtual.js +51 -13
  117. package/libx/_runtime/db/query/update.js +9 -3
  118. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +8 -9
  119. package/libx/_runtime/{common → db}/utils/propagateForeignKeys.js +11 -14
  120. package/libx/_runtime/fiori/generic/activate.js +1 -0
  121. package/libx/_runtime/fiori/generic/before.js +2 -1
  122. package/libx/_runtime/fiori/generic/edit.js +1 -0
  123. package/libx/_runtime/fiori/generic/patch.js +1 -1
  124. package/libx/_runtime/fiori/generic/read.js +123 -57
  125. package/libx/_runtime/fiori/uiflex/index.js +1 -1
  126. package/libx/_runtime/fiori/uiflex/{extensibility/index.js → service.js} +3 -3
  127. package/libx/_runtime/fiori/utils/delete.js +7 -1
  128. package/libx/_runtime/hana/Service.js +1 -8
  129. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -14
  130. package/libx/_runtime/hana/execute.js +10 -4
  131. package/libx/_runtime/hana/pool.js +55 -45
  132. package/libx/_runtime/hana/search.js +7 -6
  133. package/libx/_runtime/hana/search2cqn4sql.js +8 -5
  134. package/libx/_runtime/hana/searchToContains.js +3 -1
  135. package/libx/_runtime/index.js +5 -5
  136. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +3 -3
  137. package/libx/_runtime/messaging/Outbox.js +53 -0
  138. package/libx/_runtime/messaging/common-utils/AMQPClient.js +17 -10
  139. package/libx/_runtime/messaging/common-utils/connections.js +14 -9
  140. package/libx/_runtime/messaging/common-utils/waitingTime.js +2 -0
  141. package/libx/_runtime/messaging/enterprise-messaging-shared.js +2 -3
  142. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -2
  143. package/libx/_runtime/messaging/enterprise-messaging.js +21 -15
  144. package/libx/_runtime/messaging/file-based.js +5 -5
  145. package/libx/_runtime/messaging/message-queuing.js +2 -3
  146. package/libx/_runtime/messaging/outbox/OutboxRunner.js +75 -0
  147. package/libx/_runtime/messaging/outbox/utils.js +192 -0
  148. package/libx/_runtime/messaging/service.js +16 -30
  149. package/libx/_runtime/remote/Service.js +15 -0
  150. package/libx/_runtime/remote/utils/client.js +15 -3
  151. package/libx/_runtime/remote/utils/{dataConversion.js → data.js} +12 -2
  152. package/libx/_runtime/sqlite/Service.js +7 -10
  153. package/libx/_runtime/sqlite/customBuilder/CustomExpressionBuilder.js +19 -0
  154. package/libx/_runtime/sqlite/execute.js +18 -12
  155. package/libx/_runtime/types/api.js +2 -1
  156. package/libx/odata/{odata2cqn/afterburner.js → afterburner.js} +19 -15
  157. package/libx/odata/{cqn2odata/index.js → cqn2odata.js} +1 -1
  158. package/libx/odata/{odata2cqn/grammar.pegjs → grammar.pegjs} +171 -130
  159. package/libx/odata/index.js +16 -14
  160. package/libx/odata/parser.js +1 -0
  161. package/libx/odata/utils.js +57 -0
  162. package/libx/rest/RestAdapter.js +2 -6
  163. package/libx/rest/utils/data.js +1 -6
  164. package/package.json +4 -3
  165. package/server.js +4 -5
  166. package/srv/audit-log.cds +87 -0
  167. package/{libx/_runtime/fiori/uiflex/extensibility/index.cds → srv/flex.cds} +0 -0
  168. package/srv/flex.js +1 -0
  169. package/srv/outbox.cds +11 -0
  170. package/srv/outbox.js +0 -0
  171. package/libx/_runtime/cds-services/adapter/perf/performance.js +0 -104
  172. package/libx/_runtime/cds-services/adapter/perf/performanceMeasurement.js +0 -33
  173. package/libx/odata/odata2cqn/index.js +0 -3
  174. package/libx/odata/odata2cqn/parser.js +0 -1
  175. package/libx/odata/readme.md +0 -1
  176. 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,18 +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 (
212
- selectEntry[element.name] === null &&
213
- (entry[element.name] === null || Object.keys(entry[element.name]).length === 0)
214
- ) {
195
+ if (selectEntry[element.name] === null && entry[element.name] === null) {
215
196
  continue
216
197
  }
217
198
  _addToData(selectSubData, entity, element, selectEntry)
@@ -220,6 +201,7 @@ function _addSubDeepUpdateCQNRecursion({ definitions, compositionTree, entity, d
220
201
  _addToData(subData, entity, element, entry)
221
202
  }
222
203
  }
204
+
223
205
  _addSubDeepUpdateCQN({
224
206
  definitions,
225
207
  compositionTree: element,
@@ -229,6 +211,7 @@ function _addSubDeepUpdateCQNRecursion({ definitions, compositionTree, entity, d
229
211
  draft
230
212
  })
231
213
  }
214
+
232
215
  return cqns
233
216
  }
234
217
 
@@ -253,11 +236,11 @@ const _addSubDeepUpdateCQN = ({ definitions, compositionTree, data, selectData,
253
236
  insertCQN,
254
237
  definitions
255
238
  })
239
+
256
240
  _addSubDeepUpdateCQNCollect(definitions, cqns, updateCQNs, insertCQN, deleteCQN)
257
241
 
258
- if (deepUpdateData.length === 0) {
259
- return Promise.resolve()
260
- }
242
+ if (deepUpdateData.length === 0) return Promise.resolve()
243
+
261
244
  return _addSubDeepUpdateCQNRecursion({
262
245
  definitions,
263
246
  compositionTree,
@@ -275,15 +258,16 @@ const _addSubDeepUpdateCQN = ({ definitions, compositionTree, data, selectData,
275
258
 
276
259
  const hasDeepUpdate = (definitions, cqn) => {
277
260
  if (cqn && cqn.UPDATE && cqn.UPDATE.entity && (cqn.UPDATE.data || cqn.UPDATE.with)) {
278
- const entityName =
279
- (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
280
263
  const entity = definitions && definitions[ensureNoDraftsSuffix(entityName)]
264
+
281
265
  if (entity) {
282
- return !!Object.keys(Object.assign({}, cqn.UPDATE.data || {}, cqn.UPDATE.with || {})).find(k => {
283
- return ctUtils.isCompOrAssoc(entity, k)
284
- })
266
+ const keys = Object.keys(Object.assign({}, cqn.UPDATE.data || {}, cqn.UPDATE.with || {}))
267
+ return !!keys.find(k => ctUtils.isCompOrAssoc(entity, k))
285
268
  }
286
269
  }
270
+
287
271
  return false
288
272
  }
289
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 { ensureDraftsSuffix, 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
 
@@ -541,36 +584,9 @@ const _addNormalizedRestrictPerGrant = (grant, where, restrict, restricts, defin
541
584
  }
542
585
 
543
586
  const _addNormalizedRestrict = (restrict, restricts, definition, definitions) => {
544
- let where = restrict.where
587
+ const where = restrict.where
545
588
  ? restrict.where.replace(/\$user/g, '$user.id').replace(/\$user\.id\./g, '$user.')
546
589
  : 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
590
  restrict.grant = Array.isArray(restrict.grant) ? restrict.grant : [restrict.grant || '*']
575
591
  restrict.grant.forEach(grant => _addNormalizedRestrictPerGrant(grant, where, restrict, restricts, definition))
576
592
  }
@@ -888,9 +904,24 @@ const _secureDependentEntities = srv => {
888
904
  }
889
905
  }
890
906
 
907
+ const _restrictExpand = service => {
908
+ service.on('READ', '*', (req, next) => {
909
+ const restricted = _getRestrictedExpand(
910
+ req.query.SELECT && req.query.SELECT.columns,
911
+ req.target,
912
+ service.model.definitions
913
+ )
914
+ if (restricted) {
915
+ return req.reject(400, 'EXPAND_IS_RESTRICTED', [restricted])
916
+ }
917
+ return next()
918
+ })
919
+ }
920
+
891
921
  module.exports = cds.service.impl(function () {
892
922
  // @restrict, @requires, @readonly, @insertonly, and @Capabilities for entities
893
923
  _secureDependentEntities(this)
924
+ _restrictExpand(this)
894
925
  for (const k in this.entities) {
895
926
  const entity = this.entities[k]
896
927
  if (!_authDependsOnParents(entity)) _registerAuthHandlers(entity, this)