@sap/cds 5.5.5 → 5.6.3

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 (207) hide show
  1. package/CHANGELOG.md +139 -1
  2. package/apis/services.d.ts +31 -1
  3. package/app/index.js +22 -11
  4. package/bin/build/buildTaskFactory.js +1 -1
  5. package/bin/build/provider/buildTaskProviderInternal.js +1 -1
  6. package/bin/build/provider/fiori/index.js +1 -1
  7. package/bin/build/provider/hana/2migration.js +8 -7
  8. package/bin/build/provider/java-cf/index.js +1 -1
  9. package/bin/deploy/to-hana/hana.js +1 -17
  10. package/common.cds +8 -0
  11. package/lib/compile/to/sql.js +22 -2
  12. package/lib/connect/bindings.js +2 -1
  13. package/lib/core/reflect.js +4 -1
  14. package/lib/env/index.js +180 -42
  15. package/lib/env/requires.js +16 -1
  16. package/lib/i18n/localize.js +33 -5
  17. package/lib/index.js +3 -3
  18. package/lib/log/format/kibana.js +6 -2
  19. package/lib/ql/Query.js +1 -0
  20. package/lib/ql/SELECT.js +15 -8
  21. package/lib/ql/Whereable.js +5 -0
  22. package/lib/req/context.js +13 -5
  23. package/lib/serve/Service-dispatch.js +8 -1
  24. package/lib/utils/axios.js +7 -0
  25. package/lib/utils/data.js +1 -1
  26. package/lib/utils/tests.js +1 -1
  27. package/libx/_runtime/audit/Service.js +18 -18
  28. package/libx/_runtime/audit/generic/personal/access.js +1 -1
  29. package/libx/_runtime/audit/generic/personal/modification.js +3 -2
  30. package/libx/_runtime/audit/generic/personal/utils.js +23 -63
  31. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +4 -0
  32. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +37 -35
  33. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +3 -1
  34. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +5 -5
  35. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -7
  36. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +84 -34
  37. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +12 -4
  38. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +9 -3
  39. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +8 -6
  40. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -3
  41. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +13 -11
  42. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +11 -95
  43. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +17 -11
  44. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
  45. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/SetResponseHeadersCommand.js +1 -0
  46. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +6 -2
  47. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +3 -34
  48. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -3
  49. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +64 -31
  50. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +10 -5
  51. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +1 -1
  52. package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +1 -1
  53. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +1 -3
  54. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +20 -21
  55. package/libx/_runtime/cds-services/services/utils/columns.js +6 -1
  56. package/libx/_runtime/cds-services/services/utils/compareJson.js +1 -8
  57. package/libx/_runtime/cds-services/services/utils/differ.js +7 -26
  58. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -4
  59. package/libx/_runtime/cds-services/util/assert.js +29 -13
  60. package/libx/_runtime/cds.js +2 -1
  61. package/libx/_runtime/common/aspects/Association.js +72 -0
  62. package/libx/_runtime/common/aspects/any.js +8 -45
  63. package/libx/_runtime/common/aspects/entity.js +0 -1
  64. package/libx/_runtime/common/aspects/relation.js +40 -0
  65. package/libx/_runtime/common/aspects/utils.js +73 -1
  66. package/libx/_runtime/common/auth/strategies/utils/uaa.js +10 -14
  67. package/libx/_runtime/common/composition/data.js +3 -2
  68. package/libx/_runtime/common/composition/delete.js +3 -1
  69. package/libx/_runtime/common/composition/tree.js +23 -18
  70. package/libx/_runtime/common/composition/update.js +9 -1
  71. package/libx/_runtime/common/composition/utils.js +34 -8
  72. package/libx/_runtime/common/error/frontend.js +6 -1
  73. package/libx/_runtime/common/generic/auth.js +5 -9
  74. package/libx/_runtime/common/generic/crud.js +2 -2
  75. package/libx/_runtime/common/generic/etag.js +11 -8
  76. package/libx/_runtime/common/generic/input.js +3 -3
  77. package/libx/_runtime/common/generic/paging.js +9 -5
  78. package/libx/_runtime/common/generic/put.js +3 -2
  79. package/libx/_runtime/common/generic/sorting.js +3 -3
  80. package/libx/_runtime/common/generic/temporal.js +3 -3
  81. package/libx/_runtime/common/utils/cqn.js +20 -1
  82. package/libx/_runtime/common/utils/cqn2cqn4sql.js +125 -139
  83. package/libx/_runtime/common/utils/csn.js +50 -52
  84. package/libx/_runtime/common/utils/foreignKeyPropagations.js +41 -176
  85. package/libx/_runtime/common/utils/generateOnCond.js +40 -70
  86. package/libx/_runtime/common/utils/{enrichWithKeysFromWhere.js → keys.js} +29 -28
  87. package/libx/_runtime/common/utils/postProcessing.js +3 -0
  88. package/libx/_runtime/common/utils/propagateForeignKeys.js +84 -0
  89. package/libx/_runtime/common/utils/resolveStructured.js +1 -1
  90. package/libx/_runtime/common/utils/resolveView.js +7 -5
  91. package/libx/_runtime/common/utils/rewriteAsterisks.js +94 -0
  92. package/libx/_runtime/common/utils/search2cqn4sql.js +9 -8
  93. package/libx/_runtime/common/utils/template.js +54 -46
  94. package/libx/_runtime/db/Service.js +9 -2
  95. package/libx/_runtime/db/expand/expandCQNToJoin.js +54 -33
  96. package/libx/_runtime/db/expand/rawToExpanded.js +2 -1
  97. package/libx/_runtime/db/generic/arrayed.js +13 -28
  98. package/libx/_runtime/db/generic/create.js +1 -0
  99. package/libx/_runtime/db/generic/input.js +7 -11
  100. package/libx/_runtime/db/generic/integrity.js +2 -2
  101. package/libx/_runtime/db/generic/rewrite.js +2 -5
  102. package/libx/_runtime/db/generic/update.js +1 -0
  103. package/libx/_runtime/db/query/read.js +9 -4
  104. package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -2
  105. package/libx/_runtime/db/sql-builder/annotations.js +1 -0
  106. package/libx/_runtime/db/utils/columns.js +14 -43
  107. package/libx/_runtime/fiori/generic/activate.js +3 -2
  108. package/libx/_runtime/fiori/generic/before.js +2 -2
  109. package/libx/_runtime/fiori/generic/cancel.js +3 -2
  110. package/libx/_runtime/fiori/generic/delete.js +3 -2
  111. package/libx/_runtime/fiori/generic/edit.js +3 -3
  112. package/libx/_runtime/fiori/generic/new.js +2 -2
  113. package/libx/_runtime/fiori/generic/patch.js +2 -2
  114. package/libx/_runtime/fiori/generic/prepare.js +2 -2
  115. package/libx/_runtime/fiori/generic/read.js +45 -63
  116. package/libx/_runtime/fiori/generic/readOverDraft.js +4 -4
  117. package/libx/_runtime/fiori/uiflex/extensibility/index.cds +15 -0
  118. package/libx/_runtime/fiori/uiflex/extensibility/index.js +148 -0
  119. package/libx/_runtime/fiori/uiflex/handler/transformREAD.js +119 -0
  120. package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +43 -0
  121. package/libx/_runtime/fiori/uiflex/handler/transformWRITE.js +62 -0
  122. package/libx/_runtime/fiori/uiflex/index.js +35 -0
  123. package/libx/_runtime/fiori/uiflex/utils.js +78 -0
  124. package/libx/_runtime/fiori/utils/handler.js +3 -13
  125. package/libx/_runtime/fiori/utils/where.js +6 -1
  126. package/libx/_runtime/hana/pool.js +12 -11
  127. package/libx/_runtime/hana/search2cqn4sql.js +34 -43
  128. package/libx/_runtime/hana/searchToContains.js +3 -3
  129. package/libx/_runtime/index.js +5 -2
  130. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  131. package/libx/_runtime/messaging/common-utils/AMQPClient.js +16 -3
  132. package/libx/_runtime/messaging/common-utils/connections.js +11 -14
  133. package/libx/_runtime/messaging/common-utils/naming-conventions.js +1 -1
  134. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -1
  135. package/libx/_runtime/messaging/message-queuing.js +18 -0
  136. package/libx/_runtime/remote/Service.js +20 -4
  137. package/libx/_runtime/remote/utils/client-types.d.ts +7 -0
  138. package/libx/_runtime/remote/utils/client.js +117 -23
  139. package/libx/_runtime/sqlite/Service.js +2 -2
  140. package/libx/_runtime/sqlite/convertAssocToOneManaged.js +1 -3
  141. package/libx/gql/GraphQLAdapter.js +33 -0
  142. package/libx/gql/constants/adapter.js +69 -0
  143. package/libx/gql/constants/cds.js +18 -0
  144. package/libx/gql/constants/graphql.js +33 -0
  145. package/libx/gql/resolvers/crud/create.js +15 -0
  146. package/libx/gql/resolvers/crud/delete.js +24 -0
  147. package/libx/gql/resolvers/crud/index.js +6 -0
  148. package/libx/gql/resolvers/crud/read.js +25 -0
  149. package/libx/gql/resolvers/crud/update.js +31 -0
  150. package/libx/gql/resolvers/crud/utils/index.js +36 -0
  151. package/libx/gql/resolvers/field.js +5 -0
  152. package/libx/gql/resolvers/index.js +7 -0
  153. package/libx/gql/resolvers/mutation.js +23 -0
  154. package/libx/gql/resolvers/parse/ast/enrich.js +51 -0
  155. package/libx/gql/resolvers/parse/ast/fragment.js +11 -0
  156. package/libx/gql/resolvers/parse/ast/fromObject.js +39 -0
  157. package/libx/gql/resolvers/parse/ast/index.js +3 -0
  158. package/libx/gql/resolvers/parse/ast/meta.js +4 -0
  159. package/libx/gql/resolvers/parse/ast/variable.js +7 -0
  160. package/libx/gql/resolvers/parse/ast2cqn/columns.js +42 -0
  161. package/libx/gql/resolvers/parse/ast2cqn/entries.js +31 -0
  162. package/libx/gql/resolvers/parse/ast2cqn/index.js +8 -0
  163. package/libx/gql/resolvers/parse/ast2cqn/limit.js +6 -0
  164. package/libx/gql/resolvers/parse/ast2cqn/orderBy.js +24 -0
  165. package/libx/gql/resolvers/parse/ast2cqn/utils/index.js +3 -0
  166. package/libx/gql/resolvers/parse/ast2cqn/where.js +70 -0
  167. package/libx/gql/resolvers/parse/utils/index.js +8 -0
  168. package/libx/gql/resolvers/query.js +13 -0
  169. package/libx/gql/resolvers/root.js +34 -0
  170. package/libx/gql/schema/generate.js +18 -0
  171. package/libx/gql/schema/index.js +5 -0
  172. package/libx/gql/schema/mutation.js +76 -0
  173. package/libx/gql/schema/query.js +108 -0
  174. package/libx/gql/schema/typeDefMap.js +45 -0
  175. package/libx/gql/schema/utils/index.js +54 -0
  176. package/libx/gql/utils/index.js +12 -0
  177. package/libx/{_runtime/odata/cqn2odata.js → odata/cqn2odata/index.js} +39 -100
  178. package/libx/odata/index.js +80 -0
  179. package/libx/odata/odata2cqn/afterburner.js +170 -0
  180. package/libx/{_runtime/odata/odata2cqn.pegjs → odata/odata2cqn/grammar.pegjs} +102 -123
  181. package/libx/odata/odata2cqn/index.js +3 -0
  182. package/libx/odata/odata2cqn/parser.js +1 -0
  183. package/libx/odata/utils/index.js +64 -0
  184. package/libx/rest/RestAdapter.js +101 -0
  185. package/libx/rest/RestRequest.js +30 -0
  186. package/libx/rest/index.js +3 -0
  187. package/libx/rest/middleware/auth.js +22 -0
  188. package/libx/rest/middleware/content.js +15 -0
  189. package/libx/rest/middleware/create.js +40 -0
  190. package/libx/rest/middleware/delete.js +20 -0
  191. package/libx/rest/middleware/error.js +56 -0
  192. package/libx/rest/middleware/operation.js +39 -0
  193. package/libx/rest/middleware/parse.js +90 -0
  194. package/libx/rest/middleware/read.js +29 -0
  195. package/libx/rest/middleware/update.js +42 -0
  196. package/libx/rest/utils/data.js +65 -0
  197. package/package.json +4 -1
  198. package/server.js +29 -7
  199. package/libx/_runtime/cds-services/services/utils/diff.js +0 -53
  200. package/libx/_runtime/cds-services/util/auditlog.js +0 -247
  201. package/libx/_runtime/cds-services/util/xsenv.js +0 -51
  202. package/libx/_runtime/common/utils/backlinks.js +0 -83
  203. package/libx/_runtime/common/utils/rewriteAsterisk.js +0 -72
  204. package/libx/_runtime/odata/index.js +0 -55
  205. package/libx/_runtime/odata/odata2cqn.js +0 -1
  206. package/libx/_runtime/odata/readToCqn.js +0 -129
  207. package/libx/_runtime/remote/cqn2odata/index.js +0 -2
@@ -0,0 +1,72 @@
1
+ // global.cds is used on purpose here!
2
+ const cds = global.cds
3
+
4
+ const ODATA_CONTAINED = '@odata.contained'
5
+
6
+ const { isSelfManaged, isBacklink, getAnchor, getBacklink } = require('./utils')
7
+ const { foreignKeyPropagations } = require('../utils/foreignKeyPropagations')
8
+
9
+ module.exports = class {
10
+ get _isAssociationStrict() {
11
+ return (
12
+ this.own('__isAssociationStrict') ||
13
+ this.set('__isAssociationStrict', !!(this.isAssociation && !this.isComposition))
14
+ )
15
+ }
16
+
17
+ get _isAssociationEffective() {
18
+ return (
19
+ this.own('__isAssociationEffective') ||
20
+ this.set(
21
+ '__isAssociationEffective',
22
+ this._isAssociationStrict && (!this[ODATA_CONTAINED] || this.name === 'DraftAdministrativeData')
23
+ )
24
+ )
25
+ }
26
+
27
+ get _isCompositionEffective() {
28
+ return (
29
+ this.own('__isCompositionEffective') ||
30
+ this.set(
31
+ '__isCompositionEffective',
32
+ this.isComposition ||
33
+ (this._isAssociationStrict && this[ODATA_CONTAINED] && this.name !== 'DraftAdministrativeData')
34
+ )
35
+ )
36
+ }
37
+
38
+ get _isContained() {
39
+ return (
40
+ this.own('__isContained') ||
41
+ this.set(
42
+ '__isContained',
43
+ this.name !== 'DraftAdministrativeData_DraftUUID' &&
44
+ ((this.isAssociation && this[ODATA_CONTAINED]) || (this.isComposition && cds.env.effective.odata.containment))
45
+ )
46
+ )
47
+ }
48
+
49
+ get _isSelfManaged() {
50
+ return this.own('__isSelfManaged') || this.set('__isSelfManaged', isSelfManaged(this))
51
+ }
52
+
53
+ get _isBacklink() {
54
+ return this.own('__isBacklink') || this.set('__isBacklink', isBacklink(this))
55
+ }
56
+
57
+ get _isCompositionBacklink() {
58
+ return this.own('__isCompositionBacklink') || this.set('__isCompositionBacklink', isBacklink(this, true))
59
+ }
60
+
61
+ get _anchor() {
62
+ return this.own('__anchor') || this.set('__anchor', getAnchor(this))
63
+ }
64
+
65
+ get _backlink() {
66
+ return this.own('__backlink') || this.set('__backlink', getBacklink(this))
67
+ }
68
+
69
+ get _foreignKeys() {
70
+ return this.own('__foreignKeys') || this.set('__foreignKeys', foreignKeyPropagations(this))
71
+ }
72
+ }
@@ -1,54 +1,12 @@
1
- // global.cds is used on purpose here!
2
- const cds = global.cds
3
-
4
- const ODATA_CONTAINED = '@odata.contained'
5
-
6
- const { isMandatory, isReadOnly } = require('./utils')
1
+ const { getRelations, isMandatory, isReadOnly } = require('./utils')
7
2
 
3
+ // NOTE: Please only add things which are relevant to _any_ type,
4
+ // use specialized types otherwise (entity, Association, ...).
8
5
  module.exports = class {
9
- get _isAssociationStrict() {
10
- return (
11
- this.own('__isAssociationStrict') ||
12
- this.set('__isAssociationStrict', !!(this.isAssociation && !this.isComposition))
13
- )
14
- }
15
-
16
- get _isAssociationEffective() {
17
- return (
18
- this.own('__isAssociationEffective') ||
19
- this.set(
20
- '__isAssociationEffective',
21
- this._isAssociationStrict && (!this[ODATA_CONTAINED] || this.name === 'DraftAdministrativeData')
22
- )
23
- )
24
- }
25
-
26
- get _isCompositionEffective() {
27
- return (
28
- this.own('__isCompositionEffective') ||
29
- this.set(
30
- '__isCompositionEffective',
31
- this.isComposition ||
32
- (this._isAssociationStrict && this[ODATA_CONTAINED] && this.name !== 'DraftAdministrativeData')
33
- )
34
- )
35
- }
36
-
37
6
  get _isStructured() {
38
7
  return this.own('__isStructured') || this.set('__isStructured', !!this.elements && this.kind !== 'entity')
39
8
  }
40
9
 
41
- get _isContained() {
42
- return (
43
- this.own('__isContained') ||
44
- this.set(
45
- '__isContained',
46
- this.name !== 'DraftAdministrativeData_DraftUUID' &&
47
- ((this.isAssociation && this[ODATA_CONTAINED]) || (this.isComposition && cds.env.effective.odata.containment))
48
- )
49
- )
50
- }
51
-
52
10
  get _isMandatory() {
53
11
  return this.own('__isMandatory') || this.set('__isMandatory', !this.isAssociation && isMandatory(this))
54
12
  }
@@ -56,4 +14,9 @@ module.exports = class {
56
14
  get _isReadOnly() {
57
15
  return this.own('__isReadOnly') || this.set('__isReadOnly', !this.key && isReadOnly(this))
58
16
  }
17
+
18
+ // REVISIT: Where to put?
19
+ get _relations() {
20
+ return this.own('__relations') || this.set('__relations', getRelations(this))
21
+ }
59
22
  }
@@ -44,7 +44,6 @@ module.exports = class {
44
44
  // lazily require on first use
45
45
  getSearchableColumns =
46
46
  getSearchableColumns || require('../../cds-services/services/utils/columns').getSearchableColumns
47
-
48
47
  return this.own('__searchableColumns') || this.set('__searchableColumns', getSearchableColumns(this))
49
48
  }
50
49
 
@@ -0,0 +1,40 @@
1
+ const { getOnCond } = require('../utils/generateOnCond')
2
+
3
+ let initializing = false
4
+
5
+ module.exports = class Relation {
6
+ constructor(csn, path = []) {
7
+ if (!initializing) throw new Error(`Do not new a relation, use 'Relation.to()' instead`)
8
+ Object.defineProperty(this, 'csn', { get: () => csn })
9
+ Object.defineProperty(this, 'path', {
10
+ get: () => path,
11
+ set: _ => {
12
+ path = _
13
+ }
14
+ })
15
+ if (csn.target) Object.defineProperty(this, 'target', { get: () => csn.target })
16
+ initializing = false
17
+ }
18
+
19
+ static to(from, name) {
20
+ initializing = true
21
+ if (!name) return new Relation(from)
22
+ return from._elements[name] && new Relation(from._elements[name], [...from.path, name])
23
+ }
24
+
25
+ _has(prop) {
26
+ return Reflect.has(this, prop) && !this._elements[prop]
27
+ }
28
+
29
+ get _elements() {
30
+ if (this.csn.elements) return this.csn.elements
31
+ if (this.csn._target && this.csn._target.elements) return this.csn._target.elements
32
+ // if (csn.targetAspect) relation.elements = model.definitions[csn.targetAspect].elements
33
+ // if (csn.kind = 'type') relation.elements = model.definitions[csn.type].element
34
+ return {}
35
+ }
36
+
37
+ join(fromAlias = '', toAlias = '') {
38
+ return getOnCond(this.csn, this.path, { select: fromAlias, join: toAlias })
39
+ }
40
+ }
@@ -1,3 +1,5 @@
1
+ const Relation = require('./relation')
2
+
1
3
  const CommonFieldControl = e => {
2
4
  const cfr = e['@Common.FieldControl']
3
5
  return cfr && cfr['#']
@@ -65,10 +67,80 @@ const hasSensitiveData = entity => {
65
67
  return val
66
68
  }
67
69
 
70
+ const _exposeRelation = relation => Object.defineProperty({}, '_', { get: () => relation })
71
+
72
+ const _relationHandler = relation => ({
73
+ get: (target, name) => {
74
+ const path = name.split(',')
75
+ const prop = path.join('_')
76
+ if (!target[prop]) {
77
+ if (path.length === 1) {
78
+ // REVISIT: property 'join' must not be used in CSN to make this working
79
+ if (relation._has(prop)) return relation[prop]
80
+ const newRelation = Relation.to(relation, prop)
81
+ if (newRelation) {
82
+ target[prop] = new Proxy(_exposeRelation(newRelation), _relationHandler(newRelation))
83
+ }
84
+ return target[prop]
85
+ }
86
+ target[prop] = path.reduce((r, p) => r[p] || r.csn._relations[p], relation)
87
+ target[prop].path = path
88
+ }
89
+ return target[prop]
90
+ }
91
+ })
92
+
93
+ const getRelations = e => {
94
+ const newRelation = Relation.to(e)
95
+ return new Proxy(_exposeRelation(newRelation), _relationHandler(newRelation))
96
+ }
97
+
98
+ const _hasJoinCondition = e => e.isAssociation && e.on && e.on.length > 2
99
+
100
+ const _isSelfRef = e => e.ref && e.ref[0] === '$self'
101
+
102
+ const _getBacklinkName = on => {
103
+ const i = on.findIndex(_isSelfRef)
104
+ if (i === -1) return
105
+ let ref
106
+ if (on[i + 1] && on[i + 1] === '=') ref = on[i + 2].ref
107
+ if (on[i - 1] && on[i - 1] === '=') ref = on[i - 2].ref
108
+ return ref && ref[ref.length - 1]
109
+ }
110
+
111
+ const isSelfManaged = e => {
112
+ if (!_hasJoinCondition(e)) return
113
+ return !!e.on.find(_isSelfRef)
114
+ }
115
+
116
+ const isBacklink = (e, checkComposition) => getAnchor(e, checkComposition) && true
117
+
118
+ const _isUnManagedAssociation = (e, checkComposition) =>
119
+ e.isAssociation && (!checkComposition || e._isCompositionEffective) && _hasJoinCondition(e)
120
+
121
+ const getAnchor = (e, checkComposition) => {
122
+ if (!(e._isAssociationStrict && (e.keys || e.on))) return
123
+ for (const anchor of Object.values(e._target.associations || {})) {
124
+ if (!_isUnManagedAssociation(anchor, checkComposition)) continue
125
+ if (_getBacklinkName(anchor.on) === e.name && anchor.target === e.parent.name) return anchor
126
+ }
127
+ }
128
+
129
+ const getBacklink = (e, checkComposition) => {
130
+ if (!_isUnManagedAssociation(e, checkComposition)) return
131
+ const backlinkName = _getBacklinkName(e.on)
132
+ if (backlinkName) return e._target && e._target.elements && e._target.elements[backlinkName]
133
+ }
134
+
68
135
  module.exports = {
69
136
  isMandatory,
70
137
  isReadOnly,
71
138
  getETag,
72
139
  hasPersonalData,
73
- hasSensitiveData
140
+ hasSensitiveData,
141
+ getRelations,
142
+ isSelfManaged,
143
+ isBacklink,
144
+ getAnchor,
145
+ getBacklink
74
146
  }
@@ -1,21 +1,17 @@
1
1
  const cds = require('../../../../cds')
2
2
 
3
- const _require = require('../../../utils/require')
4
-
5
3
  const getCredentials = uaa => {
6
- uaa = uaa || cds.env.requires.uaa || {}
4
+ uaa =
5
+ uaa && uaa.credentials
6
+ ? uaa
7
+ : cds.env.requires.uaa && cds.env.requires.uaa.credentials
8
+ ? cds.env.requires.uaa
9
+ : cds.env.requires.xsuaa && cds.env.requires.xsuaa.credentials
10
+ ? cds.env.requires.xsuaa
11
+ : {}
7
12
 
8
- if (!uaa.credentials) {
9
- try {
10
- const vcap = cds.env.requires.uaa && cds.env.requires.uaa.vcap
11
- uaa.credentials = _require('@sap/xsenv').serviceCredentials(vcap || { label: 'xsuaa' })
12
- } catch (e) {
13
- const msg =
14
- 'Unable to get xsuaa credentials. Please make sure your app is bound to a single xsuaa service instance or that you provide vcap information in the requires.uaa section.'
15
- // REVISIT: switch to verror
16
- throw Object.assign(new Error(msg), { original: e })
17
- }
18
- }
13
+ if (!uaa.credentials)
14
+ throw Object.assign(new Error('No or malformed uaa credentials'), { credentials: uaa.credentials })
19
15
 
20
16
  return uaa.credentials
21
17
  }
@@ -4,8 +4,9 @@ const { getEntityNameFromUpdateCQN } = require('../utils/cqn')
4
4
 
5
5
  const { ensureNoDraftsSuffix } = require('../utils/draft')
6
6
  const { getDBTable } = require('../utils/resolveView')
7
- const cqn2cqn4sql = require('../utils/cqn2cqn4sql')
7
+ const { cqn2cqn4sql } = require('../utils/cqn2cqn4sql')
8
8
  const cds = require('../../cds')
9
+ const { SELECT } = cds.ql
9
10
 
10
11
  /*
11
12
  * own utils
@@ -192,7 +193,7 @@ const _select = ({
192
193
  }) => {
193
194
  const entity = definitions && definitions[entityName]
194
195
  const from = ctUtils.addDraftSuffix(draft, entity.name)
195
- const selectCQN = { SELECT: { from: { ref: [from] } } }
196
+ const selectCQN = SELECT.from(from)
196
197
  if (alias) selectCQN.SELECT.from.as = alias
197
198
  const selectAll = includeAllColumns || (includeAllRootColumns && root)
198
199
  selectCQN.SELECT.columns = _columns(entity, data, compositionTree, selectAll)
@@ -20,8 +20,10 @@ const _recursivelyAliasRefs = (something, newAlias, oldAlias, subselect = false)
20
20
  if (oldAlias && something.ref[0] === oldAlias) something.ref[0] = newAlias
21
21
  else if (!subselect) something.ref.unshift(newAlias)
22
22
  } else {
23
- for (const key in something)
23
+ for (const key in something) {
24
+ if (key === 'from') continue // Workaround: Deep delete to be rewritten
24
25
  _recursivelyAliasRefs(something[key], newAlias, oldAlias, subselect || key === 'SELECT')
26
+ }
25
27
  }
26
28
  }
27
29
  }
@@ -1,13 +1,9 @@
1
1
  const cds = require('../../cds')
2
2
 
3
- const { isSelfManaged, isBacklink } = require('../utils/backlinks')
4
-
5
3
  const { ensureNoDraftsSuffix } = require('../utils/draft')
6
4
  const { isRootEntity } = require('../utils/csn')
7
5
  const { getTransition, getDBTable } = require('../utils/resolveView')
8
6
 
9
- const { foreignKeyPropagations } = require('../utils/foreignKeyPropagations')
10
-
11
7
  const getError = require('../../common/error')
12
8
 
13
9
  /*
@@ -15,21 +11,25 @@ const getError = require('../../common/error')
15
11
  */
16
12
 
17
13
  const _foreignKeysToLinks = (element, inverse) =>
18
- foreignKeyPropagations(element).map(e => {
14
+ element._foreignKeys.map(e => {
19
15
  e = inverse
20
16
  ? {
21
- childFieldName: e.parentFieldName,
22
- parentFieldName: e.childFieldName,
17
+ childElement: e.parentElement,
18
+ parentElement: e.childElement,
23
19
  childFieldValue: e.parentFieldValue,
24
20
  parentFieldValue: e.childFieldValue,
25
21
  prefix: e.prefix
26
22
  }
27
23
  : e
28
- const link = {
29
- entityKey:
30
- e.prefix && !e.parentFieldName.includes(e.prefix) ? e.prefix + '_' + e.parentFieldName : e.parentFieldName,
31
- targetKey: e.prefix && !e.childFieldName.includes(e.prefix) ? e.prefix + '_' + e.childFieldName : e.childFieldName
32
- }
24
+ const link = {}
25
+ if (e.parentElement)
26
+ link.entityKey =
27
+ e.prefix && !e.parentElement.name.includes(e.prefix)
28
+ ? `${e.prefix}_${e.parentElement.name}`
29
+ : e.parentElement.name
30
+ if (e.childElement)
31
+ link.targetKey =
32
+ e.prefix && !e.childElement.name.includes(e.prefix) ? `${e.prefix}_${e.childElement.name}` : e.childElement.name
33
33
  if (e.parentFieldValue !== undefined) link.entityVal = e.parentFieldValue
34
34
  if (e.childFieldValue !== undefined) link.targetVal = e.childFieldValue
35
35
  return link
@@ -57,7 +57,7 @@ const _resolvedElement = (element, service) => {
57
57
  const _navigationExistsInCompositionMap = (element, compositionMap) =>
58
58
  compositionMap.has(element.target) && element._isCompositionEffective
59
59
 
60
- const _isUnManaged = element => element.on && !isSelfManaged(element)
60
+ const _isUnManaged = element => element.on && !element._isSelfManaged
61
61
 
62
62
  const _isNonRecursiveNavigation = (element, rootEntityName) =>
63
63
  rootEntityName !== element.target && element._isCompositionEffective
@@ -106,7 +106,7 @@ const _getCompositionTreeRec = ({
106
106
  backLinks: [],
107
107
  customBackLinks: []
108
108
  })
109
- if (!isSelfManaged(element)) {
109
+ if (!element._isSelfManaged) {
110
110
  const backLinks = _foreignKeysToLinks(element, true) || []
111
111
  if (element.is2many) {
112
112
  compositionElement.customBackLinks.push(...backLinks)
@@ -118,7 +118,11 @@ const _getCompositionTreeRec = ({
118
118
  for (const backLinkName in targetEntity.elements) {
119
119
  const _backLink = targetEntity.elements[backLinkName]
120
120
  if (!_backLink._isAssociationEffective) continue
121
- if (isBacklink(_backLink, definitions[compositionElement.target], false, element.name)) {
121
+ if (
122
+ _backLink._isCompositionBacklink &&
123
+ _backLink.target === compositionElement.target &&
124
+ _backLink._anchor.name === element.name
125
+ ) {
122
126
  const backLinks = _foreignKeysToLinks(_backLink) || []
123
127
  if (_isUnManaged(element)) {
124
128
  compositionElement.customBackLinks.push(...backLinks)
@@ -131,7 +135,7 @@ const _getCompositionTreeRec = ({
131
135
  compositionTree.compositionElements.push(compositionElement)
132
136
  } else if (_isNonRecursiveNavigation(element, rootEntityName)) {
133
137
  const subObject = _createSubElement(element, definitions)
134
- if (!isSelfManaged(element)) {
138
+ if (!element._isSelfManaged) {
135
139
  const backLinks = _foreignKeysToLinks(element, true) || []
136
140
  if (element.is2many) {
137
141
  subObject.customBackLinks.push(...backLinks)
@@ -151,7 +155,8 @@ const _getCompositionTreeRec = ({
151
155
  })
152
156
  } else if (
153
157
  element._isAssociationEffective &&
154
- isBacklink(element, definitions[compositionTree.target], false, compositionTree.name) &&
158
+ element._isCompositionBacklink &&
159
+ element.target === compositionTree.target &&
155
160
  compositionMap.has(element.target)
156
161
  ) {
157
162
  const backLinks = _foreignKeysToLinks(element) || []
@@ -218,7 +223,7 @@ const _cacheCompositionParentsOfOne = ({ definitions }) => {
218
223
  if (!parent.kind === 'entity' || !parent.elements) continue
219
224
  for (const elementName in parent.elements) {
220
225
  const element = parent.elements[elementName]
221
- if (element._isCompositionEffective && element.is2one && !isSelfManaged(element)) {
226
+ if (element._isCompositionEffective && element.is2one && !element._isSelfManaged) {
222
227
  const targetName = element.target
223
228
  const target = definitions[targetName]
224
229
  if (!target) continue
@@ -205,11 +205,19 @@ function _addSubDeepUpdateCQNRecursion({ definitions, compositionTree, entity, d
205
205
  const selectSubData = []
206
206
  for (const entry of data) {
207
207
  if (element.name in entry) {
208
- _addToData(subData, entity, element, entry)
209
208
  const selectEntry = selectDataByKey.get(_serializedKey(entity, entry))
209
+
210
210
  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
+ ) {
215
+ continue
216
+ }
211
217
  _addToData(selectSubData, entity, element, selectEntry)
212
218
  }
219
+
220
+ _addToData(subData, entity, element, entry)
213
221
  }
214
222
  }
215
223
  _addSubDeepUpdateCQN({
@@ -1,5 +1,3 @@
1
- const { isSelfManaged } = require('../utils/backlinks')
2
-
3
1
  const { ensureNoDraftsSuffix, ensureDraftsSuffix } = require('../utils/draft')
4
2
 
5
3
  const addDraftSuffix = (draft, name) => {
@@ -24,6 +22,7 @@ const keyElements = entity => {
24
22
 
25
23
  const key = (entity, data) => {
26
24
  return keyElements(entity).reduce((result, element) => {
25
+ if (element.name === 'IsActiveEntity' && !Object.prototype.hasOwnProperty.call(data, element.name)) return result
27
26
  result[element.name] = data[element.name]
28
27
  return result
29
28
  }, {})
@@ -33,10 +32,6 @@ const val = element => (element && element.val) || element
33
32
 
34
33
  const array = x => (Array.isArray(x) ? x : [x])
35
34
 
36
- const isManaged = element => {
37
- return isSelfManaged(element) || !element.on
38
- }
39
-
40
35
  const isCompOrAssoc = (entity, k, onlyToOne) => {
41
36
  return (
42
37
  entity.elements &&
@@ -60,6 +55,37 @@ const cleanDeepData = (entity, data, onlyToOne = false) => {
60
55
  })
61
56
  }
62
57
 
58
+ const _getBacklinkNameFromOnCond = element => {
59
+ if (element.on && element.on.length === 3 && element.on[0].ref && element.on[2].ref) {
60
+ if (element.on[0].ref[0] === '$self') {
61
+ return element.on[2].ref[element.on[2].ref.length - 1]
62
+ } else if (element.on[2].ref[0] === '$self') {
63
+ return element.on[0].ref[element.on[0].ref.length - 1]
64
+ }
65
+ }
66
+ }
67
+
68
+ const isBacklink = (element, parent, checkContained, backLinkName) => {
69
+ if (!element._isAssociationStrict) return false
70
+ if (!parent || !(element.keys || element.on)) return false
71
+ if (element.target !== parent.name) return false
72
+
73
+ const _isBackLink = parentElement =>
74
+ (!checkContained || parentElement._isContained) && _getBacklinkNameFromOnCond(parentElement) === element.name
75
+
76
+ if (backLinkName) {
77
+ const parentElement = parent.elements[backLinkName]
78
+ return parentElement.isAssociation && _isBackLink(parentElement)
79
+ }
80
+ for (const parentElementName in parent.elements) {
81
+ const parentElement = parent.elements[parentElementName]
82
+ if (!parentElement.isAssociation) continue
83
+ if (_isBackLink(parentElement)) return true
84
+ }
85
+
86
+ return false
87
+ }
88
+
63
89
  module.exports = {
64
90
  addDraftSuffix,
65
91
  whereKey,
@@ -67,7 +93,7 @@ module.exports = {
67
93
  key,
68
94
  val,
69
95
  array,
70
- isManaged,
71
96
  isCompOrAssoc,
72
- cleanDeepData
97
+ cleanDeepData,
98
+ isBacklink
73
99
  }
@@ -22,6 +22,8 @@ const {
22
22
  MAX_SEVERITY
23
23
  } = require('./constants')
24
24
 
25
+ const SKIP_SANITIZATION = '@cds.skip_sanitization'
26
+
25
27
  const _getFiltered = err => {
26
28
  const error = {}
27
29
 
@@ -103,10 +105,13 @@ const normalizeError = (err, req) => {
103
105
  // make sure it's a number
104
106
  statusCode = statusCode ? Number(statusCode) : 500
105
107
 
106
- if (statusCode >= 500 && process.env.NODE_ENV === 'production') {
108
+ // REVISIT: make === 500 in cds^6
109
+ // error[SKIP_SANITIZATION] is not an official API!!!
110
+ if (statusCode >= 500 && process.env.NODE_ENV === 'production' && !error[SKIP_SANITIZATION]) {
107
111
  // > return sanitized error to client
108
112
  return { error: { code: `${statusCode}`, message: i18n(statusCode, locale) }, statusCode }
109
113
  }
114
+ delete error[SKIP_SANITIZATION]
110
115
 
111
116
  // no top level null codes
112
117
  if (error.code === 'null') {
@@ -6,7 +6,7 @@ const cds = require('../../cds')
6
6
  const { SELECT } = cds.ql
7
7
 
8
8
  const { getRequiresAsArray } = require('../utils/auth')
9
- const cqn2cqn4sql = require('../utils/cqn2cqn4sql')
9
+ const { cqn2cqn4sql } = require('../utils/cqn2cqn4sql')
10
10
  const { isActiveEntityRequested, removeIsActiveEntityRecursively } = require('../../fiori/utils/where')
11
11
  const { ensureDraftsSuffix } = require('../../fiori/utils/handler')
12
12
 
@@ -888,19 +888,15 @@ const _secureDependentEntities = srv => {
888
888
  }
889
889
  }
890
890
 
891
- module.exports = function () {
892
- /*
893
- * @restrict, @requires, @readonly, @insertonly, and @Capabilities for entities
894
- */
891
+ module.exports = cds.service.impl(function () {
892
+ // @restrict, @requires, @readonly, @insertonly, and @Capabilities for entities
895
893
  _secureDependentEntities(this)
896
894
  for (const k in this.entities) {
897
895
  const entity = this.entities[k]
898
896
  if (!_authDependsOnParents(entity)) _registerAuthHandlers(entity, this)
899
897
  }
900
898
 
901
- /*
902
- * @restrict and @requires for operations
903
- */
899
+ // @restrict and @requires for operations
904
900
  for (const k in this.operations) {
905
901
  const operation = this.operations[k]
906
902
 
@@ -910,4 +906,4 @@ module.exports = function () {
910
906
  // @restrict
911
907
  _registerOperationRestrictHandlers(operation, this)
912
908
  }
913
- }
909
+ })
@@ -67,7 +67,7 @@ const _updateReqData = (req, that) => {
67
67
  }
68
68
  }
69
69
 
70
- module.exports = function () {
70
+ module.exports = cds.service.impl(function () {
71
71
  this.on(['CREATE', 'READ', 'UPDATE', 'DELETE'], '*', async function (req) {
72
72
  if (typeof req.query !== 'string' && req.target && req.target._hasPersistenceSkip) {
73
73
  req.reject(501, 'PERSISTENCE_SKIP_NO_GENERIC_CRUD', [req.target.name])
@@ -128,4 +128,4 @@ module.exports = function () {
128
128
 
129
129
  return req.data
130
130
  })
131
- }
131
+ })
@@ -2,9 +2,9 @@ const cds = require('../../cds')
2
2
  const { SELECT } = cds.ql
3
3
 
4
4
  // REVISIT: draft should not be handled here, e.g., target.name should be adjusted before
5
- const { isActiveEntityRequested, removeIsActiveEntityRecursively } = require('../../fiori/utils/where')
5
+ const { isActiveEntityRequested } = require('../../fiori/utils/where')
6
6
  const { ensureDraftsSuffix } = require('../../fiori/utils/handler')
7
- const cqn2cqn4sql = require('../../common/utils/cqn2cqn4sql')
7
+ const { cqn2cqn4sql } = require('../../common/utils/cqn2cqn4sql')
8
8
  const ODataRequest = require('../../cds-services/adapter/odata-v4/ODataRequest')
9
9
 
10
10
  const C_U_ = {
@@ -22,10 +22,11 @@ const getSelectCQN = (query, target, model) => {
22
22
  } else {
23
23
  requestTarget = query.DELETE.from
24
24
  }
25
+
26
+ const targetName = isActiveEntityRequested(requestTarget.ref[0].where) ? target.name : ensureDraftsSuffix(target.name)
25
27
  const cqn = cqn2cqn4sql(SELECT.from(requestTarget), model)
26
- cqn.SELECT.from.ref[0] = isActiveEntityRequested(cqn.SELECT.where) ? target.name : ensureDraftsSuffix(target.name)
27
28
  cqn.columns([target._etag])
28
- cqn.SELECT.where = removeIsActiveEntityRecursively(cqn.SELECT.where)
29
+ cqn.SELECT.from.ref[0] = targetName
29
30
 
30
31
  return cqn
31
32
  }
@@ -60,11 +61,12 @@ const _handler = async function (req) {
60
61
  }
61
62
  }
62
63
 
63
- /*
64
+ /**
64
65
  * handler registration
66
+ *
65
67
  */
66
68
  /* istanbul ignore next */
67
- module.exports = function () {
69
+ module.exports = cds.service.impl(function () {
68
70
  _handler._initial = true
69
71
 
70
72
  for (const k in this.entities) {
@@ -75,8 +77,9 @@ module.exports = function () {
75
77
  continue
76
78
  }
77
79
 
78
- // Handler for CREATE is registered for backwards compatiblity w.r.t. ETag generation
80
+ // handler for CREATE is registered for backwards compatibility w.r.t. ETag generation
79
81
  let events = ['CREATE', 'READ', 'UPDATE', 'DELETE']
82
+
80
83
  // if odata and fiori is separated, this will not be needed in the odata version
81
84
  if (entity._isDraftEnabled) {
82
85
  events = ['READ', 'NEW', 'DELETE', 'PATCH', 'EDIT', 'CANCEL']
@@ -88,4 +91,4 @@ module.exports = function () {
88
91
  this.before(action, entity, _handler)
89
92
  }
90
93
  }
91
- }
94
+ })