@sap/cds 5.5.5 → 5.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/CHANGELOG.md +107 -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 +3 -1
  14. package/lib/env/index.js +175 -41
  15. package/lib/env/requires.js +16 -1
  16. package/lib/i18n/localize.js +31 -4
  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 +10 -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/to.js +6 -2
  46. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +3 -34
  47. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -3
  48. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +48 -18
  49. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +10 -5
  50. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +1 -1
  51. package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +1 -1
  52. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +1 -3
  53. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +14 -19
  54. package/libx/_runtime/cds-services/services/utils/columns.js +6 -1
  55. package/libx/_runtime/cds-services/services/utils/compareJson.js +1 -8
  56. package/libx/_runtime/cds-services/services/utils/differ.js +7 -26
  57. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -4
  58. package/libx/_runtime/cds-services/util/assert.js +29 -13
  59. package/libx/_runtime/cds.js +2 -1
  60. package/libx/_runtime/common/aspects/Association.js +72 -0
  61. package/libx/_runtime/common/aspects/any.js +8 -45
  62. package/libx/_runtime/common/aspects/entity.js +0 -1
  63. package/libx/_runtime/common/aspects/relation.js +40 -0
  64. package/libx/_runtime/common/aspects/utils.js +73 -1
  65. package/libx/_runtime/common/auth/strategies/utils/uaa.js +1 -12
  66. package/libx/_runtime/common/composition/data.js +3 -2
  67. package/libx/_runtime/common/composition/delete.js +3 -1
  68. package/libx/_runtime/common/composition/tree.js +23 -18
  69. package/libx/_runtime/common/composition/utils.js +34 -8
  70. package/libx/_runtime/common/error/frontend.js +6 -1
  71. package/libx/_runtime/common/generic/auth.js +5 -9
  72. package/libx/_runtime/common/generic/crud.js +2 -2
  73. package/libx/_runtime/common/generic/etag.js +11 -8
  74. package/libx/_runtime/common/generic/input.js +3 -3
  75. package/libx/_runtime/common/generic/paging.js +9 -5
  76. package/libx/_runtime/common/generic/put.js +3 -2
  77. package/libx/_runtime/common/generic/sorting.js +3 -3
  78. package/libx/_runtime/common/generic/temporal.js +3 -3
  79. package/libx/_runtime/common/utils/cqn.js +20 -1
  80. package/libx/_runtime/common/utils/cqn2cqn4sql.js +125 -139
  81. package/libx/_runtime/common/utils/csn.js +50 -52
  82. package/libx/_runtime/common/utils/foreignKeyPropagations.js +41 -176
  83. package/libx/_runtime/common/utils/generateOnCond.js +40 -70
  84. package/libx/_runtime/common/utils/{enrichWithKeysFromWhere.js → keys.js} +29 -28
  85. package/libx/_runtime/common/utils/postProcessing.js +3 -0
  86. package/libx/_runtime/common/utils/propagateForeignKeys.js +84 -0
  87. package/libx/_runtime/common/utils/resolveStructured.js +1 -1
  88. package/libx/_runtime/common/utils/resolveView.js +7 -5
  89. package/libx/_runtime/common/utils/rewriteAsterisks.js +94 -0
  90. package/libx/_runtime/common/utils/search2cqn4sql.js +9 -8
  91. package/libx/_runtime/common/utils/template.js +54 -46
  92. package/libx/_runtime/db/Service.js +9 -2
  93. package/libx/_runtime/db/expand/expandCQNToJoin.js +10 -24
  94. package/libx/_runtime/db/expand/rawToExpanded.js +2 -1
  95. package/libx/_runtime/db/generic/create.js +1 -0
  96. package/libx/_runtime/db/generic/input.js +7 -11
  97. package/libx/_runtime/db/generic/integrity.js +2 -2
  98. package/libx/_runtime/db/generic/rewrite.js +2 -5
  99. package/libx/_runtime/db/generic/update.js +1 -0
  100. package/libx/_runtime/db/query/read.js +9 -4
  101. package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -2
  102. package/libx/_runtime/db/sql-builder/annotations.js +1 -0
  103. package/libx/_runtime/db/utils/columns.js +14 -43
  104. package/libx/_runtime/fiori/generic/activate.js +3 -2
  105. package/libx/_runtime/fiori/generic/before.js +2 -2
  106. package/libx/_runtime/fiori/generic/cancel.js +3 -2
  107. package/libx/_runtime/fiori/generic/delete.js +3 -2
  108. package/libx/_runtime/fiori/generic/edit.js +2 -2
  109. package/libx/_runtime/fiori/generic/new.js +2 -2
  110. package/libx/_runtime/fiori/generic/patch.js +2 -2
  111. package/libx/_runtime/fiori/generic/prepare.js +2 -2
  112. package/libx/_runtime/fiori/generic/read.js +17 -63
  113. package/libx/_runtime/fiori/generic/readOverDraft.js +4 -4
  114. package/libx/_runtime/fiori/uiflex/extensibility/index.cds +15 -0
  115. package/libx/_runtime/fiori/uiflex/extensibility/index.js +148 -0
  116. package/libx/_runtime/fiori/uiflex/handler/transformREAD.js +119 -0
  117. package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +43 -0
  118. package/libx/_runtime/fiori/uiflex/handler/transformWRITE.js +62 -0
  119. package/libx/_runtime/fiori/uiflex/index.js +35 -0
  120. package/libx/_runtime/fiori/uiflex/utils.js +78 -0
  121. package/libx/_runtime/fiori/utils/handler.js +3 -13
  122. package/libx/_runtime/fiori/utils/where.js +6 -1
  123. package/libx/_runtime/hana/pool.js +12 -11
  124. package/libx/_runtime/hana/search2cqn4sql.js +34 -43
  125. package/libx/_runtime/hana/searchToContains.js +3 -3
  126. package/libx/_runtime/index.js +5 -2
  127. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  128. package/libx/_runtime/messaging/common-utils/AMQPClient.js +9 -1
  129. package/libx/_runtime/messaging/common-utils/connections.js +11 -14
  130. package/libx/_runtime/messaging/common-utils/naming-conventions.js +1 -1
  131. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -1
  132. package/libx/_runtime/messaging/message-queuing.js +18 -0
  133. package/libx/_runtime/remote/Service.js +14 -2
  134. package/libx/_runtime/remote/utils/client-types.d.ts +7 -0
  135. package/libx/_runtime/remote/utils/client.js +117 -23
  136. package/libx/_runtime/sqlite/Service.js +2 -2
  137. package/libx/_runtime/sqlite/convertAssocToOneManaged.js +1 -3
  138. package/libx/gql/GraphQLAdapter.js +33 -0
  139. package/libx/gql/constants/adapter.js +69 -0
  140. package/libx/gql/constants/cds.js +18 -0
  141. package/libx/gql/constants/graphql.js +33 -0
  142. package/libx/gql/resolvers/crud/create.js +15 -0
  143. package/libx/gql/resolvers/crud/delete.js +24 -0
  144. package/libx/gql/resolvers/crud/index.js +6 -0
  145. package/libx/gql/resolvers/crud/read.js +25 -0
  146. package/libx/gql/resolvers/crud/update.js +31 -0
  147. package/libx/gql/resolvers/crud/utils/index.js +36 -0
  148. package/libx/gql/resolvers/field.js +5 -0
  149. package/libx/gql/resolvers/index.js +7 -0
  150. package/libx/gql/resolvers/mutation.js +23 -0
  151. package/libx/gql/resolvers/parse/ast/enrich.js +51 -0
  152. package/libx/gql/resolvers/parse/ast/fragment.js +11 -0
  153. package/libx/gql/resolvers/parse/ast/fromObject.js +39 -0
  154. package/libx/gql/resolvers/parse/ast/index.js +3 -0
  155. package/libx/gql/resolvers/parse/ast/meta.js +4 -0
  156. package/libx/gql/resolvers/parse/ast/variable.js +7 -0
  157. package/libx/gql/resolvers/parse/ast2cqn/columns.js +42 -0
  158. package/libx/gql/resolvers/parse/ast2cqn/entries.js +31 -0
  159. package/libx/gql/resolvers/parse/ast2cqn/index.js +8 -0
  160. package/libx/gql/resolvers/parse/ast2cqn/limit.js +6 -0
  161. package/libx/gql/resolvers/parse/ast2cqn/orderBy.js +24 -0
  162. package/libx/gql/resolvers/parse/ast2cqn/utils/index.js +3 -0
  163. package/libx/gql/resolvers/parse/ast2cqn/where.js +70 -0
  164. package/libx/gql/resolvers/parse/utils/index.js +8 -0
  165. package/libx/gql/resolvers/query.js +13 -0
  166. package/libx/gql/resolvers/root.js +34 -0
  167. package/libx/gql/schema/generate.js +18 -0
  168. package/libx/gql/schema/index.js +5 -0
  169. package/libx/gql/schema/mutation.js +76 -0
  170. package/libx/gql/schema/query.js +108 -0
  171. package/libx/gql/schema/typeDefMap.js +45 -0
  172. package/libx/gql/schema/utils/index.js +54 -0
  173. package/libx/gql/utils/index.js +12 -0
  174. package/libx/{_runtime/odata/cqn2odata.js → odata/cqn2odata/index.js} +39 -100
  175. package/libx/odata/index.js +80 -0
  176. package/libx/odata/odata2cqn/afterburner.js +170 -0
  177. package/libx/{_runtime/odata/odata2cqn.pegjs → odata/odata2cqn/grammar.pegjs} +102 -123
  178. package/libx/odata/odata2cqn/index.js +3 -0
  179. package/libx/odata/odata2cqn/parser.js +1 -0
  180. package/libx/odata/utils/index.js +64 -0
  181. package/libx/rest/RestAdapter.js +101 -0
  182. package/libx/rest/RestRequest.js +30 -0
  183. package/libx/rest/index.js +3 -0
  184. package/libx/rest/middleware/auth.js +22 -0
  185. package/libx/rest/middleware/content.js +15 -0
  186. package/libx/rest/middleware/create.js +40 -0
  187. package/libx/rest/middleware/delete.js +20 -0
  188. package/libx/rest/middleware/error.js +56 -0
  189. package/libx/rest/middleware/operation.js +39 -0
  190. package/libx/rest/middleware/parse.js +90 -0
  191. package/libx/rest/middleware/read.js +29 -0
  192. package/libx/rest/middleware/update.js +42 -0
  193. package/libx/rest/utils/data.js +65 -0
  194. package/package.json +4 -1
  195. package/server.js +20 -2
  196. package/libx/_runtime/cds-services/services/utils/diff.js +0 -53
  197. package/libx/_runtime/cds-services/util/auditlog.js +0 -247
  198. package/libx/_runtime/cds-services/util/xsenv.js +0 -51
  199. package/libx/_runtime/common/utils/backlinks.js +0 -83
  200. package/libx/_runtime/common/utils/rewriteAsterisk.js +0 -72
  201. package/libx/_runtime/odata/index.js +0 -55
  202. package/libx/_runtime/odata/odata2cqn.js +0 -1
  203. package/libx/_runtime/odata/readToCqn.js +0 -129
  204. package/libx/_runtime/remote/cqn2odata/index.js +0 -2
@@ -1,10 +1,7 @@
1
- const cds = require('../../../../cds')
2
-
3
1
  const {
4
2
  Components: { DATA_DELETE_HANDLER, DATA_READ_HANDLER, DATA_CREATE_HANDLER, DATA_UPDATE_HANDLER }
5
3
  } = require('../okra/odata-server')
6
4
 
7
- const { getOnCond } = require('../../../../common/utils/generateOnCond')
8
5
  const { findCsnTargetFor } = require('../../../../common/utils/csn')
9
6
  const { isStreaming } = require('./stream')
10
7
  const { deepCopyObject, deepCopyArray } = require('../../../../common/utils/copy')
@@ -106,27 +103,6 @@ function _entityOrTypeName(navSourceSegment) {
106
103
  .getFullQualifiedName()
107
104
  }
108
105
 
109
- const _getNavigationInfoX4 = (navProperty, navSourceSegment, csn) => {
110
- const { name, namespace } = _entityOrTypeName(navSourceSegment)
111
- const def = findCsnTargetFor(name, csn, namespace)
112
-
113
- const navigationName = navProperty.getName()
114
- const navigationDefinition = def.elements[navigationName]
115
- return { navigationName, navigationDefinition }
116
- }
117
-
118
- const _getNavigationInfo = (navProperty, navSourceSegment, csn) => {
119
- if (cds.env.effective.odata.proxies) {
120
- return _getNavigationInfoX4(navProperty, navSourceSegment, csn)
121
- }
122
-
123
- const { name, namespace } = _entityOrTypeName(navSourceSegment)
124
- const def = findCsnTargetFor(name, csn, namespace)
125
- const navigationName = navProperty.getName()
126
- const navigationDefinition = def.elements[navigationName]
127
- return { navigationName, navigationDefinition }
128
- }
129
-
130
106
  const _addForeignKeys = (service, req, data) => {
131
107
  const pathSegments = req.getUriInfo().getPathSegments()
132
108
  // retrieve keys/values from the path segment representing the navigation source
@@ -149,16 +125,9 @@ const _addForeignKeys = (service, req, data) => {
149
125
  }
150
126
  }
151
127
  } else {
152
- const { navigationName, navigationDefinition } = _getNavigationInfo(navProperty, navSourceSegment, service.model)
153
- const onConditionOptions = {
154
- associationNames: navigationName,
155
- csn: service.model,
156
- aliases: {
157
- select: 'target',
158
- join: 'source'
159
- }
160
- }
161
- const onCondition = getOnCond(navigationDefinition, onConditionOptions)
128
+ const { name, namespace } = _entityOrTypeName(navSourceSegment)
129
+ const def = findCsnTargetFor(name, service.model, namespace)
130
+ const onCondition = def._relations[navProperty.getName()].join('target', 'source')
162
131
  _addKeysToData(navSourceKeyValues, onCondition, data)
163
132
  }
164
133
  }
@@ -23,14 +23,14 @@ const _selectForFunction = (selectColumns, result, opReturnType) => {
23
23
 
24
24
  const { ensureDraftsSuffix, isDraftActivateAction } = require('../../../../fiori/utils/handler')
25
25
 
26
- const _expand = (model, uriInfo) => {
26
+ const _expand = (model, uriInfo, options) => {
27
27
  const expand = uriInfo.getQueryOption(QueryOptions.EXPAND)
28
28
 
29
29
  if (!expand || expand.length === 0) {
30
30
  return []
31
31
  }
32
32
 
33
- return expandToCQN(model, expand, uriInfo.getFinalEdmType())
33
+ return expandToCQN(model, expand, uriInfo.getFinalEdmType(), options)
34
34
  }
35
35
 
36
36
  const _expandForFunction = async (uriInfo, result, req, srv, opReturnType) => {
@@ -52,7 +52,7 @@ const _expandForFunction = async (uriInfo, result, req, srv, opReturnType) => {
52
52
  selectQuery.where(key, '=', row[key])
53
53
  }
54
54
 
55
- const expandCqn = _expand(srv.model, uriInfo)
55
+ const expandCqn = _expand(srv.model, uriInfo, { rewriteAsterisks: true })
56
56
  selectQuery.columns(expandCqn)
57
57
 
58
58
  const res = await cds.tx(req).run(selectQuery)
@@ -2,7 +2,6 @@ const cds = require('../../../../cds')
2
2
  const getTemplate = require('../../../../common/utils/template')
3
3
  const templateProcessor = require('../../../../common/utils/templateProcessor')
4
4
  const { big } = require('@sap/cds-foss')
5
- const { isBacklink } = require('../../../../common/utils/backlinks')
6
5
  const { omitValue, applyOmitValuesPreference } = require('./omitValues')
7
6
 
8
7
  const METADATA = {
@@ -157,30 +156,28 @@ const _getParent = (model, name) => {
157
156
  if (target && target.elements) {
158
157
  for (const elementName in target.elements) {
159
158
  const element = target.elements[elementName]
160
- const assoc = element.target && model.definitions[element.target]
161
- if (assoc && element._isAssociationStrict && isBacklink(element, assoc, true)) return assoc
159
+ if (element._anchor && element._anchor._isContained) return element._anchor
162
160
  }
163
161
  }
164
162
 
165
163
  return null
166
164
  }
167
165
 
168
- const _isUpAssoc = (element, parent) =>
169
- element &&
170
- cds.env.effective.odata.containment &&
171
- /^up_(_up_)*$/.test(element.name) &&
172
- _isContainedOrBackLink(element, parent)
166
+ const _isUpAssoc = element => element && /^up_(_up_)*$/.test(element.name) && _isContainedOrBackLink(element)
173
167
 
174
- const _isContainedOrBackLink = (element, parent) =>
175
- element && element.isAssociation && element.keys && (element._isContained || isBacklink(element, parent, true))
168
+ const _isContainedOrBackLink = element =>
169
+ element &&
170
+ element.isAssociation &&
171
+ element.keys &&
172
+ (element._isContained || (element._anchor && element._anchor._isContained))
176
173
 
177
- const _assocs = (element, target, parent) => {
174
+ const _assocs = (element, target) => {
178
175
  const assocName = element['@odata.foreignKey4']
179
176
  const assoc = assocName && target.elements[assocName]
180
177
 
181
178
  if (cds.env.effective.odata.refs) {
182
179
  // expand assoc keys except of up_ backlinks
183
- if (element['@odata.foreignKey4'] && !_isUpAssoc(assoc, parent)) {
180
+ if (element['@odata.foreignKey4'] && !_isUpAssoc(assoc)) {
184
181
  return ['@odata.foreignKey4']
185
182
  }
186
183
 
@@ -189,7 +186,7 @@ const _assocs = (element, target, parent) => {
189
186
  }
190
187
  }
191
188
 
192
- if (_isContainedOrBackLink(assoc, parent)) {
189
+ if (_isContainedOrBackLink(assoc)) {
193
190
  return ['@cleanup']
194
191
  }
195
192
 
@@ -201,12 +198,12 @@ const _pick = options => (element, target, parent) => {
201
198
 
202
199
  if (element['@odata.etag']) categories.push('@odata.etag')
203
200
  if (element.type === 'cds.Decimal') categories.push('@cds.Decimal')
204
- categories.push(..._assocs(element, target, parent))
201
+ categories.push(..._assocs(element, target))
205
202
  if (options.omitValuesPreference) categories.push('@odata.omitValues')
206
203
  if (categories.length) return { categories }
207
204
  }
208
205
 
209
- const _getPostProcessOptions = headers => {
206
+ const _getOptions = headers => {
210
207
  const options = {
211
208
  decimals: null,
212
209
  omitValuesPreference: null
@@ -231,14 +228,23 @@ const _getPostProcessOptions = headers => {
231
228
  return options
232
229
  }
233
230
 
231
+ const _generateCacheKey = (headers, options) => {
232
+ let key = 'postProcess' // default template cache key for post processing
233
+ if (headers.prefer) key += `:${headers.prefer}`
234
+ if (options.decimals) key += `:exponentialDecimals=true`
235
+ return key
236
+ }
237
+
234
238
  const postProcess = (req, res, service, result, previousResult) => {
235
239
  const { model } = service
236
240
  const { headers, target } = req
237
241
 
238
242
  if (!target || !result || !model || !model.definitions[target.name]) return
239
243
 
240
- const options = _getPostProcessOptions(headers)
241
- const template = getTemplate('postProcess', service, target, { pick: _pick(options) }, _getParent(model, target.name))
244
+ const options = _getOptions(headers)
245
+ const cacheKey = _generateCacheKey(headers, options)
246
+ const parent = _getParent(model, target.name)
247
+ const template = getTemplate(cacheKey, service, target, { pick: _pick(options) }, parent)
242
248
 
243
249
  if (template.elements.size === 0) return
244
250
 
@@ -265,7 +271,31 @@ const postProcess = (req, res, service, result, previousResult) => {
265
271
  applyOmitValuesPreference(res, options.omitValuesPreference)
266
272
  }
267
273
 
274
+ const postProcessMinimal = (req, result) => {
275
+ const { target } = req
276
+
277
+ if (!target || !result) return
278
+
279
+ const etagElement = Object.values(target.elements).find(el => el['@odata.etag'])
280
+
281
+ if (!etagElement) return
282
+
283
+ const etag = etagElement.name
284
+
285
+ // normalize result to rows
286
+ result = result.value && Object.keys(result).filter(k => !k.match(/^\W/)).length === 1 ? result.value : result
287
+ const rows = Array.isArray(result) ? result : [result]
288
+
289
+ // process each row
290
+ for (const row of rows) {
291
+ if (typeof row !== 'object' || !Object.prototype.hasOwnProperty.call(row, etag)) return
292
+
293
+ addEtags(row, etag)
294
+ }
295
+ }
296
+
268
297
  module.exports = {
269
298
  toODataResult,
270
- postProcess
299
+ postProcess,
300
+ postProcessMinimal
271
301
  }
@@ -1,7 +1,8 @@
1
1
  const cds = require('../../../../cds')
2
2
  const { SELECT } = cds.ql
3
- const { adaptStreamCQN } = require('../../../../fiori/utils/handler')
4
- const cqn2cqn4sql = require('../../../../common/utils/cqn2cqn4sql')
3
+ const { isActiveEntityRequested } = require('../../../../fiori/utils/where')
4
+ const { ensureDraftsSuffix } = require('../../../../fiori/utils/handler')
5
+ const { cqn2cqn4sql } = require('../../../../common/utils/cqn2cqn4sql')
5
6
  const { findCsnTargetFor } = require('../../../../common/utils/csn')
6
7
 
7
8
  const isStreaming = segments => {
@@ -14,6 +15,8 @@ const isStreaming = segments => {
14
15
  }
15
16
 
16
17
  const _getProperties = async (properties, req) => {
18
+ const isActiveRequested = isActiveEntityRequested(req.query.SELECT.from.ref[0].where)
19
+
17
20
  // REVISIT DRAFT HANDLING: cqn2cqn4sql should not happen here, but adaptStreamCQN relies on exists clause
18
21
  const cqn = cqn2cqn4sql(SELECT.one(req.query.SELECT.from), req._model).columns(properties)
19
22
 
@@ -22,7 +25,9 @@ const _getProperties = async (properties, req) => {
22
25
  cqn.SELECT.from.ref[0] = req.target.query._target.name
23
26
  }
24
27
 
25
- adaptStreamCQN(cqn)
28
+ if (!isActiveRequested) {
29
+ cqn.SELECT.from.ref[0] = ensureDraftsSuffix(cqn.SELECT.from.ref[0])
30
+ }
26
31
 
27
32
  try {
28
33
  return await cds.tx(req).run(cqn)
@@ -44,7 +49,7 @@ const _getDynamicProperties = (contentType, contentDisposition) => {
44
49
  }
45
50
 
46
51
  const getStreamProperties = async (segments, srv, req) => {
47
- // REVISIT: we need to read direcly from db, which might not be there!
52
+ // REVISIT: we need to read directly from db, which might not be there!
48
53
  if (!cds.db) return {}
49
54
 
50
55
  let contentType, entityName, namespace, contentDisposition
@@ -52,7 +57,7 @@ const getStreamProperties = async (segments, srv, req) => {
52
57
  if (previous.getKind() === 'ENTITY') {
53
58
  entityName = previous.getEntitySet().getName()
54
59
  namespace = previous.getEdmType().getFullQualifiedName().namespace
55
- } else if (previous.getKind() === 'NAVIGATION.TO.ONE') {
60
+ } else if (previous.getKind() === 'NAVIGATION.TO.ONE' && previous.getTarget()) {
56
61
  entityName = previous.getTarget().getName()
57
62
  namespace = previous.getTarget().getEntityType().getFullQualifiedName().namespace
58
63
  }
@@ -39,7 +39,7 @@ module.exports = service => {
39
39
  if (!operation.returns) {
40
40
  status = 204
41
41
  } else {
42
- validateReturnType(req, operation, result)
42
+ validateReturnType(operation, result)
43
43
  bufferToBase64(result, segments[0])
44
44
  body = _convertCustomOperationReturnValue(operation.returns, result)
45
45
  }
@@ -23,7 +23,7 @@ const _updateThenCreate = async (parsed, restReq, restRes, tx) => {
23
23
  req = new RestRequest(parsed, _getData(parsed, restReq), restReq, restRes, tx)
24
24
  result = await tx.dispatch(req)
25
25
  } catch (e) {
26
- if (e.code === 404 && UPSERT_ALLOWED) {
26
+ if ((e.code === 404 || e.status === 404 || e.statusCode === 404) && UPSERT_ALLOWED) {
27
27
  // REVISIT: remove error (and child?) from tx.context? -> would require a unique req.id
28
28
 
29
29
  parsed.event = 'CREATE'
@@ -1,7 +1,5 @@
1
1
  const cds = require('../../../../cds')
2
2
 
3
- const { _newReadToCQN } = require('../../../../odata/readToCqn')
4
-
5
3
  const { INSERT, SELECT, UPDATE, DELETE } = cds.ql
6
4
 
7
5
  const { createCqlString } = require('./utils')
@@ -47,7 +45,7 @@ module.exports = (parsed, data, restReq, service) => {
47
45
  case 'CREATE':
48
46
  return INSERT.into(target).entries(data)
49
47
  case 'READ':
50
- return odata2cqn ? _newReadToCQN(service, target, restReq) : _readToCQN(parsed, target, restReq)
48
+ return odata2cqn ? cds.odata.parse(restReq, { service }) : _readToCQN(parsed, target, restReq)
51
49
  case 'UPDATE':
52
50
  return UPDATE(createCqlString(target, key, value)).data(data)
53
51
  case 'DELETE':
@@ -39,25 +39,18 @@ const validationChecks = (event, data, target) => {
39
39
  const _enrichErrorDetails = (isPrimitive, error) => {
40
40
  const element = error.target ? ` '${error.target}' ` : ' '
41
41
  const typeDetails = isPrimitive ? '.' : ` according to type definition '${error.type}'.`
42
- return `Value '${error.value}' of element${element}is invalid${typeDetails}`
42
+ const value = typeof error.value === 'string' ? `'${error.value}'` : error.value
43
+ if (element && element.match(/\w/)) return `Value ${value} of element${element}is invalid${typeDetails}`
44
+ return `Value ${value} is invalid${typeDetails}`
43
45
  }
44
46
 
45
47
  // REVISIT: use i18n
46
- const _buildErrorMessage = (context, operation, type, typeErrors) => {
47
- return `Failed to validate return value ${type ? `of type '${type}' ` : ''}for custom ${operation.kind} '${
48
- context.event
48
+ const _getTypeError = (operation, type, errorDetails) => {
49
+ const typeErrors = errorDetails.map(error => _enrichErrorDetails(cds.builtin.types[type], error))
50
+ const msg = `Failed to validate return value ${type ? `of type '${type}' ` : ''}for custom ${operation.kind} '${
51
+ operation.name
49
52
  }': ${typeErrors.join(' ')}`
50
- }
51
-
52
- const _getTypeError = (context, operation, type, errorDetails) => {
53
- return getError(
54
- _buildErrorMessage(
55
- context,
56
- operation,
57
- type,
58
- errorDetails.map(error => _enrichErrorDetails(cds.builtin.types[type], error))
59
- )
60
- )
53
+ return getError(msg)
61
54
  }
62
55
 
63
56
  const _buildTypeErrorObject = (type, value) => {
@@ -79,14 +72,13 @@ const _checkSingle = (type, check, data) => {
79
72
  * Validate the return type values of custom operations (actions and functions) for primitive or complex values as
80
73
  * single values or arrays.
81
74
  *
82
- * @param {Context} context
83
75
  * @param {Operation} operation
84
76
  * @param {object} data
85
77
  * @throws Will throw an error with error code 500 if the validation fails. Contains a detailed error message of the
86
78
  * type and name of the custom operation, the invalid values, their names and their expected types.
87
79
  * @returns {boolean} Returns true if return type validation has passed.
88
80
  */
89
- const validateReturnType = (context, operation, data) => {
81
+ const validateReturnType = (operation, data) => {
90
82
  // array of or single return type
91
83
  // in case of modeled return type: { type: 'bookModel.Books', _type: csnDefinition }
92
84
  // in case of inline return type: { elements: ... } and no explicit name of return type
@@ -118,10 +110,13 @@ const validateReturnType = (context, operation, data) => {
118
110
  }
119
111
 
120
112
  if (checkResult.length !== 0) {
121
- throw _getTypeError(context, operation, returnType.type, checkResult)
113
+ throw _getTypeError(operation, returnType.type, checkResult)
122
114
  }
123
115
 
124
116
  return true
125
117
  }
126
118
 
127
- module.exports = { validationChecks, validateReturnType }
119
+ module.exports = {
120
+ validationChecks,
121
+ validateReturnType
122
+ }
@@ -127,7 +127,12 @@ const computeColumnsToBeSearched = (cqn, entity = { _searchableColumns: [] }) =>
127
127
  cqn.SELECT.columns.forEach(column => {
128
128
  if (column.func) {
129
129
  // exclude $count by SELECT of number of Items in a Collection
130
- if (cqn.SELECT.columns.length === 1 && column.func === 'count' && column.as === '_counted_') return
130
+ if (
131
+ cqn.SELECT.columns.length === 1 &&
132
+ column.func === 'count' &&
133
+ (column.as === '_counted_' || column.as === '$count')
134
+ )
135
+ return
131
136
  toBeSearched.push(column)
132
137
  return
133
138
  }
@@ -141,15 +141,8 @@ const _addKeysToEntryIfNotExists = (keys, newEntry) => {
141
141
  }
142
142
  }
143
143
 
144
- const isSelfManaged = element => {
145
- if (element.on && element.on.length > 2) {
146
- return element.on[0].ref[0] === '$self' || element.on[2].ref[0] === '$self'
147
- }
148
- return false
149
- }
150
-
151
144
  const _isUnManaged = element => {
152
- return element.on && !isSelfManaged(element)
145
+ return element.on && !element._isSelfManaged
153
146
  }
154
147
 
155
148
  const _skip = (entity, prop) => entity.elements[prop]._target._hasPersistenceSkip
@@ -7,8 +7,9 @@ const { selectDeepUpdateData } = require('../../../common/composition')
7
7
  const { ensureDraftsSuffix } = require('../../../fiori/utils/handler')
8
8
 
9
9
  const { DRAFT_COLUMNS } = require('../../../common/constants/draft')
10
- const cqn2cqn4sql = require('../../../common/utils/cqn2cqn4sql')
10
+ const { cqn2cqn4sql, convertPathExpressionToWhere } = require('../../../common/utils/cqn2cqn4sql')
11
11
  const { revertData } = require('../../../common/utils/resolveView')
12
+ const { removeIsActiveEntityRecursively } = require('../../../fiori/utils/where')
12
13
 
13
14
  module.exports = class {
14
15
  constructor(srv) {
@@ -32,31 +33,11 @@ module.exports = class {
32
33
  return columns
33
34
  }
34
35
 
35
- _createWhereCondition(entity, data) {
36
- return Object.keys(entity.keys).reduce((prev, curr) => {
37
- if (!DRAFT_COLUMNS.includes(curr)) {
38
- prev[curr] = data[curr]
39
- }
40
-
41
- return prev
42
- }, {})
43
- }
44
-
45
36
  _diffDelete(req) {
46
37
  const { DELETE } = (req._ && req._.query) || req.query
47
38
  const query = SELECT.from(DELETE.from).columns(this._createSelectColumnsForDelete(req.target))
48
39
  if (DELETE.where) query.where(...DELETE.where)
49
40
 
50
- // REVISIT: should be done in cqn2cqn4sql
51
- if (req.target._isDraftEnabled && query.SELECT.from.ref.some(r => r.where)) {
52
- query.SELECT.from.ref.forEach((r, i) => {
53
- if (!r.where) return
54
- const j = r.where.findIndex(w => w.ref && w.ref.some(r => r === 'IsActiveEntity'))
55
- if (j === -1) return
56
- r.where.splice(Math.max(j - 1, 0), 4)
57
- })
58
- }
59
-
60
41
  return cds
61
42
  .tx(req)
62
43
  .run(query)
@@ -92,14 +73,14 @@ module.exports = class {
92
73
 
93
74
  async _diffPatch(req, providedData) {
94
75
  if (cds.db) {
76
+ const { target, alias, where = [] } = convertPathExpressionToWhere(req.query.UPDATE.entity, this._srv.model, {})
77
+
78
+ const draftRef = { ref: [ensureDraftsSuffix(target)], as: alias }
79
+
95
80
  // SELECT because req.query in custom handler does not have access to _drafts
96
81
  req._.partialPersistentState = await cds
97
82
  .tx(req)
98
- .run(
99
- SELECT.from(ensureDraftsSuffix(req.target.name))
100
- .where(this._createWhereCondition(req.target, req.data))
101
- .limit(1)
102
- )
83
+ .run(SELECT.from(draftRef).where(removeIsActiveEntityRecursively(where)).limit(1))
103
84
 
104
85
  return compareJson(providedData || req.data, req._.partialPersistentState, req.target)
105
86
  }
@@ -2,7 +2,6 @@ const cds = require('../../../cds')
2
2
 
3
3
  const { SELECT } = cds.ql
4
4
 
5
- const { foreignKeyPropagations } = require('../../../common/utils/foreignKeyPropagations')
6
5
  const { checkReferenceIntegrity } = require('../../util/assert')
7
6
  const { processDeep, processDeepAsync } = require('../../util/dataProcessUtils')
8
7
 
@@ -74,9 +73,8 @@ const _getSelectCQN = (req, columns) => {
74
73
  }
75
74
 
76
75
  function _fillForeignKeysWithNull(managedAssocToOneElement, row) {
77
- const keys = foreignKeyPropagations(managedAssocToOneElement)
78
- for (const key of keys) {
79
- row[key.parentFieldName] = null
76
+ for (const key of managedAssocToOneElement._foreignKeys) {
77
+ if (key.parentElement) row[key.parentElement.name] = null
80
78
  }
81
79
  }
82
80
 
@@ -1,8 +1,10 @@
1
+ const cds = require('../../cds')
1
2
  const { all, resolve } = require('../../common/utils/thenable')
2
3
  const { getDependents } = require('../../common/utils/csn')
3
4
 
4
5
  // REVISIT: replace with cds.Request
5
6
  const getEntry = require('../../common/error/entry')
7
+ const crypto = require('crypto')
6
8
 
7
9
  const ISO_DATE_PART1 =
8
10
  '[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)'
@@ -174,11 +176,12 @@ const checkComplexType = ([key, value], elements, ignoreNonModelledData) => {
174
176
  return found || ignoreNonModelledData
175
177
  }
176
178
 
177
- const _checkStaticElementByKey = (entity, key, value, result, ignoreNonModelledData) => {
178
- const element = entity.elements && entity.elements[key]
179
+ const _checkStaticElementByKey = (definition, key, value, result, ignoreNonModelledData) => {
180
+ const elementsOrParameters = definition.elements || definition.params
181
+ const elementOrParameter = elementsOrParameters[key]
179
182
 
180
- if (!element) {
181
- if (!checkComplexType([key, value], entity.elements, ignoreNonModelledData)) {
183
+ if (!elementOrParameter) {
184
+ if (!checkComplexType([key, value], elementsOrParameters, ignoreNonModelledData)) {
182
185
  result.push(assertError(ASSERT_VALID_ELEMENT, { name: key }))
183
186
  }
184
187
 
@@ -186,17 +189,17 @@ const _checkStaticElementByKey = (entity, key, value, result, ignoreNonModelledD
186
189
  }
187
190
 
188
191
  let check
189
- if (element.type === 'cds.UUID' && entity.name === 'ProvisioningService.tenant') {
192
+ if (elementOrParameter.type === 'cds.UUID' && definition.name === 'ProvisioningService.tenant') {
190
193
  // > old SCP accounts don't have UUID ids
191
194
  check = CDS_TYPE_CHECKS['cds.String']
192
195
  } else {
193
- check = CDS_TYPE_CHECKS[element.type]
196
+ check = CDS_TYPE_CHECKS[elementOrParameter.type]
194
197
  }
195
198
 
196
- if (check && !check(value, element)) {
199
+ if (check && !check(value, elementOrParameter)) {
197
200
  // code, entity, element, value
198
- const args = [typeof value === 'string' ? '"' + value + '"' : value, element.type]
199
- result.push(assertError({ code: ASSERT_DATA_TYPE, args }, element, value, key))
201
+ const args = [typeof value === 'string' ? '"' + value + '"' : value, elementOrParameter.type]
202
+ result.push(assertError({ code: ASSERT_DATA_TYPE, args }, elementOrParameter, value, key))
200
203
  }
201
204
 
202
205
  return result
@@ -294,14 +297,14 @@ const checkInputConstraints = ({ element, value, errors, key, pathSegments, even
294
297
  return errors
295
298
  }
296
299
 
297
- const checkStatic = (entity, data, ignoreNonModelledData = false) => {
300
+ const checkStatic = (definition, data, ignoreNonModelledData = false) => {
298
301
  if (!Array.isArray(data)) data = [data]
299
302
 
300
303
  return data.reduce((result, row) => {
301
304
  return Object.entries(row)
302
305
  .filter(([key, value]) => value !== null && value !== undefined)
303
306
  .reduce((result, [key, value]) => {
304
- return _checkStaticElementByKey(entity, key, value, result, ignoreNonModelledData)
307
+ return _checkStaticElementByKey(definition, key, value, result, ignoreNonModelledData)
305
308
  }, result)
306
309
  }, [])
307
310
  }
@@ -320,6 +323,20 @@ const _checkExistsWhere = (entity, whereList, run) => {
320
323
  }
321
324
  }
322
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
+ }
323
340
  return run(cqn).then(exists => {
324
341
  return exists.length !== 0
325
342
  })
@@ -553,9 +570,8 @@ const checkKeys = (entity, data) => {
553
570
  const entityKeys = Object.keys(entity.keys)
554
571
  return data.reduce((result, row) => {
555
572
  for (const key of entityKeys) {
556
- if (entityKeys.some(key => row[key] === undefined)) {
573
+ if (row[key] === undefined && entity.elements[key].type !== 'cds.Association')
557
574
  result.push(assertError(ASSERT_NOT_NULL, entity.elements[key]))
558
- }
559
575
  }
560
576
  return result
561
577
  }, [])
@@ -5,8 +5,9 @@ module.exports = cds
5
5
  /*
6
6
  * csn aspects
7
7
  */
8
- const { any, entity } = cds.builtin.classes
8
+ const { any, entity, Association } = cds.builtin.classes
9
9
  cds.extend(any).with(require('./common/aspects/any'))
10
+ cds.extend(Association).with(require('./common/aspects/Association'))
10
11
  cds.extend(entity).with(require('./common/aspects/entity'))
11
12
 
12
13
  /*
@@ -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
+ }