@sap/cds 5.8.4 → 5.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. package/CHANGELOG.md +198 -77
  2. package/app/fiori/preview.js +16 -11
  3. package/app/fiori/routes.js +15 -8
  4. package/app/index.js +1 -1
  5. package/bin/build/buildTaskFactory.js +3 -3
  6. package/bin/build/buildTaskProviderFactory.js +1 -1
  7. package/bin/build/constants.js +1 -1
  8. package/bin/build/provider/buildTaskHandlerEdmx.js +12 -7
  9. package/bin/build/provider/buildTaskHandlerInternal.js +1 -1
  10. package/bin/build/provider/buildTaskProviderInternal.js +8 -2
  11. package/bin/build/provider/hana/2migration.js +27 -24
  12. package/bin/build/provider/hana/index.js +17 -18
  13. package/bin/build/provider/hana/migrationtable.js +9 -10
  14. package/bin/build/provider/java-cf/index.js +4 -5
  15. package/bin/build/provider/node-cf/index.js +99 -6
  16. package/bin/cds.js +17 -18
  17. package/bin/deploy/to-hana/cfUtil.js +16 -19
  18. package/bin/deploy/to-hana/hana.js +7 -24
  19. package/bin/deploy/to-hana/hdiDeployUtil.js +8 -4
  20. package/bin/mtx/in-cds.js +2 -2
  21. package/bin/serve.js +10 -3
  22. package/bin/utils/modules.js +7 -0
  23. package/bin/version.js +56 -3
  24. package/lib/compile/cdsc.js +7 -2
  25. package/lib/compile/etc/_localized.js +37 -25
  26. package/lib/compile/etc/csv.js +8 -8
  27. package/lib/compile/for/drafts.js +9 -0
  28. package/lib/compile/for/java.js +16 -0
  29. package/lib/compile/for/nodejs.js +12 -0
  30. package/lib/compile/index.js +3 -0
  31. package/lib/compile/minify.js +16 -2
  32. package/lib/compile/parse.js +2 -2
  33. package/lib/compile/resolve.js +35 -18
  34. package/lib/compile/to/json.js +3 -1
  35. package/lib/compile/to/sql.js +2 -2
  36. package/lib/compile/to/srvinfo.js +4 -2
  37. package/lib/connect/bindings.js +1 -1
  38. package/lib/connect/index.js +3 -4
  39. package/lib/core/entities.js +15 -14
  40. package/lib/core/index.js +39 -36
  41. package/lib/core/reflect.js +4 -2
  42. package/lib/deploy.js +114 -127
  43. package/lib/env/defaults.js +1 -0
  44. package/lib/env/index.js +165 -165
  45. package/lib/env/presets.js +1 -0
  46. package/lib/env/requires.js +121 -50
  47. package/lib/index.js +2 -0
  48. package/lib/log/format/kibana.js +2 -2
  49. package/lib/ql/SELECT.js +10 -0
  50. package/lib/ql/parse.js +1 -0
  51. package/lib/req/cds-context.js +4 -1
  52. package/lib/req/context.js +50 -56
  53. package/lib/req/event.js +1 -6
  54. package/lib/req/locale.js +6 -5
  55. package/lib/req/request.js +2 -0
  56. package/lib/req/user.js +7 -5
  57. package/lib/serve/Service-api.js +10 -7
  58. package/lib/serve/Service-dispatch.js +9 -11
  59. package/lib/serve/Service-methods.js +30 -41
  60. package/lib/serve/Transaction.js +10 -7
  61. package/lib/serve/adapters.js +11 -9
  62. package/lib/serve/factory.js +14 -9
  63. package/lib/serve/index.js +28 -15
  64. package/lib/utils/data.js +1 -1
  65. package/lib/utils/index.js +27 -30
  66. package/lib/utils/resources/index.js +101 -0
  67. package/lib/utils/resources/tar.js +71 -0
  68. package/lib/utils/resources/utils.js +11 -0
  69. package/libx/_runtime/audit/Service.js +36 -39
  70. package/libx/_runtime/audit/generic/personal/access.js +3 -4
  71. package/libx/_runtime/audit/generic/personal/modification.js +3 -4
  72. package/libx/_runtime/audit/utils/v2.js +1 -2
  73. package/libx/_runtime/auth/index.js +126 -84
  74. package/libx/_runtime/auth/strategies/JWT.js +12 -19
  75. package/libx/_runtime/auth/strategies/dummy.js +1 -5
  76. package/libx/_runtime/auth/strategies/dwc.js +11 -9
  77. package/libx/_runtime/auth/strategies/mock.js +0 -4
  78. package/libx/_runtime/auth/strategies/{utils/xssec.js → xssecUtils.js} +7 -4
  79. package/libx/_runtime/auth/strategies/xsuaa.js +12 -19
  80. package/libx/_runtime/auth/utils.js +22 -1
  81. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +104 -98
  82. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +8 -3
  83. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -1
  84. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -1
  85. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/language.js +2 -8
  86. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +4 -29
  87. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +2 -1
  88. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +3 -2
  89. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +2 -2
  90. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +4 -6
  91. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +24 -21
  92. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +8 -2
  93. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +2 -0
  94. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/DispatcherCommand.js +2 -6
  95. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -12
  96. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +33 -9
  97. package/libx/_runtime/cds-services/adapter/odata-v4/utils/dispatcherUtils.js +56 -0
  98. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +2 -2
  99. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +10 -3
  100. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +9 -11
  101. package/libx/_runtime/cds-services/adapter/rest/RestRequest.js +6 -3
  102. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +4 -2
  103. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/utils.js +1 -1
  104. package/libx/_runtime/cds-services/adapter/rest/utils/binary.js +1 -1
  105. package/libx/_runtime/cds-services/adapter/rest/utils/key-value-utils.js +2 -3
  106. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +6 -4
  107. package/libx/_runtime/cds-services/adapter/rest/utils/result.js +1 -0
  108. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +8 -5
  109. package/libx/_runtime/cds-services/services/Service.js +40 -0
  110. package/libx/_runtime/cds-services/services/utils/columns.js +4 -3
  111. package/libx/_runtime/cds-services/services/utils/compareJson.js +4 -4
  112. package/libx/_runtime/cds-services/services/utils/differ.js +3 -3
  113. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +4 -4
  114. package/libx/_runtime/cds-services/util/assert.js +20 -14
  115. package/libx/_runtime/cds.js +9 -1
  116. package/libx/_runtime/common/aspects/any.js +5 -0
  117. package/libx/_runtime/common/aspects/entity.js +25 -7
  118. package/libx/_runtime/common/aspects/utils.js +2 -2
  119. package/libx/_runtime/common/composition/data.js +6 -0
  120. package/libx/_runtime/common/composition/insert.js +3 -2
  121. package/libx/_runtime/common/composition/tree.js +4 -10
  122. package/libx/_runtime/common/composition/update.js +4 -4
  123. package/libx/_runtime/common/constants/draft.js +29 -26
  124. package/libx/_runtime/common/error/constants.js +2 -2
  125. package/libx/_runtime/common/error/frontend.js +7 -15
  126. package/libx/_runtime/common/generic/auth/capabilities.js +59 -0
  127. package/libx/_runtime/common/generic/auth/constants.js +20 -0
  128. package/libx/_runtime/common/generic/auth/expand.js +54 -0
  129. package/libx/_runtime/common/generic/auth/index.js +32 -0
  130. package/libx/_runtime/common/generic/auth/insertOnly.js +15 -0
  131. package/libx/_runtime/common/generic/auth/readOnly.js +26 -0
  132. package/libx/_runtime/common/generic/auth/requires.js +34 -0
  133. package/libx/_runtime/common/generic/auth/restrict.js +298 -0
  134. package/libx/_runtime/common/generic/auth/restrictions.js +85 -0
  135. package/libx/_runtime/common/generic/auth/utils.js +213 -0
  136. package/libx/_runtime/common/generic/crud.js +8 -6
  137. package/libx/_runtime/common/generic/etag.js +1 -1
  138. package/libx/_runtime/common/generic/input.js +35 -35
  139. package/libx/_runtime/common/generic/sorting.js +2 -3
  140. package/libx/_runtime/common/generic/temporal.js +2 -2
  141. package/libx/_runtime/common/i18n/messages.properties +1 -1
  142. package/libx/_runtime/common/toggles/handler.js +21 -0
  143. package/libx/_runtime/common/utils/copy.js +10 -1
  144. package/libx/_runtime/common/utils/cqn2cqn4sql.js +111 -35
  145. package/libx/_runtime/common/utils/csn.js +63 -1
  146. package/libx/_runtime/common/utils/dollar.js +10 -1
  147. package/libx/_runtime/common/utils/draft.js +46 -7
  148. package/libx/_runtime/common/utils/entityFromCqn.js +13 -9
  149. package/libx/_runtime/common/utils/extensibilityUtils.js +18 -0
  150. package/libx/_runtime/common/utils/foreignKeyPropagations.js +88 -104
  151. package/libx/_runtime/common/utils/generateOnCond.js +4 -1
  152. package/libx/_runtime/common/utils/quotingStyles.js +2 -0
  153. package/libx/_runtime/common/utils/resolveStructured.js +25 -9
  154. package/libx/_runtime/common/utils/resolveView.js +4 -1
  155. package/libx/_runtime/common/utils/rewriteAsterisks.js +3 -16
  156. package/libx/_runtime/common/utils/structured.js +33 -37
  157. package/libx/_runtime/common/utils/template.js +17 -8
  158. package/libx/_runtime/common/utils/templateProcessor.js +28 -28
  159. package/libx/_runtime/db/data-conversion/post-processing.js +118 -412
  160. package/libx/_runtime/db/expand/expandCQNToJoin.js +45 -41
  161. package/libx/_runtime/db/expand/rawToExpanded.js +29 -8
  162. package/libx/_runtime/db/generic/index.js +1 -3
  163. package/libx/_runtime/db/generic/input.js +5 -10
  164. package/libx/_runtime/db/generic/rewrite.js +5 -2
  165. package/libx/_runtime/db/generic/structured.js +2 -2
  166. package/libx/_runtime/db/query/delete.js +2 -2
  167. package/libx/_runtime/db/query/insert.js +1 -1
  168. package/libx/_runtime/db/query/update.js +9 -14
  169. package/libx/_runtime/db/sql-builder/CreateBuilder.js +4 -3
  170. package/libx/_runtime/db/sql-builder/FunctionBuilder.js +8 -8
  171. package/libx/_runtime/db/sql-builder/InsertBuilder.js +14 -1
  172. package/libx/_runtime/db/sql-builder/SelectBuilder.js +3 -2
  173. package/libx/_runtime/db/sql-builder/dataTypes.js +3 -3
  174. package/libx/_runtime/db/utils/columns.js +3 -3
  175. package/libx/_runtime/db/utils/normalizeTimeData.js +2 -2
  176. package/libx/_runtime/db/utils/propagateForeignKeys.js +6 -2
  177. package/libx/_runtime/extensibility/mps/index.js +5 -0
  178. package/libx/_runtime/extensibility/mps/service.js +111 -0
  179. package/libx/_runtime/extensibility/mps/tar.js +42 -0
  180. package/libx/_runtime/extensibility/mps/utils.js +11 -0
  181. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformREAD.js +0 -0
  182. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformRESULT.js +17 -5
  183. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformWRITE.js +1 -0
  184. package/libx/_runtime/extensibility/uiflex/index.js +54 -0
  185. package/libx/_runtime/extensibility/uiflex/service.js +276 -0
  186. package/libx/_runtime/{fiori → extensibility}/uiflex/utils.js +22 -7
  187. package/libx/_runtime/fiori/generic/activate.js +2 -2
  188. package/libx/_runtime/fiori/generic/before.js +4 -4
  189. package/libx/_runtime/fiori/generic/new.js +3 -3
  190. package/libx/_runtime/fiori/generic/patch.js +1 -1
  191. package/libx/_runtime/fiori/generic/read.js +58 -66
  192. package/libx/_runtime/fiori/generic/readOverDraft.js +74 -16
  193. package/libx/_runtime/fiori/utils/handler.js +6 -13
  194. package/libx/_runtime/fiori/utils/where.js +6 -5
  195. package/libx/_runtime/hana/Service.js +4 -10
  196. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +1 -1
  197. package/libx/_runtime/hana/driver.js +2 -2
  198. package/libx/_runtime/hana/execute.js +45 -75
  199. package/libx/_runtime/hana/pool.js +1 -1
  200. package/libx/_runtime/hana/streaming.js +2 -1
  201. package/libx/_runtime/index.js +6 -6
  202. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +5 -21
  203. package/libx/_runtime/messaging/Outbox.js +2 -2
  204. package/libx/_runtime/messaging/common-utils/AMQPClient.js +4 -14
  205. package/libx/_runtime/messaging/common-utils/connections.js +5 -7
  206. package/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +30 -0
  207. package/libx/_runtime/messaging/enterprise-messaging-shared.js +2 -1
  208. package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +36 -30
  209. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +19 -12
  210. package/libx/_runtime/messaging/enterprise-messaging.js +8 -8
  211. package/libx/_runtime/messaging/file-based.js +5 -5
  212. package/libx/_runtime/messaging/message-queuing.js +14 -12
  213. package/libx/_runtime/messaging/outbox/utils.js +18 -19
  214. package/libx/_runtime/messaging/redis-messaging.js +91 -0
  215. package/libx/_runtime/messaging/service.js +8 -6
  216. package/libx/_runtime/remote/Service.js +44 -8
  217. package/libx/_runtime/remote/utils/client.js +24 -19
  218. package/libx/_runtime/remote/utils/data.js +11 -11
  219. package/libx/_runtime/sqlite/Service.js +6 -9
  220. package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +5 -2
  221. package/libx/_runtime/types/api.js +10 -2
  222. package/libx/common/utils/ucsn.js +109 -0
  223. package/libx/gql/resolvers/crud/update.js +5 -0
  224. package/libx/gql/resolvers/parse/ast2cqn/columns.js +3 -1
  225. package/libx/gql/schema/typeDefMap.js +2 -2
  226. package/libx/odata/afterburner.js +110 -16
  227. package/libx/odata/cqn2odata.js +24 -27
  228. package/libx/odata/grammar.pegjs +9 -1
  229. package/libx/odata/parseToCqn.js +39 -0
  230. package/libx/odata/parser.js +1 -1
  231. package/libx/rest/RestAdapter.js +9 -1
  232. package/libx/rest/middleware/input.js +54 -0
  233. package/libx/rest/middleware/operation.js +14 -1
  234. package/libx/rest/middleware/parse.js +11 -7
  235. package/package.json +2 -2
  236. package/server.js +34 -19
  237. package/srv/audit-log.cds +2 -2
  238. package/srv/flex.cds +8 -2
  239. package/srv/flex.js +1 -1
  240. package/srv/mps.cds +23 -0
  241. package/srv/mps.js +1 -0
  242. package/libx/_runtime/auth/strategies/utils/uaa.js +0 -21
  243. package/libx/_runtime/common/generic/auth.js +0 -874
  244. package/libx/_runtime/common/toggles/alpha.js +0 -43
  245. package/libx/_runtime/db/generic/arrayed.js +0 -33
  246. package/libx/_runtime/fiori/uiflex/index.js +0 -35
  247. package/libx/_runtime/fiori/uiflex/service.js +0 -150
  248. package/libx/rest/utils/data.js +0 -60
@@ -4,7 +4,8 @@ const {
4
4
 
5
5
  const { findCsnTargetFor } = require('../../../../common/utils/csn')
6
6
  const { isStreaming } = require('./stream')
7
- const { deepCopyObject, deepCopyArray } = require('../../../../common/utils/copy')
7
+ const { convertStructured } = require('../../../../../common/utils/ucsn')
8
+ const { deepCopy } = require('../../../../common/utils/copy')
8
9
 
9
10
  const _isFunctionInvocation = req =>
10
11
  req.getUriInfo().getLastSegment().getFunction || req.getUriInfo().getLastSegment().getFunctionImport
@@ -146,7 +147,7 @@ const _addForeignKeys = (service, req, data) => {
146
147
  }
147
148
  }
148
149
 
149
- const _getFunctionParameters = (lastSegment, keyValues) => {
150
+ const _getFunctionParameters = (lastSegment, keyValues, service, target) => {
150
151
  const functionParameters = lastSegment.getFunctionParameters()
151
152
  const paramValues = _getParamData(functionParameters)
152
153
 
@@ -154,18 +155,41 @@ const _getFunctionParameters = (lastSegment, keyValues) => {
154
155
  for (const key in keyValues) {
155
156
  paramValues[key] = keyValues[key]
156
157
  }
158
+ if (lastSegment.getKind() === 'BOUND.FUNCTION') {
159
+ const targetFunction = target && target.actions && target.actions[lastSegment.getFunction().getName()]
160
+ if (!targetFunction.params) return {}
161
+ convertStructured(service, targetFunction, paramValues)
162
+ } else if (lastSegment.getKind() === 'FUNCTION.IMPORT') {
163
+ const { namespace, name } = lastSegment.getFunctionImport().getFullQualifiedName()
164
+ const targetFunction = service.model && service.model.definitions[`${namespace}.${name}`]
165
+ if (!targetFunction.params) return {}
166
+ convertStructured(service, targetFunction, paramValues)
167
+ }
157
168
  return paramValues
158
169
  }
159
170
 
160
- const _getCopiedData = (odataReq, streaming, lastSegment) => {
171
+ const _getCopiedData = (odataReq, streaming, lastSegment, service, target) => {
161
172
  let data = odataReq.getBody() || {}
162
173
 
163
174
  if (streaming || lastSegment.getKind() === 'PRIMITIVE.PROPERTY') {
164
- data = { [lastSegment.getProperty().getName()]: data }
165
- return data
175
+ return { [lastSegment.getProperty().getName()]: data }
166
176
  }
167
177
 
168
- return Array.isArray(data) ? deepCopyArray(data) : deepCopyObject(data)
178
+ data = deepCopy(data)
179
+
180
+ if (lastSegment.getKind() === 'BOUND.ACTION') {
181
+ const targetAction = target.actions && target.actions[lastSegment.getAction().getName()]
182
+ if (!targetAction || !targetAction.params) return {}
183
+ convertStructured(service, targetAction, data)
184
+ } else if (lastSegment.getKind() === 'ACTION.IMPORT') {
185
+ const { namespace, name } = lastSegment.getActionImport().getFullQualifiedName()
186
+ const targetAction = service.model && service.model.definitions[`${namespace}.${name}`]
187
+ if (!targetAction || !targetAction.params) return {}
188
+ convertStructured(service, targetAction, data)
189
+ } else {
190
+ convertStructured(service, target, data)
191
+ }
192
+ return data
169
193
  }
170
194
 
171
195
  /**
@@ -181,7 +205,7 @@ const _getCopiedData = (odataReq, streaming, lastSegment) => {
181
205
  * @returns {object | Array}
182
206
  * @private
183
207
  */
184
- const getData = (component, odataReq, service) => {
208
+ const getData = (component, odataReq, service, target) => {
185
209
  const segments = odataReq.getUriInfo().getPathSegments()
186
210
  const lastSegment = odataReq.getUriInfo().getLastSegment()
187
211
  const streaming = isStreaming(segments)
@@ -189,7 +213,7 @@ const getData = (component, odataReq, service) => {
189
213
  const keyValues = _getParamData(keyPredicates)
190
214
 
191
215
  if (component === DATA_READ_HANDLER && _isFunctionInvocation(odataReq)) {
192
- return _getFunctionParameters(lastSegment, keyValues)
216
+ return _getFunctionParameters(lastSegment, keyValues, service, target)
193
217
  }
194
218
 
195
219
  if (component === DATA_DELETE_HANDLER || component === DATA_READ_HANDLER) {
@@ -200,7 +224,7 @@ const getData = (component, odataReq, service) => {
200
224
  }
201
225
 
202
226
  // copy so that original payload is preserved
203
- const data = _getCopiedData(odataReq, streaming, lastSegment)
227
+ const data = _getCopiedData(odataReq, streaming, lastSegment, service, target)
204
228
 
205
229
  // Only to be done for post via navigation
206
230
  if (
@@ -0,0 +1,56 @@
1
+ const cds = require('../../../../cds')
2
+ const OData = require('../OData')
3
+ const DEBUG = cds.debug('extensibility')
4
+
5
+ const { alias2ref } = require('../../../../common/utils/csn')
6
+ const { BASE_TENANT } = require('../../../../common/utils/extensibilityUtils')
7
+
8
+ const SYSTEM_SERVICES = ['cds_r.ExtensibilityService', 'cds_r.ModelProviderService']
9
+
10
+ function createOdataService(service) {
11
+ const name = (service.definition && service.definition.name) || service.name
12
+ const edm = cds.compile.to.edm(service.model, { service: name })
13
+ alias2ref(service, edm)
14
+
15
+ const odataService = new OData(edm, service.model, service.options)
16
+ odataService.addCDSServiceToChannel(service)
17
+
18
+ return odataService
19
+ }
20
+
21
+ async function createNewService(name, model, options) {
22
+ const { constructor: Service, path } = cds.services[name]
23
+ const service = new Service(name, model, { ...options }) // cloning options to be safe
24
+ if (service.init) await service.prepend(service.init)
25
+ if (options.impl) await service.prepend(options.impl)
26
+ if (path) service.path = path
27
+ DEBUG &&
28
+ DEBUG('Created tenant-specific service:', service.name, '= new', Service.name, {
29
+ _handlers: {
30
+ on: service._handlers.on.map(h => ({ on: h.on, handler: () => {} }))
31
+ }
32
+ })
33
+ return createOdataService(service)
34
+ }
35
+
36
+ const getModelHash = (tenant, features) => {
37
+ if (!features) features = []
38
+
39
+ let hash
40
+ if (cds.requires.multitenancy && tenant) {
41
+ hash = tenant
42
+ } else {
43
+ hash = BASE_TENANT
44
+ }
45
+
46
+ hash += ':' + features.join(';')
47
+
48
+ return hash
49
+ }
50
+
51
+ module.exports = {
52
+ SYSTEM_SERVICES,
53
+ createOdataService,
54
+ createNewService,
55
+ getModelHash
56
+ }
@@ -2,13 +2,13 @@ const cds = require('../../../../cds')
2
2
  const { SELECT } = cds.ql
3
3
 
4
4
  const { getDeepSelect } = require('../../../services/utils/handlerUtils')
5
- const { DRAFT_COLUMNS } = require('../../../../common/constants/draft')
5
+ const { DRAFT_COLUMNS_MAP } = require('../../../../common/constants/draft')
6
6
  const { filterKeys } = require('../../../../fiori/utils/handler')
7
7
 
8
8
  const _getColumns = target => {
9
9
  const columns = []
10
10
  for (const k in target.elements) {
11
- if (!target.elements[k].isAssociation && !DRAFT_COLUMNS.includes(k)) columns.push(k)
11
+ if (!target.elements[k].isAssociation && !(k in DRAFT_COLUMNS_MAP)) columns.push(k)
12
12
  }
13
13
  return columns
14
14
  }
@@ -9,7 +9,15 @@ const {
9
9
  const getError = require('../../../../common/error')
10
10
 
11
11
  const _unboundActionsAndFunctions = ['ACTION.IMPORT', 'FUNCTION.IMPORT']
12
+ const _unboundCustomOperationKinds = _unboundActionsAndFunctions.reduce((acc, cur) => {
13
+ acc[cur] = 1
14
+ return acc
15
+ }, {})
12
16
  const _actionsAndFunctions = [..._unboundActionsAndFunctions, 'BOUND.ACTION', 'BOUND.FUNCTION']
17
+ const _boundCustomOperationKinds = _actionsAndFunctions.reduce((acc, cur) => {
18
+ acc[cur] = 1
19
+ return acc
20
+ }, {})
13
21
 
14
22
  /**
15
23
  * Checks if a custom operation was requested.
@@ -20,10 +28,9 @@ const _actionsAndFunctions = [..._unboundActionsAndFunctions, 'BOUND.ACTION', 'B
20
28
  * @private
21
29
  */
22
30
  const isCustomOperation = (pathSegments, includingBound = true) => {
23
- const customOperationKinds = includingBound ? _actionsAndFunctions : _unboundActionsAndFunctions
24
31
  const kind = pathSegments[pathSegments.length - 1].getKind()
25
-
26
- if (customOperationKinds.includes(kind)) {
32
+ const kinds = includingBound ? _boundCustomOperationKinds : _unboundCustomOperationKinds
33
+ if (kind in kinds) {
27
34
  return kind
28
35
  }
29
36
  }
@@ -118,7 +118,7 @@ const convertDecimal = (row, key, options) => {
118
118
  }
119
119
 
120
120
  const addAssociationToRow = (row, foreignKey, foreignKeyElement) => {
121
- const assocName = foreignKeyElement['@odata.foreignKey4']
121
+ const assocName = foreignKeyElement._foreignKey4
122
122
  const assoc = foreignKeyElement.parent.elements[assocName]
123
123
 
124
124
  if (!row[assocName]) {
@@ -128,7 +128,8 @@ const addAssociationToRow = (row, foreignKey, foreignKeyElement) => {
128
128
  const keyOfAssociatedEntity = foreignKey.replace(`${assocName}_`, '')
129
129
 
130
130
  // REVISIT: structured keys, see xtests in structured-x4
131
- if (assoc._target.keys[keyOfAssociatedEntity] && assoc._target.keys[keyOfAssociatedEntity]['@odata.foreignKey4']) {
131
+ const assocTargetKey = assoc._target.keys[keyOfAssociatedEntity]
132
+ if (assocTargetKey && assocTargetKey._foreignKey4) {
132
133
  // assoc as key
133
134
  row[assocName][keyOfAssociatedEntity] = row[foreignKey]
134
135
  delete row[foreignKey]
@@ -173,7 +174,7 @@ const _processCategory = (category, processArgs, req, options, previousResult) =
173
174
  if (options.decimals && row[key]) convertDecimal(row, key, options)
174
175
  break
175
176
 
176
- case '@odata.foreignKey4':
177
+ case 'foreignKey':
177
178
  addAssociationToRow(row, key, element)
178
179
  break
179
180
 
@@ -221,13 +222,13 @@ const _isContainedOrBackLink = element =>
221
222
  (element._isContained || (element._anchor && element._anchor._isContained))
222
223
 
223
224
  const _assocs = (element, target) => {
224
- const assocName = element['@odata.foreignKey4']
225
+ const assocName = element._foreignKey4
225
226
  const assoc = assocName && target.elements[assocName]
226
227
 
227
228
  if (cds.env.effective.odata.refs) {
228
229
  // expand assoc keys except of up_ backlinks
229
- if (element['@odata.foreignKey4'] && !_isUpAssoc(assoc)) {
230
- return ['@odata.foreignKey4']
230
+ if (assoc && !_isUpAssoc(assoc)) {
231
+ return ['foreignKey']
231
232
  }
232
233
 
233
234
  if (element['@odata.containment.ignore']) {
@@ -247,7 +248,7 @@ const _pick = options => (element, target, parent) => {
247
248
 
248
249
  if (element['@odata.etag']) categories.push('@odata.etag')
249
250
 
250
- if (element.type === 'cds.Decimal') categories.push('@cds.Decimal')
251
+ if (element._type === 'cds.Decimal') categories.push('@cds.Decimal')
251
252
 
252
253
  categories.push(..._assocs(element, target))
253
254
 
@@ -320,10 +321,7 @@ const postProcess = (req, res, service, result, previousResult) => {
320
321
  const args = {
321
322
  processFn,
322
323
  row,
323
- template,
324
- pathOptions: {
325
- includeKeyValues: false
326
- }
324
+ template
327
325
  }
328
326
 
329
327
  templateProcessor(args)
@@ -19,15 +19,18 @@ class RestRequest extends cds.Request {
19
19
  constructor(parsed, data, req, res, service) {
20
20
  const { event, target } = parsed
21
21
 
22
+ // for functions, data is in params (if provided)
23
+ if (parsed.kind === 'function' && parsed.params) data = parsed.params
24
+
22
25
  /*
23
26
  * query
24
27
  */
25
28
  const query = restToCqn(parsed, data, req, service)
26
29
 
27
30
  /*
28
- * method, params, headers
31
+ * method, headers
29
32
  */
30
- const { method, params, headers } = req
33
+ const { method, headers } = req
31
34
 
32
35
  /*
33
36
  * super
@@ -37,7 +40,7 @@ class RestRequest extends cds.Request {
37
40
  const _model = service.model
38
41
  // REVISIT: public API for query options (express style req.query already in use)?
39
42
  const _queryOptions = req.query
40
- super({ event, target, data, query, user, method, params, headers, req, res, _model, _queryOptions })
43
+ super({ event, target, data, query, user, method, headers, req, res, _model, _queryOptions })
41
44
 
42
45
  // REVISIT: validate associations for deep insert
43
46
  flattenDeepToOneAssociations(this, this.model)
@@ -53,8 +53,10 @@ module.exports = service => {
53
53
  if (err) next(err)
54
54
  else {
55
55
  // only set status if not yet modified
56
- if (restRes.statusCode === 200 && status) restRes.status(status)
57
- restRes.send(toRestResult(body))
56
+ const { statusCode } = restRes
57
+ if (statusCode === 200 && status) restRes.status(status)
58
+ if (body === undefined) restRes.status(statusCode === 200 ? 204 : statusCode).end()
59
+ else restRes.send(toRestResult(body))
58
60
  }
59
61
  }
60
62
  }
@@ -22,7 +22,7 @@ const createCqlString = (target, key, keyValue) => {
22
22
  let keyString = ''
23
23
 
24
24
  if (keyValue !== undefined) {
25
- keyString = `[${key}=${_convertKeyForCompiler(keyValue, target.keys[key].type)}]`
25
+ keyString = `[${key}=${_convertKeyForCompiler(keyValue, target.keys[key]._type)}]`
26
26
  }
27
27
 
28
28
  return `${target.name}${keyString}`
@@ -2,7 +2,7 @@ const _isKeyTypeBinary = (entity, key) => {
2
2
  return (
3
3
  entity.elements &&
4
4
  entity.elements[key] &&
5
- (entity.elements[key].type === 'cds.Binary' || entity.elements[key].type === 'cds.LargeBinary')
5
+ (entity.elements[key]._type === 'cds.Binary' || entity.elements[key]._type === 'cds.LargeBinary')
6
6
  )
7
7
  }
8
8
 
@@ -22,10 +22,9 @@ const getConvertedValue = (type, value) => {
22
22
 
23
23
  module.exports = {
24
24
  getKeyValuePair: (entity, value) => {
25
+ if (value === undefined) return {}
25
26
  const key = _keyFromEntity(entity)
26
- return {
27
- [key.name]: getConvertedValue(key.type, value)
28
- }
27
+ return { [key.name]: getConvertedValue(key._type, value) }
29
28
  },
30
29
  getConvertedValue
31
30
  }
@@ -19,7 +19,9 @@ const _normalizeAndSplitUrl = req => {
19
19
  }
20
20
 
21
21
  const _enrichCustomOperation = (csnElement, customOperationName) => {
22
- return Object.assign({ name: customOperationName }, csnElement)
22
+ const obj = { name: customOperationName }
23
+ Object.setPrototypeOf(obj, csnElement)
24
+ return obj
23
25
  }
24
26
 
25
27
  const _initializeParsed = event => {
@@ -93,9 +95,9 @@ const _validateAndConvertParamValues = (csnElement, params = {}) => {
93
95
  if (!csnElementParam) {
94
96
  throw getError(400, 'INVALID_PARAMETER', [param])
95
97
  }
96
- const convertedParam = getConvertedValue(csnElementParam.type, params[param])
98
+ const convertedParam = getConvertedValue(csnElementParam._type, params[param])
97
99
  if (Number.isNaN(convertedParam)) {
98
- throw getError(400, 'INVALID_PARAMETER_VALUE_TYPE', [param, csnElementParam.type])
100
+ throw getError(400, 'INVALID_PARAMETER_VALUE_TYPE', [param, csnElementParam._type])
99
101
  }
100
102
  params[param] = convertedParam
101
103
  }
@@ -123,7 +125,7 @@ const _setConvenienceProperties = parsed => {
123
125
  parsed.isCollection = false
124
126
  } else if (lastElement.kind === 'entity') {
125
127
  parsed.isCollection = true
126
- } else if (lastElement.type === 'cds.Association' || lastElement.type === 'cds.Composition') {
128
+ } else if (lastElement.isAssociation) {
127
129
  parsed.isCollection = true
128
130
  } else {
129
131
  parsed.operation = lastElement
@@ -13,6 +13,7 @@ const toRestResult = result => {
13
13
  count: result.$count,
14
14
  value: result
15
15
  }
16
+ // need to convert number to string because express interprets integer as status code
16
17
  } else if (typeof result === 'number') {
17
18
  restResult = result.toString()
18
19
  }
@@ -84,14 +84,11 @@ const validateReturnType = (operation, data) => {
84
84
  // in case of inline return type: { elements: ... } and no explicit name of return type
85
85
  const returnType = operation.returns.items ? operation.returns.items : operation.returns
86
86
 
87
- if (typeof data === 'undefined') {
88
- const { kind, name } = operation
89
- // REVISIT: use i18n
90
- throw getError(`'undefined' is invalid according to return type definition of custom ${kind} '${name}'.`)
91
- }
87
+ if (typeof data === 'undefined') return true
92
88
 
93
89
  let checkResult
94
90
 
91
+ // .type of action/function behaves different to .type of other csn elements
95
92
  // Return type contains primitives
96
93
  if (cds.builtin.types[returnType.type]) {
97
94
  const check = CDS_TYPE_CHECKS[returnType.type]
@@ -100,6 +97,12 @@ const validateReturnType = (operation, data) => {
100
97
  ? _checkArray(returnType.type, check, data)
101
98
  : _checkSingle(returnType.type, check, data)
102
99
  } else {
100
+ if (typeof data !== 'object') {
101
+ throw new Error(
102
+ `Invalid scalar value ${typeof data === 'string' ? `"${data}"` : data} for return type "${returnType.type}"`
103
+ )
104
+ }
105
+
103
106
  // Only check complex objects, ignore non-modelled data
104
107
  data = (Array.isArray(data) ? data : [data]).filter(entry => typeof entry === 'object' && !Array.isArray(entry))
105
108
 
@@ -7,6 +7,11 @@ const { postProcess } = require('../../common/utils/postProcessing')
7
7
 
8
8
  const _isSimpleCqnQuery = q => typeof q === 'object' && q !== null && !Array.isArray(q) && Object.keys(q).length > 0
9
9
 
10
+ // for getRestrictions()
11
+ const { getNormalizedRestrictions, getApplicableRestrictions } = require('../../common/generic/auth/restrictions.js')
12
+ const WRITE_EVENTS = { CREATE: 1, NEW: 1, UPDATE: 1, PATCH: 1, DELETE: 1, CANCEL: 1, EDIT: 1 }
13
+ const CRUD = Object.assign({ READ: 1 }, WRITE_EVENTS)
14
+
10
15
  /**
11
16
  * Generic Service Event Handler.
12
17
  */
@@ -108,6 +113,41 @@ class ApplicationService extends cds.Service {
108
113
  }
109
114
  }
110
115
 
116
+ /**
117
+ * Returns the applicable restrictions for the current request as follows:
118
+ * - null: unrestricted access
119
+ * - []: no access
120
+ * - [{ grant: '...', to: ['...'], where: '...' }, ...]: applicable restrictions with grant normalized to strings,
121
+ * i.e., grant: ['CREATE', 'UPDATE'] in model becomes [{ grant: 'CREATE' }, { grant: 'UPDATE' }]
122
+ * - Promise resovling to any of the above (needed for CAS overrides)
123
+ *
124
+ * @param {object} definition - then csn definition of an entity or an (un)bound action or function
125
+ * @param {string} event - the event name
126
+ * @param {import('../../../../lib/req/user')} user - the current user
127
+ * @returns {Promise | Array | null}
128
+ */
129
+ getRestrictions(definition, event, user) {
130
+ let restrictions = getNormalizedRestrictions(definition, this.model.definitions, event)
131
+ if ((!restrictions || !restrictions.length) && (event in CRUD || !definition.parent)) {
132
+ // > unrestricted entity or unbound
133
+ return null
134
+ }
135
+ if (event in CRUD && restrictions.every(r => r.grant !== '*' && !(r.grant in CRUD))) {
136
+ // > only bounds are restricted
137
+ return null
138
+ }
139
+ if (!(event in CRUD) && (!restrictions || !restrictions.length) && definition.parent) {
140
+ // > bound without own restrictions -> get from parent
141
+ restrictions = getNormalizedRestrictions(definition.parent, this.model.definitions, event)
142
+ if (!restrictions || !restrictions.length) {
143
+ // > unrestricted bound
144
+ return null
145
+ }
146
+ }
147
+ // return the applicable restrictions (grant and to fit to request and user)
148
+ return getApplicableRestrictions(restrictions, event, user)
149
+ }
150
+
111
151
  // Overload .handle in order to resolve projections up to a definition that is known by the remote service instance.
112
152
  // Result is post processed according to the inverse projection in order to reflect the correct result of the original query.
113
153
  async handle(req) {
@@ -2,8 +2,9 @@ const cds = require('../../../cds')
2
2
  // requesting logger without module on purpose!
3
3
  const LOG = cds.log()
4
4
 
5
- const { DRAFT_COLUMNS_UNION } = require('../../../common/constants/draft')
5
+ const { DRAFT_COLUMNS_UNION_MAP } = require('../../../common/constants/draft')
6
6
 
7
+ // REVISIT: Can we combine that with db/utils/columns.js?
7
8
  /**
8
9
  * This method gets all columns for an entity.
9
10
  * It includes the generated foreign keys from managed associations, structured elements and complex and custom types.
@@ -30,7 +31,7 @@ const getColumns = (
30
31
  if (element.isAssociation) continue
31
32
  if (filterVirtual && element.virtual) continue
32
33
  if (removeIgnore && element['@cds.api.ignore']) continue
33
- if (skipDraft && DRAFT_COLUMNS_UNION.includes(each)) continue
34
+ if (skipDraft && each in DRAFT_COLUMNS_UNION_MAP) continue
34
35
  if (keysOnly && !element.key) continue
35
36
  columns.push(onlyNames ? each : element)
36
37
  }
@@ -82,7 +83,7 @@ const getSearchableColumns = entity => {
82
83
  // - The @cds.search annotation is provided, the column name is not included, and the column
83
84
  // is typed as string.
84
85
  // - The @cds.search annotation is not provided, and the column is typed as string
85
- return annotatedColumnValue === undefined && column.type === defaultSearchableType
86
+ return annotatedColumnValue === undefined && column._type === defaultSearchableType
86
87
  })
87
88
 
88
89
  // if the @cds.search annotation is provided -->
@@ -1,4 +1,4 @@
1
- const { DRAFT_COLUMNS } = require('../../../common/constants/draft')
1
+ const { DRAFT_COLUMNS_MAP } = require('../../../common/constants/draft')
2
2
 
3
3
  const _deepEqual = (val1, val2) => {
4
4
  if (val1 && typeof val1 === 'object' && val2 && typeof val2 === 'object') {
@@ -19,7 +19,7 @@ const _getIdxCorrespondingEntryWithSameKeys = (source, entry, keys) =>
19
19
  source.findIndex(sourceEntry => keys.every(key => _deepEqual(sourceEntry[key], entry[key])))
20
20
 
21
21
  const _getKeysOfEntity = entity =>
22
- Object.keys(entity.keys).filter(key => !DRAFT_COLUMNS.includes(key) && !entity.elements[key].isAssociation)
22
+ Object.keys(entity.keys).filter(key => !(key in DRAFT_COLUMNS_MAP) && !entity.elements[key].isAssociation)
23
23
 
24
24
  const _getCompositionsOfEntity = entity => Object.keys(entity.elements).filter(e => entity.elements[e].isComposition)
25
25
 
@@ -29,7 +29,7 @@ const _createToBeDeletedEntries = (oldEntry, entity, keys, compositions) => {
29
29
  }
30
30
 
31
31
  for (const prop in oldEntry) {
32
- if (DRAFT_COLUMNS.includes(prop)) {
32
+ if (prop in DRAFT_COLUMNS_MAP) {
33
33
  continue
34
34
  }
35
35
  if (keys.includes(prop)) {
@@ -162,7 +162,7 @@ const _iteratePropsInNewEntry = (newEntry, keys, result, oldEntry, entity) => {
162
162
  }
163
163
 
164
164
  // if value did not change --> ignored
165
- if (newEntry[prop] === (oldEntry && oldEntry[prop]) || DRAFT_COLUMNS.includes(prop)) {
165
+ if (newEntry[prop] === (oldEntry && oldEntry[prop]) || prop in DRAFT_COLUMNS_MAP) {
166
166
  continue
167
167
  }
168
168
 
@@ -6,7 +6,7 @@ const { compareJson } = require('./compareJson')
6
6
  const { selectDeepUpdateData } = require('../../../common/composition')
7
7
  const { ensureDraftsSuffix } = require('../../../fiori/utils/handler')
8
8
 
9
- const { DRAFT_COLUMNS } = require('../../../common/constants/draft')
9
+ const { DRAFT_COLUMNS_MAP } = require('../../../common/constants/draft')
10
10
  const { cqn2cqn4sql, convertPathExpressionToWhere } = require('../../../common/utils/cqn2cqn4sql')
11
11
  const { revertData } = require('../../../common/utils/resolveView')
12
12
  const { removeIsActiveEntityRecursively } = require('../../../fiori/utils/where')
@@ -25,7 +25,7 @@ module.exports = class {
25
25
  ref: [element.name],
26
26
  expand: this._createSelectColumnsForDelete(element._target)
27
27
  })
28
- } else if (!element._isAssociationStrict && !DRAFT_COLUMNS.includes(element.name)) {
28
+ } else if (!element._isAssociationStrict && !(element.name in DRAFT_COLUMNS_MAP)) {
29
29
  columns.push({ ref: [element.name] })
30
30
  }
31
31
  }
@@ -58,7 +58,7 @@ module.exports = class {
58
58
  } catch (e) {
59
59
  // NOTE: unofficial compat flag!
60
60
  if (cds.env.features.throw_diff_error !== false) throw e
61
- else LOG._error && LOG.error('Unable to calculate diff due to error: ' + e.message, e)
61
+ else LOG.error('Unable to calculate diff due to error: ' + e.message, e)
62
62
  }
63
63
  }
64
64
  const newQuery = cqn2cqn4sql(req.query, this._srv.model)
@@ -3,7 +3,7 @@ const { SELECT } = cds.ql
3
3
 
4
4
  const { processDeep } = require('../../util/dataProcessUtils')
5
5
 
6
- const { DRAFT_COLUMNS } = require('../../../common/constants/draft')
6
+ const { DRAFT_COLUMNS_MAP } = require('../../../common/constants/draft')
7
7
 
8
8
  const _isAssociationToOneManaged = element => element._isAssociationEffective && element.is2one && !element.on
9
9
 
@@ -18,7 +18,7 @@ const _getWheres = (key, data) => {
18
18
  const allKeysAreProvided = req => {
19
19
  const data = req.data && (Array.isArray(req.data) ? req.data : [req.data])
20
20
  for (const key of Object.values(req.target.keys || {})) {
21
- if (key._isAssociationStrict || DRAFT_COLUMNS.includes(key.name)) {
21
+ if (key._isAssociationStrict || key.name in DRAFT_COLUMNS_MAP) {
22
22
  continue
23
23
  }
24
24
  for (const d of data) {
@@ -53,7 +53,7 @@ const _getSelectCQN = (req, columns) => {
53
53
  const data = req.data && (Array.isArray(req.data) ? req.data : [req.data])
54
54
 
55
55
  for (const key of Object.values(req.target.keys || {})) {
56
- if (key._isAssociationStrict || DRAFT_COLUMNS.includes(key.name)) {
56
+ if (key._isAssociationStrict || key.name in DRAFT_COLUMNS_MAP) {
57
57
  continue
58
58
  }
59
59
 
@@ -168,7 +168,7 @@ const getDeepSelect = req => {
168
168
  const cols = []
169
169
 
170
170
  for (const each in elements) {
171
- if (DRAFT_COLUMNS.includes(each)) continue
171
+ if (each in DRAFT_COLUMNS_MAP) continue
172
172
  const e = elements[each]
173
173
  if (!e.isAssociation) cols.push(each)
174
174
  else if (e._isCompositionEffective) {