@sap/cds 5.5.5 → 5.6.3

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 (207) hide show
  1. package/CHANGELOG.md +139 -1
  2. package/apis/services.d.ts +31 -1
  3. package/app/index.js +22 -11
  4. package/bin/build/buildTaskFactory.js +1 -1
  5. package/bin/build/provider/buildTaskProviderInternal.js +1 -1
  6. package/bin/build/provider/fiori/index.js +1 -1
  7. package/bin/build/provider/hana/2migration.js +8 -7
  8. package/bin/build/provider/java-cf/index.js +1 -1
  9. package/bin/deploy/to-hana/hana.js +1 -17
  10. package/common.cds +8 -0
  11. package/lib/compile/to/sql.js +22 -2
  12. package/lib/connect/bindings.js +2 -1
  13. package/lib/core/reflect.js +4 -1
  14. package/lib/env/index.js +180 -42
  15. package/lib/env/requires.js +16 -1
  16. package/lib/i18n/localize.js +33 -5
  17. package/lib/index.js +3 -3
  18. package/lib/log/format/kibana.js +6 -2
  19. package/lib/ql/Query.js +1 -0
  20. package/lib/ql/SELECT.js +15 -8
  21. package/lib/ql/Whereable.js +5 -0
  22. package/lib/req/context.js +13 -5
  23. package/lib/serve/Service-dispatch.js +8 -1
  24. package/lib/utils/axios.js +7 -0
  25. package/lib/utils/data.js +1 -1
  26. package/lib/utils/tests.js +1 -1
  27. package/libx/_runtime/audit/Service.js +18 -18
  28. package/libx/_runtime/audit/generic/personal/access.js +1 -1
  29. package/libx/_runtime/audit/generic/personal/modification.js +3 -2
  30. package/libx/_runtime/audit/generic/personal/utils.js +23 -63
  31. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +4 -0
  32. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +37 -35
  33. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +3 -1
  34. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +5 -5
  35. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -7
  36. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +84 -34
  37. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +12 -4
  38. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +9 -3
  39. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +8 -6
  40. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -3
  41. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +13 -11
  42. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +11 -95
  43. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +17 -11
  44. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
  45. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/SetResponseHeadersCommand.js +1 -0
  46. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +6 -2
  47. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +3 -34
  48. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -3
  49. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +64 -31
  50. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +10 -5
  51. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +1 -1
  52. package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +1 -1
  53. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +1 -3
  54. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +20 -21
  55. package/libx/_runtime/cds-services/services/utils/columns.js +6 -1
  56. package/libx/_runtime/cds-services/services/utils/compareJson.js +1 -8
  57. package/libx/_runtime/cds-services/services/utils/differ.js +7 -26
  58. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -4
  59. package/libx/_runtime/cds-services/util/assert.js +29 -13
  60. package/libx/_runtime/cds.js +2 -1
  61. package/libx/_runtime/common/aspects/Association.js +72 -0
  62. package/libx/_runtime/common/aspects/any.js +8 -45
  63. package/libx/_runtime/common/aspects/entity.js +0 -1
  64. package/libx/_runtime/common/aspects/relation.js +40 -0
  65. package/libx/_runtime/common/aspects/utils.js +73 -1
  66. package/libx/_runtime/common/auth/strategies/utils/uaa.js +10 -14
  67. package/libx/_runtime/common/composition/data.js +3 -2
  68. package/libx/_runtime/common/composition/delete.js +3 -1
  69. package/libx/_runtime/common/composition/tree.js +23 -18
  70. package/libx/_runtime/common/composition/update.js +9 -1
  71. package/libx/_runtime/common/composition/utils.js +34 -8
  72. package/libx/_runtime/common/error/frontend.js +6 -1
  73. package/libx/_runtime/common/generic/auth.js +5 -9
  74. package/libx/_runtime/common/generic/crud.js +2 -2
  75. package/libx/_runtime/common/generic/etag.js +11 -8
  76. package/libx/_runtime/common/generic/input.js +3 -3
  77. package/libx/_runtime/common/generic/paging.js +9 -5
  78. package/libx/_runtime/common/generic/put.js +3 -2
  79. package/libx/_runtime/common/generic/sorting.js +3 -3
  80. package/libx/_runtime/common/generic/temporal.js +3 -3
  81. package/libx/_runtime/common/utils/cqn.js +20 -1
  82. package/libx/_runtime/common/utils/cqn2cqn4sql.js +125 -139
  83. package/libx/_runtime/common/utils/csn.js +50 -52
  84. package/libx/_runtime/common/utils/foreignKeyPropagations.js +41 -176
  85. package/libx/_runtime/common/utils/generateOnCond.js +40 -70
  86. package/libx/_runtime/common/utils/{enrichWithKeysFromWhere.js → keys.js} +29 -28
  87. package/libx/_runtime/common/utils/postProcessing.js +3 -0
  88. package/libx/_runtime/common/utils/propagateForeignKeys.js +84 -0
  89. package/libx/_runtime/common/utils/resolveStructured.js +1 -1
  90. package/libx/_runtime/common/utils/resolveView.js +7 -5
  91. package/libx/_runtime/common/utils/rewriteAsterisks.js +94 -0
  92. package/libx/_runtime/common/utils/search2cqn4sql.js +9 -8
  93. package/libx/_runtime/common/utils/template.js +54 -46
  94. package/libx/_runtime/db/Service.js +9 -2
  95. package/libx/_runtime/db/expand/expandCQNToJoin.js +54 -33
  96. package/libx/_runtime/db/expand/rawToExpanded.js +2 -1
  97. package/libx/_runtime/db/generic/arrayed.js +13 -28
  98. package/libx/_runtime/db/generic/create.js +1 -0
  99. package/libx/_runtime/db/generic/input.js +7 -11
  100. package/libx/_runtime/db/generic/integrity.js +2 -2
  101. package/libx/_runtime/db/generic/rewrite.js +2 -5
  102. package/libx/_runtime/db/generic/update.js +1 -0
  103. package/libx/_runtime/db/query/read.js +9 -4
  104. package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -2
  105. package/libx/_runtime/db/sql-builder/annotations.js +1 -0
  106. package/libx/_runtime/db/utils/columns.js +14 -43
  107. package/libx/_runtime/fiori/generic/activate.js +3 -2
  108. package/libx/_runtime/fiori/generic/before.js +2 -2
  109. package/libx/_runtime/fiori/generic/cancel.js +3 -2
  110. package/libx/_runtime/fiori/generic/delete.js +3 -2
  111. package/libx/_runtime/fiori/generic/edit.js +3 -3
  112. package/libx/_runtime/fiori/generic/new.js +2 -2
  113. package/libx/_runtime/fiori/generic/patch.js +2 -2
  114. package/libx/_runtime/fiori/generic/prepare.js +2 -2
  115. package/libx/_runtime/fiori/generic/read.js +45 -63
  116. package/libx/_runtime/fiori/generic/readOverDraft.js +4 -4
  117. package/libx/_runtime/fiori/uiflex/extensibility/index.cds +15 -0
  118. package/libx/_runtime/fiori/uiflex/extensibility/index.js +148 -0
  119. package/libx/_runtime/fiori/uiflex/handler/transformREAD.js +119 -0
  120. package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +43 -0
  121. package/libx/_runtime/fiori/uiflex/handler/transformWRITE.js +62 -0
  122. package/libx/_runtime/fiori/uiflex/index.js +35 -0
  123. package/libx/_runtime/fiori/uiflex/utils.js +78 -0
  124. package/libx/_runtime/fiori/utils/handler.js +3 -13
  125. package/libx/_runtime/fiori/utils/where.js +6 -1
  126. package/libx/_runtime/hana/pool.js +12 -11
  127. package/libx/_runtime/hana/search2cqn4sql.js +34 -43
  128. package/libx/_runtime/hana/searchToContains.js +3 -3
  129. package/libx/_runtime/index.js +5 -2
  130. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  131. package/libx/_runtime/messaging/common-utils/AMQPClient.js +16 -3
  132. package/libx/_runtime/messaging/common-utils/connections.js +11 -14
  133. package/libx/_runtime/messaging/common-utils/naming-conventions.js +1 -1
  134. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -1
  135. package/libx/_runtime/messaging/message-queuing.js +18 -0
  136. package/libx/_runtime/remote/Service.js +20 -4
  137. package/libx/_runtime/remote/utils/client-types.d.ts +7 -0
  138. package/libx/_runtime/remote/utils/client.js +117 -23
  139. package/libx/_runtime/sqlite/Service.js +2 -2
  140. package/libx/_runtime/sqlite/convertAssocToOneManaged.js +1 -3
  141. package/libx/gql/GraphQLAdapter.js +33 -0
  142. package/libx/gql/constants/adapter.js +69 -0
  143. package/libx/gql/constants/cds.js +18 -0
  144. package/libx/gql/constants/graphql.js +33 -0
  145. package/libx/gql/resolvers/crud/create.js +15 -0
  146. package/libx/gql/resolvers/crud/delete.js +24 -0
  147. package/libx/gql/resolvers/crud/index.js +6 -0
  148. package/libx/gql/resolvers/crud/read.js +25 -0
  149. package/libx/gql/resolvers/crud/update.js +31 -0
  150. package/libx/gql/resolvers/crud/utils/index.js +36 -0
  151. package/libx/gql/resolvers/field.js +5 -0
  152. package/libx/gql/resolvers/index.js +7 -0
  153. package/libx/gql/resolvers/mutation.js +23 -0
  154. package/libx/gql/resolvers/parse/ast/enrich.js +51 -0
  155. package/libx/gql/resolvers/parse/ast/fragment.js +11 -0
  156. package/libx/gql/resolvers/parse/ast/fromObject.js +39 -0
  157. package/libx/gql/resolvers/parse/ast/index.js +3 -0
  158. package/libx/gql/resolvers/parse/ast/meta.js +4 -0
  159. package/libx/gql/resolvers/parse/ast/variable.js +7 -0
  160. package/libx/gql/resolvers/parse/ast2cqn/columns.js +42 -0
  161. package/libx/gql/resolvers/parse/ast2cqn/entries.js +31 -0
  162. package/libx/gql/resolvers/parse/ast2cqn/index.js +8 -0
  163. package/libx/gql/resolvers/parse/ast2cqn/limit.js +6 -0
  164. package/libx/gql/resolvers/parse/ast2cqn/orderBy.js +24 -0
  165. package/libx/gql/resolvers/parse/ast2cqn/utils/index.js +3 -0
  166. package/libx/gql/resolvers/parse/ast2cqn/where.js +70 -0
  167. package/libx/gql/resolvers/parse/utils/index.js +8 -0
  168. package/libx/gql/resolvers/query.js +13 -0
  169. package/libx/gql/resolvers/root.js +34 -0
  170. package/libx/gql/schema/generate.js +18 -0
  171. package/libx/gql/schema/index.js +5 -0
  172. package/libx/gql/schema/mutation.js +76 -0
  173. package/libx/gql/schema/query.js +108 -0
  174. package/libx/gql/schema/typeDefMap.js +45 -0
  175. package/libx/gql/schema/utils/index.js +54 -0
  176. package/libx/gql/utils/index.js +12 -0
  177. package/libx/{_runtime/odata/cqn2odata.js → odata/cqn2odata/index.js} +39 -100
  178. package/libx/odata/index.js +80 -0
  179. package/libx/odata/odata2cqn/afterburner.js +170 -0
  180. package/libx/{_runtime/odata/odata2cqn.pegjs → odata/odata2cqn/grammar.pegjs} +102 -123
  181. package/libx/odata/odata2cqn/index.js +3 -0
  182. package/libx/odata/odata2cqn/parser.js +1 -0
  183. package/libx/odata/utils/index.js +64 -0
  184. package/libx/rest/RestAdapter.js +101 -0
  185. package/libx/rest/RestRequest.js +30 -0
  186. package/libx/rest/index.js +3 -0
  187. package/libx/rest/middleware/auth.js +22 -0
  188. package/libx/rest/middleware/content.js +15 -0
  189. package/libx/rest/middleware/create.js +40 -0
  190. package/libx/rest/middleware/delete.js +20 -0
  191. package/libx/rest/middleware/error.js +56 -0
  192. package/libx/rest/middleware/operation.js +39 -0
  193. package/libx/rest/middleware/parse.js +90 -0
  194. package/libx/rest/middleware/read.js +29 -0
  195. package/libx/rest/middleware/update.js +42 -0
  196. package/libx/rest/utils/data.js +65 -0
  197. package/package.json +4 -1
  198. package/server.js +29 -7
  199. package/libx/_runtime/cds-services/services/utils/diff.js +0 -53
  200. package/libx/_runtime/cds-services/util/auditlog.js +0 -247
  201. package/libx/_runtime/cds-services/util/xsenv.js +0 -51
  202. package/libx/_runtime/common/utils/backlinks.js +0 -83
  203. package/libx/_runtime/common/utils/rewriteAsterisk.js +0 -72
  204. package/libx/_runtime/odata/index.js +0 -55
  205. package/libx/_runtime/odata/odata2cqn.js +0 -1
  206. package/libx/_runtime/odata/readToCqn.js +0 -129
  207. package/libx/_runtime/remote/cqn2odata/index.js +0 -2
@@ -0,0 +1,78 @@
1
+ const { ensureNoDraftsSuffix } = require('../../common/utils/draft')
2
+ const { ensureUnlocalized } = require('../../fiori/utils/handler')
3
+
4
+ const EXT_BACK_PACK = 'extensions__'
5
+
6
+ const getTargetRead = req => {
7
+ let name = ''
8
+ if (req.query.SELECT.from.join) {
9
+ // join
10
+ name = req.query.SELECT.from.args.find(arg => arg.ref && arg.ref[0] !== 'DRAFT.DraftAdministativeData').ref[0]
11
+ } else if (req.target.name.SET) {
12
+ // union
13
+ name = req.target.name.SET.args[0]._target.name
14
+ } else {
15
+ // simple select
16
+ name = req.target.name
17
+ }
18
+
19
+ return { name: ensureUnlocalized(ensureNoDraftsSuffix(name)) }
20
+ }
21
+
22
+ const getTargetWrite = (target, model) => {
23
+ return model.definitions[ensureUnlocalized(ensureNoDraftsSuffix(target.name))]
24
+ }
25
+
26
+ const isExtendedEntity = (entityName, model) => {
27
+ // REVISIT: Dass alle unsere und auch custom handlers immer die ensureUnlocalized + ensureNoDraftsSuffix schleife drehen müssen, kann nicht sein
28
+ const entity = model.definitions[ensureUnlocalized(ensureNoDraftsSuffix(entityName))]
29
+ return entity.elements[EXT_BACK_PACK] || Object.values(entity.elements).some(el => el['@cds.extension'])
30
+ }
31
+
32
+ const _hasExtendedEntityArgs = (args, model) => {
33
+ return args.find(arg => {
34
+ if (arg.ref) {
35
+ return arg.ref[0] !== 'DRAFT.DraftAdministativeData' && isExtendedEntity(arg.ref[0], model)
36
+ }
37
+
38
+ if (arg.join) {
39
+ return _hasExtendedEntityArgs(arg.args, model)
40
+ }
41
+ })
42
+ }
43
+
44
+ const hasExtendedEntity = (req, model) => {
45
+ if (!req.query.SELECT) return false
46
+
47
+ if (req.query.SELECT.from.join) {
48
+ // join
49
+ return _hasExtendedEntityArgs(req.query.SELECT.from.args, model)
50
+ } else if (req.target.name.SET) {
51
+ // union
52
+ return isExtendedEntity(req.target.name.SET.args[0]._target.name, model)
53
+ } else {
54
+ // simple select
55
+ return isExtendedEntity(req.target.name, model)
56
+ }
57
+ }
58
+
59
+ const getExtendedFields = (entityName, model) => {
60
+ const elements = model.definitions[ensureUnlocalized(ensureNoDraftsSuffix(entityName))].elements
61
+
62
+ return Object.values(elements)
63
+ .filter(element => {
64
+ return element['@cds.extension']
65
+ })
66
+ .map(element => {
67
+ return element.name
68
+ })
69
+ }
70
+
71
+ module.exports = {
72
+ EXT_BACK_PACK,
73
+ getTargetRead,
74
+ getTargetWrite,
75
+ isExtendedEntity,
76
+ hasExtendedEntity,
77
+ getExtendedFields
78
+ }
@@ -231,11 +231,12 @@ const addColumnAlias = (columns, alias) => {
231
231
  const getCompositionTargets = (entity, srv) => {
232
232
  if (!entity.own('_deepCompositionTargets')) {
233
233
  const _deepCompositionTargets = []
234
- getTemplate('delete-drafts', srv, entity, {
234
+ getTemplate(undefined, srv, entity, {
235
235
  pick: element => {
236
236
  if (element.isAssociation && !element._isAssociationStrict && srv.model.definitions[element.target].drafts)
237
237
  _deepCompositionTargets.push(element.target)
238
- }
238
+ },
239
+ ignore: element => !element.isAssociation || element._isAssociationStrict
239
240
  })
240
241
  entity.set('_deepCompositionTargets', new Set(_deepCompositionTargets))
241
242
  }
@@ -268,16 +269,6 @@ const getKeyProperty = keys => {
268
269
  })
269
270
  }
270
271
 
271
- const hasKeyInWhere = (where, target) => {
272
- if (!where) {
273
- return false
274
- }
275
-
276
- const key = getKeyProperty(target.keys)
277
-
278
- return where.some(element => (element.ref ? key === element.ref[element.ref.length - 1] : false))
279
- }
280
-
281
272
  const filterKeys = keys => {
282
273
  return Object.keys(keys).filter(key => {
283
274
  return key !== 'IsActiveEntity' && !keys[key]._isAssociationStrict
@@ -301,7 +292,6 @@ module.exports = {
301
292
  adaptStreamCQN,
302
293
  replaceRefWithDraft,
303
294
  getKeyProperty,
304
- hasKeyInWhere,
305
295
  filterKeys,
306
296
  getDeleteDraftAdminCqn,
307
297
  getCompositionTargets
@@ -55,6 +55,9 @@ const _removeIsActiveEntityCondition = where => {
55
55
  i = i + 4
56
56
  } else if (where[i] === 'and' && where[i + 1] === '(' && _isActiveEntity(where[i + 2])) {
57
57
  i = i + 6
58
+ } else if (where[i].xpr) {
59
+ newWhere.push({ xpr: _removeIsActiveEntityCondition(where[i].xpr) })
60
+ i++
58
61
  } else {
59
62
  newWhere.push(where[i])
60
63
  i++
@@ -132,7 +135,7 @@ const readAndDeleteKeywords = (keywords, whereCondition, toDelete = true) => {
132
135
  const removeIsActiveEntityRecursively = where => {
133
136
  for (const entry of where) {
134
137
  if (entry.SELECT && entry.SELECT.where && entry.SELECT.from.ref && !entry.SELECT.from.ref[0].endsWith('_drafts')) {
135
- entry.SELECT.where = _removeIsActiveEntityCondition(entry.SELECT.where)
138
+ entry.SELECT.where = removeIsActiveEntityRecursively(entry.SELECT.where)
136
139
 
137
140
  if (entry.SELECT.where.length === 0) {
138
141
  delete entry.SELECT.where
@@ -144,6 +147,8 @@ const removeIsActiveEntityRecursively = where => {
144
147
  }
145
148
 
146
149
  const isActiveEntityRequested = where => {
150
+ if (!where) return true
151
+
147
152
  let i = 0
148
153
 
149
154
  while (where[i]) {
@@ -9,19 +9,20 @@ const _require = require('../common/utils/require')
9
9
  let im
10
10
 
11
11
  function multiTenantInstanceManager(db = cds.env.requires.db) {
12
- let creds = db.credentials && (db.credentials.get_managed_instance_url || db.credentials.sm_url) && db.credentials
13
- if (!creds) creds = _require('@sap/xsenv').serviceCredentials(db.vcap || { label: 'managed-hana' })
14
- if (!creds) creds = _require('@sap/xsenv').serviceCredentials({ label: 'service-manager' })
15
-
16
- if (!creds || typeof creds !== 'object' || !(creds.get_managed_instance_url || creds.sm_url)) {
17
- throw Object.assign(new Error('No or malformed Managed HANA credentials'), { credentials: creds })
12
+ const credentials = db.credentials
13
+ if (
14
+ !credentials ||
15
+ typeof credentials !== 'object' ||
16
+ !(credentials.get_managed_instance_url || credentials.sm_url)
17
+ ) {
18
+ throw Object.assign(new Error('No or malformed db credentials'), { credentials: credentials })
18
19
  }
19
20
 
20
21
  // new instance manager
21
22
  return new Promise((resolve, reject) => {
22
23
  // REVISIT: better cache settings? current copied from old cds-hana...
23
24
  // note: may need to be low for mtx tests -> configurable?
24
- const opts = Object.assign(creds, {
25
+ const opts = Object.assign(credentials, {
25
26
  cache_max_items: 1,
26
27
  cache_item_expire_seconds: 1
27
28
  })
@@ -37,16 +38,16 @@ function multiTenantInstanceManager(db = cds.env.requires.db) {
37
38
  }
38
39
 
39
40
  function singleTenantInstanceManager(db = cds.env.requires.db) {
40
- const creds = db.credentials || _require('@sap/xsenv').serviceCredentials(db.vcap || { label: 'hana' })
41
+ const credentials = db.credentials
41
42
 
42
- if (!creds || typeof creds !== 'object' || !creds.host) {
43
- throw Object.assign(new Error('No or malformed HANA credentials'), { credentials: creds })
43
+ if (!credentials || typeof credentials !== 'object' || !credentials.host) {
44
+ throw Object.assign(new Error('No or malformed db credentials'), { credentials: credentials })
44
45
  }
45
46
 
46
47
  // mock instance manager
47
48
  return {
48
49
  get: (_, cb) => {
49
- cb(null, { credentials: creds })
50
+ cb(null, { credentials: credentials })
50
51
  }
51
52
  }
52
53
  }
@@ -1,7 +1,6 @@
1
1
  const { computeColumnsToBeSearched } = require('../cds-services/services/utils/columns')
2
2
  const searchToLike = require('../common/utils/searchToLike')
3
3
  const { isContainsPredicateSupported, searchToContains } = require('./searchToContains')
4
- const { getOnCond } = require('../common/utils/generateOnCond')
5
4
 
6
5
  /**
7
6
  * Computes a CQN expression for a search query.
@@ -15,30 +14,27 @@ const { getOnCond } = require('../common/utils/generateOnCond')
15
14
  * But in contrast to the explicitly written `LIKE ?`, the parameter is already resolved to its concrete value, making
16
15
  * it better optimizable by the HANA optimizer.
17
16
  *
18
- * @param {object} cqn The CQN object
17
+ * @param {object} query The CQN object
19
18
  * @param {import('@sap/cds-compiler/lib/api/main').CSN} entity The target entity for the search query
20
19
  * @param {import('../types/api').search2cqnOptions} [options]
21
20
  * @returns {object} The modified CQN object
22
21
  */
23
- const search2cqn4sql = (cqn, entity, options) => {
24
- const cqnSearchPhrase = cqn.SELECT.search
25
- if (!cqnSearchPhrase) return cqn
22
+ const search2cqn4sql = (query, entity, options) => {
23
+ const cqnSearchPhrase = query.SELECT.search
24
+ if (!cqnSearchPhrase) return query
26
25
 
27
- let { columns = computeColumnsToBeSearched(cqn, entity), locale } = options
26
+ let { columns: columnsToBeSearched = computeColumnsToBeSearched(query, entity), locale } = options
28
27
  const localizedAssociation = _getLocalizedAssociation(entity)
29
28
 
30
29
  // If the localized association is defined for the target entity,
31
30
  // there should be at least one localized element.
32
31
  const resolveLocalizedTextsAtRuntime = !!localizedAssociation
33
32
 
34
- if (resolveLocalizedTextsAtRuntime) {
35
- // suppress the localize handler from modifying the from target
36
- // The `_suppressLocalization` property is:
37
- // enumerable: false (default), writable: false (default)
38
- Object.defineProperty(cqn, '_suppressLocalization', { value: true })
33
+ // suppress the localize handler from redirecting the query's target to the localized view
34
+ Object.defineProperty(query, '_suppressLocalization', { value: true })
39
35
 
40
- const onConditionOptions = _getOnConditionOptions(entity, localizedAssociation)
41
- const onCondition = getOnCond(localizedAssociation, onConditionOptions)
36
+ if (resolveLocalizedTextsAtRuntime) {
37
+ const onCondition = entity._relations[localizedAssociation.name].join(localizedAssociation.target, entity.name)
42
38
 
43
39
  // replace $user_locale placeholder with the user locale or the HANA session context
44
40
  onCondition[onCondition.length - 2] = { val: locale || "SESSION_CONTEXT('LOCALE')" }
@@ -46,31 +42,26 @@ const search2cqn4sql = (cqn, entity, options) => {
46
42
  // inner join the target table with the _texts table (the _texts table contains
47
43
  // the translated texts)
48
44
  const localizedEntityName = localizedAssociation.target
49
- cqn.join(localizedEntityName).on(onCondition)
50
-
51
- // The inner join modifies the original SELECT ... FROM query and adds ambiguity,
52
- // therefore add the table/entity name (as a preceding element) to the columns ref
53
- // to prevent a SQL ambiguity error. E.g., SqlError message: column ambiguously
54
- // defined.
55
- cqn.SELECT.columns = _unshiftEntityNameToColumnRef(entity, cqn.SELECT.columns)
56
- columns = _unshiftEntityNameToColumnRef(entity, columns)
57
- if (cqn.SELECT.groupBy) cqn.SELECT.groupBy = _unshiftEntityNameToColumnRef(entity, cqn.SELECT.groupBy)
45
+ query.join(localizedEntityName).on(onCondition)
46
+
47
+ // prevent SQL ambiguity error for columns with the same name
48
+ columnsToBeSearched = _addAliasToColumns(query, entity, columnsToBeSearched)
58
49
  } // else --> resolve localized texts via localized view (default)
59
50
 
60
- const useContains = resolveLocalizedTextsAtRuntime && isContainsPredicateSupported(cqn)
51
+ const useContains = isContainsPredicateSupported(query)
61
52
  let expression
62
53
 
63
54
  if (useContains) {
64
- expression = searchToContains(cqnSearchPhrase, columns)
55
+ expression = searchToContains(cqnSearchPhrase, columnsToBeSearched)
65
56
  } else {
66
57
  // No CONTAINS optimization possible. The search implementation for localized
67
58
  // texts falls back to the LIKE predicate.
68
- expression = searchToLike(cqnSearchPhrase, columns)
59
+ expression = searchToLike(cqnSearchPhrase, columnsToBeSearched)
69
60
  }
70
61
 
71
62
  // REVISIT: find out here if where or having must be used
72
- cqn._aggregated ? cqn.having(expression) : cqn.where(expression)
73
- return cqn
63
+ query._aggregated ? query.having(expression) : query.where(expression)
64
+ return query
74
65
  }
75
66
 
76
67
  const _getLocalizedAssociation = entity => {
@@ -78,28 +69,28 @@ const _getLocalizedAssociation = entity => {
78
69
  return associations && associations.localized
79
70
  }
80
71
 
81
- const _getOnConditionOptions = (entity, localizedAssociation) => {
82
- return {
83
- associationNames: [localizedAssociation.name],
84
- aliases: {
85
- select: localizedAssociation.target,
86
- join: entity.name
87
- }
88
- }
89
- }
90
-
91
- const _unshiftEntityNameToColumnRef = (entity, columns) => {
72
+ // The inner join modifies the original SELECT ... FROM query and adds ambiguity,
73
+ // therefore add the table/entity name (as a preceding element) to the columns ref
74
+ // to prevent a SQL ambiguity error.
75
+ const _addAliasToColumns = (query, entity, columnsToBeSearched) => {
92
76
  const localizedEntityName = _getLocalizedAssociation(entity).target
93
77
  const elements = entity.elements
94
-
95
- columns = columns.map(column => {
78
+ const entityName = entity.name
79
+ const _addAliasToColumn = (entityName, localizedEntityName, elements) => column => {
96
80
  const columnRef = column.ref
97
81
  if (!columnRef) return column
98
82
  const columnName = columnRef[0]
99
83
  const localizedElement = elements[columnName].localized
100
- const entityName = localizedElement ? localizedEntityName : entity.name
101
- return { ref: [entityName, columnName] }
102
- })
84
+ const targetEntityName = localizedElement ? localizedEntityName : entityName
85
+ return { ref: [targetEntityName, columnName] }
86
+ }
87
+
88
+ query.SELECT.columns = query.SELECT.columns.map(_addAliasToColumn(entityName, localizedEntityName, elements))
89
+ const columns = columnsToBeSearched.map(_addAliasToColumn(entityName, localizedEntityName, elements))
90
+
91
+ if (query.SELECT.groupBy) {
92
+ query.SELECT.groupBy = query.SELECT.groupBy.map(_addAliasToColumn(entityName, localizedEntityName, elements))
93
+ }
103
94
 
104
95
  return columns
105
96
  }
@@ -64,12 +64,12 @@ const searchToContains = (cqnSearchPhrase, columns) => {
64
64
  return expression
65
65
  }
66
66
 
67
- const isContainsPredicateSupported = cqn => {
68
- const cqnSearchPhrase = cqn.SELECT.search
67
+ const isContainsPredicateSupported = query => {
68
+ const cqnSearchPhrase = query.SELECT.search
69
69
 
70
70
  // REVISIT: In the future, to further optimize search queries, you might
71
71
  // want to remove the following condition(s).
72
- if (cqn._aggregated) return false
72
+ if (query._aggregated) return false
73
73
 
74
74
  // REVISIT: search terms starting with whitespace after a `NOT` operator does not
75
75
  // return the expected result on SAP HANA (BCP 2180256508). In addition, double
@@ -5,9 +5,12 @@ module.exports = {
5
5
  return this._odatav4 || (this._odatav4 = require('./cds-services/adapter/odata-v4/to'))
6
6
  },
7
7
 
8
- /** @type {import('./cds-services/adapter/rest/to')} */
9
8
  get rest() {
10
- return this._rest || (this._rest = require('./cds-services/adapter/rest/to'))
9
+ if (!this._rest) {
10
+ if (global.cds.env.features.rest_new_adapter) this._rest = require('../rest')
11
+ else this._rest = require('./cds-services/adapter/rest/to')
12
+ }
13
+ return this._rest
11
14
  }
12
15
  },
13
16
 
@@ -37,7 +37,7 @@ class AMQPWebhookMessaging extends MessagingService {
37
37
  // Some messaging systems don't adhere to the standard that the payload has a `data` property.
38
38
  // For these cases, we interpret the whole payload as `data`.
39
39
  let data, headers
40
- if ('data' in _payload) {
40
+ if (typeof _payload === 'object' && 'data' in _payload) {
41
41
  data = _payload.data
42
42
  headers = { ..._payload }
43
43
  delete headers.data
@@ -3,6 +3,14 @@ const LOG = cds.log('messaging')
3
3
  const ClientAmqp = require('@sap/xb-msg-amqp-v100').Client
4
4
  const { connect, disconnect } = require('./connections')
5
5
 
6
+ const _JSONorString = string => {
7
+ try {
8
+ return JSON.parse(string)
9
+ } catch (e) {
10
+ return string
11
+ }
12
+ }
13
+
6
14
  const addDataListener = (client, queue, prefix, cb) =>
7
15
  new Promise((resolve, reject) => {
8
16
  const source = `${prefix}${queue}`
@@ -10,9 +18,14 @@ const addDataListener = (client, queue, prefix, cb) =>
10
18
  .receiver(queue)
11
19
  .attach(source)
12
20
  .on('data', async raw => {
13
- const buffer = Buffer.concat(raw.payload.chunks)
14
- const payload = JSON.parse(buffer.toString())
15
- const topic = raw.source.properties.to.replace(/^topic:\/*/, '')
21
+ const buffer = raw.payload && Buffer.concat(raw.payload.chunks)
22
+ const payload = buffer && _JSONorString(buffer.toString())
23
+ const topic =
24
+ raw.source &&
25
+ raw.source.properties &&
26
+ raw.source.properties.to &&
27
+ raw.source.properties.to.replace(/^topic:\/*/, '')
28
+ if (!topic) return raw.done()
16
29
  await cb(topic, payload, null, { done: raw.done, failed: raw.failed })
17
30
  })
18
31
  .on('subscribed', () => {
@@ -1,17 +1,15 @@
1
1
  const cds = require('../../cds')
2
2
  const LOG = cds.log('messaging')
3
3
 
4
- const MAX_NUMBER_RECONNECTS = 1000
5
4
  const MAX_WAITING_TIME = 1480000
6
5
 
7
6
  const _waitingTime = x => (x > 18 ? MAX_WAITING_TIME : (Math.pow(1.5, x) + Math.random()) * 1000)
8
7
 
9
- const _periodicallyReconnect = (client, x, n) => {
8
+ const _connectUntilConnected = (client, x) => {
10
9
  setTimeout(() => {
11
10
  connect(client, true).catch(e => {
12
- LOG._warn && LOG.warn(`Connection to Enterprise Messaging Client lost: Unsuccessful attempt to reconnect (${n}).`)
13
- /* istanbul ignore else */
14
- if (n < MAX_NUMBER_RECONNECTS) _periodicallyReconnect(client, x + 1, n + 1)
11
+ LOG._warn && LOG.warn(`Connection to Enterprise Messaging Client lost: Unsuccessful attempt to reconnect (${x}).`)
12
+ _connectUntilConnected(client, x + 1)
15
13
  })
16
14
  }, _waitingTime(x))
17
15
  }
@@ -30,22 +28,21 @@ const connect = (client, keepAlive) => {
30
28
  client.disconnect()
31
29
  })
32
30
 
31
+ if (keepAlive) {
32
+ client.once('disconnected', () => {
33
+ client.removeAllListeners('error')
34
+ client.removeAllListeners('connected')
35
+ _connectUntilConnected(client, 0)
36
+ })
37
+ }
38
+
33
39
  resolve(this)
34
40
  })
35
41
  .once('error', err => {
36
- client.removeAllListeners('disconnected')
37
42
  client.removeAllListeners('connected')
38
43
  reject(err)
39
44
  })
40
45
 
41
- if (keepAlive) {
42
- client.once('disconnected', () => {
43
- client.removeAllListeners('error')
44
- client.removeAllListeners('connected')
45
- _periodicallyReconnect(client, 0, 0)
46
- })
47
- }
48
-
49
46
  client.connect()
50
47
  })
51
48
  }
@@ -6,7 +6,7 @@ const _queueName = ({ appName, appID, ownNamespace }) => {
6
6
  const queueName = (options, optionsApp = {}) => {
7
7
  const namespace = options.credentials && options.credentials.namespace
8
8
  if (options.queue && options.queue.name) {
9
- if (options.credentials.namespace) return options.queue.name.replace(/\$namespace/g, options.credentials.namespace)
9
+ if (namespace) return options.queue.name.replace(/\$namespace/g, namespace)
10
10
  return options.queue.name
11
11
  }
12
12
  const ownNamespace = namespace
@@ -65,7 +65,8 @@ class EndpointRegistry {
65
65
  const other = authInfo
66
66
  ? {
67
67
  _: { req: { authInfo, headers: {}, query: {} } }, // for messaging to retrieve subdomain
68
- user: new cds.User.Privileged({ tenant: tenantId }) // usual tenant identification
68
+ user: new cds.User.Privileged(),
69
+ tenant: tenantId
69
70
  }
70
71
  : {}
71
72
  if (!cb) return res.sendStatus(200)
@@ -29,6 +29,20 @@ class MQManagement {
29
29
  return res.body
30
30
  }
31
31
 
32
+ async getQueues() {
33
+ const res = await authorizedRequest({
34
+ method: 'GET',
35
+ uri: this.options.url,
36
+ path: `/v1/management/queues`,
37
+ oa2: this.options.auth.oauth2,
38
+ attemptInfo: () => LOG._info && LOG.info('Get queues'),
39
+ errMsg: `Queues could not be retrieved`,
40
+ target: { kind: 'QUEUE' },
41
+ tokenStore: this
42
+ })
43
+ return res.body && res.body.results
44
+ }
45
+
32
46
  createQueue(queueName = this.queueName) {
33
47
  return authorizedRequest({
34
48
  method: 'PUT',
@@ -121,6 +135,10 @@ class MQManagement {
121
135
  }
122
136
  await Promise.all([...this.subscribedTopics].map(kv => kv[0]).map(t => this.createSubscription(t)))
123
137
  }
138
+
139
+ waitUntilReady() {
140
+ return this
141
+ }
124
142
  }
125
143
 
126
144
  class MessageQueuing extends AMQPWebhookMessaging {
@@ -5,14 +5,18 @@ const LOG = cds.log('remote')
5
5
 
6
6
  // disable sdk logger if not in debug mode
7
7
  if (!LOG._debug) {
8
- const sdkUtils = require('@sap-cloud-sdk/util')
9
- sdkUtils.setGlobalLogLevel('error')
8
+ try {
9
+ const sdkUtils = require('@sap-cloud-sdk/util')
10
+ sdkUtils.setGlobalLogLevel('error')
11
+ } catch (err) {
12
+ /* might fail in cds repl due to winston's exception handler, see cap/issues#10134 */
13
+ }
10
14
  }
11
15
 
12
16
  const { resolveView, getTransition, restoreLink, findQueryTarget } = require('../common/utils/resolveView')
13
17
  const { postProcess } = require('../common/utils/postProcessing')
14
18
  const { getKind, run, getDestination, getAdditionalOptions, getReqOptions } = require('./utils/client')
15
- const { formatVal } = require('../odata/cqn2odata')
19
+ const { formatVal } = require('../../odata/utils')
16
20
 
17
21
  const _isSimpleCqnQuery = q => typeof q === 'object' && q !== null && !Array.isArray(q) && Object.keys(q).length > 0
18
22
 
@@ -125,6 +129,7 @@ class RemoteService extends cds.Service {
125
129
  }
126
130
 
127
131
  this.datasource = this.options.datasource
132
+ this.destinationOptions = this.options.destinationOptions
128
133
  this.destination =
129
134
  this.options.credentials.destination ||
130
135
  getDestination((this.definition && this.definition.name) || this.datasource, this.options.credentials)
@@ -133,6 +138,11 @@ class RemoteService extends cds.Service {
133
138
  this.path = this.options.credentials.path
134
139
  this.kind = getKind(this.options) // TODO: Simplify
135
140
 
141
+ const clearKeysFromData = function (req) {
142
+ if (req.target && req.target.keys) for (const k of Object.keys(req.target.keys)) delete req.data[k]
143
+ }
144
+ this.before('UPDATE', '*', Object.assign(clearKeysFromData, { _initial: true }))
145
+
136
146
  for (const each of this.entities) {
137
147
  for (const a in each.actions) {
138
148
  _addHandlerActionFunction(this, each.actions[a], each)
@@ -153,7 +163,13 @@ class RemoteService extends cds.Service {
153
163
  const resolvedTarget = resolvedTargetOfQuery(query) || getTransition(req.target, this).target
154
164
  const reqOptions = getReqOptions(req, query, this)
155
165
  reqOptions.headers = _setHeaders(reqOptions.headers, req)
156
- const additionalOptions = getAdditionalOptions(req, this.destination, this.kind, resolvedTarget)
166
+ const additionalOptions = getAdditionalOptions(
167
+ req,
168
+ this.destination,
169
+ this.kind,
170
+ resolvedTarget,
171
+ this.destinationOptions
172
+ )
157
173
 
158
174
  // hidden compat flag in order to suppress logging response body of failed request
159
175
  if (req._suppressRemoteResponseBody) {
@@ -0,0 +1,7 @@
1
+ import sdkCore from '@sap-cloud-sdk/core'
2
+ export interface DestinationOptions extends Omit<sdkCore.DestinationOptions, 'selectionStrategy'> {
3
+ /*
4
+ * @see https://sap.github.io/cloud-sdk/api/1.50.0/modules/sap_cloud_sdk_core#DestinationSelectionStrategies
5
+ */
6
+ selectionStrategy?: 'alwaysProvider' | 'alwaysSubscriber' | 'subscriberFirst'
7
+ }