@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
@@ -1,84 +1,100 @@
1
1
  const cds = require('../../../cds')
2
2
  const LOG = cds.log('odata')
3
+ const { BASE_TENANT, channelName } = require('../../../common/utils/extensibilityUtils')
3
4
 
4
- const OData = require('./OData')
5
-
6
- const { alias2ref } = require('../../../common/utils/csn')
5
+ const { SYSTEM_SERVICES, createOdataService, createNewService, getModelHash } = require('./utils/dispatcherUtils')
7
6
 
8
7
  const { normalizeError } = require('../../../common/error/frontend')
9
8
 
10
- function _createNewService(name, csn, defaultOptions) {
11
- const reflectedModel = cds.linked(cds.compile.for.odata(csn))
12
- const options = Object.assign({}, defaultOptions, { reflectedModel })
13
-
14
- const service = new cds.ApplicationService(name, csn, options)
15
- service.init()
16
- if (options.impl) service.impl(options.impl)
17
- service._isExtended = true
18
-
19
- const edm = cds.compile.to.edm(csn, { service: name })
20
- alias2ref(service, edm)
21
-
22
- const odataService = new OData(edm, csn, options)
23
- odataService.addCDSServiceToChannel(service)
24
-
25
- return odataService
26
- }
27
-
28
9
  class Dispatcher {
29
10
  /**
30
- * Constructs an Dispatcher for OData service.
31
- * New OData services will be created in case of extensibility.
11
+ * Constructs an Dispatcher for cds service.
12
+ * New OData services will be created.
32
13
  *
33
- * @param odata
14
+ * @param service
34
15
  */
35
- constructor(odata) {
36
- this._odata = odata
16
+ constructor(service) {
17
+ this._serviceName = service.definition.name
18
+ this._options = service.options
19
+
20
+ this._extMap = new Map()
21
+ this._extMap.set(getModelHash(BASE_TENANT, []), createOdataService(service))
22
+
23
+ if (cds.requires.extensibility && !SYSTEM_SERVICES.includes(this._serviceName)) {
24
+ cds.services['mtx-messaging'].on(channelName(), async msg => {
25
+ const tenant = msg.data.tenant
26
+ const hash = getModelHash(tenant, [])
27
+ for (const entry of this._extMap.entries()) {
28
+ if (entry[0].startsWith(hash)) {
29
+ this._extMap.delete(entry[0])
30
+ }
31
+ }
32
+ })
33
+ }
34
+
35
+ if (cds._mtxEnabled) {
36
+ cds.mtx.eventEmitter.on(cds.mtx.events.TENANT_UPDATED, async hash => {
37
+ this._extMap.delete(hash)
38
+ })
39
+ }
40
+ }
41
+
42
+ async _getService4Tenant(req) {
43
+ // here, req is express' req -> req.tenant not available
44
+ const tenant = req.user && req.user.tenant
45
+ const isExtended = await cds.mtx.isExtended(tenant) // REVISIT: avoid await
46
+ if (!isExtended) return false
47
+
48
+ const csn = await cds.mtx.getCsn(tenant)
49
+ const model = cds.compile.for.nodejs(csn)
50
+ const service = await createNewService(this._serviceName, model, this._options)
51
+ service._cdsService._isExtended = true
52
+ return service
53
+ }
54
+
55
+ async _getService4(tenant, features) {
56
+ const csn = await this._mps.getCsn(tenant, features || [], 'nodejs')
57
+ const model = cds.compile.for.nodejs(csn)
58
+ return createNewService(this._serviceName, model, this._options)
37
59
  }
38
60
 
39
- _getService4Tenant(req) {
40
- const {
41
- user: { tenant }
42
- } = req
61
+ async _getService(tenant, features, hash, req) {
62
+ if (cds._mtxEnabled) {
63
+ const service = await this._getService4Tenant(req)
43
64
 
44
- // eslint-disable-next-line no-async-promise-executor
45
- return new Promise(async (resolve, reject) => {
46
- try {
47
- const isExtended = await cds.mtx.isExtended(tenant)
48
- if (!isExtended) return resolve(false)
65
+ if (service) return service
49
66
 
50
- const csn = await cds.mtx.getCsn(tenant)
67
+ return this._extMap.get(getModelHash(BASE_TENANT, []))
68
+ }
51
69
 
52
- resolve(_createNewService(this._odata._cdsService.definition.name, csn, this._odata._options))
53
- } catch (e) {
54
- reject(e)
70
+ if (!this._mps) this._mps = await cds.connect.to('cds_r.ModelProviderService')
71
+ const hashBase = getModelHash(BASE_TENANT, features)
72
+ if (tenant && tenant !== BASE_TENANT && hash !== hashBase) {
73
+ const isExtended = cds.requires.extensibility && (await this._mps.isExtended(tenant))
74
+ if (isExtended) {
75
+ return this._getService4(tenant, features)
76
+ } else {
77
+ if (!this._extMap.has(hashBase)) {
78
+ this._extMap.set(hashBase, this._getService4(BASE_TENANT, features))
79
+ }
80
+ return this._extMap.get(hashBase)
55
81
  }
56
- })
82
+ } else {
83
+ return this._getService4(BASE_TENANT, features)
84
+ }
57
85
  }
58
86
 
59
- _getService4Toggles(req) {
60
- const {
61
- user: { tenant }
62
- } = req
63
-
64
- // eslint-disable-next-line no-async-promise-executor
65
- return new Promise(async (resolve, reject) => {
66
- try {
67
- if (!this._mps) this._mps = await cds.connect.to('ModelProviderService')
68
-
69
- /*
70
- * ModelProviderService:
71
- * action csn(tenant:TenantID, version:String, toggles: array of String) returns CSN;
72
- * action edmx(tenant:TenantID, version:String, toggles: array of String, service:String, locale:Locale, odataVersion:String) returns XML;
73
- */
74
- const toggles = (req.features && Object.keys(req.features)) || []
75
- const csn = await this._mps.csn(tenant, 'dummy', toggles)
76
-
77
- resolve(_createNewService(this._odata._cdsService.definition.name, csn, this._odata._options))
78
- } catch (e) {
79
- reject(e)
80
- }
81
- })
87
+ async _handleError(err, hash, req, res) {
88
+ if (LOG._error) {
89
+ err.message = 'Unable to get service from service map due to error: ' + err.message
90
+ LOG.error(err)
91
+ }
92
+ // clear map entry
93
+ this._extMap.delete(hash)
94
+ // return 503 to client
95
+ const { error } = normalizeError(Object.assign(err, { statusCode: 503 }), req)
96
+
97
+ return res.status(503).send({ error })
82
98
  }
83
99
 
84
100
  /**
@@ -91,46 +107,36 @@ class Dispatcher {
91
107
  */
92
108
  async dispatch(req, res) {
93
109
  // here, req is express' req -> req.tenant not available
94
- if (cds._mtxEnabled && req.user && req.user.tenant) {
95
- // enable mtx, if not done yet
96
- if (!this._extMap) {
97
- this._extMap = new Map()
98
- cds.mtx.eventEmitter.on(cds.mtx.events.TENANT_UPDATED, async hash => {
99
- this._extMap.delete(hash)
100
- })
101
- }
110
+ const tenant = req.user && req.user.tenant
102
111
 
103
- const { alpha_toggles: alphaToggles } = cds.env.features
104
-
105
- // here, req is express' req -> req.tenant not available
106
- const hash = alphaToggles ? cds.mtx._getHash(req) : req.user.tenant
107
-
108
- // add hash to map, if not done yet
109
- if (!this._extMap.has(hash)) {
110
- this._extMap.set(hash, alphaToggles ? this._getService4Toggles(req) : this._getService4Tenant(req))
111
- }
112
+ if (SYSTEM_SERVICES.includes(this._serviceName) || (!tenant && !cds._mpsEnabled)) {
113
+ const service = this._extMap.get(getModelHash(BASE_TENANT, []))
114
+ return service.process(req, res)
115
+ }
112
116
 
113
- // await extended service promise
114
- let service
115
- try {
116
- service = await this._extMap.get(hash)
117
- } catch (e) {
118
- if (LOG._error) {
119
- e.message = 'Unable to get service from service map due to error: ' + e.message
120
- LOG.error(e)
121
- }
122
- // clear map entry
123
- this._extMap.delete(hash)
124
- // return 503 to client
125
- const { error } = normalizeError(Object.assign(e, { statusCode: 503 }), req)
126
- return res.status(503).send({ error })
127
- }
117
+ // ensure features is an array (some stakeholders set req.features themselves)
118
+ const features = req.features
119
+ ? Array.isArray(req.features)
120
+ ? req.features
121
+ : Object.keys(req.features)
122
+ .filter(k => req.features[k])
123
+ .sort()
124
+ : []
125
+ const hash = getModelHash(tenant, features)
126
+
127
+ // set promise into the map to avoid conflicts
128
+ if (!this._extMap.has(hash)) {
129
+ this._extMap.set(hash, this._getService(tenant, features, hash, req))
130
+ }
128
131
 
129
- // invoke extended service, if exists
130
- if (service) return service.process(req, res)
132
+ let service
133
+ try {
134
+ service = await this._extMap.get(hash)
135
+ } catch (err) {
136
+ return this._handleError(err, hash, req, res)
131
137
  }
132
138
 
133
- this._odata.process(req, res)
139
+ service.process(req, res)
134
140
  }
135
141
 
136
142
  /**
@@ -66,7 +66,12 @@ function _log(level, arg) {
66
66
 
67
67
  // reduce 4xx to warning
68
68
  if (isClientError(obj)) {
69
- if (!LOG._warn) return
69
+ if (!LOG._warn) {
70
+ // restore
71
+ obj.message = _message
72
+ if (_details) obj.details = _details
73
+ return
74
+ }
70
75
  level = 'warn'
71
76
  }
72
77
  }
@@ -84,8 +89,8 @@ const _logger = {
84
89
  path: () => {},
85
90
  info: arg => LOG._info && _log('info', arg),
86
91
  warning: arg => LOG._warn && _log('warn', arg),
87
- error: arg => LOG._error && _log('error', arg),
88
- fatal: arg => LOG._error && _log('error', arg)
92
+ error: arg => _log('error', arg),
93
+ fatal: arg => _log('error', arg)
89
94
  }
90
95
 
91
96
  /**
@@ -79,7 +79,7 @@ class ODataRequest extends cds.Request {
79
79
  /*
80
80
  * data
81
81
  */
82
- const data = getData(type, odataReq, service)
82
+ const data = getData(type, odataReq, service, target)
83
83
 
84
84
  /*
85
85
  * query
@@ -32,7 +32,7 @@ const _postProcessDraftActivate = async (req, result, service) => {
32
32
  result.HasDraftEntity = false
33
33
 
34
34
  // remove children from result, excluding localized composition 'text'
35
- if (!cds.env.effective.odata.structs) {
35
+ if (!(cds.env.effective.odata.structs || cds.env.features.ucsn_struct_conversion)) {
36
36
  for (const k in req.target.elements) {
37
37
  if (_isAssocOrCompNotLocalized(req.target, k)) delete result[k]
38
38
  }
@@ -1,13 +1,7 @@
1
- const cds = require('../../../../cds')
1
+ const localeFrom = require('../../../../../../lib/req/locale')
2
2
  const { toODataResult } = require('../utils/result')
3
3
 
4
4
  module.exports = (req, res, next) => {
5
- const _ = {
6
- req: req.getIncomingRequest(),
7
- odataReq: req
8
- }
9
- const user = new cds.User()
10
- Object.defineProperty(user, '_req', { enumerable: false, value: _.req })
11
- const locale = user.locale
5
+ const locale = localeFrom(req.getIncomingRequest())
12
6
  next(null, toODataResult(locale))
13
7
  }
@@ -2,30 +2,10 @@ const cds = require('../../../../cds')
2
2
  const LOG = cds.log('odata')
3
3
 
4
4
  const { toODataResult } = require('../utils/result')
5
-
6
5
  const { normalizeError } = require('../../../../common/error/frontend')
7
6
 
8
- let _mps
9
-
10
- const _get4Tenant = async (tenant, locale, service) => {
11
- if (cds._mtxEnabled && service._isExtended) {
12
- const edmx = await cds.mtx.getEdmx(tenant, service.name, locale)
13
- return edmx
14
- }
15
- }
16
-
17
- const _get4Toggles = async (tenant, locale, service, req) => {
18
- if (!_mps) _mps = await cds.connect.to('ModelProviderService')
19
-
20
- /*
21
- * ModelProviderService:
22
- * action csn(tenant:TenantID, version:String, toggles: array of String) returns CSN;
23
- * action edmx(tenant:TenantID, version:String, toggles: array of String, service:String, locale:Locale, odataVersion:String) returns XML;
24
- */
25
- const toggles = (req.features && Object.keys(req.features)) || []
26
- const edmx = await _mps.edmx(tenant, 'dummy', toggles, service.name, locale, 'v4')
27
-
28
- return edmx
7
+ const _getMetadata4Tenant = async (tenant, locale, service) => {
8
+ return await cds.mtx.getEdmx(tenant, service.name, locale)
29
9
  }
30
10
 
31
11
  /**
@@ -37,20 +17,15 @@ const _get4Toggles = async (tenant, locale, service, req) => {
37
17
  const metadata = service => {
38
18
  return async (odataReq, odataRes, next) => {
39
19
  const req = odataReq.getIncomingRequest()
40
-
41
20
  const tenant = req.user && req.user.tenant
42
-
43
21
  // REVISIT: can we take locale from user, or is there some odata special wrt metadata?
44
22
  const locale = odataRes.getContract().getLocale()
45
23
 
46
24
  try {
47
25
  let edmx
48
26
 
49
- if (tenant) {
50
- const { alpha_toggles: alphaToggles } = cds.env.features
51
- edmx = alphaToggles
52
- ? await _get4Toggles(tenant, locale, service, req)
53
- : await _get4Tenant(tenant, locale, service)
27
+ if (cds._mtxEnabled && service._isExtended) {
28
+ edmx = await _getMetadata4Tenant(tenant, locale, service)
54
29
  }
55
30
 
56
31
  if (!edmx) {
@@ -13,6 +13,7 @@ const {
13
13
  }
14
14
  }
15
15
  } = require('../okra/odata-server')
16
+ const FUNCTION = { [BOUND_FUNCTION]: 1, [FUNCTION_IMPORT]: 1 }
16
17
 
17
18
  const { isCustomOperation } = require('../utils/request')
18
19
  const { actionAndFunctionQueries, getActionOrFunctionReturnType } = require('../utils/handlerUtils')
@@ -31,7 +32,7 @@ const { getSapMessages } = require('../../../../common/error/frontend')
31
32
  * @returns {boolean} - True if a function is invoked, else false.
32
33
  * @private
33
34
  */
34
- const _isFunction = segments => [BOUND_FUNCTION, FUNCTION_IMPORT].includes(segments[segments.length - 1].getKind())
35
+ const _isFunction = segments => segments[segments.length - 1].getKind() in FUNCTION
35
36
 
36
37
  const _selectOrExpandInQueryOptions = queryOptions => {
37
38
  return queryOptions && (queryOptions.$select || queryOptions.$expand)
@@ -1,9 +1,10 @@
1
1
  const cds = require('../../../../cds')
2
2
 
3
- const { UNAUTHORIZED, FORBIDDEN, getRequiresAsArray } = require('../../../../auth/utils')
3
+ const { UNAUTHORIZED, FORBIDDEN, getRequiresAsArray, isRestricted } = require('../../../../auth/utils')
4
4
 
5
5
  module.exports = srv => {
6
6
  const requires = getRequiresAsArray(srv.definition)
7
+ const restricted = isRestricted(srv)
7
8
 
8
9
  return (odataReq, odataRes, next) => {
9
10
  const req = odataReq.getBatchApplicationData()
@@ -18,7 +19,7 @@ module.exports = srv => {
18
19
  }
19
20
 
20
21
  // in case of $batch we need to challenge directly, as the header is not processed if in $batch response body
21
- if (user && user._challenges && path.endsWith('/$batch')) {
22
+ if (path.endsWith('/$batch') && user && user._challenges && restricted) {
22
23
  res.set('WWW-Authenticate', user._challenges.join(';'))
23
24
  return next(UNAUTHORIZED)
24
25
  }
@@ -23,7 +23,7 @@ const _isUpsertAllowed = target => {
23
23
  const _infoForeignKeyInParent = (req, odataReq, odataRes, tx) => {
24
24
  const info = {}
25
25
  // keys not in data
26
- if (req.target.keys && Object.keys(req.target.keys).some(key => Object.keys(req.data).includes(key))) {
26
+ if (req.target.keys && Object.keys(req.target.keys).some(key => key in req.data)) {
27
27
  return info
28
28
  }
29
29
 
@@ -62,7 +62,7 @@ const _create = async (req, odataReq, odataRes, tx) => {
62
62
  let parentKeyVal, parentUpdateRequired
63
63
  if (parentKeyObj.length !== 0 && parentKeyObj[0][key.parentElement.name] !== null) {
64
64
  parentKeyVal = parentKeyObj[0][key.parentElement.name]
65
- } else if (key.childElement.type === 'cds.UUID' && key.childElement.key) {
65
+ } else if (key.childElement.isUUID && key.childElement.key) {
66
66
  parentUpdateRequired = true
67
67
  parentKeyVal = cds.utils.uuid()
68
68
  } else {
@@ -16,6 +16,8 @@ const _binaryOperatorToCQN = new Map([
16
16
  [BinaryOperatorKind.LT, '<']
17
17
  ])
18
18
 
19
+ const LAMBDA_EXPRESSION = { [ResourceKind.ANY_EXPRESSION]: 1, [ResourceKind.ALL_EXPRESSION]: 1 }
20
+
19
21
  class ExpressionToCQN {
20
22
  constructor(entity, model, columns = []) {
21
23
  this._model = model
@@ -145,11 +147,7 @@ class ExpressionToCQN {
145
147
 
146
148
  _member(expression, operator) {
147
149
  const pathSegments = expression.getPathSegments()
148
- if (
149
- pathSegments.some(segment =>
150
- [ResourceKind.ANY_EXPRESSION, ResourceKind.ALL_EXPRESSION].includes(segment.getKind())
151
- )
152
- ) {
150
+ if (pathSegments.some(segment => segment.getKind() in LAMBDA_EXPRESSION)) {
153
151
  return this._lambda(pathSegments, operator)
154
152
  }
155
153
 
@@ -252,7 +250,7 @@ class ExpressionToCQN {
252
250
 
253
251
  // add cds type to right operand for use in _convert
254
252
  if (left.ref && left.ref.length === 1 && this._entity && this._entity.elements[left.ref[0]]) {
255
- expression.getRightOperand()._cdsType = this._entity.elements[left.ref[0]].type
253
+ expression.getRightOperand()._cdsType = this._entity.elements[left.ref[0]]._type
256
254
  }
257
255
 
258
256
  const right = this.parse(expression.getRightOperand())
@@ -23,15 +23,14 @@ const getError = require('../../../../common/error')
23
23
  * @private
24
24
  */
25
25
  const _getExpandItem = (isAll, expandItems, name) => {
26
- if (isAll) {
27
- return null
28
- }
26
+ if (isAll) return null
29
27
 
30
28
  return expandItems.find(item => {
31
29
  const pathSegments = item.getPathSegments()
32
30
  if (pathSegments[pathSegments.length - 1].getKind() === 'COUNT') {
33
31
  throw getError(501, 'EXPAND_COUNT_UNSUPPORTED')
34
32
  }
33
+
35
34
  const navigation = pathSegments[pathSegments.length - 1].getNavigationProperty()
36
35
  return navigation && navigation.getName() === name
37
36
  })
@@ -44,9 +43,7 @@ const _getExpandItem = (isAll, expandItems, name) => {
44
43
  * @private
45
44
  */
46
45
  const _notSupported = expandItem => {
47
- if (!expandItem) {
48
- return
49
- }
46
+ if (!expandItem) return
50
47
 
51
48
  if (expandItem.getOption(QueryOptions.COUNT)) {
52
49
  throw getFeatureNotSupportedError(`Expand with query option "${QueryOptions.COUNT}"`)
@@ -73,24 +70,27 @@ const _getColumnsFromTargetType = (targetType, relatedEntity, all = false) => {
73
70
 
74
71
  if (all) {
75
72
  return getColumns(relatedEntity, { onlyNames: true, removeIgnore: true, filterDraft: false })
76
- .filter(c => c !== 'DraftAdministrativeData_DraftUUID')
77
- .map(c => ({
78
- ref: [c]
73
+ .filter(column => column !== 'DraftAdministrativeData_DraftUUID')
74
+ .map(column => ({
75
+ ref: [column]
79
76
  }))
80
77
  }
81
78
 
82
- return Object.keys(relatedEntity.keys)
83
- .filter(k => !relatedEntity.keys[k].is2one && !relatedEntity.keys[k].is2many)
84
- .map(element => ({
85
- ref: [element]
86
- }))
79
+ const columnNames = Object.values(relatedEntity.elements)
80
+ .filter(element => element['@odata.etag'])
81
+ .map(element => element.name)
82
+
83
+ const keyNames = Object.keys(relatedEntity.keys).filter(keyName => {
84
+ const key = relatedEntity.keys[keyName]
85
+ return !key.is2one && !key.is2many
86
+ })
87
+
88
+ columnNames.push(...keyNames)
89
+ return columnNames.map(element => ({ ref: [element] }))
87
90
  }
88
91
 
89
92
  const _getInnerSelect = expandItem => {
90
- if (!expandItem) {
91
- return []
92
- }
93
-
93
+ if (!expandItem) return []
94
94
  return expandItem.getOption(QueryOptions.SELECT) || []
95
95
  }
96
96
 
@@ -110,6 +110,7 @@ const _getSelectedElements = (expandItem, targetType, relatedEntity, options) =>
110
110
  targetType.getProperties().forEach((value, key) => {
111
111
  if (!relatedEntity.keys[key]) proxy = false
112
112
  })
113
+
113
114
  if (proxy) {
114
115
  return _getColumnsFromTargetType(targetType, relatedEntity)
115
116
  }
@@ -181,9 +182,10 @@ const _getItemCQN = (model, name, navigationProperty, expandItem, options) => {
181
182
 
182
183
  // autoexposed entities now used . in csn and _ in edm
183
184
  const relatedEntity = findCsnTargetFor(entityName, model, namespace)
185
+ const expand = _getSelectedElements(expandItem, targetType, relatedEntity, options)
184
186
  const item = {
185
187
  ref: name, // ['structured', 'nested_', nestedAssocToOne] if expand on structured
186
- expand: _getSelectedElements(expandItem, targetType, relatedEntity, options)
188
+ expand
187
189
  }
188
190
 
189
191
  item.expand.push(..._getInnerExpandItems(model, expandItem, targetType))
@@ -203,7 +205,6 @@ const _getItemCQN = (model, name, navigationProperty, expandItem, options) => {
203
205
  addLimit(item, top != null ? top : null, expandItem.getOption(QueryOptions.SKIP) || 0)
204
206
 
205
207
  _filter(item, expandItem.getOption(QueryOptions.FILTER))
206
-
207
208
  return item
208
209
  }
209
210
 
@@ -237,13 +238,15 @@ const expandToCQN = (model, expandItems, type, options) => {
237
238
  const expandItem = _getExpandItem(isAll, expandItems, name)
238
239
 
239
240
  if (isAll || expandItem) {
240
- allElements.push(_getItemCQN(model, [name], navigationProperty, expandItem, options))
241
+ const itemCQN = _getItemCQN(model, [name], navigationProperty, expandItem, options)
242
+ allElements.push(itemCQN)
241
243
  }
242
244
  }
243
245
 
244
246
  // structured
245
247
  for (const expandItem of expandItems) {
246
248
  const pathSegments = expandItem.getPathSegments()
249
+
247
250
  if (pathSegments.length && pathSegments[0].getKind() === 'COMPLEX.PROPERTY') {
248
251
  const navigationProperty = _getNavigationProperty(pathSegments)
249
252
 
@@ -9,6 +9,7 @@ const readToCQN = require('./readToCQN')
9
9
  const updateToCQN = require('./updateToCQN')
10
10
  const createToCQN = require('./createToCQN')
11
11
  const deleteToCQN = require('./deleteToCQN')
12
+ const parseToCqn = require('../../../../../odata/parseToCqn')
12
13
 
13
14
  /**
14
15
  * This method transforms an odata request into a CQN object.
@@ -22,7 +23,12 @@ const deleteToCQN = require('./deleteToCQN')
22
23
  * @returns {object} - The CQN object
23
24
  */
24
25
  module.exports = (component, service, target, data, odataReq, upsert) => {
25
- const odata2cqn = cds.env.features.odata_new_parser
26
+ if (
27
+ cds.env.features.odata_new_parser &&
28
+ !(component in { 'BOUND.ACTION': 1, 'BOUND.FUNCTION': 1, 'FUNCTION.IMPORT': 1, 'ACTION.IMPORT': 1 })
29
+ ) {
30
+ return parseToCqn(component, service, target, data, odataReq, upsert)
31
+ }
26
32
 
27
33
  switch (component) {
28
34
  case DATA_CREATE_HANDLER:
@@ -30,7 +36,7 @@ module.exports = (component, service, target, data, odataReq, upsert) => {
30
36
  case DATA_DELETE_HANDLER:
31
37
  return deleteToCQN(service, odataReq)
32
38
  case DATA_READ_HANDLER:
33
- return odata2cqn ? cds.odata.parse(odataReq, { service }) : readToCQN(service, target, odataReq)
39
+ return readToCQN(service, target, odataReq)
34
40
  case DATA_UPDATE_HANDLER:
35
41
  return updateToCQN(service, data, odataReq)
36
42
  case 'BOUND.ACTION':
@@ -1,5 +1,7 @@
1
1
  'use strict'
2
2
 
3
+ const cds = require('../../../../../../cds')
4
+
3
5
  const stream = require('stream')
4
6
  const commons = require('../../odata-commons')
5
7
  const PrimitiveValueDecoder = commons.utils.PrimitiveValueDecoder
@@ -34,11 +34,7 @@ class DispatcherCommand extends Command {
34
34
  super()
35
35
  this._request = request
36
36
  this._response = response
37
-
38
- // dummy cache if alpha_toggles
39
- if (cds.env.features.alpha_toggles) this._metadataCache = { get: () => {}, set: () => {} }
40
- else this._metadataCache = metadataCache
41
-
37
+ this._metadataCache = metadataCache
42
38
  this._componentManager = componentManager
43
39
  this._dispatcher = dispatcher
44
40
  this._logger = logger
@@ -78,7 +74,7 @@ class DispatcherCommand extends Command {
78
74
  locale,
79
75
  result.data.value
80
76
  )
81
- if (!cds.env.features.alpha_toggles && !this._metadataCache.get(contract.getContentTypeInfo().getMimeType(), locale)) {
77
+ if (!this._metadataCache.get(contract.getContentTypeInfo().getMimeType(), locale)) {
82
78
  this._logger.info(
83
79
  'Metadata size exceeds cache boundary. Use cds option odata.metadataCacheLimit to increase the cache size.'
84
80
  )
@@ -1,18 +1,7 @@
1
- const cds = require('../../../cds')
2
-
3
- const OData = require('./OData')
4
1
  const Dispatcher = require('./Dispatcher')
5
2
 
6
- const { alias2ref } = require('../../../common/utils/csn')
7
-
8
3
  const to = service => {
9
- const edm = service._edm || cds.compile.to.edm(service.model, { service: service.definition.name })
10
- alias2ref(service, edm)
11
-
12
- const odata = new OData(edm, service.model, service.options)
13
- odata.addCDSServiceToChannel(service)
14
-
15
- return new Dispatcher(odata).getService()
4
+ return new Dispatcher(service).getService()
16
5
  }
17
6
 
18
7
  module.exports = to