@sap/cds 5.5.4 → 5.6.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 (210) hide show
  1. package/CHANGELOG.md +138 -1
  2. package/apis/services.d.ts +27 -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 +175 -41
  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/serve/Service-methods.js +1 -1
  25. package/lib/utils/axios.js +7 -0
  26. package/lib/utils/data.js +1 -1
  27. package/lib/utils/tests.js +1 -1
  28. package/libx/_runtime/audit/Service.js +18 -18
  29. package/libx/_runtime/audit/generic/personal/access.js +1 -1
  30. package/libx/_runtime/audit/generic/personal/modification.js +3 -2
  31. package/libx/_runtime/audit/generic/personal/utils.js +23 -63
  32. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +4 -0
  33. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +37 -35
  34. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +3 -1
  35. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +5 -5
  36. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -7
  37. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +84 -34
  38. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +10 -4
  39. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +9 -3
  40. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +8 -6
  41. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -3
  42. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +13 -11
  43. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +11 -95
  44. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +17 -11
  45. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
  46. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/SetResponseHeadersCommand.js +1 -0
  47. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +6 -2
  48. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +3 -34
  49. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -3
  50. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +64 -31
  51. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +10 -5
  52. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +1 -1
  53. package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +1 -1
  54. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +1 -3
  55. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +20 -21
  56. package/libx/_runtime/cds-services/services/utils/columns.js +6 -1
  57. package/libx/_runtime/cds-services/services/utils/compareJson.js +1 -8
  58. package/libx/_runtime/cds-services/services/utils/differ.js +7 -26
  59. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -4
  60. package/libx/_runtime/cds-services/util/assert.js +29 -13
  61. package/libx/_runtime/cds.js +2 -1
  62. package/libx/_runtime/common/aspects/Association.js +72 -0
  63. package/libx/_runtime/common/aspects/any.js +8 -45
  64. package/libx/_runtime/common/aspects/entity.js +0 -1
  65. package/libx/_runtime/common/aspects/relation.js +40 -0
  66. package/libx/_runtime/common/aspects/utils.js +73 -1
  67. package/libx/_runtime/common/auth/strategies/utils/uaa.js +10 -14
  68. package/libx/_runtime/common/composition/data.js +3 -2
  69. package/libx/_runtime/common/composition/delete.js +3 -1
  70. package/libx/_runtime/common/composition/tree.js +23 -18
  71. package/libx/_runtime/common/composition/update.js +6 -1
  72. package/libx/_runtime/common/composition/utils.js +34 -8
  73. package/libx/_runtime/common/error/frontend.js +6 -1
  74. package/libx/_runtime/common/generic/auth.js +15 -13
  75. package/libx/_runtime/common/generic/crud.js +2 -2
  76. package/libx/_runtime/common/generic/etag.js +11 -8
  77. package/libx/_runtime/common/generic/input.js +3 -3
  78. package/libx/_runtime/common/generic/paging.js +9 -5
  79. package/libx/_runtime/common/generic/put.js +3 -2
  80. package/libx/_runtime/common/generic/sorting.js +3 -3
  81. package/libx/_runtime/common/generic/temporal.js +3 -3
  82. package/libx/_runtime/common/utils/cqn.js +20 -1
  83. package/libx/_runtime/common/utils/cqn2cqn4sql.js +125 -139
  84. package/libx/_runtime/common/utils/csn.js +50 -52
  85. package/libx/_runtime/common/utils/foreignKeyPropagations.js +41 -176
  86. package/libx/_runtime/common/utils/generateOnCond.js +40 -70
  87. package/libx/_runtime/common/utils/{enrichWithKeysFromWhere.js → keys.js} +29 -28
  88. package/libx/_runtime/common/utils/postProcessing.js +3 -0
  89. package/libx/_runtime/common/utils/propagateForeignKeys.js +84 -0
  90. package/libx/_runtime/common/utils/resolveStructured.js +1 -1
  91. package/libx/_runtime/common/utils/resolveView.js +7 -5
  92. package/libx/_runtime/common/utils/rewriteAsterisks.js +94 -0
  93. package/libx/_runtime/common/utils/search2cqn4sql.js +9 -8
  94. package/libx/_runtime/common/utils/template.js +54 -46
  95. package/libx/_runtime/db/Service.js +9 -2
  96. package/libx/_runtime/db/expand/expandCQNToJoin.js +54 -33
  97. package/libx/_runtime/db/expand/rawToExpanded.js +2 -1
  98. package/libx/_runtime/db/generic/arrayed.js +13 -28
  99. package/libx/_runtime/db/generic/create.js +1 -0
  100. package/libx/_runtime/db/generic/input.js +7 -11
  101. package/libx/_runtime/db/generic/integrity.js +2 -2
  102. package/libx/_runtime/db/generic/rewrite.js +2 -5
  103. package/libx/_runtime/db/generic/update.js +1 -0
  104. package/libx/_runtime/db/query/read.js +9 -4
  105. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +6 -0
  106. package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -2
  107. package/libx/_runtime/db/sql-builder/annotations.js +1 -0
  108. package/libx/_runtime/db/utils/columns.js +14 -43
  109. package/libx/_runtime/db/utils/deep.js +5 -7
  110. package/libx/_runtime/fiori/generic/activate.js +3 -2
  111. package/libx/_runtime/fiori/generic/before.js +2 -2
  112. package/libx/_runtime/fiori/generic/cancel.js +3 -2
  113. package/libx/_runtime/fiori/generic/delete.js +3 -2
  114. package/libx/_runtime/fiori/generic/edit.js +3 -3
  115. package/libx/_runtime/fiori/generic/new.js +2 -2
  116. package/libx/_runtime/fiori/generic/patch.js +2 -2
  117. package/libx/_runtime/fiori/generic/prepare.js +2 -2
  118. package/libx/_runtime/fiori/generic/read.js +17 -63
  119. package/libx/_runtime/fiori/generic/readOverDraft.js +4 -4
  120. package/libx/_runtime/fiori/uiflex/extensibility/index.cds +15 -0
  121. package/libx/_runtime/fiori/uiflex/extensibility/index.js +148 -0
  122. package/libx/_runtime/fiori/uiflex/handler/transformREAD.js +119 -0
  123. package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +43 -0
  124. package/libx/_runtime/fiori/uiflex/handler/transformWRITE.js +62 -0
  125. package/libx/_runtime/fiori/uiflex/index.js +35 -0
  126. package/libx/_runtime/fiori/uiflex/utils.js +78 -0
  127. package/libx/_runtime/fiori/utils/handler.js +3 -13
  128. package/libx/_runtime/fiori/utils/where.js +6 -1
  129. package/libx/_runtime/hana/pool.js +12 -11
  130. package/libx/_runtime/hana/search2cqn4sql.js +34 -43
  131. package/libx/_runtime/hana/searchToContains.js +3 -3
  132. package/libx/_runtime/index.js +5 -2
  133. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  134. package/libx/_runtime/messaging/common-utils/AMQPClient.js +16 -3
  135. package/libx/_runtime/messaging/common-utils/connections.js +11 -14
  136. package/libx/_runtime/messaging/common-utils/naming-conventions.js +1 -1
  137. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -1
  138. package/libx/_runtime/messaging/message-queuing.js +18 -0
  139. package/libx/_runtime/remote/Service.js +20 -4
  140. package/libx/_runtime/remote/utils/client-types.d.ts +7 -0
  141. package/libx/_runtime/remote/utils/client.js +117 -23
  142. package/libx/_runtime/sqlite/Service.js +2 -2
  143. package/libx/_runtime/sqlite/convertAssocToOneManaged.js +1 -3
  144. package/libx/gql/GraphQLAdapter.js +33 -0
  145. package/libx/gql/constants/adapter.js +69 -0
  146. package/libx/gql/constants/cds.js +18 -0
  147. package/libx/gql/constants/graphql.js +33 -0
  148. package/libx/gql/resolvers/crud/create.js +15 -0
  149. package/libx/gql/resolvers/crud/delete.js +24 -0
  150. package/libx/gql/resolvers/crud/index.js +6 -0
  151. package/libx/gql/resolvers/crud/read.js +25 -0
  152. package/libx/gql/resolvers/crud/update.js +31 -0
  153. package/libx/gql/resolvers/crud/utils/index.js +36 -0
  154. package/libx/gql/resolvers/field.js +5 -0
  155. package/libx/gql/resolvers/index.js +7 -0
  156. package/libx/gql/resolvers/mutation.js +23 -0
  157. package/libx/gql/resolvers/parse/ast/enrich.js +51 -0
  158. package/libx/gql/resolvers/parse/ast/fragment.js +11 -0
  159. package/libx/gql/resolvers/parse/ast/fromObject.js +39 -0
  160. package/libx/gql/resolvers/parse/ast/index.js +3 -0
  161. package/libx/gql/resolvers/parse/ast/meta.js +4 -0
  162. package/libx/gql/resolvers/parse/ast/variable.js +7 -0
  163. package/libx/gql/resolvers/parse/ast2cqn/columns.js +42 -0
  164. package/libx/gql/resolvers/parse/ast2cqn/entries.js +31 -0
  165. package/libx/gql/resolvers/parse/ast2cqn/index.js +8 -0
  166. package/libx/gql/resolvers/parse/ast2cqn/limit.js +6 -0
  167. package/libx/gql/resolvers/parse/ast2cqn/orderBy.js +24 -0
  168. package/libx/gql/resolvers/parse/ast2cqn/utils/index.js +3 -0
  169. package/libx/gql/resolvers/parse/ast2cqn/where.js +70 -0
  170. package/libx/gql/resolvers/parse/utils/index.js +8 -0
  171. package/libx/gql/resolvers/query.js +13 -0
  172. package/libx/gql/resolvers/root.js +34 -0
  173. package/libx/gql/schema/generate.js +18 -0
  174. package/libx/gql/schema/index.js +5 -0
  175. package/libx/gql/schema/mutation.js +76 -0
  176. package/libx/gql/schema/query.js +108 -0
  177. package/libx/gql/schema/typeDefMap.js +45 -0
  178. package/libx/gql/schema/utils/index.js +54 -0
  179. package/libx/gql/utils/index.js +12 -0
  180. package/libx/{_runtime/odata/cqn2odata.js → odata/cqn2odata/index.js} +39 -100
  181. package/libx/odata/index.js +80 -0
  182. package/libx/odata/odata2cqn/afterburner.js +170 -0
  183. package/libx/{_runtime/odata/odata2cqn.pegjs → odata/odata2cqn/grammar.pegjs} +102 -123
  184. package/libx/odata/odata2cqn/index.js +3 -0
  185. package/libx/odata/odata2cqn/parser.js +1 -0
  186. package/libx/odata/utils/index.js +64 -0
  187. package/libx/rest/RestAdapter.js +101 -0
  188. package/libx/rest/RestRequest.js +30 -0
  189. package/libx/rest/index.js +3 -0
  190. package/libx/rest/middleware/auth.js +22 -0
  191. package/libx/rest/middleware/content.js +15 -0
  192. package/libx/rest/middleware/create.js +40 -0
  193. package/libx/rest/middleware/delete.js +20 -0
  194. package/libx/rest/middleware/error.js +56 -0
  195. package/libx/rest/middleware/operation.js +39 -0
  196. package/libx/rest/middleware/parse.js +90 -0
  197. package/libx/rest/middleware/read.js +29 -0
  198. package/libx/rest/middleware/update.js +42 -0
  199. package/libx/rest/utils/data.js +65 -0
  200. package/package.json +4 -1
  201. package/server.js +29 -7
  202. package/libx/_runtime/cds-services/services/utils/diff.js +0 -53
  203. package/libx/_runtime/cds-services/util/auditlog.js +0 -247
  204. package/libx/_runtime/cds-services/util/xsenv.js +0 -51
  205. package/libx/_runtime/common/utils/backlinks.js +0 -83
  206. package/libx/_runtime/common/utils/rewriteAsterisk.js +0 -72
  207. package/libx/_runtime/odata/index.js +0 -55
  208. package/libx/_runtime/odata/odata2cqn.js +0 -1
  209. package/libx/_runtime/odata/readToCqn.js +0 -129
  210. 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,16 @@ 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 (selectEntry[element.name] === null && Object.keys(entry[element.name]).length === 0) {
212
+ continue
213
+ }
211
214
  _addToData(selectSubData, entity, element, selectEntry)
212
215
  }
216
+
217
+ _addToData(subData, entity, element, entry)
213
218
  }
214
219
  }
215
220
  _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
 
@@ -204,9 +204,10 @@ const _findTableName = (ref, aliases) => {
204
204
 
205
205
  const _getTableForColumn = (col, aliases, model) => {
206
206
  for (let i = 0; i < aliases.length; i++) {
207
- const alias = aliases[i]
207
+ const index = aliases.length - i - 1
208
+ const alias = aliases[index]
208
209
  if (Object.keys(model.definitions[alias].elements).includes(col)) {
209
- return { index: i, table: alias.replace(/\./g, '_') }
210
+ return { index, table: alias.replace(/\./g, '_') }
210
211
  }
211
212
  }
212
213
 
@@ -549,13 +550,18 @@ const _addNormalizedRestrict = (restrict, restricts, definition, definitions) =>
549
550
  if (where) {
550
551
  // operate on a copy
551
552
  let _where = where
552
- const paths = where.match(/ (\w*)\.(\w*)/g) || []
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 !== ' ')
553
555
  for (let i = 0; i < paths.length; i++) {
554
556
  const parts = paths[i].trim().split('.')
555
557
  let current = definition
556
558
  while (parts.length) {
557
559
  current = current.elements[parts.shift()]
558
- if (current.is2many) _where = _where.replace(current.name + '.', current.name + '[exists ') + ']'
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
+ }
559
565
  if (current.target) current = definitions[current.target]
560
566
  }
561
567
  }
@@ -882,19 +888,15 @@ const _secureDependentEntities = srv => {
882
888
  }
883
889
  }
884
890
 
885
- module.exports = function () {
886
- /*
887
- * @restrict, @requires, @readonly, @insertonly, and @Capabilities for entities
888
- */
891
+ module.exports = cds.service.impl(function () {
892
+ // @restrict, @requires, @readonly, @insertonly, and @Capabilities for entities
889
893
  _secureDependentEntities(this)
890
894
  for (const k in this.entities) {
891
895
  const entity = this.entities[k]
892
896
  if (!_authDependsOnParents(entity)) _registerAuthHandlers(entity, this)
893
897
  }
894
898
 
895
- /*
896
- * @restrict and @requires for operations
897
- */
899
+ // @restrict and @requires for operations
898
900
  for (const k in this.operations) {
899
901
  const operation = this.operations[k]
900
902
 
@@ -904,4 +906,4 @@ module.exports = function () {
904
906
  // @restrict
905
907
  _registerOperationRestrictHandlers(operation, this)
906
908
  }
907
- }
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
+ })