@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
@@ -37,6 +37,7 @@ const _dataByKey = (entity, data) => {
37
37
  function _addSubDeepUpdateCQNForDelete({ entity, data, selectData, deleteCQN }) {
38
38
  const dataByKey = _dataByKey(entity, data)
39
39
  for (const selectEntry of selectData) {
40
+ if (!selectEntry) continue
40
41
  const dataEntry = dataByKey.get(_serializedKey(entity, selectEntry))
41
42
  if (!dataEntry) {
42
43
  if (deleteCQN.DELETE.where.length > 0) {
@@ -67,7 +68,7 @@ function _fillLinkFromStructuredData(entity, entry) {
67
68
  }
68
69
  }
69
70
 
70
- const _diffData = (newData, oldData, entity, newEntry, oldEntry, definitions) => {
71
+ const _diffData = (newData, oldData, entity, newEntry, oldEntry, model) => {
71
72
  const result = {}
72
73
 
73
74
  const keysSet = new Set(Object.keys(newData).concat(Object.keys(oldData)))
@@ -96,7 +97,7 @@ const _diffData = (newData, oldData, entity, newEntry, oldEntry, definitions) =>
96
97
  nav.isComposition &&
97
98
  nav.is2one &&
98
99
  newEntry[nav.name] !== undefined &&
99
- !definitions[nav.target]._hasPersistenceSkip
100
+ !model.definitions[nav.target]._hasPersistenceSkip
100
101
  ) {
101
102
  result[key] = null
102
103
  }
@@ -106,15 +107,7 @@ const _diffData = (newData, oldData, entity, newEntry, oldEntry, definitions) =>
106
107
  return result
107
108
  }
108
109
 
109
- function _addSubDeepUpdateCQNForUpdateInsert({
110
- entity,
111
- entityName,
112
- data,
113
- selectData,
114
- updateCQNs,
115
- insertCQN,
116
- definitions
117
- }) {
110
+ function _addSubDeepUpdateCQNForUpdateInsert({ entity, entityName, data, selectData, updateCQNs, insertCQN, model }) {
118
111
  const selectDataByKey = _dataByKey(entity, selectData)
119
112
  const deepUpdateData = []
120
113
  for (const entry of data) {
@@ -127,7 +120,7 @@ function _addSubDeepUpdateCQNForUpdateInsert({
127
120
  deepUpdateData.push(entry)
128
121
  const newData = ctUtils.cleanDeepData(entity, entry)
129
122
  const oldData = ctUtils.cleanDeepData(entity, selectEntry)
130
- const diff = _diffData(newData, oldData, entity, entry, selectEntry, definitions)
123
+ const diff = _diffData(newData, oldData, entity, entry, selectEntry, model)
131
124
  // empty updates will be removed later
132
125
  updateCQNs.push({ UPDATE: { entity: entityName, data: diff, where: ctUtils.whereKey(key) } })
133
126
  } else {
@@ -138,7 +131,7 @@ function _addSubDeepUpdateCQNForUpdateInsert({
138
131
  return deepUpdateData
139
132
  }
140
133
 
141
- function _addSubDeepUpdateCQNCollect(definitions, cqns, updateCQNs, insertCQN, deleteCQN) {
134
+ async function _addSubDeepUpdateCQNCollect(model, cqns, updateCQNs, insertCQN, deleteCQN, req) {
142
135
  if (updateCQNs.length > 0) {
143
136
  cqns[0] = cqns[0] || []
144
137
  cqns[0].push(...updateCQNs)
@@ -146,7 +139,7 @@ function _addSubDeepUpdateCQNCollect(definitions, cqns, updateCQNs, insertCQN, d
146
139
 
147
140
  if (insertCQN.INSERT.entries.length > 0) {
148
141
  cqns[0] = cqns[0] || []
149
- const deepInsertCQNs = getDeepInsertCQNs(definitions, insertCQN)
142
+ const deepInsertCQNs = getDeepInsertCQNs(model, insertCQN)
150
143
  deepInsertCQNs.forEach(insertCQN => {
151
144
  const intoCQN = cqns[0].find(cqn => {
152
145
  return cqn.INSERT && cqn.INSERT.into === insertCQN.INSERT.into
@@ -161,7 +154,7 @@ function _addSubDeepUpdateCQNCollect(definitions, cqns, updateCQNs, insertCQN, d
161
154
 
162
155
  if (deleteCQN.DELETE.where.length > 0) {
163
156
  cqns[0] = cqns[0] || []
164
- const deepDeleteCQNs = getDeepDeleteCQNs(definitions, deleteCQN)
157
+ const deepDeleteCQNs = await getDeepDeleteCQNs(model, req, deleteCQN)
165
158
  deepDeleteCQNs.forEach((deleteCQNs, index) => {
166
159
  deleteCQNs.forEach(el => {
167
160
  cqns[index] = cqns[index] || []
@@ -180,7 +173,7 @@ const _addToData = (subData, entity, element, entry) => {
180
173
  subData.push(...unwrappedSubData)
181
174
  }
182
175
 
183
- function _addSubDeepUpdateCQNRecursion({ definitions, compositionTree, entity, data, selectData, cqns, draft }) {
176
+ async function _addSubDeepUpdateCQNRecursion({ model, compositionTree, entity, data, selectData, cqns, draft, req }) {
184
177
  const selectDataByKey = _dataByKey(entity, selectData)
185
178
 
186
179
  for (const element of compositionTree.compositionElements) {
@@ -202,23 +195,24 @@ function _addSubDeepUpdateCQNRecursion({ definitions, compositionTree, entity, d
202
195
  }
203
196
  }
204
197
 
205
- _addSubDeepUpdateCQN({
206
- definitions,
198
+ await _addSubDeepUpdateCQN({
199
+ model,
207
200
  compositionTree: element,
208
201
  data: subData,
209
202
  selectData: selectSubData,
210
203
  cqns,
211
- draft
204
+ draft,
205
+ req
212
206
  })
213
207
  }
214
208
 
215
209
  return cqns
216
210
  }
217
211
 
218
- const _addSubDeepUpdateCQN = ({ definitions, compositionTree, data, selectData, cqns, draft }) => {
212
+ const _addSubDeepUpdateCQN = async ({ model, compositionTree, data, selectData, cqns, draft, req }) => {
219
213
  // We handle each level for deepUpdate, the moment we see that there will be an INSERT,
220
214
  // it'll be removed from our deepUpdateData (and handled deep separately).
221
- const entity = definitions && definitions[compositionTree.source]
215
+ const entity = model.definitions[compositionTree.source]
222
216
 
223
217
  if (entity._hasPersistenceSkip) return Promise.resolve()
224
218
 
@@ -234,21 +228,22 @@ const _addSubDeepUpdateCQN = ({ definitions, compositionTree, data, selectData,
234
228
  selectData,
235
229
  updateCQNs,
236
230
  insertCQN,
237
- definitions
231
+ model
238
232
  })
239
233
 
240
- _addSubDeepUpdateCQNCollect(definitions, cqns, updateCQNs, insertCQN, deleteCQN)
234
+ await _addSubDeepUpdateCQNCollect(model, cqns, updateCQNs, insertCQN, deleteCQN, req)
241
235
 
242
236
  if (deepUpdateData.length === 0) return Promise.resolve()
243
237
 
244
238
  return _addSubDeepUpdateCQNRecursion({
245
- definitions,
239
+ model,
246
240
  compositionTree,
247
241
  entity,
248
242
  data: deepUpdateData,
249
243
  selectData,
250
244
  cqns,
251
- draft
245
+ draft,
246
+ req
252
247
  })
253
248
  }
254
249
 
@@ -256,11 +251,11 @@ const _addSubDeepUpdateCQN = ({ definitions, compositionTree, data, selectData,
256
251
  * exports
257
252
  */
258
253
 
259
- const hasDeepUpdate = (definitions, cqn) => {
254
+ const hasDeepUpdate = (model, cqn) => {
260
255
  if (cqn && cqn.UPDATE && cqn.UPDATE.entity && (cqn.UPDATE.data || cqn.UPDATE.with)) {
261
256
  const updateEntity = cqn.UPDATE.entity
262
257
  const entityName = (updateEntity.ref && updateEntity.ref[0]) || updateEntity.name || updateEntity
263
- const entity = definitions && definitions[ensureNoDraftsSuffix(entityName)]
258
+ const entity = model.definitions[ensureNoDraftsSuffix(entityName)]
264
259
 
265
260
  if (entity) {
266
261
  const keys = Object.keys(Object.assign({}, cqn.UPDATE.data || {}, cqn.UPDATE.with || {}))
@@ -271,7 +266,8 @@ const hasDeepUpdate = (definitions, cqn) => {
271
266
  return false
272
267
  }
273
268
 
274
- const getDeepUpdateCQNs = (definitions, cqn, selectData) => {
269
+ const getDeepUpdateCQNs = async (model, req, selectData) => {
270
+ const { query } = req
275
271
  if (!Array.isArray(selectData)) selectData = [selectData]
276
272
 
277
273
  if (selectData.length === 0) return []
@@ -279,22 +275,31 @@ const getDeepUpdateCQNs = (definitions, cqn, selectData) => {
279
275
  if (selectData.length > 1) throw getError('Deep update can only be performed on a single instance')
280
276
 
281
277
  const cqns = []
282
- const from = (cqn.UPDATE.entity.ref && cqn.UPDATE.entity.ref[0]) || cqn.UPDATE.entity.name || cqn.UPDATE.entity
278
+ const from =
279
+ (query.UPDATE.entity.ref && query.UPDATE.entity.ref[0]) || query.UPDATE.entity.name || query.UPDATE.entity
283
280
  const entityName = ensureNoDraftsSuffix(from)
284
281
  const draft = entityName !== from
285
- const data = cqn.UPDATE.data ? deepCopyObject(cqn.UPDATE.data) : {}
286
- const withObj = cqn.UPDATE.with ? deepCopyObject(cqn.UPDATE.with) : {}
287
- const entity = definitions && definitions[entityName]
282
+ const data = query.UPDATE.data ? deepCopyObject(query.UPDATE.data) : {}
283
+ const withObj = query.UPDATE.with ? deepCopyObject(query.UPDATE.with) : {}
284
+ const entity = model.definitions[entityName]
288
285
  const entry = Object.assign({}, data, withObj, ctUtils.key(entity, selectData[0]))
289
286
  const compositionTree = getCompositionTree({
290
- definitions,
287
+ definitions: model.definitions,
291
288
  rootEntityName: entityName,
292
289
  checkRoot: false,
293
290
  resolveViews: !draft,
294
291
  service: cds.db
295
292
  })
296
293
 
297
- const subCQNs = _addSubDeepUpdateCQN({ definitions, compositionTree, data: [entry], selectData, cqns: [], draft })
294
+ const subCQNs = await _addSubDeepUpdateCQN({
295
+ model,
296
+ compositionTree,
297
+ data: [entry],
298
+ selectData,
299
+ cqns: [],
300
+ draft,
301
+ req
302
+ })
298
303
  subCQNs.forEach((subCQNs, index) => {
299
304
  cqns[index] = cqns[index] || []
300
305
  cqns[index].push(...subCQNs)
@@ -30,9 +30,8 @@ const _getFiltered = err => {
30
30
  Object.keys(err)
31
31
  .concat(['message'])
32
32
  .forEach(k => {
33
- if (k === 'innererror' && process.env.NODE_ENV === 'production') {
34
- return
35
- }
33
+ // REVISIT: do not remove innererror with cds^6
34
+ if (k === 'innererror' && process.env.NODE_ENV === 'production' && !err[SKIP_SANITIZATION]) return
36
35
  if (ALLOWED_PROPERTIES.includes(k) || k.startsWith('@')) {
37
36
  error[k] = err[k]
38
37
  } else if (k === 'numericSeverity') {
@@ -90,6 +89,20 @@ const _anonymousUser = req => {
90
89
  return Object.defineProperty(new cds.User(), '_req', { enumerable: false, value: req })
91
90
  }
92
91
 
92
+ // - for one unique value, we use it
93
+ // - if at least one 5xx exists, we use 500
94
+ // - else if at least one 4xx exists, we use 400
95
+ // - else we use 500
96
+ const _statusCodeFromDetails = details => {
97
+ const uniqueStatusCodes = new Set(
98
+ details.map(d => d.status || d.statusCode || d.code).map(c => (!isNaN(c) && Number(c)) || c)
99
+ )
100
+ if (uniqueStatusCodes.size === 1) return uniqueStatusCodes.values().next().value
101
+ if ([...uniqueStatusCodes].some(s => s >= 500)) return 500
102
+ if ([...uniqueStatusCodes].some(s => s >= 400)) return 400
103
+ return 500
104
+ }
105
+
93
106
  const normalizeError = (err, req) => {
94
107
  const user = (req && req.user) || _anonymousUser(req)
95
108
  const locale = user.locale
@@ -98,8 +111,9 @@ const normalizeError = (err, req) => {
98
111
 
99
112
  // derive status code from err status OR root code OR matching detail codes
100
113
  let statusCode = err.status || err.statusCode || (_isAllowedError(error.code) && error.code)
101
- if (!statusCode && error.details && error.details.every(ele => ele.code === error.details[0].code)) {
102
- statusCode = _isAllowedError(error.details[0].code) < 505 && error.details[0].code
114
+ if (!statusCode && error.details) {
115
+ const detailsCode = _statusCodeFromDetails(error.details)
116
+ if (_isAllowedError(detailsCode)) statusCode = detailsCode
103
117
  }
104
118
 
105
119
  // make sure it's a number
@@ -5,7 +5,7 @@
5
5
  const cds = require('../../cds')
6
6
  const { SELECT } = cds.ql
7
7
 
8
- const { getRequiresAsArray } = require('../utils/auth')
8
+ const { getRequiresAsArray } = require('../../auth/utils')
9
9
  const { cqn2cqn4sql } = require('../utils/cqn2cqn4sql')
10
10
  const { isActiveEntityRequested, removeIsActiveEntityRecursively } = require('../../fiori/utils/where')
11
11
  const { ensureNoDraftsSuffix } = require('../../fiori/utils/handler')
@@ -226,88 +226,6 @@ const _getMergedWhere = restricts => {
226
226
  return xprs
227
227
  }
228
228
 
229
- const _findTableName = (ref, aliases) => {
230
- const maxLength = Math.max(...aliases.map(alias => alias.length))
231
- let name = ''
232
- for (let i = 0; i < ref.length; i++) {
233
- name += name.length !== 0 ? `.${ref[i]}` : ref[i]
234
-
235
- if (name >= maxLength) {
236
- break
237
- }
238
-
239
- const aliasIndex = aliases.indexOf(name)
240
- if (aliasIndex !== -1) {
241
- return { refIndex: i, aliasIndex: aliasIndex, name: name }
242
- }
243
- }
244
-
245
- return { refIndex: -1 }
246
- }
247
-
248
- const _getTableForColumn = (col, aliases, model) => {
249
- for (let i = 0; i < aliases.length; i++) {
250
- const index = aliases.length - i - 1
251
- const alias = aliases[index]
252
- if (Object.keys(model.definitions[alias].elements).includes(col)) {
253
- return { index, table: alias.replace(/\./g, '_') }
254
- }
255
- }
256
-
257
- return { index: -1 }
258
- }
259
-
260
- const _adaptTableName = (ref, index, name) => {
261
- const tableName = name.replace(/\./g, '_')
262
- ref.splice(0, index + 1, tableName)
263
- }
264
-
265
- const _ensureTableAlias = (ref, aliases, targetFrom, model, hasExpand) => {
266
- const nameObj = _findTableName(ref, aliases)
267
- if (nameObj.refIndex === -1) {
268
- const { index, table } = _getTableForColumn(ref[0], aliases, model)
269
- if (index !== -1) {
270
- nameObj.aliasIndex = index
271
- if (table === targetFrom.name && targetFrom.as) {
272
- ref.unshift(targetFrom.as)
273
- } else {
274
- ref.unshift(table)
275
- }
276
- }
277
- } else {
278
- _adaptTableName(ref, nameObj.refIndex, nameObj.name)
279
- }
280
- }
281
-
282
- const _enhanceAnnotationSubSelect = (select, model, targetName, targetFrom, hasExpand) => {
283
- if (select.where) {
284
- for (const v of select.where) {
285
- if (v.ref && select.from.ref) {
286
- _ensureTableAlias(v.ref, [targetName, select.from.ref[0]], targetFrom, model, hasExpand)
287
- }
288
- }
289
- }
290
- }
291
-
292
- // Add alias symbols to refs if needed and mark ref (for expand) and SELECT.from (for draft)
293
- const _enhanceAnnotationWhere = (query, where, model) => {
294
- const cqn2cqn4sqlOptions = { suppressSearch: true }
295
- query = cqn2cqn4sql(query, model, cqn2cqn4sqlOptions)
296
- const hasExpand = query.SELECT && query.SELECT.columns && query.SELECT.columns.some(col => col.expand)
297
- const targetFrom = query.SELECT
298
- ? { name: query.SELECT.from.ref[0].replace(/\./g, '_'), as: query.SELECT.from.as }
299
- : {}
300
- for (const w of where) {
301
- if (w.ref) {
302
- // REVISIT: can this case be removed permanently?
303
- // _ensureTableAlias(w.ref, [query._target.name], targetFrom, model, hasExpand)
304
- } else if (w.SELECT) {
305
- _enhanceAnnotationSubSelect(w.SELECT, model, query._target.name, targetFrom, hasExpand)
306
- w.SELECT.__targetFrom = targetFrom
307
- }
308
- }
309
- }
310
-
311
229
  const _getApplicables = (restricts, req) => {
312
230
  return restricts.filter(restrict => {
313
231
  const event = DRAFT2CRUD[req.event] || req.event
@@ -419,9 +337,26 @@ const _addRestrictionsToRead = async (req, model, resolvedApplicables) => {
419
337
 
420
338
  const restrictionForTarget = _getRestrictionForTarget(resolvedApplicables, req.target)
421
339
  if (restrictionForTarget) {
340
+ // adjust free subselects, if necessary
341
+ if (resolvedApplicables.some(ra => ra.where.match(/\s*exists\s*\(\s*select\s*1\s*/i))) {
342
+ for (const ele of restrictionForTarget) {
343
+ if (typeof ele !== 'object' || !ele.SELECT || !ele.SELECT.where) continue
344
+ for (const w of ele.SELECT.where) {
345
+ if (w.ref && w.ref.length > 2) {
346
+ let path = w.ref[0]
347
+ if (!model.definitions[path]) continue
348
+ let i = 1
349
+ for (; i < w.ref.length; i++) {
350
+ if (model.definitions[`${path}.${w.ref[i]}`]) path += `.${w.ref[i]}`
351
+ else break
352
+ }
353
+ w.ref = [path, ...w.ref.slice(i)]
354
+ }
355
+ }
356
+ }
357
+ }
358
+ // apply restriction
422
359
  req.query.where(restrictionForTarget)
423
- // REVISIT: remove with cds^6
424
- _enhanceAnnotationWhere(req.query, restrictionForTarget, model)
425
360
  }
426
361
  }
427
362
 
@@ -4,6 +4,7 @@ const { SELECT } = cds.ql
4
4
  const getTemplate = require('../utils/template')
5
5
  const templateProcessor = require('../utils/templateProcessor')
6
6
  const replaceManagedData = require('../utils/dollar')
7
+ const { deepCopyArray } = require('../utils/copy')
7
8
 
8
9
  const onlyKeysRemain = require('../utils/onlyKeysRemain')
9
10
 
@@ -67,6 +68,7 @@ const _updateReqData = (req, that) => {
67
68
  }
68
69
 
69
70
  module.exports = cds.service.impl(function () {
71
+ // eslint-disable-next-line complexity
70
72
  this.on(['CREATE', 'READ', 'UPDATE', 'DELETE'], '*', async function (req) {
71
73
  if (typeof req.query !== 'string' && req.target && req.target._hasPersistenceSkip) {
72
74
  req.reject(501, 'PERSISTENCE_SKIP_NO_GENERIC_CRUD', [req.target.name])
@@ -77,6 +79,19 @@ module.exports = cds.service.impl(function () {
77
79
 
78
80
  let result
79
81
 
82
+ // validate that all elements in path exist on db, if necessary
83
+ // - INSERT has no where clause to do this in one roundtrip
84
+ // - SELECT returns [] -> really empty collection or invalid path?
85
+ let pathExistsQuery
86
+ const { ref } = (req.query.INSERT && req.query.INSERT.into) || (req.query.SELECT && req.query.SELECT.from) || {}
87
+ // REVISIT: why is copy necessary?
88
+ if (ref && ref.length > 1) pathExistsQuery = SELECT(1).from({ ref: deepCopyArray(ref.slice(0, -1)) })
89
+
90
+ if (req.event === 'CREATE' && pathExistsQuery) {
91
+ const res = await pathExistsQuery
92
+ if (res.length === 0) req.reject(404)
93
+ }
94
+
80
95
  // no changes, no op (otherwise, @cds.on.update gets new values), but we need to check existence
81
96
  if (req.event === 'UPDATE' && onlyKeysRemain(req)) {
82
97
  if (await _targetEntityDoesNotExist(req)) req.reject(404)
@@ -95,7 +110,13 @@ module.exports = cds.service.impl(function () {
95
110
  result = await cds.tx(req).run(req.query, req.data)
96
111
  }
97
112
 
98
- if (req.event === 'READ') return result
113
+ if (req.event === 'READ') {
114
+ if ((result == null || result.length === 0) && pathExistsQuery) {
115
+ const res = await pathExistsQuery
116
+ if (res.length === 0) req.reject(404)
117
+ }
118
+ return result
119
+ }
99
120
 
100
121
  if (req.event === 'DELETE') {
101
122
  if (result === 0) req.reject(404)
@@ -55,6 +55,7 @@ NON_WRITABLE_VIEW={0} on views with join and/or union is not supported
55
55
  # db
56
56
  NO_DATABASE_CONNECTION=No database connection
57
57
  ENTITY_ALREADY_EXISTS=Entity already exists
58
+ ENTITY_LOCKED=Entity locked
58
59
  UNIQUE_CONSTRAINT_VIOLATION=Unique constraint violation
59
60
  FK_CONSTRAINT_VIOLATION=Foreign key constraint violation
60
61
 
@@ -72,8 +73,8 @@ ENTITY_IS_NOT_CRUD=Entity "{0}" is not {1}
72
73
  ENTITY_IS_NOT_CRUD_VIA_NAVIGATION=Entity "{0}" is not {1} via association "{2}"
73
74
  ENTITY_IS_AUTOEXPOSED=Entity "{0}" is not explicitely exposed as part of the service
74
75
  EXPAND_IS_RESTRICTED=Navigation property "{0}" is not allowed for expand operation
75
-
76
76
  EXPAND_COUNT_UNSUPPORTED="$count" is not supported for expand operation
77
+ ORDERBY_LAMBDA_UNSUPPORTED="$orderby" does not support lambda
77
78
 
78
79
  # rest protocol adapter
79
80
  INVALID_RESOURCE="{0}" is not a valid resource
@@ -52,12 +52,8 @@ function targetFromPath(path, model) {
52
52
  ? definitions[current.elements[r.id].target]
53
53
  : definitions[r.id] || definitions[r.id.replace(/_drafts$/, '')]
54
54
  } else {
55
- const next = current.elements[r]
56
- if (next.isAssociation) {
57
- current = definitions[next.target]
58
- } else {
59
- current = next
60
- }
55
+ const next = current ? current.elements[r] : definitions[r]
56
+ current = next.isAssociation ? definitions[next.target] : next
61
57
  }
62
58
  }
63
59
  return current