@sap/cds 5.7.3 → 5.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/CHANGELOG.md +111 -0
  2. package/app/fiori/routes.js +1 -1
  3. package/bin/deploy/to-hana/cfUtil.js +251 -138
  4. package/bin/deploy/to-hana/gitUtil.js +55 -0
  5. package/bin/deploy/to-hana/hana.js +92 -93
  6. package/bin/deploy/to-hana/hdiDeployUtil.js +42 -27
  7. package/bin/deploy/to-hana/index.js +14 -13
  8. package/bin/mtx/in-cds.js +1 -0
  9. package/bin/serve.js +1 -1
  10. package/bin/version.js +1 -0
  11. package/lib/compile/cdsc.js +0 -6
  12. package/lib/compile/minify.js +1 -1
  13. package/lib/compile/resolve.js +1 -1
  14. package/lib/compile/to/srvinfo.js +1 -1
  15. package/lib/core/classes.js +21 -1
  16. package/lib/env/index.js +3 -2
  17. package/lib/env/requires.js +4 -0
  18. package/lib/i18n/localize.js +5 -8
  19. package/lib/index.js +1 -0
  20. package/lib/log/errors.js +1 -1
  21. package/lib/ql/SELECT.js +2 -2
  22. package/lib/req/cds-context.js +1 -1
  23. package/lib/req/context.js +1 -1
  24. package/lib/serve/Transaction.js +9 -5
  25. package/lib/serve/index.js +13 -21
  26. package/lib/utils/tests.js +90 -66
  27. package/libx/_runtime/audit/generic/personal/modification.js +0 -8
  28. package/libx/_runtime/auth/index.js +7 -6
  29. package/libx/_runtime/auth/strategies/dwc.js +43 -0
  30. package/libx/_runtime/auth/utils.js +24 -0
  31. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +11 -38
  32. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +12 -5
  33. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +7 -4
  34. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +24 -3
  35. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +43 -38
  36. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
  37. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +11 -5
  38. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +13 -7
  39. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/boundToCQN.js +1 -2
  40. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/deleteToCQN.js +0 -1
  41. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -2
  42. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/orderByToCQN.js +9 -0
  43. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +17 -30
  44. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +12 -1
  45. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/AbstractEdmStructuredType.js +2 -1
  46. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriHelper.js +7 -6
  47. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriTokenizer.js +5 -8
  48. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +19 -47
  49. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +4 -11
  50. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +7 -1
  51. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/CommandExecutor.js +0 -3
  52. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -1
  53. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ContextURLFactory.js +2 -2
  54. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ErrorJsonSerializer.js +2 -0
  55. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/TrustedResourceJsonSerializer.js +2 -5
  56. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +6 -6
  57. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
  58. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +18 -5
  59. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +41 -17
  60. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +1 -17
  61. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +80 -21
  62. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +47 -10
  63. package/libx/_runtime/cds-services/adapter/rest/Rest.js +22 -1
  64. package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +8 -3
  65. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +3 -0
  66. package/libx/_runtime/cds-services/services/Service.js +1 -1
  67. package/libx/_runtime/cds-services/services/utils/columns.js +5 -1
  68. package/libx/_runtime/cds-services/services/utils/compareJson.js +15 -16
  69. package/libx/_runtime/cds-services/services/utils/differ.js +2 -8
  70. package/libx/_runtime/common/aspects/Association.js +16 -0
  71. package/libx/_runtime/common/composition/data.js +28 -37
  72. package/libx/_runtime/common/composition/delete.js +107 -58
  73. package/libx/_runtime/common/composition/index.js +3 -3
  74. package/libx/_runtime/common/composition/insert.js +14 -27
  75. package/libx/_runtime/common/composition/update.js +39 -34
  76. package/libx/_runtime/common/error/frontend.js +19 -5
  77. package/libx/_runtime/common/generic/auth.js +20 -85
  78. package/libx/_runtime/common/generic/crud.js +22 -1
  79. package/libx/_runtime/common/i18n/messages.properties +2 -1
  80. package/libx/_runtime/common/utils/cqn.js +2 -6
  81. package/libx/_runtime/common/utils/cqn2cqn4sql.js +95 -122
  82. package/libx/_runtime/common/utils/csn.js +15 -4
  83. package/libx/_runtime/common/utils/foreignKeyPropagations.js +18 -1
  84. package/libx/_runtime/common/utils/keys.js +2 -1
  85. package/libx/_runtime/common/utils/path.js +1 -1
  86. package/libx/_runtime/common/utils/resolveView.js +12 -4
  87. package/libx/_runtime/common/utils/rewriteAsterisks.js +27 -13
  88. package/libx/_runtime/common/utils/search2cqn4sql.js +11 -6
  89. package/libx/_runtime/common/utils/structured.js +11 -5
  90. package/libx/_runtime/common/utils/vcap.js +27 -10
  91. package/libx/_runtime/db/data-conversion/post-processing.js +42 -35
  92. package/libx/_runtime/db/expand/expand-v2.js +21 -12
  93. package/libx/_runtime/db/expand/expandCQNToJoin.js +57 -29
  94. package/libx/_runtime/db/expand/index.js +3 -0
  95. package/libx/_runtime/db/generic/create.js +0 -10
  96. package/libx/_runtime/db/generic/index.js +3 -0
  97. package/libx/_runtime/db/generic/read.js +2 -24
  98. package/libx/_runtime/db/generic/rewrite.js +1 -3
  99. package/libx/_runtime/db/generic/update.js +1 -1
  100. package/libx/_runtime/db/query/delete.js +10 -4
  101. package/libx/_runtime/db/query/insert.js +3 -4
  102. package/libx/_runtime/db/query/read.js +4 -1
  103. package/libx/_runtime/db/query/update.js +5 -5
  104. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +9 -2
  105. package/libx/_runtime/db/sql-builder/FunctionBuilder.js +3 -0
  106. package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -3
  107. package/libx/_runtime/db/sql-builder/index.js +3 -0
  108. package/libx/_runtime/db/utils/columns.js +5 -2
  109. package/libx/_runtime/db/utils/deep.js +6 -8
  110. package/libx/_runtime/db/utils/generateAliases.js +56 -6
  111. package/libx/_runtime/fiori/generic/before.js +73 -49
  112. package/libx/_runtime/fiori/generic/edit.js +14 -18
  113. package/libx/_runtime/fiori/generic/patch.js +8 -11
  114. package/libx/_runtime/fiori/generic/read.js +22 -20
  115. package/libx/_runtime/fiori/generic/readOverDraft.js +1 -4
  116. package/libx/_runtime/fiori/utils/handler.js +1 -11
  117. package/libx/_runtime/hana/Service.js +1 -1
  118. package/libx/_runtime/hana/conversion.js +12 -1
  119. package/libx/_runtime/hana/execute.js +31 -16
  120. package/libx/_runtime/hana/localized.js +1 -1
  121. package/libx/_runtime/hana/search.js +3 -3
  122. package/libx/_runtime/hana/search2cqn4sql.js +23 -25
  123. package/libx/_runtime/hana/searchToContains.js +1 -1
  124. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +4 -2
  125. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +0 -1
  126. package/libx/_runtime/messaging/enterprise-messaging.js +1 -0
  127. package/libx/_runtime/messaging/file-based.js +3 -1
  128. package/libx/_runtime/messaging/service.js +16 -7
  129. package/libx/_runtime/remote/utils/client.js +37 -20
  130. package/libx/_runtime/remote/utils/data.js +53 -12
  131. package/libx/_runtime/sqlite/Service.js +1 -1
  132. package/libx/_runtime/sqlite/conversion.js +10 -0
  133. package/libx/_runtime/sqlite/localized.js +1 -1
  134. package/libx/_runtime/types/api.js +2 -2
  135. package/libx/gql/resolvers/crud/update.js +8 -5
  136. package/libx/gql/resolvers/parse/ast/enrich.js +1 -0
  137. package/libx/odata/afterburner.js +29 -6
  138. package/libx/odata/cqn2odata.js +9 -0
  139. package/libx/odata/grammar.pegjs +50 -22
  140. package/libx/odata/index.js +2 -2
  141. package/libx/odata/parser.js +1 -1
  142. package/libx/odata/utils.js +2 -2
  143. package/libx/rest/RestAdapter.js +29 -1
  144. package/libx/rest/middleware/auth.js +1 -3
  145. package/libx/rest/middleware/parse.js +1 -0
  146. package/package.json +1 -1
  147. package/server.js +1 -1
  148. package/bin/deploy/to-hana/logger.js +0 -27
  149. package/bin/deploy/to-hana/runCommand.js +0 -113
  150. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +0 -37
  151. package/libx/_runtime/common/utils/auth.js +0 -16
@@ -1,7 +1,10 @@
1
1
  const cds = require('../../../../cds')
2
+
3
+ const { Readable } = require('stream')
4
+ const { big } = require('@sap/cds-foss')
5
+
2
6
  const getTemplate = require('../../../../common/utils/template')
3
7
  const templateProcessor = require('../../../../common/utils/templateProcessor')
4
- const { big } = require('@sap/cds-foss')
5
8
  const { omitValue, applyOmitValuesPreference } = require('./omitValues')
6
9
 
7
10
  const METADATA = {
@@ -23,36 +26,59 @@ const METADATA = {
23
26
  $mediaEditLink: '*@odata.mediaEditLink',
24
27
  $mediaReadLink: '*@odata.mediaReadLink',
25
28
  $mediaContentType: '*@odata.mediaContentType',
29
+ $mediaContentDispositionFilename: '*@odata.mediaContentDispositionFilename', // > not supported by okra
30
+ $mediaContentDispositionType: '*@odata.mediaContentDispositionType', // > not supported by okra
26
31
  $mediaEtag: '*@odata.mediaEtag'
27
32
  }
28
33
 
34
+ const _getPropertyName = req => {
35
+ const segments = req._.odataReq.getUriInfo().getPathSegments()
36
+ if (segments[segments.length - 1].getKind().match(/\w*\.PROPERTY$/)) {
37
+ return segments[segments.length - 1].getProperty().getName()
38
+ }
39
+ if (
40
+ segments[segments.length - 1].getKind() === 'VALUE' &&
41
+ segments[segments.length - 2].getKind() === 'PRIMITIVE.PROPERTY'
42
+ ) {
43
+ return segments[segments.length - 2].getProperty().getName()
44
+ }
45
+ }
46
+
29
47
  /**
30
48
  * Convert any result to the result object structure, which is expected of odata-v4.
31
49
  *
32
50
  * @param {*} result
33
- * @param {*} [arg]
51
+ * @param {*} [req]
34
52
  * @returns {string | object}
35
53
  */
36
- const toODataResult = (result, arg) => {
37
- if (result === undefined || result === null) return ''
38
-
39
- if (arg) {
40
- if (typeof arg === 'object') {
41
- arg = arg._.odataReq.getUriInfo().getLastSegment().isCollection() ? 'Array' : ''
42
- }
43
-
44
- if (!Array.isArray(result) && arg === 'Array') {
45
- result = [result]
46
- } else if (Array.isArray(result) && arg !== 'Array') {
47
- result = result[0]
48
- }
54
+ // eslint-disable-next-line complexity
55
+ const toODataResult = (result, req) => {
56
+ if (result == null) return ''
57
+
58
+ let propertyName, isStream
59
+ if (req) {
60
+ propertyName = _getPropertyName(req)
61
+ isStream =
62
+ req._.odataReq.getUriInfo().getLastSegment().getProperty() &&
63
+ req._.odataReq.getUriInfo().getLastSegment().getProperty().getType().toString() === 'Edm.Stream'
64
+
65
+ const isCollection = !propertyName && req._.odataReq.getUriInfo().getLastSegment().isCollection()
66
+ if (isCollection && !Array.isArray(result)) result = [result]
67
+ else if (!isCollection && Array.isArray(result)) result = result[0]
49
68
  }
50
69
 
51
- const odataResult = {
52
- value: result
70
+ let value = result
71
+ if (typeof result === 'object') {
72
+ if ('value' in result && (result.value instanceof Readable || isStream)) value = result.value
73
+ else if (propertyName) value = result[propertyName]
53
74
  }
54
75
 
76
+ const odataResult = { value }
77
+
55
78
  if (typeof result === 'object') {
79
+ // backwards compatibility for content-type in stream
80
+ if (result['*@odata.mediaContentType']) result.$mediaContentType = result['*@odata.mediaContentType']
81
+
56
82
  for (const key in METADATA) {
57
83
  // do not set "@odata.context" as it may be inherited of remote service
58
84
  if (key === '$context') {
@@ -60,6 +86,18 @@ const toODataResult = (result, arg) => {
60
86
  continue
61
87
  }
62
88
 
89
+ // REVISIT: okra doesn't support content disposition
90
+ if (key === '$mediaContentDispositionFilename' && result[key] && req) {
91
+ const cdt = result.$mediaContentDispositionType || 'attachment'
92
+ req._.odataRes.setHeader('Content-Disposition', `${cdt}; filename="${encodeURIComponent(result[key])}"`)
93
+ delete result[key]
94
+ continue
95
+ }
96
+ if (key === '$mediaContentDispositionType') {
97
+ delete result[key]
98
+ continue
99
+ }
100
+
63
101
  if (key in result) {
64
102
  odataResult[METADATA[key]] = result[key]
65
103
  delete result[key]
@@ -112,6 +150,13 @@ const addAssociationToRow = (row, foreignKey, foreignKeyElement) => {
112
150
  delete row[foreignKey]
113
151
  }
114
152
 
153
+ const localizeAfterDraftActivate = (row, key, locale) => {
154
+ if (row.texts && Object.prototype.hasOwnProperty.call(row, key)) {
155
+ const texts = row.texts.filter(t => t.locale === locale)[0]
156
+ if (texts && texts[key]) row[key] = texts[key]
157
+ }
158
+ }
159
+
115
160
  const _processCategory = (category, processArgs, req, options, previousResult) => {
116
161
  const { row, key, element } = processArgs
117
162
 
@@ -136,6 +181,10 @@ const _processCategory = (category, processArgs, req, options, previousResult) =
136
181
  if (key !== 'DraftAdministrativeData_DraftUUID') delete row[key]
137
182
  break
138
183
 
184
+ case 'localizeAfterDraftActivate':
185
+ localizeAfterDraftActivate(row, key, req.locale)
186
+ break
187
+
139
188
  // no default
140
189
  }
141
190
  }
@@ -197,16 +246,25 @@ const _pick = options => (element, target, parent) => {
197
246
  const categories = []
198
247
 
199
248
  if (element['@odata.etag']) categories.push('@odata.etag')
249
+
200
250
  if (element.type === 'cds.Decimal') categories.push('@cds.Decimal')
251
+
201
252
  categories.push(..._assocs(element, target))
253
+
202
254
  if (options.omitValuesPreference) categories.push('@odata.omitValues')
255
+ if (options.event === 'draftActivate' && options.locale && options.locale !== 'en' && element.localized === true) {
256
+ categories.push('localizeAfterDraftActivate')
257
+ }
258
+
203
259
  if (categories.length) return { categories }
204
260
  }
205
261
 
206
- const _getOptions = headers => {
262
+ const _getOptions = ({ headers, locale, event }) => {
207
263
  const options = {
208
264
  decimals: null,
209
- omitValuesPreference: null
265
+ omitValuesPreference: null,
266
+ locale,
267
+ event
210
268
  }
211
269
 
212
270
  if (!headers) return options
@@ -231,7 +289,8 @@ const _getOptions = headers => {
231
289
  const _generateCacheKey = (headers, options) => {
232
290
  let key = 'postProcess' // default template cache key for post processing
233
291
  if (headers.prefer) key += `:${headers.prefer}`
234
- if (options.decimals) key += `:exponentialDecimals=true`
292
+ if (options.decimals) key += `:ExponentialDecimals=true`
293
+ if (options.event === 'draftActivate' && options.locale) key += `:locale=${options.locale}`
235
294
  return key
236
295
  }
237
296
 
@@ -241,7 +300,7 @@ const postProcess = (req, res, service, result, previousResult) => {
241
300
 
242
301
  if (!target || !result || !model || !model.definitions[target.name]) return
243
302
 
244
- const options = _getOptions(headers)
303
+ const options = _getOptions(req)
245
304
  const cacheKey = _generateCacheKey(headers, options)
246
305
  const parent = _getParent(model, target.name)
247
306
  const template = getTemplate(cacheKey, service, target, { pick: _pick(options) }, parent)
@@ -5,6 +5,9 @@ const { SELECT } = cds.ql
5
5
 
6
6
  const { targetFromPath, isPathToDraft } = require('../../../../common/utils/cqn')
7
7
  const { deepCopyArray } = require('../../../../common/utils/copy')
8
+ const { cqn2cqn4sql } = require('../../../../common/utils/cqn2cqn4sql')
9
+ const { ensureDraftsSuffix } = require('../../../../fiori/utils/handler')
10
+ const { removeIsActiveEntityRecursively, isActiveEntityRequested } = require('../../../../fiori/utils/where')
8
11
 
9
12
  const isStreaming = segments => {
10
13
  const lastSegment = segments[segments.length - 1]
@@ -15,10 +18,38 @@ const isStreaming = segments => {
15
18
  )
16
19
  }
17
20
 
21
+ const _adaptSubSelectsDraft = select => {
22
+ if (select.SELECT.from.ref) {
23
+ const index = select.SELECT.from.ref.length - 1
24
+ select.SELECT.from.ref[index] = ensureDraftsSuffix(select.SELECT.from.ref[index])
25
+ }
26
+
27
+ if (select.SELECT.where) {
28
+ for (let i = 0; i < select.SELECT.where.length; i++) {
29
+ const element = select.SELECT.where[i]
30
+ if (element.SELECT) {
31
+ _adaptSubSelectsDraft(element)
32
+ } else if (element.xpr) {
33
+ for (const ele of element.xpr.filter(e => e.SELECT)) {
34
+ _adaptSubSelectsDraft(ele)
35
+ }
36
+ }
37
+ }
38
+ }
39
+ }
40
+
41
+ const adaptStreamCQN = (cqn, isDraft = false) => {
42
+ if (isDraft || !isActiveEntityRequested(cqn.SELECT.where)) {
43
+ _adaptSubSelectsDraft(cqn)
44
+ } else {
45
+ cqn.SELECT.where = removeIsActiveEntityRecursively(cqn.SELECT.where)
46
+ }
47
+ }
48
+
18
49
  const getStreamProperties = (req, model) => {
19
50
  const mediaTypeProperty = Object.values(req.target.elements).find(val => val['@Core.MediaType'])
20
51
 
21
- let contentType, contentDisposition
52
+ let contentType, contentDispositionFilename
22
53
  const columns = []
23
54
  if (typeof mediaTypeProperty['@Core.MediaType'] === 'object') {
24
55
  let contentTypeProperty = mediaTypeProperty['@Core.MediaType']['=']
@@ -46,36 +77,42 @@ const getStreamProperties = (req, model) => {
46
77
  `@Core.ContentDisposition.Filename in entity "${req.target.name}" points to property "${contentDispositionProperty}" which was renamed or is not part of the projection. You must update the annotation value.`
47
78
  )
48
79
  } else {
49
- columns.push({ ref: [contentDispositionProperty], as: 'contentDisposition' })
80
+ columns.push({ ref: [contentDispositionProperty], as: 'contentDispositionFilename' })
50
81
  }
51
82
  } else {
52
- contentDisposition = mediaTypeProperty['@Core.ContentDisposition.Filename']
83
+ contentDispositionFilename = mediaTypeProperty['@Core.ContentDisposition.Filename']
53
84
  }
54
85
  }
86
+ const contentDispositionType = mediaTypeProperty['@Core.ContentDisposition.Type']
55
87
 
56
88
  if (columns.length && cds.db && !req.target._hasPersistenceSkip) {
57
89
  // used cloned path
58
- const select = SELECT.one.from({ ref: deepCopyArray(req.query.SELECT.from.ref) }).columns(columns)
59
-
60
- if (req.target._isDraftEnabled && isPathToDraft(select.SELECT.from.ref, model))
61
- select.SELECT.from.ref[0].id = select.SELECT.from.ref[0].id + '_drafts'
90
+ let select = SELECT.one.from({ ref: deepCopyArray(req.query.SELECT.from.ref) }).columns(columns)
62
91
 
63
92
  // new parser has media property as last ref element -> remove
64
93
  if (targetFromPath(select.SELECT.from.ref, model).kind === 'element') select.SELECT.from.ref.pop()
65
94
 
95
+ const pathToDraft = isPathToDraft(select.SELECT.from.ref, model)
96
+ if (req.target._isDraftEnabled && pathToDraft) {
97
+ select = cqn2cqn4sql(select, model)
98
+ adaptStreamCQN(select, pathToDraft)
99
+ }
100
+
66
101
  return cds
67
102
  .tx(req)
68
103
  .run(select)
69
104
  .then(res => ({
70
105
  contentType: (res && res.contentType) || contentType,
71
- contentDisposition: (res && res.contentDisposition) || contentDisposition
106
+ contentDispositionFilename: (res && res.contentDispositionFilename) || contentDispositionFilename,
107
+ contentDispositionType
72
108
  }))
73
109
  }
74
110
 
75
- return Promise.resolve({ contentType, contentDisposition })
111
+ return Promise.resolve({ contentType, contentDispositionFilename, contentDispositionType })
76
112
  }
77
113
 
78
114
  module.exports = {
79
115
  isStreaming,
80
- getStreamProperties
116
+ getStreamProperties,
117
+ adaptStreamCQN
81
118
  }
@@ -12,9 +12,10 @@ const { contentTypeCheck } = require('./utils/header-checks')
12
12
  const parse = require('./utils/parse-url')
13
13
  const { base64toBuffer } = require('./utils/binary')
14
14
 
15
- const { UNAUTHORIZED, FORBIDDEN, getRequiresAsArray } = require('../../../common/utils/auth')
15
+ const { UNAUTHORIZED, FORBIDDEN, getRequiresAsArray } = require('../../../auth/utils')
16
16
 
17
17
  const PPP = { POST: 1, PUT: 1, PATCH: 1 }
18
+ const GH = { GET: 1, HEAD: 1 }
18
19
 
19
20
  let _i18n
20
21
  const i18n = (...args) => {
@@ -93,6 +94,21 @@ class Rest {
93
94
  throw FORBIDDEN
94
95
  }
95
96
 
97
+ // service root
98
+ if (req.path === '/' && GH[req.method]) {
99
+ if (req.method === 'HEAD') return res.json({})
100
+ else
101
+ return res.json(
102
+ Object.keys(srv.entities).reduce(
103
+ (acc, cur) => {
104
+ acc.entities.push({ name: cur, url: cur })
105
+ return acc
106
+ },
107
+ { entities: [] }
108
+ )
109
+ )
110
+ }
111
+
96
112
  // content-type check, parse url, and base64 to buffer
97
113
  try {
98
114
  if (PPP[req.method]) contentTypeCheck(req)
@@ -115,6 +131,11 @@ class Rest {
115
131
  }
116
132
  })
117
133
 
134
+ // HEAD
135
+ this.router.head('/*', (req, res, next) => {
136
+ read(req, res, next)
137
+ })
138
+
118
139
  // GET
119
140
  this.router.get('/*', (req, res, next) => {
120
141
  // READ or custom operation?
@@ -28,9 +28,7 @@ module.exports = service => {
28
28
 
29
29
  result = await tx.dispatch(req)
30
30
 
31
- if (result == null) {
32
- throw getError(404, 'NO_MATCHING_RESOURCE')
33
- }
31
+ if (result == null) throw getError(404, 'NO_MATCHING_RESOURCE')
34
32
 
35
33
  bufferToBase64(result, segments[0])
36
34
 
@@ -41,6 +39,13 @@ module.exports = service => {
41
39
  await tx.rollback(e).catch(() => {})
42
40
  } finally {
43
41
  if (err) next(err)
42
+ else if (restReq.method === 'HEAD')
43
+ restRes
44
+ .set({
45
+ 'content-type': 'application/json; charset=utf-8',
46
+ 'content-length': JSON.stringify(result).length
47
+ })
48
+ .end()
44
49
  else restRes.send(toRestResult(result))
45
50
  }
46
51
  }
@@ -230,6 +230,9 @@ module.exports = {
230
230
  POST: (service, req) => {
231
231
  return parseCreateOrReadUrl('CREATE', service, req)
232
232
  },
233
+ HEAD: (service, req) => {
234
+ return parseCreateOrReadUrl('READ', service, req)
235
+ },
233
236
  GET: (service, req) => {
234
237
  return parseCreateOrReadUrl('READ', service, req)
235
238
  },
@@ -128,7 +128,7 @@ class ApplicationService extends cds.Service {
128
128
  // compat
129
129
  restoreLink(req)
130
130
  if (req.query.SELECT && req.query.SELECT._4odata) {
131
- q.SELECT._4odata = req.query.SELECT._4odata
131
+ Object.defineProperty(q.SELECT, '_4odata', { value: req.query.SELECT._4odata })
132
132
  }
133
133
 
134
134
  // REVISIT: We need to provide target explicitly because it's cached already within ensure_target
@@ -17,7 +17,10 @@ const { DRAFT_COLUMNS_UNION } = require('../../../common/constants/draft')
17
17
  * @param [options.filterVirtual=false]
18
18
  * @returns {Array<object>} - array of columns
19
19
  */
20
- const getColumns = (entity, { onlyNames = false, removeIgnore = false, filterDraft = true, filterVirtual = false }) => {
20
+ const getColumns = (
21
+ entity,
22
+ { onlyNames = false, removeIgnore = false, filterDraft = true, filterVirtual = false, keysOnly = false }
23
+ ) => {
21
24
  const skipDraft = filterDraft && entity._isDraftEnabled
22
25
  const columns = []
23
26
  const elements = entity.elements
@@ -28,6 +31,7 @@ const getColumns = (entity, { onlyNames = false, removeIgnore = false, filterDra
28
31
  if (filterVirtual && element.virtual) continue
29
32
  if (removeIgnore && element['@cds.api.ignore']) continue
30
33
  if (skipDraft && DRAFT_COLUMNS_UNION.includes(each)) continue
34
+ if (keysOnly && !element.key) continue
31
35
  columns.push(onlyNames ? each : element)
32
36
  }
33
37
 
@@ -285,30 +285,29 @@ const _mergeArrays = (entity, oldValue, newValue) => {
285
285
  return merged
286
286
  }
287
287
 
288
- const mergeJsonDeep = (entity, oldValue, newValue) => {
288
+ const mergeJsonDeep = (entity, oldValue, value) => {
289
+ // REVISIT readAfterWrite -> commit -> postProcessing
290
+ // Detach result from req.data
291
+ const newValue = value ? Object.assign({}, value) : oldValue // if newValue === undefined
289
292
  if (_isObject(oldValue) && _isObject(newValue)) {
290
- Object.keys(newValue).forEach(key => {
291
- if (_isObject(newValue[key])) {
292
- if (!(key in oldValue)) Object.assign(oldValue, { [key]: newValue[key] })
293
- else {
294
- const target = entity && entity.elements[key] && entity.elements[key]._target
295
- oldValue[key] = mergeJsonDeep(target, oldValue[key], newValue[key])
296
- }
297
- } else if (Array.isArray(newValue[key])) {
298
- if (!(key in oldValue)) Object.assign(oldValue, { [key]: newValue[key] })
299
- else {
300
- const target = entity && entity.elements[key] && entity.elements[key]._target
293
+ // append to newValue to keep an order of attributes as might be defined in custom handler
294
+ Object.keys(oldValue).forEach(key => {
295
+ if (!(key in newValue)) {
296
+ Object.assign(newValue, { [key]: oldValue[key] })
297
+ } else {
298
+ const target = entity && entity.elements[key] && entity.elements[key]._target
299
+ if (_isObject(newValue[key])) {
300
+ newValue[key] = mergeJsonDeep(target, oldValue[key], newValue[key])
301
+ } else if (Array.isArray(newValue[key])) {
301
302
  if (target) {
302
- oldValue[key] = _mergeArrays(target, oldValue[key], newValue[key])
303
+ newValue[key] = _mergeArrays(target, oldValue[key], newValue[key])
303
304
  }
304
305
  // Can't merge items without target
305
306
  }
306
- } else {
307
- Object.assign(oldValue, { [key]: newValue[key] })
308
307
  }
309
308
  })
310
309
  }
311
- return oldValue
310
+ return newValue
312
311
  }
313
312
 
314
313
  // Signature similar to Object.assign(oldValue, newValue)
@@ -45,14 +45,8 @@ module.exports = class {
45
45
  }
46
46
 
47
47
  async _addPartialPersistentState(req) {
48
- const deepUpdateData = await selectDeepUpdateData(
49
- this._srv.model.definitions,
50
- req.query,
51
- req,
52
- true,
53
- true,
54
- this._srv
55
- )
48
+ // REVISIT: cds.context.model?
49
+ const deepUpdateData = await selectDeepUpdateData(this._srv, this._srv.model, req, true)
56
50
  req._.partialPersistentState = deepUpdateData
57
51
  }
58
52
 
@@ -1,11 +1,24 @@
1
1
  // global.cds is used on purpose here!
2
2
  const cds = global.cds
3
3
 
4
+ // requesting logger without module on purpose!
5
+ const LOG = cds.log()
6
+
4
7
  const ODATA_CONTAINED = '@odata.contained'
5
8
 
6
9
  const { isSelfManaged, isBacklink, getAnchor, getBacklink } = require('./utils')
7
10
  const { foreignKeyPropagations } = require('../utils/foreignKeyPropagations')
8
11
 
12
+ const _logDeprecationForODataContained = () => {
13
+ if (!cds._deprecationWarningForODataContained) {
14
+ LOG._warn &&
15
+ LOG.warn(
16
+ 'Annotation "@odata.contained" is deprecated and will be removed in an upcoming release. Use compositions instead of associations.'
17
+ )
18
+ cds._deprecationWarningForODataContained = true
19
+ }
20
+ }
21
+
9
22
  module.exports = class {
10
23
  get _isAssociationStrict() {
11
24
  return (
@@ -15,6 +28,7 @@ module.exports = class {
15
28
  }
16
29
 
17
30
  get _isAssociationEffective() {
31
+ this[ODATA_CONTAINED] && _logDeprecationForODataContained()
18
32
  return (
19
33
  this.own('__isAssociationEffective') ||
20
34
  this.set(
@@ -25,6 +39,7 @@ module.exports = class {
25
39
  }
26
40
 
27
41
  get _isCompositionEffective() {
42
+ this[ODATA_CONTAINED] && _logDeprecationForODataContained()
28
43
  return (
29
44
  this.own('__isCompositionEffective') ||
30
45
  this.set(
@@ -36,6 +51,7 @@ module.exports = class {
36
51
  }
37
52
 
38
53
  get _isContained() {
54
+ this[ODATA_CONTAINED] && _logDeprecationForODataContained()
39
55
  return (
40
56
  this.own('__isContained') ||
41
57
  this.set(