@sap/cds 6.1.2 → 6.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/CHANGELOG.md +92 -8
  2. package/apis/cds.d.ts +18 -6
  3. package/apis/connect.d.ts +1 -1
  4. package/apis/cqn.d.ts +1 -1
  5. package/apis/log.d.ts +23 -5
  6. package/apis/ql.d.ts +128 -61
  7. package/apis/services.d.ts +11 -0
  8. package/apis/test.d.ts +61 -0
  9. package/apis/utils.d.ts +15 -0
  10. package/app/fiori/preview.js +1 -0
  11. package/bin/build/buildTaskEngine.js +70 -22
  12. package/bin/build/buildTaskFactory.js +18 -11
  13. package/bin/build/buildTaskHandler.js +1 -1
  14. package/bin/build/buildTaskProviderFactory.js +3 -13
  15. package/bin/build/constants.js +0 -1
  16. package/bin/build/index.js +14 -6
  17. package/bin/build/provider/buildTaskHandlerEdmx.js +2 -3
  18. package/bin/build/provider/buildTaskHandlerFeatureToggles.js +2 -2
  19. package/bin/build/provider/buildTaskHandlerInternal.js +3 -6
  20. package/bin/build/provider/buildTaskProviderInternal.js +51 -39
  21. package/bin/build/provider/fiori/index.js +3 -3
  22. package/bin/build/provider/hana/2migration.js +1 -1
  23. package/bin/build/provider/hana/index.js +34 -27
  24. package/bin/build/provider/java/index.js +6 -7
  25. package/bin/build/provider/mtx/index.js +20 -18
  26. package/bin/build/provider/mtx/resourcesTarBuilder.js +8 -11
  27. package/bin/build/provider/mtx-sidecar/index.js +13 -17
  28. package/bin/build/provider/nodejs/index.js +8 -7
  29. package/bin/build/util.js +22 -4
  30. package/bin/cds.js +8 -4
  31. package/bin/deploy/to-hana/cfUtil.js +53 -18
  32. package/bin/mtx/in-cds.js +1 -0
  33. package/bin/serve.js +37 -30
  34. package/lib/auth/basic-auth.js +33 -0
  35. package/lib/auth/dummy-auth.js +7 -0
  36. package/lib/auth/ias-auth.js +2 -0
  37. package/lib/auth/index.js +31 -0
  38. package/lib/auth/jwt-auth.js +3 -0
  39. package/lib/auth/mocked-users.js +72 -0
  40. package/lib/auth/passport-basic.js +12 -0
  41. package/lib/auth/passport-digest.js +14 -0
  42. package/lib/auth/xsuaa-auth.js +3 -0
  43. package/lib/compile/cds-compile.js +3 -3
  44. package/lib/compile/to/cdl.js +5 -1
  45. package/lib/compile/to/edm.js +8 -0
  46. package/lib/compile/to/gql.js +1 -0
  47. package/lib/compile/to/json.js +30 -5
  48. package/lib/compile/to/sql.js +3 -1
  49. package/lib/core/index.js +5 -1
  50. package/lib/dbs/cds-deploy.js +36 -6
  51. package/lib/env/cds-env.js +15 -5
  52. package/lib/env/cds-requires.js +51 -58
  53. package/lib/env/defaults.js +1 -0
  54. package/lib/env/schemas/cds-package.json +4 -0
  55. package/lib/env/schemas/cds-rc.json +63 -77
  56. package/lib/i18n/localize.js +16 -5
  57. package/lib/index.js +9 -4
  58. package/lib/log/cds-error.js +4 -6
  59. package/lib/log/cds-log.js +89 -53
  60. package/lib/log/service/index.js +1 -0
  61. package/lib/ql/CREATE.js +2 -5
  62. package/lib/ql/DELETE.js +1 -1
  63. package/lib/ql/DROP.js +1 -3
  64. package/lib/ql/INSERT.js +3 -3
  65. package/lib/ql/Query.js +10 -23
  66. package/lib/ql/SELECT.js +1 -2
  67. package/lib/ql/UPDATE.js +2 -2
  68. package/lib/ql/Whereable.js +7 -15
  69. package/lib/ql/cds-ql.js +9 -3
  70. package/lib/req/cds-context.js +11 -3
  71. package/lib/req/context.js +29 -23
  72. package/lib/req/locale.js +9 -5
  73. package/lib/req/request.js +1 -0
  74. package/lib/req/user.js +2 -1
  75. package/lib/srv/cds-connect.js +1 -1
  76. package/lib/srv/cds-serve.js +21 -14
  77. package/lib/srv/middlewares/cds-context.js +29 -0
  78. package/lib/srv/middlewares/ctx-model.js +24 -0
  79. package/lib/srv/middlewares/errors.js +9 -0
  80. package/lib/srv/middlewares/index.js +22 -0
  81. package/lib/srv/middlewares/sap-statistics.js +13 -0
  82. package/lib/srv/middlewares/trace.js +102 -0
  83. package/lib/srv/protocols/_legacy.js +42 -0
  84. package/lib/srv/protocols/graphql.js +39 -0
  85. package/lib/srv/protocols/hcql.js +37 -0
  86. package/lib/srv/protocols/index.js +86 -0
  87. package/lib/srv/protocols/odata-v2-proxy.js +3767 -0
  88. package/lib/srv/protocols/odata-v2.js +26 -0
  89. package/lib/srv/protocols/odata-v4.js +16 -0
  90. package/lib/srv/protocols/rest.js +13 -0
  91. package/lib/srv/srv-api.js +5 -0
  92. package/lib/srv/srv-models.js +4 -6
  93. package/lib/utils/axios.js +3 -2
  94. package/lib/utils/cds-test.js +27 -21
  95. package/lib/utils/cds-utils.js +19 -20
  96. package/lib/utils/tar.js +175 -0
  97. package/libx/_runtime/audit/generic/personal/utils.js +18 -7
  98. package/libx/_runtime/audit/utils/v2.js +1 -0
  99. package/libx/_runtime/auth/index.js +4 -0
  100. package/libx/_runtime/auth/strategies/ias-auth.js +76 -0
  101. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +8 -3
  102. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +15 -4
  103. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -1
  104. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/orderByToCQN.js +1 -1
  105. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +1 -1
  106. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +9 -0
  107. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriInfo.js +5 -1
  108. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +12 -0
  109. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/BufferedWriter.js +6 -2
  110. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/RequestValidator.js +47 -7
  111. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
  112. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +0 -2
  113. package/libx/_runtime/cds-services/services/utils/compareJson.js +3 -1
  114. package/libx/_runtime/cds-services/util/assert.js +7 -0
  115. package/libx/_runtime/common/aspects/relation.js +1 -1
  116. package/libx/_runtime/common/composition/data.js +61 -15
  117. package/libx/_runtime/common/composition/delete.js +0 -1
  118. package/libx/_runtime/common/composition/insert.js +0 -1
  119. package/libx/_runtime/common/composition/tree.js +4 -10
  120. package/libx/_runtime/common/composition/update.js +44 -21
  121. package/libx/_runtime/common/generic/auth/capabilities.js +8 -10
  122. package/libx/_runtime/common/generic/crud.js +1 -2
  123. package/libx/_runtime/common/generic/etag.js +4 -4
  124. package/libx/_runtime/common/generic/input.js +21 -6
  125. package/libx/_runtime/common/generic/paging.js +3 -3
  126. package/libx/_runtime/common/generic/put.js +7 -4
  127. package/libx/_runtime/common/generic/sorting.js +4 -4
  128. package/libx/_runtime/common/generic/temporal.js +3 -6
  129. package/libx/_runtime/common/i18n/messages.properties +0 -7
  130. package/libx/_runtime/common/utils/cqn2cqn4sql.js +11 -6
  131. package/libx/_runtime/common/utils/csn.js +0 -28
  132. package/libx/_runtime/common/utils/draft.js +8 -1
  133. package/libx/_runtime/common/utils/path.js +7 -1
  134. package/libx/_runtime/common/utils/propagateForeignKeys.js +122 -0
  135. package/libx/_runtime/common/utils/resolveView.js +2 -3
  136. package/libx/_runtime/common/utils/template.js +2 -3
  137. package/libx/_runtime/db/data-conversion/post-processing.js +3 -44
  138. package/libx/_runtime/db/generic/input.js +6 -6
  139. package/libx/_runtime/db/sql-builder/dataTypes.js +4 -0
  140. package/libx/_runtime/fiori/generic/activate.js +2 -2
  141. package/libx/_runtime/fiori/generic/before.js +40 -72
  142. package/libx/_runtime/fiori/generic/cancel.js +2 -2
  143. package/libx/_runtime/fiori/generic/delete.js +2 -2
  144. package/libx/_runtime/fiori/generic/edit.js +2 -2
  145. package/libx/_runtime/fiori/generic/new.js +3 -5
  146. package/libx/_runtime/fiori/generic/patch.js +49 -43
  147. package/libx/_runtime/fiori/generic/prepare.js +2 -2
  148. package/libx/_runtime/fiori/generic/read.js +27 -37
  149. package/libx/_runtime/fiori/utils/where.js +4 -2
  150. package/libx/_runtime/hana/Service.js +1 -3
  151. package/libx/_runtime/hana/conversion.js +3 -0
  152. package/libx/_runtime/hana/driver.js +33 -3
  153. package/libx/_runtime/hana/dynatrace.js +1 -0
  154. package/libx/_runtime/hana/search2Contains.js +12 -1
  155. package/libx/_runtime/hana/search2cqn4sql.js +10 -27
  156. package/libx/_runtime/hana/streaming.js +1 -0
  157. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +4 -2
  158. package/libx/_runtime/messaging/common-utils/AMQPClient.js +1 -0
  159. package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +5 -2
  160. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -0
  161. package/libx/_runtime/messaging/enterprise-messaging.js +62 -3
  162. package/libx/_runtime/messaging/outbox/utils.js +1 -1
  163. package/libx/_runtime/messaging/redis-messaging.js +1 -0
  164. package/libx/_runtime/remote/Service.js +2 -2
  165. package/libx/_runtime/remote/utils/client.js +35 -11
  166. package/libx/_runtime/remote/utils/data.js +7 -2
  167. package/libx/_runtime/sqlite/Service.js +18 -7
  168. package/libx/_runtime/sqlite/conversion.js +3 -0
  169. package/libx/_runtime/sqlite/convertAssocToOneManaged.js +3 -3
  170. package/libx/_runtime/sqlite/localized.js +8 -8
  171. package/libx/odata/afterburner.js +39 -7
  172. package/libx/odata/cqn2odata.js +6 -3
  173. package/libx/odata/grammar.pegjs +66 -18
  174. package/libx/odata/index.js +3 -2
  175. package/libx/odata/parser.js +1 -1
  176. package/libx/odata/utils.js +2 -0
  177. package/libx/rest/RestAdapter.js +62 -43
  178. package/libx/rest/middleware/input.js +2 -3
  179. package/libx/rest/middleware/parse.js +2 -1
  180. package/libx/rest/middleware/update.js +1 -1
  181. package/package.json +2 -2
  182. package/server.js +5 -4
  183. package/srv/mtx.cds +1 -1
  184. package/srv/mtx.js +4 -24
  185. package/lib/srv/adapters.js +0 -85
  186. package/lib/utils/resources/index.js +0 -48
  187. package/lib/utils/resources/tar.js +0 -49
  188. package/lib/utils/resources/utils.js +0 -11
  189. package/libx/_runtime/db/utils/propagateForeignKeys.js +0 -93
  190. package/libx/_runtime/extensibility/activate.js +0 -69
  191. package/libx/_runtime/extensibility/add.js +0 -50
  192. package/libx/_runtime/extensibility/addExtension.js +0 -72
  193. package/libx/_runtime/extensibility/defaults.js +0 -34
  194. package/libx/_runtime/extensibility/handler/transformREAD.js +0 -121
  195. package/libx/_runtime/extensibility/handler/transformRESULT.js +0 -51
  196. package/libx/_runtime/extensibility/handler/transformWRITE.js +0 -64
  197. package/libx/_runtime/extensibility/linter/allowlist_checker.js +0 -373
  198. package/libx/_runtime/extensibility/linter/annotations_checker.js +0 -113
  199. package/libx/_runtime/extensibility/linter/checker_base.js +0 -20
  200. package/libx/_runtime/extensibility/linter/namespace_checker.js +0 -180
  201. package/libx/_runtime/extensibility/linter.js +0 -32
  202. package/libx/_runtime/extensibility/push.js +0 -118
  203. package/libx/_runtime/extensibility/service.js +0 -38
  204. package/libx/_runtime/extensibility/token.js +0 -57
  205. package/libx/_runtime/extensibility/utils.js +0 -131
  206. package/libx/_runtime/extensibility/validation.js +0 -50
  207. package/libx/_runtime/extensibility/views.js +0 -12
  208. package/srv/extensibility-service.cds +0 -59
  209. package/srv/extensibility-service.js +0 -1
  210. package/srv/extensions.cds +0 -8
  211. package/srv/model-provider.cds +0 -61
  212. package/srv/model-provider.js +0 -143
@@ -3,30 +3,14 @@ const cds = require('../../cds')
3
3
  const { SELECT } = cds.ql
4
4
 
5
5
  const { isNavigationToMany } = require('../utils/req')
6
- const { getKeysCondition } = require('../utils/where')
6
+ const { getKeysCondition, removeIsActiveEntityRecursively } = require('../utils/where')
7
7
  const { isDraftActivateAction, ensureNoDraftsSuffix, ensureDraftsSuffix, draftIsLocked } = require('../utils/handler')
8
8
 
9
- const { isCustomOperation } = require('../../cds-services/adapter/odata-v4/utils/request')
10
-
11
9
  const { DRAFT_COLUMNS_ADMIN_MAP } = require('../../common/constants/draft')
10
+ const { deepCopyArray } = require('../../common/utils/copy')
12
11
  const DRAFT_COLUMNS_ADMIN = Object.keys(DRAFT_COLUMNS_ADMIN_MAP)
13
12
  const PREFIX_DRAFT_COLUMNS = DRAFT_COLUMNS_ADMIN.map(col => ({ ref: ['DRAFT_DraftAdministrativeData', col] }))
14
13
 
15
- // copied from adapter/odata-v4/utils/context-object
16
- const _getTargetEntityName = (service, pathSegments) => {
17
- if (isCustomOperation(pathSegments, false)) return
18
-
19
- let navSegmentName
20
- let entityName = `${service.name}.${pathSegments[0].getEntitySet().getName()}`
21
-
22
- for (const navSegment of pathSegments.filter(segment => segment.getNavigationProperty() !== null)) {
23
- navSegmentName = navSegment.getNavigationProperty().getName()
24
- entityName = service.model.definitions[entityName].elements[navSegmentName].target
25
- }
26
-
27
- return entityName
28
- }
29
-
30
14
  /**
31
15
  * Provide information about the parent entity, i.e. the entity that has the to-many composition element.
32
16
  * Limitation: only works for one key (besides IsActiveEntity)
@@ -36,34 +20,6 @@ const _getTargetEntityName = (service, pathSegments) => {
36
20
  * @returns {object}
37
21
  * @private
38
22
  */
39
- const _getParent = (req, service) => {
40
- // REVISIT: get rid of getUriInfo
41
- if (!req.getUriInfo) return
42
-
43
- const segments = req.getUriInfo().getPathSegments()
44
- if (segments.length === 1) return
45
-
46
- const parent = {
47
- entityName: _getTargetEntityName(service, segments.slice(0, segments.length - 1))
48
- }
49
-
50
- const parentKeyPredicates = segments[segments.length - 2].getKeyPredicates()
51
- let keyPredicateName, keyPredicateText
52
-
53
- for (const keyPredicate of parentKeyPredicates) {
54
- keyPredicateName = keyPredicate.getEdmRef().getName()
55
- keyPredicateText = keyPredicate.getText()
56
-
57
- if (keyPredicateName === 'IsActiveEntity') {
58
- parent.IsActiveEntity = keyPredicateText === 'true'
59
- } else {
60
- parent.keyName = keyPredicateName
61
- parent.keyValue = keyPredicateText
62
- }
63
- }
64
-
65
- return parent
66
- }
67
23
 
68
24
  const _validateDraft = (req, draftResult, isBoundAction) => {
69
25
  if (!draftResult || draftResult.length === 0) req.reject(404)
@@ -102,36 +58,48 @@ const _getSelectDraftDataCqn = (entityName, where) => {
102
58
  .where(where)
103
59
  }
104
60
 
105
- const _getDraftDataFromExistingDraft = async (req, service, parent = _getParent(req, service)) => {
106
- if (parent) {
107
- if (parent.IsActiveEntity === false) {
108
- const parentWhere = [{ ref: [parent.keyName] }, '=', { val: parent.keyValue }]
109
- const query = _getSelectDraftDataCqn(parent.entityName, parentWhere)
110
- const result = await cds.tx(req).run(query)
111
- return result
61
+ const _getRoot = req => {
62
+ if (!req.query) return
63
+ const refObj = req.query.SELECT?.from || req.query.UPDATE?.entity || req.query.INSERT?.into || req.query.DELETE?.from
64
+ const root = {
65
+ entityName: ensureDraftsSuffix(refObj.ref[0].id),
66
+ where: removeIsActiveEntityRecursively(deepCopyArray(refObj.ref[0].where))
67
+ }
68
+
69
+ for (const item of refObj.ref[0].where) {
70
+ if (item.ref && item.ref[item.ref.length - 1] === 'IsActiveEntity') {
71
+ const index = refObj.ref[0].where.indexOf(item)
72
+ root.IsActiveEntity = refObj.ref[0].where[index + 2].val
73
+ break
112
74
  }
75
+ }
76
+
77
+ return root
78
+ }
113
79
 
114
- return []
80
+ const _getDraftDataFromExistingDraft = async (req, root) => {
81
+ if (!root) return []
82
+ if (root?.IsActiveEntity === false) {
83
+ const query = _getSelectDraftDataCqn(root.entityName, root.where)
84
+ const result = await cds.tx(req).run(query)
85
+ return result
115
86
  }
116
87
 
117
- const rootWhere = getKeysCondition(req.target, req.data)
88
+ const rootWhere = getKeysCondition(req)
118
89
  const query = _getSelectDraftDataCqn(ensureNoDraftsSuffix(req.target.name), rootWhere)
119
90
  const result = await cds.tx(req).run(query)
120
91
  return result
121
92
  }
122
93
 
123
- const _addDraftDataFromExistingDraft = async (req, service) => {
124
- const parent = _getParent(req, service)
125
- const result = await _getDraftDataFromExistingDraft(req, service, parent)
126
-
127
- if (parent) {
128
- if (parent.IsActiveEntity === false) {
129
- _validateDraft(req, result)
130
- _addDraftDataToContext(req, result)
131
- return result
132
- }
94
+ const _addDraftDataFromExistingDraft = async req => {
95
+ const root = _getRoot(req)
96
+ const result = await _getDraftDataFromExistingDraft(req, root)
133
97
 
134
- return []
98
+ if (!root) return []
99
+ if (root.IsActiveEntity === false) {
100
+ _validateDraft(req, result)
101
+ _addDraftDataToContext(req, result)
102
+ return result
135
103
  }
136
104
 
137
105
  if (result && result.length > 0) _validateDraft(req, result)
@@ -147,7 +115,7 @@ const _new = async function (req) {
147
115
 
148
116
  if (isNavigationToMany(req)) {
149
117
  // REVISIT: How can NEW be a navigation to many?
150
- const result = await _addDraftDataFromExistingDraft(req, this)
118
+ const result = await _addDraftDataFromExistingDraft(req)
151
119
 
152
120
  // in order to fix corner case where active subitems are created in draft case
153
121
  if (result.length === 0) req.reject(404)
@@ -165,7 +133,7 @@ const _new = async function (req) {
165
133
  const _patchUpdate = async function (req) {
166
134
  if (isDraftActivateAction(req)) return
167
135
 
168
- const result = await _addDraftDataFromExistingDraft(req, this)
136
+ const result = await _addDraftDataFromExistingDraft(req)
169
137
 
170
138
  // means that the draft does not exists
171
139
  if (result.length === 0) req.reject(404)
@@ -175,11 +143,11 @@ const _patchUpdate = async function (req) {
175
143
  * Generic Handler for before DELETE and CANCEL requests.
176
144
  */
177
145
  const _deleteCancel = async function (req) {
178
- await _addDraftDataFromExistingDraft(req, this)
146
+ await _addDraftDataFromExistingDraft(req)
179
147
  }
180
148
 
181
- const _validateDraftBoundAction = async function (req, srv) {
182
- const result = await _getDraftDataFromExistingDraft(req, srv)
149
+ const _validateDraftBoundAction = async function (req) {
150
+ const result = await _getDraftDataFromExistingDraft(req, _getRoot(req))
183
151
  const isBoundAction = true
184
152
  if (result && result.length > 0) _validateDraft(req, result, isBoundAction)
185
153
  }
@@ -196,7 +164,7 @@ const _registerBoundActionHandlers = function (entityName, actions) {
196
164
  )
197
165
 
198
166
  for (const action of boundActions) {
199
- this.before(action.name, entityName, req => _validateDraftBoundAction(req, this))
167
+ this.before(action.name, entityName, req => _validateDraftBoundAction(req))
200
168
  }
201
169
  }
202
170
 
@@ -8,10 +8,10 @@ const { deleteDraft } = require('../utils/delete')
8
8
  *
9
9
  * @param req
10
10
  */
11
- const _handler = function (req) {
11
+ const fioriGenericCancel = function (req) {
12
12
  return deleteDraft(req, this)
13
13
  }
14
14
 
15
15
  module.exports = cds.service.impl(function (srv, entity) {
16
- srv.on('CANCEL', entity, _handler)
16
+ srv.on('CANCEL', entity, fioriGenericCancel)
17
17
  })
@@ -8,12 +8,12 @@ const { deleteDraft } = require('../utils/delete')
8
8
  *
9
9
  * @param req
10
10
  */
11
- const _handler = function (req) {
11
+ const fioriGenericDelete = function (req) {
12
12
  // we should call deleteDraft only for draft tables and call next
13
13
  // to pass delete of active tables to our general implementation
14
14
  return deleteDraft(req, this, true)
15
15
  }
16
16
 
17
17
  module.exports = cds.service.impl(function (srv, entity) {
18
- srv.on('DELETE', entity, _handler)
18
+ srv.on('DELETE', entity, fioriGenericDelete)
19
19
  })
@@ -68,7 +68,7 @@ const _select = async (lockRecordCQN, draftExistsCQN, selectCQNs, req, dbtx) =>
68
68
  *
69
69
  * @param req
70
70
  */
71
- const _handler = async function (req) {
71
+ const fioriGenericEdit = async function (req) {
72
72
  if (!isActiveEntityRequested(req.query.SELECT.where || [])) {
73
73
  req.reject(400)
74
74
  }
@@ -160,5 +160,5 @@ const _handler = async function (req) {
160
160
  }
161
161
 
162
162
  module.exports = cds.service.impl(function (srv, entity) {
163
- srv.on('EDIT', entity, _handler)
163
+ srv.on('EDIT', entity, fioriGenericEdit)
164
164
  })
@@ -1,11 +1,9 @@
1
1
  const cds = require('../../cds')
2
- const { INSERT, SELECT, UPDATE } = cds.ql
2
+ const { INSERT, UPDATE } = cds.ql
3
3
 
4
4
  const onDraftActivate = require('./activate')._handler
5
5
  const { isNavigationToMany } = require('../utils/req')
6
- const { getKeysCondition } = require('../utils/where')
7
6
  const { ensureDraftsSuffix } = require('../utils/handler')
8
- const { DRAFT_COLUMNS_MAP } = require('../../common/constants/draft')
9
7
 
10
8
  const _getUpdateDraftAdminCQN = ({ user, timestamp }, draftUUID) => {
11
9
  return UPDATE('DRAFT.DraftAdministrativeData')
@@ -50,7 +48,7 @@ const _getInsertDataCQN = (req, draftUUID) => {
50
48
  * @param req
51
49
  * @param next
52
50
  */
53
- const _handler = async function (req, next) {
51
+ const fioriGenericNew = async function (req, next) {
54
52
  if (!req._draftMetadata) {
55
53
  // REVISIT: when is this the case?
56
54
  return onDraftActivate(req, next)
@@ -72,5 +70,5 @@ const _handler = async function (req, next) {
72
70
  }
73
71
 
74
72
  module.exports = cds.service.impl(function (srv, entity) {
75
- srv.on('NEW', entity, _handler)
73
+ srv.on('NEW', entity, fioriGenericNew)
76
74
  })
@@ -1,46 +1,25 @@
1
1
  const cds = require('../../cds')
2
2
  const { UPDATE, SELECT } = cds.ql
3
3
 
4
- const {
5
- getUpdateDraftAdminCQN,
6
- removeDraftUUIDIfNecessary,
7
- ensureDraftsSuffix,
8
- ensureNoDraftsSuffix,
9
- addColumnAlias
10
- } = require('../utils/handler')
11
- const { getKeysCondition } = require('../utils/where')
12
- const { getColumns } = require('../../cds-services/services/utils/columns')
13
- const { DRAFT_COLUMNS_CQN } = require('../../common/constants/draft')
14
-
15
- const _getSelectCQN = (model, { target: { name } }, keysCondition, checkUser = true) => {
16
- const activeName = ensureNoDraftsSuffix(name)
17
- const draftName = ensureDraftsSuffix(name)
18
-
19
- const columns = [
20
- ...addColumnAlias(
21
- getColumns(model.definitions[activeName], { removeIgnore: true, filterVirtual: true }).map(obj => obj.name),
22
- draftName
23
- ),
24
- ...DRAFT_COLUMNS_CQN
25
- ]
26
-
27
- if (checkUser) {
28
- columns.push({ ref: ['DRAFT.DraftAdministrativeData', 'inProcessByUser'], as: 'draftAdmin_inProcessByUser' })
4
+ const { getUpdateDraftAdminCQN, ensureDraftsSuffix } = require('../utils/handler')
5
+ const { removeIsActiveEntityRecursively } = require('../utils/where')
6
+ const { deepCopyArray } = require('../../common/utils/copy')
7
+ const { cqn2cqn4sql } = require('../../common/utils/cqn2cqn4sql')
8
+
9
+ const _getSelectCQN = ({ query }) => {
10
+ const fromRef = deepCopyArray(query.UPDATE.entity.ref)
11
+ for (const item of fromRef) {
12
+ if (item.where) {
13
+ item.where = removeIsActiveEntityRecursively(item.where)
14
+ }
29
15
  }
16
+ fromRef[0].id = ensureDraftsSuffix(fromRef[0].id)
17
+ const from = { ref: fromRef }
30
18
 
31
- // REVISIT: support navigation to one
32
- return SELECT.one(draftName)
33
- .columns(columns)
34
- .join('DRAFT.DraftAdministrativeData')
35
- .on([
36
- { ref: [draftName, 'DraftAdministrativeData_DraftUUID'] },
37
- '=',
38
- { ref: ['DRAFT.DraftAdministrativeData', 'DraftUUID'] }
39
- ])
40
- .where(keysCondition)
19
+ return SELECT.from(from)
41
20
  }
42
21
 
43
- const _getUpdateDraftCQN = ({ query, target: { name } }, keysCondition) => {
22
+ const _getUpdateDraftCQN = ({ query }, where, targetRef) => {
44
23
  const set = {}
45
24
 
46
25
  for (const entry in query.UPDATE.data) {
@@ -52,7 +31,23 @@ const _getUpdateDraftCQN = ({ query, target: { name } }, keysCondition) => {
52
31
  }
53
32
 
54
33
  if (set.IsActiveEntity) set.IsActiveEntity = false
55
- return UPDATE(ensureDraftsSuffix(name)).data(set).where(keysCondition)
34
+
35
+ return UPDATE(targetRef).data(set).where(where)
36
+ }
37
+
38
+ const _joinDraftAdministrativeData = (selectResolved, target) => {
39
+ const columns = []
40
+ columns.push({ ref: ['DRAFT.DraftAdministrativeData', 'inProcessByUser'], as: 'draftAdmin_inProcessByUser' })
41
+ columns.push({ ref: [target, 'DraftAdministrativeData_DraftUUID'] })
42
+
43
+ return selectResolved
44
+ .columns(columns)
45
+ .join('DRAFT.DraftAdministrativeData')
46
+ .on([
47
+ { ref: [target, 'DraftAdministrativeData_DraftUUID'] },
48
+ '=',
49
+ { ref: ['DRAFT.DraftAdministrativeData', 'DraftUUID'] }
50
+ ])
56
51
  }
57
52
 
58
53
  /**
@@ -63,20 +58,31 @@ const _getUpdateDraftCQN = ({ query, target: { name } }, keysCondition) => {
63
58
  *
64
59
  * @param req
65
60
  */
66
- const _handler = async function (req) {
61
+ const fioriGenericPatch = async function (req) {
67
62
  if (req.data.IsActiveEntity === true) req.reject(400, 'Patch can only be applied to a draft entity')
68
63
 
69
- const keysCondition = getKeysCondition(req.target, req.data)
70
64
  const dbtx = cds.tx(req)
71
- let result = await dbtx.run(_getSelectCQN(this.model, req, keysCondition))
65
+
66
+ const selectResolved = cqn2cqn4sql(_getSelectCQN(req), this.model)
67
+
68
+ const targetName = selectResolved.SELECT.from.ref[selectResolved.SELECT.from.ref.length - 1]
69
+ const alias = selectResolved.SELECT.from.as
70
+ const selectWithAdmin = _joinDraftAdministrativeData(selectResolved, alias || targetName)
71
+ const results = await dbtx.run(selectWithAdmin)
72
+
73
+ if (results.length === 0) req.reject(404)
74
+
75
+ const result = results[0]
72
76
 
73
77
  // Potential timeout scenario supported
74
78
  if (result.draftAdmin_inProcessByUser && result.draftAdmin_inProcessByUser !== req.user.id) {
75
79
  // REVISIT: security log?
76
80
  req.reject(403)
77
81
  }
78
-
79
- const updateDraftCQN = _getUpdateDraftCQN(req, keysCondition)
82
+ const updateDraftCQN = _getUpdateDraftCQN(req, selectResolved.SELECT.where, {
83
+ ref: [targetName],
84
+ as: alias || targetName
85
+ })
80
86
  const updateDraftAdminCQN = getUpdateDraftAdminCQN(req, result.DraftAdministrativeData_DraftUUID)
81
87
 
82
88
  await Promise.all([dbtx.run(updateDraftCQN), dbtx.run(updateDraftAdminCQN)])
@@ -86,5 +92,5 @@ const _handler = async function (req) {
86
92
  }
87
93
 
88
94
  module.exports = cds.service.impl(function (srv, entity) {
89
- srv.on('PATCH', entity, _handler)
95
+ srv.on('PATCH', entity, fioriGenericPatch)
90
96
  })
@@ -11,7 +11,7 @@ const { getColumns } = require('../../cds-services/services/utils/columns')
11
11
  *
12
12
  * @param req
13
13
  */
14
- const _handler = async function (req) {
14
+ const fioriGenericPrepare = async function (req) {
15
15
  if (req.query.SELECT.from.ref.length > 1 || isActiveEntityRequested(req.query.SELECT.from.ref[0].where || [])) {
16
16
  req.reject(400, 'Action "draftPrepare" can only be called on a draft entity')
17
17
  }
@@ -44,5 +44,5 @@ const _handler = async function (req) {
44
44
  }
45
45
 
46
46
  module.exports = cds.service.impl(function (srv, entity) {
47
- srv.on('draftPrepare', entity, _handler)
47
+ srv.on('draftPrepare', entity, fioriGenericPrepare)
48
48
  })
@@ -4,6 +4,7 @@ const { SELECT } = cds.ql
4
4
  const { cqn2cqn4sql, convertWhereExists } = require('../../common/utils/cqn2cqn4sql')
5
5
  const { getElementDeep } = require('../../common/utils/csn')
6
6
  const { DRAFT_COLUMNS_MAP, SCENARIO } = require('../../common/constants/draft')
7
+ const { filterNonDraftColumns } = require('../../common/utils/draft')
7
8
  const {
8
9
  addColumnAlias,
9
10
  draftIsLocked,
@@ -16,15 +17,11 @@ const {
16
17
  filterKeys
17
18
  } = require('../utils/handler')
18
19
  const { deleteCondition, readAndDeleteKeywords, removeIsActiveEntityRecursively } = require('../utils/where')
19
- const { getColumns } = require('../../cds-services/services/utils/columns')
20
20
  const { adaptStreamCQN } = require('../../cds-services/adapter/odata-v4/utils/stream')
21
21
 
22
22
  const _findSubselect = where => {
23
23
  return where.find((e, i) => {
24
- if (e.xpr) {
25
- return _findSubselect(e.xpr)
26
- }
27
-
24
+ if (e.xpr) return _findSubselect(e.xpr)
28
25
  return e.SELECT && where[i - 1] === 'exists'
29
26
  })
30
27
  }
@@ -626,6 +623,9 @@ const _siblingEntity = (
626
623
 
627
624
  if (siblingIndex === 0) {
628
625
  const columnCqnPartial = columns.map(col => {
626
+ if (col.val) {
627
+ return Object.assign({}, col)
628
+ }
629
629
  const colName = col.ref ? col.ref[col.ref.length - 1] : col
630
630
  const ref = col.ref ? [table.as, ...col.ref] : [table.as, colName]
631
631
  return Object.assign({}, col, { ref })
@@ -694,7 +694,7 @@ function _siblingSubScenario(nav, siblingIndex, siblingQuery, target, params, mo
694
694
  const existsIdx = siblingQuery.SELECT.where.indexOf('exists')
695
695
  if (existsIdx > -1) subReq.query.where(siblingQuery.SELECT.where.slice(existsIdx, existsIdx + 2))
696
696
  const subOrigFrom = { ref: [...subNav].reverse() }
697
- subScenario = _generateCQN(subOrigFrom, subReq, [{ val: 1 }], model)
697
+ subScenario = _generateCQN(subReq, [{ val: 1 }], subOrigFrom, model)
698
698
  subScenario.cqn.where(onCond)
699
699
  }
700
700
 
@@ -757,9 +757,8 @@ const _replaceWhereExists = (query, _siblingIndex, siblingCQN) => {
757
757
  }
758
758
  }
759
759
 
760
- const _mergeSiblingIntoCQN = (cqn, { cqn: siblingCQN }, siblingIndex) => {
761
- return _replaceWhereExists(cqn, siblingIndex, siblingCQN)
762
- }
760
+ const _mergeSiblingIntoCQN = (cqn, { cqn: siblingCQN }, siblingIndex) =>
761
+ _replaceWhereExists(cqn, siblingIndex, siblingCQN)
763
762
 
764
763
  const _getDraftDoc = (req, draftName, draftWhere) => {
765
764
  const refDraft = req.query.SELECT.from.as ? { ref: [draftName], as: req.query.SELECT.from.as } : draftName
@@ -784,8 +783,10 @@ const _getDraftDoc = (req, draftName, draftWhere) => {
784
783
 
785
784
  const _getOrderByEnrichedColumns = (orderBy, columns, entity) => {
786
785
  const enrichedCol = []
786
+
787
787
  if (orderBy && orderBy.length > 1) {
788
788
  const colNames = columns.map(el => el.ref[el.ref.length - 1])
789
+
789
790
  // REVISIT: GET Books?$select=title&$expand=NotBooks($select=pages)&$orderby=NotBooks/title - what's then?
790
791
  for (const el of orderBy) {
791
792
  // For associations we need to 'materialise' the resulting field, otherwise we cannot access it in an outer SELECT.
@@ -906,11 +907,11 @@ const _getUnionCQN = (req, draftName, columns, subSelect, draftWhere, model, ent
906
907
  }
907
908
 
908
909
  return union
909
- .columns(...columns)
910
+ .columns(...columns.map(ref => (ref.as ? { ref: [ref.as], as: ref.as } : ref))) // needed for aliased stream property ref@odata.mediaContentType
910
911
  .columns(..._filterDraftColumnsBySelected(DRAFT_COLUMNS_CASTED, req.query.SELECT.columns))
911
912
  }
912
913
 
913
- const _excludeActiveDraftExists = (req, draftWhere, columns, model) => {
914
+ const _excludeActiveDraftExists = (req, columns, draftWhere, model) => {
914
915
  const { table, name } = _getTableName(req, true)
915
916
  const draftName = table.ref[0]
916
917
 
@@ -964,8 +965,9 @@ const _validatedWithSiblingInProcess = (req, draftWhere, draftParameters, column
964
965
  if (
965
966
  !draftInProcessByUser &&
966
967
  _isValidExcludeActiveDraftExists(draftParameters.isActiveEntity, draftParameters.siblingIsActive)
967
- )
968
- return _excludeActiveDraftExists(req, draftWhere, columns, model)
968
+ ) {
969
+ return _excludeActiveDraftExists(req, columns, draftWhere, model)
970
+ }
969
971
 
970
972
  if (
971
973
  draftInProcessByUser &&
@@ -1005,8 +1007,8 @@ const _draftInSubSelect = (where, req) => {
1005
1007
  const _isDraftAdminScenario = req =>
1006
1008
  req.target.query && req.target.query._target && req.target.query._target.name === 'DRAFT.DraftAdministrativeData'
1007
1009
 
1008
- const _generateCQN = (originalFrom, req, columns, model) => {
1009
- const nav = [...originalFrom.ref].reverse() || []
1010
+ const _generateCQN = (req, columns, from, model) => {
1011
+ const nav = [...from.ref].reverse() || []
1010
1012
  let siblingIndex = nav.indexOf('SiblingEntity')
1011
1013
 
1012
1014
  // it can also be a property access (new parser), then we must shift it
@@ -1018,20 +1020,12 @@ const _generateCQN = (originalFrom, req, columns, model) => {
1018
1020
  let siblingScenario
1019
1021
  if (siblingIndex > -1) {
1020
1022
  siblingScenario = _getSiblingScenario(req, columns, model, siblingIndex, nav)
1021
- if (siblingIndex === 0) {
1022
- return siblingScenario
1023
- }
1024
-
1023
+ if (siblingIndex === 0) return siblingScenario
1025
1024
  _mergeSiblingIntoCQN(req.query, siblingScenario, siblingIndex - 1)
1026
1025
  }
1027
1026
 
1028
- if (_isDraftAdminScenario(req)) {
1029
- return _draftAdminTable(req)
1030
- }
1031
-
1032
- if (!req.query.SELECT.where) {
1033
- return _allActive(req, columns)
1034
- }
1027
+ if (_isDraftAdminScenario(req)) return _draftAdminTable(req)
1028
+ if (!req.query.SELECT.where) return _allActive(req, columns)
1035
1029
 
1036
1030
  // REVISIT this function does not only read, but modifies where!
1037
1031
  const draftParameters = _readDraftParameters(req.query.SELECT.where)
@@ -1065,11 +1059,6 @@ const _generateCQN = (originalFrom, req, columns, model) => {
1065
1059
  return _validatedDraftOfWhichIAmOwner(req, req.query.SELECT.where, draftParameters, columns)
1066
1060
  }
1067
1061
 
1068
- const _getColumns = ({ query: { SELECT } }) =>
1069
- SELECT.columns.filter(
1070
- col => (col.ref && !(col.ref[col.ref.length - 1] in DRAFT_COLUMNS_MAP)) || (!col.ref && !(col in DRAFT_COLUMNS_MAP))
1071
- )
1072
-
1073
1062
  const _isIsActiveEntity = element => element.ref && element.ref[element.ref.length - 1] === 'IsActiveEntity'
1074
1063
 
1075
1064
  const _adaptSubSelects = ({ SELECT: { from, where } }, scenario) => {
@@ -1182,6 +1171,7 @@ const _setLastSubQuery = (query, last, prev = query) => {
1182
1171
  const _adaptDraftAdminExpand = cqn => {
1183
1172
  const draftAdminExpand =
1184
1173
  cqn.SELECT.columns && cqn.SELECT.columns.find(c => c.expand && c.ref[0] === 'DraftAdministrativeData')
1174
+
1185
1175
  if (draftAdminExpand) {
1186
1176
  _ensureDraftAdminColumnsForCalculation(draftAdminExpand.expand)
1187
1177
  }
@@ -1274,7 +1264,7 @@ const _adaptColumns4readAfterWrite = (req, cqnScenario, query4sql) => {
1274
1264
  *
1275
1265
  * @param req
1276
1266
  */
1277
- const _handler = async function (req) {
1267
+ const fioriGenericRead = async function (req) {
1278
1268
  const query = req.query
1279
1269
  const originalFrom = _copyCQNPartial(query.SELECT.from)
1280
1270
 
@@ -1299,12 +1289,12 @@ const _handler = async function (req) {
1299
1289
  // just to make existing tests working with new parser. not really tested, not needed to be supported
1300
1290
  if (reqClone.query.SELECT.from.SELECT) {
1301
1291
  const subQueryReq = { __proto__: req, query: _copyCQNPartial(_getLastSubQuery(reqClone.query)) }
1302
- const columns = _getColumns(subQueryReq)
1303
- cqnScenario = _generateCQN(originalFrom.SELECT.from, subQueryReq, columns, this.model)
1292
+ const nonDraftColumns = filterNonDraftColumns(subQueryReq.query.SELECT.columns)
1293
+ cqnScenario = _generateCQN(subQueryReq, nonDraftColumns, originalFrom.SELECT.from, this.model)
1304
1294
  cqnScenario.cqn = _setLastSubQuery(reqClone.query, cqnScenario.cqn)
1305
1295
  } else {
1306
- const columns = _getColumns(reqClone)
1307
- cqnScenario = _generateCQN(originalFrom, reqClone, columns, this.model)
1296
+ const nonDraftColumns = filterNonDraftColumns(reqClone.query.SELECT.columns)
1297
+ cqnScenario = _generateCQN(reqClone, nonDraftColumns, originalFrom, this.model)
1308
1298
  }
1309
1299
 
1310
1300
  if (!cqnScenario) req.reject(400)
@@ -1334,5 +1324,5 @@ const _handler = async function (req) {
1334
1324
  }
1335
1325
 
1336
1326
  module.exports = cds.service.impl(function (srv, entity) {
1337
- srv.on('READ', entity, _handler)
1327
+ srv.on('READ', entity, fioriGenericRead)
1338
1328
  })
@@ -210,11 +210,13 @@ const extractKeyConditions = whereCondition => {
210
210
  return result
211
211
  }
212
212
 
213
- const getKeysCondition = (target, data) => {
213
+ const getKeysCondition = req => {
214
+ const data = req.data
215
+ const target = req.target
214
216
  const where = []
215
217
  for (const k in target.keys) {
216
218
  const key = target.keys[k]
217
- if (!key.isAssociation && key.name !== 'IsActiveEntity') {
219
+ if (!key.isAssociation && key.name !== 'IsActiveEntity' && data[key.name] != undefined) {
218
220
  if (where.length) where.push('and')
219
221
  where.push({ ref: [key.name] }, '=', { val: data[key.name] })
220
222
  }
@@ -163,10 +163,8 @@ class HanaDatabase extends DatabaseService {
163
163
  }
164
164
 
165
165
  // REVISIT: should happen automatically after a configurable time
166
- // poolOnly: private param for mtx
167
- async disconnect(tenant, poolOnly) {
166
+ async disconnect(tenant) {
168
167
  await pool.drain(tenant)
169
- if (!poolOnly) super.disconnect(tenant)
170
168
  }
171
169
  }
172
170
 
@@ -55,6 +55,7 @@ const convertToString = element => {
55
55
  const HANA_TYPE_CONVERSION_MAP = new Map([
56
56
  ['cds.Boolean', convertToBoolean],
57
57
  ['cds.Integer64', convertInt64ToString],
58
+ ['cds.Int64', convertInt64ToString],
58
59
  ['cds.DateTime', convertToISONoMillis],
59
60
  ['cds.Timestamp', convertToISO],
60
61
  ['cds.LargeString', convertToString],
@@ -62,10 +63,12 @@ const HANA_TYPE_CONVERSION_MAP = new Map([
62
63
  ])
63
64
 
64
65
  if (cds.env.features.bigjs) {
66
+ // eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
65
67
  const Big = require('big.js')
66
68
  const convertToBig = value => new Big(value)
67
69
 
68
70
  HANA_TYPE_CONVERSION_MAP.set('cds.Integer64', convertToBig)
71
+ HANA_TYPE_CONVERSION_MAP.set('cds.Int64', convertToBig)
69
72
  HANA_TYPE_CONVERSION_MAP.set('cds.Decimal', convertToBig)
70
73
  }
71
74