@sap/cds 5.4.6 → 5.5.0

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 (228) hide show
  1. package/CHANGELOG.md +208 -2
  2. package/apis/ql.d.ts +17 -15
  3. package/app/index.js +1 -1
  4. package/bin/build/buildTaskEngine.js +26 -42
  5. package/bin/build/buildTaskFactory.js +6 -10
  6. package/bin/build/buildTaskHandler.js +2 -4
  7. package/bin/build/buildTaskProvider.js +3 -1
  8. package/bin/build/buildTaskProviderFactory.js +9 -15
  9. package/bin/build/constants.js +15 -3
  10. package/bin/build/index.js +5 -4
  11. package/bin/build/mtaUtil.js +8 -11
  12. package/bin/build/provider/buildTaskHandlerEdmx.js +63 -6
  13. package/bin/build/provider/buildTaskHandlerInternal.js +2 -34
  14. package/bin/build/provider/buildTaskProviderInternal.js +16 -42
  15. package/bin/build/provider/fiori/index.js +13 -24
  16. package/bin/build/provider/hana/2migration.js +17 -15
  17. package/bin/build/provider/hana/2tabledata.js +52 -48
  18. package/bin/build/provider/hana/index.js +27 -25
  19. package/bin/build/provider/hana/migrationtable.js +91 -67
  20. package/bin/build/provider/java-cf/index.js +14 -24
  21. package/bin/build/provider/mtx/index.js +12 -14
  22. package/bin/build/provider/node-cf/index.js +18 -32
  23. package/bin/cds.js +5 -5
  24. package/bin/serve.js +29 -23
  25. package/bin/version.js +0 -1
  26. package/lib/compile/etc/_localized.js +4 -9
  27. package/lib/compile/for/sql.js +5 -2
  28. package/lib/compile/parse.js +25 -17
  29. package/lib/compile/to/srvinfo.js +2 -1
  30. package/lib/connect/bindings.js +2 -1
  31. package/lib/connect/index.js +48 -49
  32. package/lib/core/classes.js +1 -1
  33. package/lib/core/reflect.js +10 -2
  34. package/lib/deploy.js +26 -23
  35. package/lib/env/defaults.js +13 -6
  36. package/lib/env/index.js +73 -78
  37. package/lib/env/requires.js +38 -19
  38. package/lib/index.js +9 -10
  39. package/lib/lazy.js +2 -2
  40. package/lib/log/index.js +33 -45
  41. package/lib/log/service/index.js +2 -2
  42. package/lib/ql/CREATE.js +14 -9
  43. package/lib/ql/DELETE.js +6 -5
  44. package/lib/ql/DROP.js +12 -9
  45. package/lib/ql/INSERT.js +40 -16
  46. package/lib/ql/Query.js +67 -40
  47. package/lib/ql/SELECT.js +162 -127
  48. package/lib/ql/UPDATE.js +74 -42
  49. package/lib/ql/Whereable.js +77 -87
  50. package/lib/ql/index.js +36 -24
  51. package/lib/ql/parse.js +35 -0
  52. package/lib/req/context.js +44 -8
  53. package/lib/req/locale.js +7 -7
  54. package/lib/serve/Service-api.js +21 -14
  55. package/lib/serve/Service-dispatch.js +28 -12
  56. package/lib/serve/Transaction.js +22 -10
  57. package/lib/serve/index.js +16 -11
  58. package/lib/utils/axios.js +23 -16
  59. package/lib/utils/data.js +35 -0
  60. package/lib/utils/tests.js +27 -18
  61. package/libx/_runtime/audit/generic/personal/access.js +81 -0
  62. package/libx/_runtime/audit/generic/personal/constants.js +4 -0
  63. package/libx/_runtime/audit/generic/personal/index.js +50 -0
  64. package/libx/_runtime/audit/generic/personal/modification.js +138 -0
  65. package/libx/_runtime/audit/generic/personal/utils.js +186 -0
  66. package/libx/_runtime/audit/utils/v2.js +10 -4
  67. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +5 -5
  68. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +6 -7
  69. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +5 -7
  70. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +5 -7
  71. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +2 -3
  72. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +4 -0
  73. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +7 -4
  74. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +59 -8
  75. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +11 -1
  76. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +6 -10
  77. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +3 -46
  78. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +2 -5
  79. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/createToCQN.js +2 -2
  80. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/deleteToCQN.js +4 -3
  81. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -2
  82. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +0 -1
  83. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +1 -1
  84. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/updateToCQN.js +2 -2
  85. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +16 -18
  86. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/EdmEntityType.js +6 -3
  87. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/format/RepresentationKind.js +4 -1
  88. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/core/OdataRequest.js +1 -0
  89. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/SerializerFactory.js +15 -2
  90. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/OperationValidator.js +1 -0
  91. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
  92. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +8 -1
  93. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +6 -1
  94. package/libx/_runtime/cds-services/adapter/odata-v4/utils/omitValues.js +12 -5
  95. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +1 -7
  96. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +7 -7
  97. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +14 -18
  98. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +13 -13
  99. package/libx/_runtime/cds-services/adapter/rest/handlers/create.js +0 -1
  100. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +2 -1
  101. package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +2 -2
  102. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +2 -4
  103. package/libx/_runtime/cds-services/adapter/rest/utils/result.js +4 -2
  104. package/libx/_runtime/cds-services/services/Service.js +40 -5
  105. package/libx/_runtime/cds-services/services/utils/columns.js +13 -7
  106. package/libx/_runtime/cds-services/services/utils/compareJson.js +88 -4
  107. package/libx/_runtime/cds-services/services/utils/differ.js +24 -6
  108. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -2
  109. package/libx/_runtime/common/composition/data.js +44 -55
  110. package/libx/_runtime/common/composition/delete.js +97 -71
  111. package/libx/_runtime/common/composition/index.js +2 -1
  112. package/libx/_runtime/common/composition/insert.js +34 -11
  113. package/libx/_runtime/common/composition/tree.js +119 -92
  114. package/libx/_runtime/common/composition/update.js +4 -1
  115. package/libx/_runtime/common/composition/utils.js +1 -3
  116. package/libx/_runtime/common/constants/draft.js +12 -1
  117. package/libx/_runtime/common/generic/auth.js +6 -22
  118. package/libx/_runtime/common/generic/crud.js +14 -13
  119. package/libx/_runtime/common/generic/input.js +23 -26
  120. package/libx/_runtime/common/generic/put.js +1 -1
  121. package/libx/_runtime/common/generic/sorting.js +16 -16
  122. package/libx/_runtime/common/i18n/index.js +1 -1
  123. package/libx/_runtime/common/i18n/messages.properties +4 -0
  124. package/libx/_runtime/common/utils/backlinks.js +12 -5
  125. package/libx/_runtime/common/utils/cqn.js +6 -1
  126. package/libx/_runtime/common/utils/cqn2cqn4sql.js +102 -101
  127. package/libx/_runtime/common/utils/csn.js +47 -4
  128. package/libx/_runtime/common/utils/data.js +0 -37
  129. package/libx/_runtime/common/utils/enrichWithKeysFromWhere.js +1 -1
  130. package/libx/_runtime/common/utils/entityFromCqn.js +7 -24
  131. package/libx/_runtime/common/utils/foreignKeyPropagations.js +39 -7
  132. package/libx/_runtime/common/utils/generateOnCond.js +11 -12
  133. package/libx/_runtime/common/utils/onlyKeysRemain.js +10 -0
  134. package/libx/_runtime/common/utils/path.js +35 -0
  135. package/libx/_runtime/common/utils/postProcessing.js +86 -0
  136. package/libx/_runtime/common/utils/quotingStyles.js +37 -26
  137. package/libx/_runtime/common/utils/resolveView.js +223 -171
  138. package/libx/_runtime/common/utils/rewriteAsterisk.js +46 -26
  139. package/libx/_runtime/common/utils/structured.js +6 -12
  140. package/libx/_runtime/common/utils/template.js +10 -5
  141. package/libx/_runtime/common/utils/templateDelimiter.js +1 -0
  142. package/libx/_runtime/common/utils/templateProcessor.js +22 -30
  143. package/libx/_runtime/common/utils/union.js +31 -0
  144. package/libx/_runtime/common/utils/unionCqnTemplate.js +184 -0
  145. package/libx/_runtime/db/Service.js +1 -1
  146. package/libx/_runtime/db/data-conversion/timestamp.js +2 -9
  147. package/libx/_runtime/db/expand/expandCQNToJoin.js +204 -297
  148. package/libx/_runtime/db/expand/index.js +3 -3
  149. package/libx/_runtime/db/expand/rawToExpanded.js +36 -7
  150. package/libx/_runtime/db/generic/index.js +1 -1
  151. package/libx/_runtime/db/generic/input.js +5 -7
  152. package/libx/_runtime/db/generic/integrity.js +1 -1
  153. package/libx/_runtime/db/generic/rewrite.js +2 -10
  154. package/libx/_runtime/db/generic/update.js +13 -5
  155. package/libx/_runtime/db/generic/virtual.js +22 -58
  156. package/libx/_runtime/db/query/delete.js +7 -4
  157. package/libx/_runtime/db/query/insert.js +6 -4
  158. package/libx/_runtime/db/query/read.js +13 -20
  159. package/libx/_runtime/db/query/run.js +4 -1
  160. package/libx/_runtime/db/query/update.js +5 -4
  161. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +35 -2
  162. package/libx/_runtime/db/sql-builder/FunctionBuilder.js +17 -2
  163. package/libx/_runtime/db/sql-builder/InsertBuilder.js +6 -5
  164. package/libx/_runtime/db/sql-builder/ReferenceBuilder.js +10 -0
  165. package/libx/_runtime/db/sql-builder/SelectBuilder.js +35 -24
  166. package/libx/_runtime/db/sql-builder/UpdateBuilder.js +14 -4
  167. package/libx/_runtime/db/sql-builder/arrayed.js +4 -0
  168. package/libx/_runtime/db/utils/deep.js +8 -0
  169. package/libx/_runtime/db/utils/generateAliases.js +2 -1
  170. package/libx/_runtime/fiori/generic/activate.js +19 -15
  171. package/libx/_runtime/fiori/generic/before.js +3 -11
  172. package/libx/_runtime/fiori/generic/cancel.js +1 -1
  173. package/libx/_runtime/fiori/generic/delete.js +3 -1
  174. package/libx/_runtime/fiori/generic/edit.js +12 -2
  175. package/libx/_runtime/fiori/generic/new.js +5 -5
  176. package/libx/_runtime/fiori/generic/patch.js +0 -18
  177. package/libx/_runtime/fiori/generic/read.js +241 -189
  178. package/libx/_runtime/fiori/utils/delete.js +36 -7
  179. package/libx/_runtime/fiori/utils/handler.js +43 -44
  180. package/libx/_runtime/fiori/utils/where.js +30 -15
  181. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +4 -6
  182. package/libx/_runtime/hana/execute.js +2 -2
  183. package/libx/_runtime/hana/localized.js +4 -4
  184. package/libx/_runtime/hana/pool.js +29 -14
  185. package/libx/_runtime/hana/search2cqn4sql.js +2 -1
  186. package/libx/_runtime/hana/searchToContains.js +18 -14
  187. package/libx/_runtime/index.js +0 -5
  188. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +13 -5
  189. package/libx/_runtime/messaging/common-utils/naming-conventions.js +4 -1
  190. package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +31 -19
  191. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +1 -2
  192. package/libx/_runtime/messaging/enterprise-messaging.js +6 -4
  193. package/libx/_runtime/messaging/service.js +7 -6
  194. package/libx/_runtime/odata/cqn2odata.js +110 -43
  195. package/libx/_runtime/odata/index.js +26 -48
  196. package/libx/_runtime/odata/odata2cqn.js +1 -6154
  197. package/libx/_runtime/odata/odata2cqn.pegjs +559 -0
  198. package/libx/_runtime/odata/readToCqn.js +94 -64
  199. package/libx/_runtime/remote/Service.js +74 -21
  200. package/libx/_runtime/remote/cqn2odata/index.js +1 -5
  201. package/libx/_runtime/remote/utils/client.js +24 -101
  202. package/libx/_runtime/remote/utils/dataConversion.js +27 -12
  203. package/libx/_runtime/sqlite/Service.js +3 -5
  204. package/libx/_runtime/sqlite/execute.js +23 -24
  205. package/libx/_runtime/sqlite/localized.js +12 -7
  206. package/libx/_runtime/types/api.js +10 -0
  207. package/package.json +1 -1
  208. package/server.js +16 -2
  209. package/lib/ql/grammar.pegjs +0 -208
  210. package/lib/ql/parser.js +0 -1
  211. package/lib/ql/rt/DELETE.js +0 -29
  212. package/lib/ql/rt/INSERT.js +0 -23
  213. package/lib/ql/rt/Query.js +0 -84
  214. package/lib/ql/rt/SELECT.js +0 -174
  215. package/lib/ql/rt/UPDATE.js +0 -119
  216. package/lib/ql/rt/_helpers.js +0 -91
  217. package/lib/ql/rt/index.js +0 -32
  218. package/libx/_runtime/audit/generic/personal.js +0 -260
  219. package/libx/_runtime/cds-services/statements/BaseStatement.js +0 -72
  220. package/libx/_runtime/cds-services/statements/Create.js +0 -57
  221. package/libx/_runtime/cds-services/statements/Delete.js +0 -33
  222. package/libx/_runtime/cds-services/statements/Drop.js +0 -42
  223. package/libx/_runtime/cds-services/statements/Insert.js +0 -201
  224. package/libx/_runtime/cds-services/statements/Select.js +0 -826
  225. package/libx/_runtime/cds-services/statements/Update.js +0 -181
  226. package/libx/_runtime/cds-services/statements/Where.js +0 -726
  227. package/libx/_runtime/cds-services/statements/index.js +0 -25
  228. package/libx/_runtime/common/generic/resolve-mock.js +0 -9
@@ -2,9 +2,14 @@ const cds = require('../../cds')
2
2
  const { SELECT, DELETE } = cds.ql
3
3
 
4
4
  const { isDraftRootEntity } = require('./csn')
5
- const { getUpdateDraftAdminCQN, ensureDraftsSuffix, ensureNoDraftsSuffix } = require('./handler')
5
+ const {
6
+ getUpdateDraftAdminCQN,
7
+ ensureDraftsSuffix,
8
+ ensureNoDraftsSuffix,
9
+ getDeleteDraftAdminCqn,
10
+ getCompositionTargets
11
+ } = require('./handler')
6
12
  const { extractKeyConditions } = require('./where')
7
- const { getTargetData } = require('../../common/utils/data')
8
13
 
9
14
  const _getSelectCQN = (req, keys) => {
10
15
  return SELECT.from(ensureNoDraftsSuffix(req.target.name), [1]).where(keys.keyList)
@@ -28,23 +33,49 @@ const _validate = (activeResult, draftResult, req, IsActiveEntity) => {
28
33
  }
29
34
  }
30
35
 
31
- const deleteDraft = async (req, definitions, includingActive = false) => {
36
+ const deleteDraft = async (req, srv, includingActive = false) => {
32
37
  const dbtx = cds.tx(req)
38
+ const definitions = srv.model.definitions
33
39
 
34
40
  // REVISIT: how to handle delete of to 1 assoc
35
41
  const keys = extractKeyConditions(req.query.DELETE.from.ref[req.query.DELETE.from.ref.length - 1].where)
42
+
43
+ // IsActiveEntity is deleted from where clause in auth.js, hence keys.IsActiveEntity is undefined here.
44
+ // Intentional?
45
+ const deleteActive = keys.IsActiveEntity !== false
46
+
36
47
  const [activeResult, draftResult] = await Promise.all([
37
48
  dbtx.run(_getSelectCQN(req, keys)),
38
49
  dbtx.run(_getDraftSelectCQN(req, keys))
39
50
  ])
40
51
 
41
- _validate(activeResult, draftResult, req, keys.IsActiveEntity)
52
+ _validate(activeResult, draftResult, req, deleteActive)
53
+
54
+ if (isDraftRootEntity(definitions, ensureNoDraftsSuffix(req.target.name)) && !deleteActive) {
55
+ const draftUUID = draftResult[0].DraftUUID
56
+
57
+ const draftTablesToDeleteFrom = [req.target.name + '_drafts']
58
+ for (const [entity] of getCompositionTargets(req.target, srv).entries()) {
59
+ if (!draftTablesToDeleteFrom.includes(entity + '_drafts')) draftTablesToDeleteFrom.push(entity + '_drafts')
60
+ }
61
+
62
+ const deleteDraftAdminCqn = getDeleteDraftAdminCqn(draftUUID)
63
+
64
+ return dbtx.run([
65
+ deleteDraftAdminCqn,
66
+ ...draftTablesToDeleteFrom.map(dt => {
67
+ const d = DELETE.from(dt).where({ DraftAdministrativeData_DraftUUID: draftUUID })
68
+ d._suppressDeepDelete = true // hidden flag to tell db layer that no deep delete is required
69
+ return d
70
+ })
71
+ ])
72
+ }
42
73
 
43
74
  const source = definitions[ensureNoDraftsSuffix(req.target.name)]
44
75
  const delCQNs = []
45
76
 
46
77
  if (includingActive) {
47
- delCQNs.push(DELETE.from(ensureNoDraftsSuffix(getTargetData(req.target, {}).target.name)).where(keys.keyList))
78
+ delCQNs.push(DELETE.from(ensureNoDraftsSuffix(req.target.name)).where(keys.keyList))
48
79
  }
49
80
 
50
81
  if (draftResult.length !== 0) {
@@ -58,8 +89,6 @@ const deleteDraft = async (req, definitions, includingActive = false) => {
58
89
  }
59
90
  }
60
91
 
61
- req._oldData = keys.IsActiveEntity ? activeResult[0] : draftResult[0]
62
-
63
92
  return Promise.all(delCQNs.map(cqn => dbtx.run(cqn)))
64
93
  }
65
94
 
@@ -3,11 +3,12 @@ const { UPDATE, SELECT } = cds.ql
3
3
  const { removeIsActiveEntityRecursively, isActiveEntityRequested } = require('./where')
4
4
  const { getColumns } = require('../../cds-services/services/utils/columns')
5
5
  const { ensureNoDraftsSuffix, ensureDraftsSuffix, ensureUnlocalized } = require('../../common/utils/draft')
6
+ const getTemplate = require('../../common/utils/template')
6
7
 
7
8
  const { DRAFT_COLUMNS } = require('../../common/constants/draft')
8
9
 
9
- // REVISIT: remove everywhere
10
- const SYMBOL_FROM_ANNOTATION = Symbol.for('sap.cds.FROM_ANNOTATION')
10
+ // unofficial config!
11
+ const MAX_RECURSION_DEPTH = (cds.env.features.recursion_depth && Number(cds.env.features.recursion_depth)) || 2
11
12
 
12
13
  const _getParentCQNWithKeyColumn = (parentCQN, parentKeyName) => {
13
14
  const parentCQNWithKeyColumn = Object.assign({}, parentCQN)
@@ -23,33 +24,32 @@ const _getSubSelectFromCQN = (element, columns, selectFromDraft) => {
23
24
  )
24
25
  }
25
26
 
26
- const getSubCQNs = ({ definitions, context, rootCQN, compositionTree, selectFromDraft = false }) => {
27
+ const getSubCQNs = ({ definitions, rootCQN, compositionTree, selectFromDraft = false }) => {
27
28
  const subCQNs = []
28
29
  // only one backLink
29
- const _generateSubCQNs = (parentEntity, parentCQN, compositionElements, level = 1) => {
30
+ const _generateSubCQNs = (parentCQN, compositionElements, elementMap = new Map()) => {
30
31
  for (const element of compositionElements) {
31
32
  const backLink = element.backLinks[0] || element.customBackLinks[0]
32
- // to one without backlink
33
- const link = element.links[0]
34
-
35
- const parentKey = Object.keys(parentEntity.keys)[0]
36
-
37
- const columns = getColumns(definitions[element.source], { onlyNames: true, filterVirtual: true })
38
- if (parentKey && (backLink || link)) {
33
+ if (backLink) {
34
+ const fqn = element.source + ':' + element.name
35
+ const seen = elementMap.get(fqn)
36
+ if (seen && seen >= MAX_RECURSION_DEPTH) {
37
+ // recursion -> abort
38
+ continue
39
+ }
40
+
41
+ const columns = getColumns(definitions[element.source], { onlyNames: true, filterVirtual: true })
39
42
  const subCQN = _getSubSelectFromCQN(element, columns, selectFromDraft)
40
- subCQN.where([
41
- { ref: [backLink ? backLink.entityKey : link.targetKey] },
42
- 'in',
43
- _getParentCQNWithKeyColumn(parentCQN, backLink ? parentKey : link.entityKey)
44
- ])
45
-
46
- subCQNs.push({ cqn: subCQN, level })
47
- _generateSubCQNs(definitions[element.source], subCQN, element.compositionElements, level + 1)
43
+ subCQN.where([{ ref: [backLink.entityKey] }, 'in', _getParentCQNWithKeyColumn(parentCQN, backLink.targetKey)])
44
+ subCQNs.push({ cqn: subCQN })
45
+ const newElementMap = new Map(elementMap)
46
+ newElementMap.set(fqn, (seen && seen + 1) || 1)
47
+ _generateSubCQNs(subCQN, element.compositionElements, newElementMap)
48
48
  }
49
49
  }
50
50
  }
51
51
 
52
- _generateSubCQNs(context.target, rootCQN, compositionTree.compositionElements)
52
+ _generateSubCQNs(rootCQN, compositionTree.compositionElements)
53
53
 
54
54
  return subCQNs
55
55
  }
@@ -96,13 +96,8 @@ const _addAlias = (where, tableName) => {
96
96
  return where.map(element => {
97
97
  if (element.ref && element.ref.length === 1) {
98
98
  // and copy ref
99
- const ref = { ref: [tableName, element.ref[0]] }
100
- if (element[SYMBOL_FROM_ANNOTATION] === true) {
101
- ref[SYMBOL_FROM_ANNOTATION] = true
102
- }
103
- return ref
99
+ return { ref: [tableName, element.ref[0]] }
104
100
  }
105
-
106
101
  return element
107
102
  })
108
103
  }
@@ -169,6 +164,9 @@ const _aliasRef = (ref, alias) => {
169
164
  return newRef
170
165
  }
171
166
 
167
+ const getDeleteDraftAdminCqn = draftUUID =>
168
+ DELETE.from('DRAFT.DraftAdministrativeData').where([{ ref: ['DraftUUID'] }, '=', { val: draftUUID }])
169
+
172
170
  const _aliased = (arr, alias) =>
173
171
  arr.map(item => {
174
172
  if (alias && item.ref && item.ref[0] !== alias) {
@@ -230,23 +228,24 @@ const addColumnAlias = (columns, alias) => {
230
228
  })
231
229
  }
232
230
 
233
- const replaceRefWithDraft = ref => {
234
- if (!ref || !ref[0] || ref[SYMBOL_FROM_ANNOTATION] === true) return
235
- ref[0] = ensureDraftsSuffix(ref[0])
236
- }
237
-
238
- const isAnnotated = element => element[SYMBOL_FROM_ANNOTATION] === true
239
-
240
- const removeAnnotationWhere = where => {
241
- const firstIndex = where.findIndex(isAnnotated)
231
+ const getCompositionTargets = (entity, srv) => {
232
+ if (!entity.own('_deepCompositionTargets')) {
233
+ const _deepCompositionTargets = []
234
+ getTemplate('delete-drafts', srv, entity, {
235
+ pick: element => {
236
+ if (element.isAssociation && !element._isAssociationStrict && srv.model.definitions[element.target].drafts)
237
+ _deepCompositionTargets.push(element.target)
238
+ }
239
+ })
240
+ entity.set('_deepCompositionTargets', new Set(_deepCompositionTargets))
241
+ }
242
242
 
243
- if (firstIndex !== -1) {
244
- const lastIndex = where.length - 1 - [...where].reverse().findIndex(isAnnotated)
243
+ return entity.own('_deepCompositionTargets')
244
+ }
245
245
 
246
- // HANA does not support TRUE as expression
247
- where.splice(firstIndex, lastIndex - firstIndex + 1, { val: '1' }, '=', { val: '1' })
248
- }
249
- return where
246
+ const replaceRefWithDraft = ref => {
247
+ if (!ref || !ref[0]) return
248
+ ref[0] = ensureDraftsSuffix(ref[0])
250
249
  }
251
250
 
252
251
  const adaptStreamCQN = cqn => {
@@ -254,7 +253,6 @@ const adaptStreamCQN = cqn => {
254
253
  cqn.SELECT.where = removeIsActiveEntityRecursively(cqn.SELECT.where)
255
254
  } else {
256
255
  replaceRefWithDraft(cqn.SELECT.from.ref)
257
- removeAnnotationWhere(cqn.SELECT.where)
258
256
  }
259
257
  }
260
258
 
@@ -302,8 +300,9 @@ module.exports = {
302
300
  addColumnAlias,
303
301
  adaptStreamCQN,
304
302
  replaceRefWithDraft,
305
- removeAnnotationWhere,
306
303
  getKeyProperty,
307
304
  hasKeyInWhere,
308
- filterKeys
305
+ filterKeys,
306
+ getDeleteDraftAdminCqn,
307
+ getCompositionTargets
309
308
  }
@@ -1,4 +1,6 @@
1
- const _removeMultipleBrackets = (index, whereCondition) => {
1
+ const _removeMultipleBrackets = (index, whereCondition, isXpr) => {
2
+ if (isXpr) return index
3
+
2
4
  let resIndex = index
3
5
  if (whereCondition[index - 1] === '(') {
4
6
  while (resIndex - 2 >= 0 && whereCondition[resIndex - 2] === '(') {
@@ -17,25 +19,26 @@ const _removeMultipleBrackets = (index, whereCondition) => {
17
19
  return resIndex
18
20
  }
19
21
 
20
- const _calculateSpliceArgs = (index, whereCondition) => {
22
+ const _calculateSpliceArgs = (index, whereCondition, isXpr = false) => {
21
23
  const AND_OR = ['and', 'or']
24
+ const len = isXpr ? 1 : 3
22
25
  if (AND_OR.includes(whereCondition[index - 1])) {
23
- return { index: index - 1, count: 4 }
26
+ return { index: index - 1, count: 1 + len }
24
27
  }
25
- if (AND_OR.includes(whereCondition[index + 3])) {
26
- return { index: index, count: 4 }
28
+ if (AND_OR.includes(whereCondition[index + len])) {
29
+ return { index: index, count: len + 1 }
27
30
  }
28
- if (whereCondition[index - 1] === '(' && whereCondition[index + 3] === ')') {
31
+ if (whereCondition[index - 1] === '(' && whereCondition[index + len] === ')') {
29
32
  if (AND_OR.includes(whereCondition[index - 2])) {
30
- return { index: index - 2, count: 6 }
33
+ return { index: index - 2, count: len + 3 }
31
34
  }
32
- if (AND_OR.includes(whereCondition[index + 4])) {
33
- return { index: index - 1, count: 6 }
35
+ if (AND_OR.includes(whereCondition[index + len + 1])) {
36
+ return { index: index - 1, count: len + 3 }
34
37
  }
35
38
 
36
- return { index: index - 1, count: 5 }
39
+ return { index: index - 1, count: len + 2 }
37
40
  }
38
- return { index: index, count: 3 }
41
+ return { index: index, count: len }
39
42
  }
40
43
 
41
44
  const _isActiveEntity = entry => entry.ref && entry.ref[entry.ref.length - 1] === 'IsActiveEntity'
@@ -75,14 +78,26 @@ const _isKeyValue = (i, keys, where) => {
75
78
  return where[i + 1] === '=' && 'val' in where[i + 2]
76
79
  }
77
80
 
78
- const deleteCondition = (index, whereCondition) => {
79
- index = _removeMultipleBrackets(index, whereCondition)
80
- const spliceArgs = _calculateSpliceArgs(index, whereCondition)
81
+ const deleteCondition = (index, whereCondition, isXpr = false) => {
82
+ index = _removeMultipleBrackets(index, whereCondition, isXpr)
83
+ const spliceArgs = _calculateSpliceArgs(index, whereCondition, isXpr)
81
84
  whereCondition.splice(spliceArgs.index, spliceArgs.count)
85
+ return spliceArgs.index
82
86
  }
83
87
 
84
88
  const readAndDeleteKeywords = (keywords, whereCondition, toDelete = true) => {
85
- const index = whereCondition.findIndex(({ ref }) => {
89
+ let index = whereCondition.findIndex(({ xpr }) => xpr)
90
+ if (index !== -1) {
91
+ const result = readAndDeleteKeywords(keywords, whereCondition[index].xpr, toDelete)
92
+ if (result) {
93
+ if (whereCondition[index].xpr.length === 0) {
94
+ deleteCondition(index, whereCondition, true)
95
+ }
96
+ return result
97
+ }
98
+ }
99
+
100
+ index = whereCondition.findIndex(({ ref }) => {
86
101
  if (!ref) {
87
102
  return false
88
103
  }
@@ -29,11 +29,8 @@ class CustomSelectBuilder extends SelectBuilder {
29
29
  }
30
30
 
31
31
  _val(obj) {
32
- if (typeof obj.val === 'boolean') {
33
- return obj.val ? 'true' : 'false'
34
- }
35
-
36
- return obj.val
32
+ if (typeof obj.val === 'boolean') return { sql: obj.val ? 'true' : 'false', values: [] }
33
+ return super._val(obj)
37
34
  }
38
35
 
39
36
  getParameters() {
@@ -91,9 +88,10 @@ if (cds.env.sql.names === 'plain') {
91
88
  CustomSelectBuilder.prototype._buildRefElement = function (col, res, noQuoting) {
92
89
  res = new this.ReferenceBuilder(col, this._options, this._csn).build()
93
90
 
94
- if (!noQuoting && !col.as && res.sql && !res.sql.includes(' as ')) {
91
+ if (!noQuoting && !col.as && res.sql && !res.sql.match(/\sas\s/i)) {
95
92
  res.sql += ` AS ${this._options.delimiter}${col.ref[col.ref.length - 1]}${this._options.delimiter}`
96
93
  }
94
+
97
95
  return res
98
96
  }
99
97
  }
@@ -132,7 +132,7 @@ function _executeSelectSQL(dbc, sql, values, isOne, postMapper, propertyMapper,
132
132
 
133
133
  function _processExpand(model, dbc, cqn, user, locale, txTimestamp) {
134
134
  const queries = []
135
- const expandQueries = createJoinCQNFromExpanded(cqn, model, true)
135
+ const expandQueries = createJoinCQNFromExpanded(cqn, model)
136
136
 
137
137
  for (const cqn of expandQueries.queries) {
138
138
  cqn._conversionMapper = getPostProcessMapper(HANA_TYPE_CONVERSION_MAP, model, cqn)
@@ -144,7 +144,7 @@ function _processExpand(model, dbc, cqn, user, locale, txTimestamp) {
144
144
  queries.push(_executeSelectSQL(dbc, sql, values, false))
145
145
  }
146
146
 
147
- return rawToExpanded(expandQueries, queries, cqn.SELECT.one)
147
+ return rawToExpanded(expandQueries, queries, cqn.SELECT.one, cqn._rootEntity)
148
148
  }
149
149
 
150
150
  function executeSelectCQN(model, dbc, query, user, locale, txTimestamp) {
@@ -13,15 +13,15 @@ const getLocalize = (locale, model) => name => {
13
13
  }
14
14
 
15
15
  const localizedHandler = function (req) {
16
- const query = req.query
16
+ const { query } = req
17
17
 
18
18
  // do simple checks upfront and exit early
19
19
  if (!query || typeof query === 'string') return
20
20
  if (!query.SELECT) return
21
- if (!req.user || !req.user.locale) return
22
21
  if (!this.model) return
22
+ if (!req.locale) return
23
23
 
24
- // suppress localization
24
+ // suppress localization by instruction
25
25
  if (query._suppressLocalization) return
26
26
 
27
27
  // suppress localization for pure counts
@@ -31,7 +31,7 @@ const localizedHandler = function (req) {
31
31
  // suppress localization in "select for update"
32
32
  if (query.SELECT.forUpdate) return
33
33
 
34
- redirect(req.query.SELECT, getLocalize(req.user.locale, this.model))
34
+ redirect(query.SELECT, getLocalize(req.locale, this.model))
35
35
  }
36
36
 
37
37
  localizedHandler._initial = true
@@ -192,22 +192,37 @@ async function pool4(tenant, credentials) {
192
192
  return pools.get(tenant)
193
193
  }
194
194
 
195
- module.exports = {
196
- acquire: async (tenant, credentials) => {
195
+ async function resilientAcquire(pool, attempts = 1) {
196
+ // max 3 attempts
197
+ attempts = Math.min(attempts, 3)
198
+ let client
199
+ let err
200
+ let attempt = 0
201
+ while (!client && attempt < attempts) {
197
202
  try {
198
- const pool = await pool4(tenant, credentials)
199
- const client = await pool.acquire()
200
- client._pool = pool
201
- return client
202
- } catch (err) {
203
- if (err.name === 'TimeoutError') {
204
- err.statusCode = 503
205
- err.message =
206
- 'Acquiring client from pool timed out. Please review your system setup, transaction handling, and pool configuration.'
207
- }
208
-
209
- throw err
203
+ client = await pool.acquire()
204
+ } catch (e) {
205
+ if (e.name !== 'TimeoutError') throw e
206
+ err = e
207
+ attempt++
210
208
  }
209
+ }
210
+ if (client) return client
211
+ err.statusCode = 503
212
+ err.message =
213
+ 'Acquiring client from pool timed out. Please review your system setup, transaction handling, and pool configuration.'
214
+ err._attempts = attempt
215
+ throw err
216
+ }
217
+
218
+ module.exports = {
219
+ acquire: async (tenant, credentials) => {
220
+ const pool = await pool4(tenant, credentials)
221
+ const _attempts = cds.env.requires.db.connection_attempts
222
+ const attempts = _attempts && !isNaN(_attempts) && parseInt(_attempts)
223
+ const client = await resilientAcquire(pool, attempts)
224
+ client._pool = pool
225
+ return client
211
226
  },
212
227
  release: client => {
213
228
  return client._pool.release(client)
@@ -41,7 +41,7 @@ const search2cqn4sql = (cqn, entity, options) => {
41
41
  const onCondition = getOnCond(localizedAssociation, onConditionOptions)
42
42
 
43
43
  // replace $user_locale placeholder with the user locale or the HANA session context
44
- onCondition[onCondition.length - 2].ref[0] = `'${locale}'` || "SESSION_CONTEXT('LOCALE')"
44
+ onCondition[onCondition.length - 2] = { val: locale || "SESSION_CONTEXT('LOCALE')" }
45
45
 
46
46
  // inner join the target table with the _texts table (the _texts table contains
47
47
  // the translated texts)
@@ -54,6 +54,7 @@ const search2cqn4sql = (cqn, entity, options) => {
54
54
  // defined.
55
55
  cqn.SELECT.columns = _unshiftEntityNameToColumnRef(entity, cqn.SELECT.columns)
56
56
  columns = _unshiftEntityNameToColumnRef(entity, columns)
57
+ if (cqn.SELECT.groupBy) cqn.SELECT.groupBy = _unshiftEntityNameToColumnRef(entity, cqn.SELECT.groupBy)
57
58
  } // else --> resolve localized texts via localized view (default)
58
59
 
59
60
  const useContains = resolveLocalizedTextsAtRuntime && isContainsPredicateSupported(cqn)
@@ -3,14 +3,14 @@
3
3
  *
4
4
  * OData search string | `CONTAINS` search string
5
5
  * ----------------------------- | ------------------------
6
- * `foo` | `%foo%`
7
- * `NOT foo` | `-%foo%`
8
- * `foo AND bar` | `%foo% %bar%`
9
- * `foo OR bar` | `%foo% OR %bar%`
10
- * `foo or bar` | `%foo% OR %bar%`
11
- * `" foo" OR " bar"` | `%" "foo% OR %" "bar%`
12
- * ` foo` | `%" "foo%`
13
- * `foo bar` | `%foo% %bar%`
6
+ * `foo` | `"%foo%"`
7
+ * `NOT foo` | `-"%foo%"` (Currently disabled -> BCP 2180256508)
8
+ * `foo AND bar` | `"%foo%" "%bar%"`
9
+ * `foo OR bar` | `"%foo%" OR "%bar%"`
10
+ * `foo or bar` | `"%foo%" OR "%bar%"`
11
+ * `" foo" OR " bar"` | `"% foo%" OR "% bar%"`
12
+ * ` foo` | `"% foo%"`
13
+ * `foo bar` | `"%foo%" "%bar%"`
14
14
  *
15
15
  * **Some limitations of the `CONTAINS` predicate in SAP HANA:**
16
16
  * - The `-` (minus sign) search operator can not be placed directly after `OR`.
@@ -38,15 +38,15 @@ const searchToContains = (cqnSearchPhrase, columns) => {
38
38
  // the - (minus sign) is used to exclude terms from the search
39
39
  if (currentValue === 'not') return (searchStringAccumulator += '-')
40
40
 
41
- // surround whitespace(s) with "" (double quotes)
42
- const searchTermEscaped = currentValue.val.replace(/(\s+)/g, '"$&"')
41
+ // escape double quotation mark(s) with \\
42
+ const searchTermEscaped = currentValue.val.replace(/"/g, '\\$&')
43
43
 
44
44
  // A search term enclosed with the wildcard character % (percentage sign)
45
45
  // is used to match zero or more characters. In SAP HANA, the % sign is
46
46
  // replaced with an asterisk (*), and a wildcard search is run.
47
47
  // The % sign also prevents ambiguity, as a search input might contain
48
48
  // an 'OR' (uppercase characters), causing semantics issues.
49
- return (searchStringAccumulator += `%${searchTermEscaped}%`)
49
+ return (searchStringAccumulator += `"%${searchTermEscaped}%"`)
50
50
  }, '')
51
51
 
52
52
  const expressionArgs = [{ list: columns }, { val: searchString }]
@@ -71,9 +71,13 @@ const isContainsPredicateSupported = cqn => {
71
71
  // want to remove the following condition(s).
72
72
  if (cqn._aggregated) return false
73
73
 
74
- // search terms starting with a whitespace after a `NOT` operator does
75
- // not return the expected result
76
- if (cqnSearchPhrase[0] === 'not' && cqnSearchPhrase[1].val[0] === ' ') return false
74
+ // REVISIT: search terms starting with whitespace after a `NOT` operator does not
75
+ // return the expected result on SAP HANA (BCP 2180256508). In addition, double
76
+ // quotation marks after a `NOT` operator do not return the desired result.
77
+ // if (cqnSearchPhrase[0] === 'not' && cqnSearchPhrase[1].val[0] === ' ') return false
78
+
79
+ // so for now do not optimize for the `NOT` operator
80
+ if (cqnSearchPhrase[0] === 'not') return false
77
81
 
78
82
  // The `AND` operator in combination with the `OR` operator does not
79
83
  // return the expected result
@@ -16,11 +16,6 @@ module.exports = {
16
16
  return this._auth || (this._auth = require('./common/auth'))
17
17
  },
18
18
 
19
- /** @type {import('./cds-services/statements')} */
20
- get statements() {
21
- return this._statements || (this._statements = require('./cds-services/statements'))
22
- },
23
-
24
19
  // REVISIT: remove once not needed anymore
25
20
  get performanceMeasurement() {
26
21
  return this._perf || (this._perf = require('./cds-services/adapter/perf/performanceMeasurement'))
@@ -28,15 +28,23 @@ class AMQPWebhookMessaging extends MessagingService {
28
28
  return client.emit(msg)
29
29
  }
30
30
 
31
- startListening() {
31
+ startListening(opt = {}) {
32
32
  if (this.subscribedTopics.size) {
33
33
  const management = this.getManagement()
34
- this.queued(management.createQueueAndSubscriptions.bind(management))()
34
+ if (!opt.doNotDeploy) this.queued(management.createQueueAndSubscriptions.bind(management))()
35
35
  this.queued(this.listenToClient.bind(this))(async (_topic, _payload, _other, { done, failed }) => {
36
36
  const event = _topic
37
- const data = _payload.data
38
- const headers = { ..._payload }
39
- delete headers.data
37
+ // Some messaging systems don't adhere to the standard that the payload has a `data` property.
38
+ // For these cases, we interpret the whole payload as `data`.
39
+ let data, headers
40
+ if ('data' in _payload) {
41
+ data = _payload.data
42
+ headers = { ..._payload }
43
+ delete headers.data
44
+ } else {
45
+ data = _payload
46
+ headers = {}
47
+ }
40
48
  const msg = {
41
49
  event,
42
50
  data,
@@ -5,7 +5,10 @@ const _queueName = ({ appName, appID, ownNamespace }) => {
5
5
 
6
6
  const queueName = (options, optionsApp = {}) => {
7
7
  const namespace = options.credentials && options.credentials.namespace
8
- if (options.queue && options.queue.name) return options.queue.name
8
+ if (options.queue && options.queue.name) {
9
+ if (options.credentials.namespace) return options.queue.name.replace(/\$namespace/g, options.credentials.namespace)
10
+ return options.queue.name
11
+ }
9
12
  const ownNamespace = namespace
10
13
  return _queueName({
11
14
  appName: optionsApp.appName || 'CAP',
@@ -4,7 +4,6 @@ const LOG = cds.log('messaging')
4
4
  const sleep = require('util').promisify(setTimeout)
5
5
 
6
6
  const _getWebhookName = queueName => queueName
7
- const isSecured = () => cds.requires.uaa && cds.requires.uaa.credentials
8
7
 
9
8
  // REVISIT: Maybe use `error` definitions as in req.error?
10
9
 
@@ -52,6 +51,20 @@ class EMManagement {
52
51
  return res.body
53
52
  }
54
53
 
54
+ async getQueues() {
55
+ const res = await authorizedRequest({
56
+ method: 'GET',
57
+ uri: this.options.uri,
58
+ path: `/hub/rest/api/v1/management/messaging/queues`,
59
+ oa2: this.options.oa2,
60
+ attemptInfo: () => LOG._info && LOG.info('Get queues', this.subdomain ? { subdomain: this.subdomain } : {}),
61
+ errMsg: `Queues could not be retrieved ${this.subdomainInfo}`,
62
+ target: { kind: 'QUEUE' },
63
+ tokenStore: this
64
+ })
65
+ return res.body
66
+ }
67
+
55
68
  createQueue(queueName = this.queueName) {
56
69
  return authorizedRequest({
57
70
  method: 'PUT',
@@ -196,26 +209,25 @@ class EMManagement {
196
209
  const pushConfig = {
197
210
  type: 'webhook',
198
211
  endpoint: this.optionsApp.appURL + this.path,
199
- exemptHandshake: false
212
+ exemptHandshake: false,
213
+ defaultContentType: 'application/json'
200
214
  }
201
- if (isSecured()) {
202
- // Use credentials from Enterprise Messaging.
203
- // For it to work, you'll need to add scopes in your
204
- // xs-security.json:
205
- //
206
- // scopes: [{
207
- // "name": "$XSAPPNAME.em",
208
- // "description": "EM Callback Access",
209
- // "grant-as-authority-to-apps": ["$XSSERVICENAME(messaging-name)"]
210
- // }]
211
215
 
212
- pushConfig.securitySchema = {
213
- type: 'oauth2',
214
- grantType: 'client_credentials',
215
- clientId: this.optionsMessagingREST.oa2.client,
216
- clientSecret: this.optionsMessagingREST.oa2.secret,
217
- tokenUrl: this.optionsMessagingREST.oa2.endpoint
218
- }
216
+ // Use credentials from Enterprise Messaging.
217
+ // For it to work, you'll need to add scopes in your
218
+ // xs-security.json:
219
+ //
220
+ // scopes: [{
221
+ // "name": "$XSAPPNAME.em",
222
+ // "description": "EM Callback Access",
223
+ // "grant-as-authority-to-apps": ["$XSSERVICENAME(messaging-name)"]
224
+ // }]
225
+ pushConfig.securitySchema = {
226
+ type: 'oauth2',
227
+ grantType: 'client_credentials',
228
+ clientId: this.optionsMessagingREST.oa2.client,
229
+ clientSecret: this.optionsMessagingREST.oa2.secret,
230
+ tokenUrl: this.optionsMessagingREST.oa2.endpoint
219
231
  }
220
232
 
221
233
  const dataObj = {
@@ -1,5 +1,4 @@
1
1
  const cds = require('../../cds.js')
2
- const bodyParser = require('body-parser')
3
2
  const LOG = cds.log('messaging')
4
3
  const express = require('express')
5
4
  const getTenantInfo = require('./getTenantInfo.js')
@@ -28,7 +27,7 @@ class EndpointRegistry {
28
27
  paths.forEach(path => {
29
28
  cds.app.use(path, express.json({ type: 'application/*+json' }))
30
29
  cds.app.use(path, express.json())
31
- cds.app.use(path, bodyParser.urlencoded({ extended: true }))
30
+ cds.app.use(path, express.urlencoded({ extended: true }))
32
31
  })
33
32
  LOG._debug && LOG.debug('Register inbound endpoint', { basePath, method: 'OPTIONS' })
34
33