@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,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
@@ -13,54 +13,10 @@ const { getPathFromRef, getEntityFromPath } = require('../../common/utils/path')
13
13
  const { addToWhere } = require('../../common/utils/cqn')
14
14
  const { removeIsActiveEntityRecursively } = require('../../fiori/utils/where')
15
15
  const { addRefToWhereIfNecessary } = require('../../../odata/afterburner')
16
+ const { addAliasToExpression, PARENT_ALIAS, FOREIGN_ALIAS } = require('../../db/utils/generateAliases')
16
17
 
17
- const PARENT_ALIAS = '_parent_'
18
- const PARENT_ALIAS_REGEX = new RegExp('^' + PARENT_ALIAS + '\\d*$')
19
- const FOREIGN_ALIAS = '_foreign_'
20
18
  const OPERATIONS = ['=', '>', '<', '!=', '<>', '>=', '<=', 'like', 'between', 'in', 'not in']
21
19
 
22
- // special case in lambda functions
23
- const _addParentAlias = (where, alias) => {
24
- where.forEach(e => {
25
- if (e.ref && e.ref[0].match(PARENT_ALIAS_REGEX)) {
26
- e.ref[0] = alias
27
- }
28
- })
29
- }
30
-
31
- const _addAliasToElement = (e, alias) => {
32
- if (e.ref) {
33
- return { ref: [alias, ...e.ref] }
34
- }
35
-
36
- if (e.list) {
37
- return { list: e.list.map(arg => _addAliasToElement(arg, alias)) }
38
- }
39
-
40
- if (e.func) {
41
- const args = e.args.map(arg => _addAliasToElement(arg, alias))
42
- return { ...e, args }
43
- }
44
-
45
- if (e.SELECT && e.SELECT.where) {
46
- _addParentAlias(e.SELECT.where, alias)
47
- }
48
-
49
- if (e.xpr) {
50
- return { xpr: e.xpr.map(e1 => _addAliasToElement(e1, alias)) }
51
- }
52
-
53
- return e
54
- }
55
-
56
- const _addAliasToExpression = (expression, alias) => {
57
- if (!alias) {
58
- return expression
59
- }
60
-
61
- return expression.map(e => _addAliasToElement(e, alias))
62
- }
63
-
64
20
  const _elementFromRef = (name, entity) => {
65
21
  if (!entity) return
66
22
 
@@ -112,7 +68,7 @@ const convertPathExpressionToWhere = (fromClause, model, options) => {
112
68
  const currentSelect = SELECT.from(`${currentEntityName} as ${tableAlias}`)
113
69
 
114
70
  if (fromClause.ref[i].where) {
115
- currentSelect.where(_addAliasToExpression(fromClause.ref[i].where, tableAlias))
71
+ currentSelect.where(addAliasToExpression(fromClause.ref[i].where, tableAlias))
116
72
  }
117
73
 
118
74
  // REVISIT: Only args in last segment are handled, intermediate ones are ignored
@@ -326,35 +282,15 @@ const _createWindowCQN = (SELECT, model) => {
326
282
  delete SELECT.groupBy
327
283
  }
328
284
 
329
- const _isAny = element => {
330
- return Array.isArray(element.ref) && element.ref.slice(-1)[0].id
285
+ const _unshiftRefsWithNavigation = nav => el => {
286
+ if (el.ref) return { ref: [...nav, ...el.ref] }
287
+ if (el.xpr) return { xpr: el.xpr.map(_unshiftRefsWithNavigation(nav)) }
288
+ return el
331
289
  }
332
290
 
333
- const _isAll = element => {
334
- const last = Array.isArray(element.ref) && element.ref.slice(-1)[0]
335
- return last && last.id && last.where && last.where[0] === 'not' && last.where[1].xpr
336
- }
337
-
338
- // eslint-disable-next-line complexity
339
- const _getWhereExistsSubSelect = (cqn, where, index, model, options) => {
340
- const _unshiftRefsWithNavigation = nav => el => {
341
- if (el.ref) return { ref: [...nav, ...el.ref] }
342
- if (el.xpr) return { xpr: el.xpr.map(_unshiftRefsWithNavigation(nav)) }
343
- return el
344
- }
345
-
346
- if (!options.lambdaIteration) options.lambdaIteration = 1
347
- const outerAlias =
348
- (cqn.SELECT.from.ref && cqn.SELECT.from.as) ||
349
- (cqn.SELECT.from.args && cqn.SELECT.from.args[0].ref && cqn.SELECT.from.args[0].as) ||
350
- PARENT_ALIAS + options.lambdaIteration
351
- const innerAlias = FOREIGN_ALIAS + options.lambdaIteration
352
- cqn.SELECT.from.as = outerAlias
353
- const ref = cqn.SELECT.from.ref || (cqn.SELECT.from.args && cqn.SELECT.from.args[0].ref)
354
- const queryTarget = getEntityFromPath(getPathFromRef(ref), model)
355
-
356
- let nav = where[index].ref.map(el => (el.id ? el.id : el))
357
- const last = where[index].ref.slice(-1)[0]
291
+ const _getWhereExistsSubSelect = (queryTarget, outerAlias, innerAlias, ref, model, options) => {
292
+ let nav = ref.map(el => (el.id ? el.id : el))
293
+ const last = ref.slice(-1)[0]
358
294
  const navName = queryTarget.elements[nav[0]] ? nav[0] : nav[nav.length - 1]
359
295
  const navElement = queryTarget.elements[navName]
360
296
 
@@ -381,17 +317,24 @@ const _getWhereExistsSubSelect = (cqn, where, index, model, options) => {
381
317
  : last.where
382
318
  : undefined
383
319
 
320
+ if (!innerAlias && !outerAlias) {
321
+ innerAlias = navElement.target
322
+ outerAlias = navElement.parent.name
323
+ }
324
+
384
325
  const subSelect = SELECT.from({ ref: [navElement.target], as: innerAlias })
385
326
  if (condition) {
386
327
  if (subSelect.SELECT.from.as) {
387
328
  for (let i = 0; i < condition.length; i++) {
329
+ if (!condition[i].ref) continue
388
330
  if (
389
- condition[i].ref &&
390
331
  condition[i].ref.length > 1 &&
391
332
  condition[i].ref.every(r => typeof r === 'string') &&
392
333
  condition[i].ref[0] === navName
393
334
  ) {
394
335
  condition[i].ref[0] = subSelect.SELECT.from.as
336
+ } else if (condition[i].ref.length === 1) {
337
+ condition[i].ref.unshift(subSelect.SELECT.from.as)
395
338
  }
396
339
  }
397
340
  }
@@ -446,42 +389,9 @@ const _ensureExpandedNestedWhereExists = ({ ref }, aliases) => {
446
389
  return { ref: [acc] }
447
390
  }
448
391
 
449
- const _convertWhereExists = (where, cqn, model, options) => {
450
- for (let i = 0; i < where.length; i++) {
451
- const element = where[i]
452
-
453
- // ensure where exists are fully expanded
454
- if (
455
- (element === 'exists' && where[i + 1].ref && where[i > 1 ? i - 1 : 0] !== 'not') ||
456
- (element === 'not' && where[i + 1] === 'exists' && where[i + 2].ref)
457
- ) {
458
- const offset = element === 'not' ? 2 : 1
459
- const aliases = cqn.SELECT.from.as
460
- ? [cqn.SELECT.from.as]
461
- : cqn.SELECT.from.join
462
- ? cqn.SELECT.from.args.map(arg => arg.as)
463
- : []
464
- where[i + offset] = _ensureExpandedNestedWhereExists(where[i + offset], aliases)
465
- }
466
-
467
- if (element.xpr) {
468
- _convertWhereExists(element.xpr, cqn, model, options) // > recursing into nested {xpr}
469
- } else if (element === 'exists' && _isAny(where[i + 1])) {
470
- where[i + 1] = _getWhereExistsSubSelect(cqn, where, i + 1, model, options)
471
- } else if (element === 'not' && where[i + 1] === 'exists' && _isAll(where[i + 2])) {
472
- where[i + 2] = _getWhereExistsSubSelect(cqn, where, i + 2, model, options)
473
- }
474
- }
475
- }
476
-
477
- const convertWhereExists = (cqn, model, options) => {
478
- if (cqn.SELECT.where) _convertWhereExists(cqn.SELECT.where, cqn, model, options)
479
- }
480
-
481
392
  const _getRefIndex = (where, index) => {
482
393
  if (
483
394
  where[index - 1].ref &&
484
- // REVISIT DRAFT HANDLING: cqn2cqn4sql must not be called there
485
395
  where[index - 1].ref[where[index - 1].ref.length - 1] !== 'InProcessByUser' &&
486
396
  where[index + 1].val !== undefined &&
487
397
  where[index + 1].val !== null &&
@@ -491,7 +401,6 @@ const _getRefIndex = (where, index) => {
491
401
  }
492
402
  if (
493
403
  where[index + 1].ref &&
494
- // REVISIT DRAFT HANDLING: cqn2cqn4sql must not be called there
495
404
  where[index + 1].ref[where[index + 1].ref.length - 1] !== 'InProcessByUser' &&
496
405
  where[index - 1].val !== undefined &&
497
406
  where[index - 1].val !== null &&
@@ -501,6 +410,77 @@ const _getRefIndex = (where, index) => {
501
410
  }
502
411
  }
503
412
 
413
+ const _convertWhereExistsColumn = (column, model, options, queryTarget) => {
414
+ const lambdaIteration = options.lambdaIteration
415
+ if (typeof column === 'object') {
416
+ if (column.SELECT) {
417
+ convertWhereExists(column.SELECT, model, options)
418
+ options.lambdaIteration = lambdaIteration
419
+ } else if (column.where) {
420
+ convertWhereExists(column, model, options, queryTarget)
421
+ options.lambdaIteration = lambdaIteration
422
+ }
423
+ }
424
+ }
425
+
426
+ // eslint-disable-next-line complexity
427
+ const convertWhereExists = (query, model, options, currentTarget) => {
428
+ const { where, columns, expand } = query
429
+ let innerAlias, outerAlias, queryTarget
430
+ const { ref, as } =
431
+ (query.ref && query) ||
432
+ (query.from &&
433
+ ((query.from.ref && query.from) || (query.from.args && query.from.args[0].ref && query.from.args[0]))) ||
434
+ {}
435
+ if (!ref) return
436
+
437
+ if (!options.lambdaIteration) options.lambdaIteration = 1
438
+ const lambdaIteration = options.lambdaIteration
439
+ if (!currentTarget) {
440
+ queryTarget = getEntityFromPath(getPathFromRef(ref), model)
441
+ outerAlias = as || PARENT_ALIAS + lambdaIteration
442
+ innerAlias = FOREIGN_ALIAS + lambdaIteration
443
+ } else {
444
+ const element = currentTarget.elements[ref]
445
+ if (element && element.isAssociation) {
446
+ queryTarget = element._target
447
+ }
448
+ }
449
+
450
+ if (where) {
451
+ for (let i = 0; i < where.length; i++) {
452
+ const element = where[i]
453
+
454
+ // ensure where exists are fully expanded
455
+ if (element === 'exists' && where[i + 1].ref) {
456
+ const ref = query.from || query
457
+ const aliases = ref.as ? [ref.as] : ref.join ? ref.args.map(arg => arg.as) : []
458
+ where[i + 1] = _ensureExpandedNestedWhereExists(where[i + 1], aliases)
459
+ }
460
+
461
+ if (element.xpr) {
462
+ convertWhereExists({ ...query, where: element.xpr }, model, options) // > recursing into nested {xpr}
463
+ } else if (element === 'exists' && where[i + 1].ref) {
464
+ if (query.from) {
465
+ query.from.as = outerAlias
466
+ }
467
+ where[i + 1] = _getWhereExistsSubSelect(queryTarget, outerAlias, innerAlias, where[i + 1].ref, model, options)
468
+ i += 1
469
+ } else if (element.SELECT) {
470
+ convertWhereExists(element.SELECT, model, options)
471
+ }
472
+ options.lambdaIteration = lambdaIteration
473
+ }
474
+ }
475
+
476
+ if (columns) {
477
+ columns.forEach(column => _convertWhereExistsColumn(column, model, options, queryTarget))
478
+ }
479
+ if (expand && expand !== '*') {
480
+ expand.forEach(column => _convertWhereExistsColumn(column, model, options, queryTarget))
481
+ }
482
+ }
483
+
504
484
  const _convertNotEqual = (container, partName = 'where') => {
505
485
  const where = container[partName]
506
486
 
@@ -668,7 +648,7 @@ const _convertPathExpression = (SELECT, model, options = {}) => {
668
648
  if (alias) {
669
649
  SELECT.from.as = alias
670
650
  if (SELECT.where && alias !== prevAlias) {
671
- SELECT.where = _addAliasToExpression(SELECT.where, alias)
651
+ SELECT.where = addAliasToExpression(SELECT.where, alias)
672
652
  }
673
653
  }
674
654
  if (columns) {
@@ -691,7 +671,7 @@ const _convertPathExpression = (SELECT, model, options = {}) => {
691
671
  }
692
672
  // TODO: REVISIT: We need to add alias to subselect in .where, .columns, .from, ... etc
693
673
  if (where) {
694
- if (options.isDraft) {
674
+ if (options._4fiori) {
695
675
  addToWhere({ SELECT }, where)
696
676
  } else {
697
677
  addToWhere({ SELECT }, removeIsActiveEntityRecursively(where))
@@ -703,8 +683,7 @@ const _convertPathExpression = (SELECT, model, options = {}) => {
703
683
  const _convertSelect = (query, model, _options) => {
704
684
  const options = Object.assign(
705
685
  {
706
- isDB: _options.service instanceof cds.DatabaseService,
707
- isDraft: _options.draft,
686
+ _4db: _options.service instanceof cds.DatabaseService,
708
687
  isStreaming: query._streaming
709
688
  },
710
689
  _options
@@ -720,7 +699,7 @@ const _convertSelect = (query, model, _options) => {
720
699
  if (cds.env.features.odata_new_parser) _flattenCQN(query)
721
700
 
722
701
  // lambda functions
723
- convertWhereExists(query, model, options)
702
+ convertWhereExists(query.SELECT, model, options)
724
703
 
725
704
  // add 'or is null' in case of '!='
726
705
  if (query.SELECT._4odata) {
@@ -729,7 +708,7 @@ const _convertSelect = (query, model, _options) => {
729
708
  }
730
709
 
731
710
  _convertPathExpression(query.SELECT, model, options)
732
- rewriteAsterisks(query, model, options.isDB, options.isDraft)
711
+ rewriteAsterisks(query, model, options)
733
712
  if (query.SELECT.where && _isCountNavigation(query.SELECT.where)) {
734
713
  _convertCountNavigation(query.SELECT, model)
735
714
  }
@@ -780,12 +759,6 @@ const _convertInsert = (query, model, options) => {
780
759
 
781
760
  const resolvedView = resolveView(insert, model, cds.db)
782
761
 
783
- if (query.INSERT.into.ref && query.INSERT.into.ref.length > 1) {
784
- const copyFrom = [...query.INSERT.into.ref]
785
- copyFrom.pop()
786
- resolvedView._validationQuery = _convertSelect(SELECT.from({ ref: copyFrom }).columns([1]), model, options)
787
- }
788
-
789
762
  return resolvedView
790
763
  }
791
764
 
@@ -831,7 +804,7 @@ const _convertDelete = (query, model, options) => {
831
804
 
832
805
  if (alias) deleet.DELETE.from = { ref: [target], as: alias }
833
806
  if (where) deleet.where(where)
834
- if (query.DELETE.where) deleet.where(_addAliasToExpression(query.DELETE.where, alias))
807
+ if (query.DELETE.where) deleet.where(addAliasToExpression(query.DELETE.where, alias))
835
808
 
836
809
  const targetEntity = model.definitions[target]
837
810
  if (!targetEntity) return deleet
@@ -868,7 +841,7 @@ const _convertUpdate = (query, model, options) => {
868
841
 
869
842
  if (alias) update.UPDATE.entity = { ref: [target], as: alias }
870
843
  if (where) update.where(where)
871
- if (query.UPDATE.where) update.where(_addAliasToExpression(query.UPDATE.where, alias))
844
+ if (query.UPDATE.where) update.where(addAliasToExpression(query.UPDATE.where, alias))
872
845
 
873
846
  const targetEntity = model.definitions[target]
874
847
  if (!targetEntity) return update
@@ -6,17 +6,27 @@ const getEtagElement = entity => {
6
6
  return Object.values(entity.elements).find(element => element['@odata.etag'])
7
7
  }
8
8
 
9
- const _getUps = (entity, model) => {
9
+ const getComp2oneParents = (entity, model) => {
10
+ if (!entity) return []
11
+ if (entity.own('__comp2oneParents')) return entity.__comp2oneParents
12
+ const invalidationFn = element => !(element.is2one && element._isCompositionEffective)
13
+ const comp2oneParents = _getUps(entity, model, invalidationFn)
14
+ return entity.set('__comp2oneParents', comp2oneParents)
15
+ }
16
+
17
+ const _getUps = (entity, model, invalidationFn) => {
18
+ if (entity.own('__parents')) return entity.__parents
10
19
  const ups = []
11
20
  for (const def of Object.values(model.definitions)) {
12
21
  if (def.kind !== 'entity' || !def.associations) continue
13
22
  for (const element of Object.values(def.associations)) {
14
23
  if (element.target !== entity.name || element._isBacklink) continue
15
24
  if (element.name === 'SiblingEntity') continue
25
+ if (invalidationFn && invalidationFn(element)) continue
16
26
  ups.push(element)
17
27
  }
18
28
  }
19
- return ups
29
+ return entity.set('__parents', ups)
20
30
  }
21
31
 
22
32
  const _ifDataSubject = (entity, role) => {
@@ -40,7 +50,7 @@ const _getDataSubjectUp = (role, model, entity, prev, next, result) => {
40
50
  }
41
51
 
42
52
  const _getDataSubjectDown = (role, entity, prev, next) => {
43
- const associations = Object.values(entity.associations).filter(e => !e._isBacklink)
53
+ const associations = Object.values(entity.associations || {}).filter(e => !e._isBacklink)
44
54
  for (const element of associations) {
45
55
  const me = { entity, relative: entity, element }
46
56
  if (_ifDataSubject(element._target, role)) {
@@ -69,8 +79,20 @@ const getDataSubject = (entity, model, role) => {
69
79
  return entity.set(hash, dataSubject)
70
80
  }
71
81
 
72
- const _resolve = (name, model, namespace) =>
73
- model.entities(namespace)[name] || model.definitions[`${namespace}.${name}`]
82
+ const _findInModel = (name, model, namespace) => {
83
+ return model.entities(namespace)[name] || model.definitions[`${namespace}.${name}`]
84
+ }
85
+
86
+ const _resolve = (name, model, namespace) => {
87
+ const resolved = _findInModel(name, model, namespace)
88
+ // the edm name has an additional suffix 'Parameters' in case of views with parameters
89
+ if (!resolved && name.endsWith('Parameters')) {
90
+ const viewWithParam = _findInModel(name.replace(/Parameters$/, ''), model, namespace)
91
+ if (!viewWithParam || !viewWithParam.params) return
92
+ return viewWithParam
93
+ }
94
+ return resolved
95
+ }
74
96
 
75
97
  const _findRootEntity = (model, edmName, namespace) => {
76
98
  const parts = edmName.split('_')
@@ -189,5 +211,6 @@ module.exports = {
189
211
  getElementDeep,
190
212
  isRootEntity,
191
213
  getDataSubject,
192
- alias2ref
214
+ alias2ref,
215
+ getComp2oneParents
193
216
  }
@@ -178,12 +178,32 @@ const _resolveTargetForeignKey = targetKey => {
178
178
  return { targetName, propagation }
179
179
  }
180
180
 
181
+ const _resolveColumnsFromQuery = query => {
182
+ if (query && query.SET) return _resolveColumnsFromQuery(query.SET.args[0])
183
+ if (query && query.SELECT && query.SELECT.columns) return query.SELECT.columns
184
+ return []
185
+ }
186
+
181
187
  const _resolvedKeys = (foreignKeys, targetKeys, fillChild) => {
182
188
  const foreignKeyPropagations = []
183
189
 
184
190
  for (let i = 0; i < foreignKeys.length; i++) {
185
191
  const fk = foreignKeys[i]
186
- const tk = targetKeys[i]
192
+ let tk
193
+
194
+ /*
195
+ * REVISIT: poor man's look-up of target key with fallback to targetKeys[i]
196
+ * Look at elements, then try to find it in query and resolve recursively until you have the full path.
197
+ * Once you have the full path, you can find it in the target entity.
198
+ * NOTE: There can be projections upon projections and renamings in every projection. -> not yet covered!!!
199
+ */
200
+ const tkCol = _resolveColumnsFromQuery(targetKeys[i].parent.query).find(
201
+ c => c.ref && `${fk['@odata.foreignKey4']}_${c.ref.join('_')}` === fk.name
202
+ )
203
+ tk = tkCol && targetKeys.find(tk => tk.name === (tkCol.as ? tkCol.as : tkCol.ref.join('_')))
204
+ // with composition of aspects, the lookup fails -> we need this final fallback
205
+ if (!tk) tk = targetKeys[i]
206
+
187
207
  if (fk._isStructured) {
188
208
  i += _resolve4struct(targetKeys, fk, foreignKeyPropagations, fillChild, false, i)
189
209
  } else if (tk._isStructured) {
@@ -1,4 +1,5 @@
1
1
  const { where2obj } = require('./cqn')
2
+ const { deepCopyArray } = require('./copy')
2
3
 
3
4
  function _getOnCondElements(onCond, onCondElements = []) {
4
5
  const andIndex = onCond.indexOf('and')
@@ -17,7 +18,7 @@ function _getOnCondElements(onCond, onCondElements = []) {
17
18
  function _modifyWhereWithNavigations(where, newWhere, targetKeyElement, keyName) {
18
19
  if (where) {
19
20
  // copy where else query will be modified
20
- const whereCopy = JSON.parse(JSON.stringify(where))
21
+ const whereCopy = deepCopyArray(where)
21
22
  if (newWhere.length > 0) newWhere.push('and')
22
23
  newWhere.push(...whereCopy)
23
24
  }
@@ -24,7 +24,7 @@ const getEntityFromPath = (path, model) => {
24
24
  while (segments.length) {
25
25
  const segment = segments.shift()
26
26
  current = current.elements[segment.id || segment]
27
- if (current.target) current = model.definitions[current.target]
27
+ if (current && current.target) current = model.definitions[current.target]
28
28
  }
29
29
  return current
30
30
  }