@sap/cds 5.7.5 → 5.8.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 (151) hide show
  1. package/CHANGELOG.md +97 -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/resolve.js +1 -1
  13. package/lib/compile/to/srvinfo.js +1 -1
  14. package/lib/core/classes.js +21 -1
  15. package/lib/env/index.js +3 -2
  16. package/lib/env/requires.js +4 -0
  17. package/lib/i18n/localize.js +5 -8
  18. package/lib/index.js +1 -0
  19. package/lib/log/errors.js +1 -1
  20. package/lib/log/format/kibana.js +3 -3
  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/ODataRequest.js +1 -3
  32. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +11 -32
  33. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +12 -5
  34. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +7 -4
  35. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +24 -3
  36. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +43 -38
  37. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
  38. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +11 -5
  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/ResourcePathParser.js +23 -2
  47. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriHelper.js +7 -6
  48. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriTokenizer.js +2 -5
  49. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +19 -47
  50. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +4 -11
  51. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +7 -1
  52. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/CommandExecutor.js +0 -3
  53. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -1
  54. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ContextURLFactory.js +2 -2
  55. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ErrorJsonSerializer.js +2 -0
  56. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/TrustedResourceJsonSerializer.js +2 -5
  57. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +6 -6
  58. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
  59. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +1 -4
  60. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +41 -17
  61. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +1 -17
  62. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +60 -18
  63. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +47 -10
  64. package/libx/_runtime/cds-services/adapter/rest/Rest.js +22 -1
  65. package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +8 -3
  66. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +3 -0
  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/tree.js +1 -1
  76. package/libx/_runtime/common/composition/update.js +39 -34
  77. package/libx/_runtime/common/error/frontend.js +19 -5
  78. package/libx/_runtime/common/generic/auth.js +20 -85
  79. package/libx/_runtime/common/generic/crud.js +22 -1
  80. package/libx/_runtime/common/i18n/messages.properties +2 -1
  81. package/libx/_runtime/common/utils/cqn.js +2 -6
  82. package/libx/_runtime/common/utils/cqn2cqn4sql.js +95 -122
  83. package/libx/_runtime/common/utils/csn.js +29 -6
  84. package/libx/_runtime/common/utils/foreignKeyPropagations.js +21 -1
  85. package/libx/_runtime/common/utils/keys.js +2 -1
  86. package/libx/_runtime/common/utils/path.js +1 -1
  87. package/libx/_runtime/common/utils/resolveView.js +12 -4
  88. package/libx/_runtime/common/utils/rewriteAsterisks.js +27 -13
  89. package/libx/_runtime/common/utils/search2cqn4sql.js +11 -6
  90. package/libx/_runtime/common/utils/structured.js +10 -4
  91. package/libx/_runtime/common/utils/vcap.js +27 -10
  92. package/libx/_runtime/db/data-conversion/post-processing.js +20 -13
  93. package/libx/_runtime/db/expand/expand-v2.js +21 -12
  94. package/libx/_runtime/db/expand/expandCQNToJoin.js +67 -26
  95. package/libx/_runtime/db/expand/index.js +3 -0
  96. package/libx/_runtime/db/generic/create.js +0 -10
  97. package/libx/_runtime/db/generic/index.js +3 -0
  98. package/libx/_runtime/db/generic/read.js +2 -24
  99. package/libx/_runtime/db/generic/rewrite.js +1 -3
  100. package/libx/_runtime/db/generic/update.js +1 -1
  101. package/libx/_runtime/db/query/delete.js +10 -4
  102. package/libx/_runtime/db/query/insert.js +3 -4
  103. package/libx/_runtime/db/query/read.js +4 -1
  104. package/libx/_runtime/db/query/update.js +5 -5
  105. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +9 -2
  106. package/libx/_runtime/db/sql-builder/FunctionBuilder.js +3 -0
  107. package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -3
  108. package/libx/_runtime/db/sql-builder/index.js +3 -0
  109. package/libx/_runtime/db/utils/columns.js +5 -2
  110. package/libx/_runtime/db/utils/deep.js +16 -14
  111. package/libx/_runtime/db/utils/generateAliases.js +56 -6
  112. package/libx/_runtime/fiori/generic/before.js +73 -49
  113. package/libx/_runtime/fiori/generic/edit.js +14 -18
  114. package/libx/_runtime/fiori/generic/patch.js +8 -11
  115. package/libx/_runtime/fiori/generic/read.js +20 -19
  116. package/libx/_runtime/fiori/generic/readOverDraft.js +1 -4
  117. package/libx/_runtime/fiori/utils/handler.js +1 -11
  118. package/libx/_runtime/hana/Service.js +1 -1
  119. package/libx/_runtime/hana/conversion.js +12 -1
  120. package/libx/_runtime/hana/dynatrace.js +11 -5
  121. package/libx/_runtime/hana/execute.js +132 -19
  122. package/libx/_runtime/hana/search.js +3 -3
  123. package/libx/_runtime/hana/search2cqn4sql.js +23 -25
  124. package/libx/_runtime/hana/searchToContains.js +1 -1
  125. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  126. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +0 -1
  127. package/libx/_runtime/messaging/enterprise-messaging.js +1 -0
  128. package/libx/_runtime/messaging/file-based.js +3 -1
  129. package/libx/_runtime/messaging/service.js +4 -1
  130. package/libx/_runtime/remote/utils/client.js +41 -24
  131. package/libx/_runtime/remote/utils/data.js +54 -12
  132. package/libx/_runtime/sqlite/Service.js +1 -1
  133. package/libx/_runtime/sqlite/conversion.js +10 -0
  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 +49 -21
  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
@@ -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
  },
@@ -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(
@@ -120,26 +120,26 @@ const _subWhere = (result, element) => {
120
120
  if (links && links.length > 0) {
121
121
  where = []
122
122
  for (const row of result) {
123
- if (where.length > 0) {
124
- where.push('or')
125
- }
126
123
  const whereObj = links.reduce((res, currentLink) => {
127
- if (Object.prototype.hasOwnProperty.call(row, currentLink.targetKey))
124
+ if (Object.prototype.hasOwnProperty.call(row, currentLink.targetKey) && row[currentLink.targetKey] !== null)
128
125
  res[currentLink.entityKey] = row[currentLink.targetKey]
129
126
  return res
130
127
  }, {})
131
128
  const whereCQN = ctUtils.whereKey(whereObj)
132
- if (whereCQN.length) where.push('(', ...whereCQN, ')')
129
+ if (whereCQN.length) {
130
+ if (where.length > 0) where.push('or')
131
+ where.push('(', ...whereCQN, ')')
132
+ }
133
133
  }
134
134
  }
135
135
  return where
136
136
  }
137
137
 
138
- const _mergeResults = (result, selectData, root, definitions, compositionTree, entityName) => {
138
+ const _mergeResults = (result, selectData, root, model, compositionTree, entityName) => {
139
139
  if (root) {
140
140
  return [...selectData, ...result]
141
141
  } else {
142
- const parent = definitions[compositionTree.target] || definitions[entityName]
142
+ const parent = model.definitions[compositionTree.target] || model.definitions[entityName]
143
143
  const assoc = (parent && parent.elements[compositionTree.name]) || {}
144
144
  return selectData.map(selectEntry => {
145
145
  if (assoc.is2one) {
@@ -148,8 +148,9 @@ const _mergeResults = (result, selectData, root, definitions, compositionTree, e
148
148
  selectEntry[compositionTree.name] = selectEntry[compositionTree.name] || []
149
149
  }
150
150
  const newData = _findWhere(result, _parentKey(compositionTree, selectEntry))
151
- if (assoc.is2one && newData[0]) {
152
- selectEntry[compositionTree.name] = Object.assign(selectEntry[compositionTree.name], newData[0])
151
+ if (assoc.is2one) {
152
+ if (newData[0]) selectEntry[compositionTree.name] = Object.assign(selectEntry[compositionTree.name], newData[0])
153
+ else selectEntry[compositionTree.name] = null
153
154
  } else if (assoc.is2many) {
154
155
  selectEntry[compositionTree.name].push(...newData)
155
156
  }
@@ -158,14 +159,14 @@ const _mergeResults = (result, selectData, root, definitions, compositionTree, e
158
159
  }
159
160
  }
160
161
 
161
- const _columns = (entity, data, compositionTree, selectAll) => {
162
+ const _columns = (entity, data, compositionTree, selectAllColumns) => {
162
163
  const backLinkKeys = _getLinksOfCompTree(compositionTree)
163
164
  const columns = []
164
165
  for (const elementName in entity.elements) {
165
166
  const element = entity.elements[elementName]
166
167
  if (element.virtual || element.isAssociation) continue
167
168
  if (
168
- selectAll ||
169
+ selectAllColumns ||
169
170
  element.key ||
170
171
  backLinkKeys.includes(element.name) ||
171
172
  (Array.isArray(data) && data.find(entry => element.name in entry))
@@ -177,45 +178,42 @@ const _columns = (entity, data, compositionTree, selectAll) => {
177
178
  }
178
179
 
179
180
  const _select = ({
180
- definitions,
181
+ model,
181
182
  entityName,
182
183
  draft,
183
184
  alias,
184
185
  compositionTree,
185
186
  data,
186
- root,
187
- includeAllRootColumns,
188
- includeAllColumns,
187
+ selectAllColumns,
189
188
  where,
190
189
  parentKeys,
191
190
  orderBy,
192
191
  singleton
193
192
  }) => {
194
- const entity = definitions && definitions[entityName]
193
+ const entity = model.definitions[entityName]
195
194
  const from = ctUtils.addDraftSuffix(draft, entity.name)
196
195
  const selectCQN = SELECT.from(from)
197
196
  if (alias) selectCQN.SELECT.from.as = alias
198
- const selectAll = includeAllColumns || (includeAllRootColumns && root)
199
- selectCQN.SELECT.columns = _columns(entity, data, compositionTree, selectAll)
197
+ selectCQN.SELECT.columns = _columns(entity, data, compositionTree, selectAllColumns)
200
198
  if (where) selectCQN.SELECT.where = where
201
199
  else if (parentKeys) selectCQN.SELECT.where = _whereKeys(parentKeys)
202
200
  if (orderBy) selectCQN.SELECT.orderBy = orderBy
203
201
  if (singleton) selectCQN.SELECT.limit = { rows: { val: 1 } }
204
202
  // REVISIT: remove once SELECT builder does flattening!
205
- return cqn2cqn4sql(selectCQN, { definitions })
203
+ return cqn2cqn4sql(selectCQN, model)
206
204
  }
207
205
 
208
206
  const _selectDeepUpdateData = async args => {
209
- const { definitions, compositionTree, entityName, data, includeAllColumns, root, selectData, tx } = args
207
+ const { model, compositionTree, entityName, data, root, selectData, tx, selectAllColumns } = args
210
208
  const selectCQN = _select(args)
211
209
  const result = await tx.run(selectCQN)
212
210
  if (!result.length) return Promise.resolve(result)
213
211
 
214
- const keys = _keys(definitions[entityName], result)
212
+ const keys = _keys(model.definitions[entityName], result)
215
213
  await Promise.all(
216
214
  compositionTree.compositionElements.map(element => {
217
215
  if (element.skipPersistence) return Promise.resolve()
218
- if (data !== undefined && !data.find(entry => element.name in entry) && !(includeAllColumns && result.length))
216
+ if (data !== undefined && !data.find(entry => element.name in entry) && !(selectAllColumns && result.length))
219
217
  return Promise.resolve()
220
218
  const subs = {
221
219
  compositionTree: element,
@@ -235,23 +233,17 @@ const _selectDeepUpdateData = async args => {
235
233
  })
236
234
  )
237
235
 
238
- return _mergeResults(result, selectData || [], root, definitions, compositionTree, entityName)
236
+ return _mergeResults(result, selectData || [], root, model, compositionTree, entityName)
239
237
  }
240
238
 
241
239
  /*
242
240
  * exports
243
241
  */
244
242
 
245
- const selectDeepUpdateData = (
246
- definitions,
247
- cqn,
248
- req,
249
- includeAllRootColumns = false,
250
- includeAllColumns = false,
251
- service
252
- ) => {
243
+ const selectDeepUpdateData = (service, model, req, selectAllColumns = false) => {
244
+ const query = req.query
253
245
  // REVISIT this should be done somewhere before, so it is not done twice for deep updates
254
- const sqlQuery = cqn2cqn4sql(cqn, { definitions })
246
+ const sqlQuery = cqn2cqn4sql(query, model)
255
247
 
256
248
  if (req && _isSameEntity(sqlQuery, req)) {
257
249
  return Promise.resolve(req._.partialPersistentState)
@@ -263,9 +255,9 @@ const selectDeepUpdateData = (
263
255
  const entityName = ensureNoDraftsSuffix(from)
264
256
  const draft = entityName !== from
265
257
  const orderBy = req && req.target && req.target.query && req.target.query.SELECT && req.target.query.SELECT.orderBy
266
- const data = Object.assign({}, sqlQuery.UPDATE.data || {}, cqn.UPDATE.with || {})
258
+ const data = Object.assign({}, sqlQuery.UPDATE.data || {}, query.UPDATE.with || {})
267
259
  const compositionTree = getCompositionTree({
268
- definitions,
260
+ definitions: model.definitions,
269
261
  rootEntityName: entityName, // REVISIT: drafts are resolved too eagerly
270
262
  checkRoot: false,
271
263
  resolveViews: !draft,
@@ -274,17 +266,16 @@ const selectDeepUpdateData = (
274
266
 
275
267
  return _selectDeepUpdateData({
276
268
  tx: cds.tx(req),
277
- definitions,
269
+ model,
278
270
  compositionTree,
279
271
  entityName,
280
272
  data: [data],
281
273
  where,
282
274
  orderBy,
283
275
  draft,
284
- includeAllRootColumns,
285
276
  singleton: req && req.target && req.target._isSingleton,
286
277
  alias,
287
- includeAllColumns: cqn._selectAll || includeAllColumns,
278
+ selectAllColumns,
288
279
  root: true,
289
280
  service
290
281
  })