@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,7 @@ const { ensureNoDraftsSuffix } = require('../../common/utils/draft.js')
4
4
 
5
5
  const _convertDateTimeElement = (value, element) => {
6
6
  value = new Date(value).toISOString()
7
- if (element.type === 'cds.DateTime') value = value.replace(/\.\d\d\d/, '')
7
+ if (element._type === 'cds.DateTime') value = value.replace(/\.\d\d\d/, '')
8
8
  return value
9
9
  }
10
10
 
@@ -38,7 +38,7 @@ const _convertColumns = (data, elements, model, queryColumns) => {
38
38
  // check all columns
39
39
  for (let i = 0, length = queryColumns.length; i < length; i++) {
40
40
  const col = queryColumns[i]
41
- if (elements[col] && _isToConvert(elements[col].type)) {
41
+ if (elements[col] && _isToConvert(elements[col]._type)) {
42
42
  const dataArray = Array.isArray(data[0]) ? data : [data]
43
43
  for (const d of dataArray) {
44
44
  const elementValue = d[i]
@@ -1,6 +1,8 @@
1
1
  const cds = require('../../cds')
2
2
 
3
- const _autoGenerate = e => e && e.type === 'cds.UUID' && e.key
3
+ const { prefixForStruct } = require('../../common/utils/csn')
4
+
5
+ const _autoGenerate = e => e && e.isUUID && e.key
4
6
 
5
7
  const _generateParentField = ({ parentElement }, row) => {
6
8
  if (_autoGenerate(parentElement) && !row[parentElement.name]) {
@@ -19,6 +21,7 @@ const _generateChildField = ({ deep, childElement }, childRow) => {
19
21
  const _getNestedVal = (row, prefix) => {
20
22
  let val = row
21
23
  const splitted = prefix.split('_')
24
+ splitted.pop() // remove last `_`
22
25
  let k = ''
23
26
 
24
27
  while (splitted.length > 0) {
@@ -34,9 +37,10 @@ const _getNestedVal = (row, prefix) => {
34
37
  return val
35
38
  }
36
39
 
37
- const _propagateToChid = ({ parentElement, childElement, prefix, parentFieldValue }, row, childRow) => {
40
+ const _propagateToChid = ({ parentElement, childElement, parentFieldValue }, row, childRow) => {
38
41
  if (!childElement) return
39
42
  if (parentElement) {
43
+ const prefix = prefixForStruct(parentElement)
40
44
  if (prefix) {
41
45
  const nested = _getNestedVal(row, prefix)
42
46
  childRow[childElement.name] = nested[parentElement.name]
@@ -0,0 +1,5 @@
1
+ const cds = require('../../cds')
2
+ if (cds.requires.db && !(cds.model && 'cds_r.ModelProviderService' in cds.model.definitions)) {
3
+ const model = require('path').join(__dirname, '../../../..', 'srv/mps.cds')
4
+ module.exports = cds.serve(model, { silent: true }).in(cds.app)
5
+ }
@@ -0,0 +1,111 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+
4
+ const cds = require('../../cds')
5
+ const { exists } = require('./utils')
6
+ const { supplementaryFiles } = require('./tar')
7
+
8
+ const { BASE_TENANT } = require('../../common/utils/extensibilityUtils')
9
+
10
+ const _getAllFeatures = async function () {
11
+ const features = path.join(global.cds.root, 'fts')
12
+ if (await exists(features)) {
13
+ return (await fs.promises.readdir(features, { withFileTypes: true })).map(dir => path.join(features, dir.name))
14
+ }
15
+
16
+ return []
17
+ }
18
+
19
+ const _addExtensions = async function (csn, tenant) {
20
+ const rs = await cds.tx({ tenant }, tx => tx.run(SELECT.from('cds_r.Extensions')))
21
+
22
+ if (rs.length !== 0) {
23
+ const extensions = []
24
+ const definitions = {}
25
+ rs.forEach(row => {
26
+ const csn = JSON.parse(row.csn)
27
+ if (csn.extensions) extensions.push(...csn.extensions)
28
+ if (csn.definitions) {
29
+ Object.keys(csn.definitions).forEach(key => {
30
+ definitions[key] = csn.definitions[key]
31
+ })
32
+ }
33
+ })
34
+ const extCsn = cds.compile({
35
+ 'base.csn': cds.compile.to.json(csn),
36
+ 'ext.csn': cds.compile.to.json({ extensions, definitions })
37
+ })
38
+ // Sources are used to load resources like i18n
39
+ extCsn.$sources = csn.$sources
40
+ extCsn.$location = csn.$location
41
+
42
+ return extCsn
43
+ }
44
+
45
+ return csn
46
+ }
47
+
48
+ const _isExtended = async function (req) {
49
+ const rs = await cds.tx({ tenant: req.data.tenant }, tx => tx.run('SELECT * from cds_r_Extensions'))
50
+ return rs.length !== 0
51
+ }
52
+
53
+ const _csn4 = async function (req, _flavor) {
54
+ let { tenant, toggles, flavor = _flavor } = req.data
55
+ if (!tenant) tenant = BASE_TENANT
56
+
57
+ let features = []
58
+ // Star is only for deployment - not a valid DwC protocol configuration.
59
+ if (toggles && toggles.includes('*')) {
60
+ features = await _getAllFeatures()
61
+ } else if (toggles) {
62
+ features = toggles.map(t => path.join(global.cds.root, 'fts', t))
63
+ }
64
+
65
+ let csn = await cds.load(['*', ...features]).then(cds.minify)
66
+
67
+ if (cds.requires.extensibility && !(cds.requires.multitenancy && tenant === BASE_TENANT))
68
+ csn = await _addExtensions(csn, tenant)
69
+
70
+ if (flavor) csn = cds.compile.for[flavor](csn)
71
+
72
+ return csn
73
+ }
74
+
75
+ const _edmx4 = async function (req) {
76
+ const csn = await _csn4(req)
77
+ const { serviceName, locale, odataFlavor } = req.data
78
+
79
+ return cds.localize(csn, locale, cds.compile.to.edmx(csn, { service: serviceName, version: odataFlavor }))
80
+ }
81
+
82
+ module.exports = function (srv) {
83
+ srv.on('getCsn', async function (req) {
84
+ return await _csn4(req)
85
+ })
86
+
87
+ // REVISIT: Replace by getCsn(..., flavor=node/java)
88
+ srv.on('getOdataCsn', async function (req) {
89
+ return await _csn4(req, 'java')
90
+ })
91
+
92
+ srv.on('getEdmx', async function (req) {
93
+ return await _edmx4(req)
94
+ })
95
+
96
+ srv.after('getEdmx', function (result, req, res) {
97
+ req._.res && req._.res.set('Content-Type', 'application/xml')
98
+ })
99
+
100
+ srv.on('isExtended', async function (req) {
101
+ return await _isExtended(req)
102
+ })
103
+
104
+ srv.on('getResources', async function (req) {
105
+ return await supplementaryFiles(req)
106
+ })
107
+
108
+ srv.after('getResources', function (_, req) {
109
+ req._.res && req._.res.set('content-type', 'application/octet-stream; charset=binary')
110
+ })
111
+ }
@@ -0,0 +1,42 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+ const cds = require('../../cds')
4
+ const { TAR_CLI, findCsvFiles, collectCsvFiles, packTarArchive } = require('../../../../lib/utils/resources')
5
+ const { exists } = require('./utils')
6
+
7
+ // REVISIT: resource folder and file to be defined
8
+ const RESOURCE_FILE = 'to_be_defined/to_be_defined.tgz'
9
+
10
+ const supplementaryFiles = async req => {
11
+ // first look for files in the resource folder
12
+ const resource = path.join(global.cds.root, RESOURCE_FILE)
13
+ if (await exists(resource)) {
14
+ return fs.promises.readFile(resource, 'binary')
15
+ }
16
+
17
+ // check if collected in sdc folder
18
+ // REVISIT: to be deleted
19
+ let files
20
+ const sdc = path.join(global.cds.root, 'sdc')
21
+ if (await exists(sdc)) {
22
+ files = await findCsvFiles(sdc, true)
23
+ }
24
+
25
+ // if not collected, then collect
26
+ if (!files || !files.length) {
27
+ const features = (cds.env.features && cds.env.features.folders) || 'fts/*'
28
+ const csn = await cds.load(['*', features])
29
+ files = await collectCsvFiles(csn.$sources)
30
+ }
31
+
32
+ if (!files.length) {
33
+ req.reject(404)
34
+ }
35
+
36
+ return await packTarArchive(files, global.cds.root, req.data.flat, TAR_CLI)
37
+ }
38
+
39
+ module.exports = {
40
+ TAR_CLI,
41
+ supplementaryFiles
42
+ }
@@ -0,0 +1,11 @@
1
+ const fs = require('fs')
2
+
3
+ const exists = async fileOrDir => {
4
+ try {
5
+ return await fs.promises.stat(fileOrDir)
6
+ } catch (_) {
7
+ return false
8
+ }
9
+ }
10
+
11
+ module.exports = { exists }
@@ -7,15 +7,27 @@ const _pick = element => {
7
7
  return element['@cds.extension']
8
8
  }
9
9
 
10
+ // The problem here are the extended columss, which are in SELECT.column, but not in the backpack.
11
+ // Otherwise all backpack values could be inserted for row at once.
10
12
  const _processorFn = ({ row, key }) => {
11
13
  if (row[EXT_BACK_PACK]) {
12
14
  const extensions = JSON.parse(row[EXT_BACK_PACK])
13
- Object.keys(extensions).forEach(field => {
14
- row[field] = extensions[field]
15
- })
16
-
17
- delete row[EXT_BACK_PACK]
15
+ if (extensions[key]) {
16
+ row[key] = extensions[key]
17
+ delete extensions[key]
18
+ if (Object.keys(extensions).length === 0) {
19
+ delete row[EXT_BACK_PACK]
20
+ } else {
21
+ row[EXT_BACK_PACK] = JSON.stringify(extensions)
22
+ }
23
+
24
+ return
25
+ }
18
26
  }
27
+
28
+ // Extended fields are not in SELECT.columns.
29
+ // Workaround for fields not from backpack: provide them all
30
+ row[key] = null
19
31
  }
20
32
 
21
33
  function transformExtendedFieldsRESULT(result, req) {
@@ -22,6 +22,7 @@ const _processorFn = ({ row, key }) => {
22
22
 
23
23
  function transformExtendedFieldsCREATE(req) {
24
24
  if (!req.target) return
25
+ if (!req.query.INSERT.entries) return // REVISIT: breaks at cds.deploy -> should anyways not kick in during cds.deploy
25
26
 
26
27
  const target = getTargetWrite(req.target, this.model)
27
28
  const template = getTemplate('transform-write', this, target, { pick: _pick })
@@ -0,0 +1,54 @@
1
+ module.exports = (async () => {
2
+ const cds = require('../../cds')
3
+ const path = require('path')
4
+
5
+ if (!cds.requires.db) return
6
+ if (cds.db) return // avoid duplicates
7
+
8
+ // do not add extensions in case of multitenancy
9
+ if (!cds.requires.multitenancy) {
10
+ // add extensions
11
+ if (cds.requires.db.credentials && cds.requires.db.credentials.database !== ':memory:') {
12
+ const db = await cds.connect.to({ ...cds.requires.db, model: null, silent: true })
13
+ const rs = await db.read('cds_r.Extensions')
14
+ if (rs.length !== 0) {
15
+ const extensions = []
16
+ rs.forEach(row => extensions.push(...JSON.parse(row.csn).extensions))
17
+ cds.once('loaded', csn => {
18
+ if (cds.model) return // extend cds.model only
19
+ const extended = cds.compile({
20
+ 'base.csn': cds.compile.to.json(csn),
21
+ 'ext.csn': cds.compile.to.json({ extensions })
22
+ })
23
+ csn.definitions = extended.definitions
24
+ })
25
+ }
26
+ await db.disconnect()
27
+ }
28
+
29
+ cds.on('connect', async srv => {
30
+ if (srv.name !== 'db') return
31
+
32
+ // deploy in case of in-memory
33
+ if (cds.requires.db.credentials && cds.requires.db.credentials.database === ':memory:') {
34
+ const mf = await cds.load(path.join(__dirname, '../../../..', 'srv/flex.cds'))
35
+ await cds.deploy(mf).to(srv)
36
+ }
37
+ })
38
+ }
39
+
40
+ cds.once('served', async () => {
41
+ const { transformExtendedFieldsCREATE, transformExtendedFieldsUPDATE } = require('./handler/transformWRITE')
42
+ const { transformExtendedFieldsREAD } = require('./handler/transformREAD')
43
+ const { transformExtendedFieldsRESULT } = require('./handler/transformRESULT')
44
+ cds.db
45
+ .before('CREATE', transformExtendedFieldsCREATE)
46
+ .before('UPDATE', transformExtendedFieldsUPDATE)
47
+ .before('READ', transformExtendedFieldsREAD)
48
+ .after('READ', transformExtendedFieldsRESULT)
49
+ if ('cds_r.ExtensibilityService' in cds.services) return
50
+ await require('../mps')
51
+ const model = require('path').join(__dirname, '../../../..', 'srv/flex.cds')
52
+ return cds.serve(model, { silent: true }).in(cds.app)
53
+ })
54
+ })()
@@ -0,0 +1,276 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+ const cds = require('../../cds')
4
+ const { ensureDraftsSuffix } = require('../../common/utils/draft')
5
+ const { BASE_TENANT, channelName } = require('../../common/utils/extensibilityUtils')
6
+ const { EXT_BACK_PACK } = require('./utils')
7
+ const TMP_DIR = fs.realpathSync(require('os').tmpdir())
8
+
9
+ const _getDraftTable = view => {
10
+ return cds.model.definitions[view]._isDraftEnabled ? ensureDraftsSuffix(view) : undefined
11
+ }
12
+
13
+ const _addAnnotation = extension => {
14
+ Object.values(extension.elements).forEach(el => {
15
+ el['@cds.extension'] = true
16
+ })
17
+ }
18
+
19
+ const _isProjection = target => target && target.query && target.query._target
20
+
21
+ const _resolveViews = (target, views_ = []) => {
22
+ if (_isProjection(target)) {
23
+ views_.push(target)
24
+ return _resolveViews(target.query._target, views_)
25
+ }
26
+
27
+ return target
28
+ }
29
+
30
+ const _getCsn = req => {
31
+ const csn = {
32
+ extensions: req.data.extensions.map(ext => JSON.parse(ext))
33
+ }
34
+
35
+ return csn
36
+ }
37
+
38
+ const _addViews = csn => {
39
+ csn.extensions.forEach(extension => {
40
+ const target = cds.model.definitions[extension.extend]
41
+ const views_ = []
42
+ const view = _resolveViews(target, views_)
43
+ extension.extend = view && view.name
44
+ _addAnnotation(extension)
45
+
46
+ // All projection views leading to the db entity are extended with back pack in case view columns are explicitly listed.
47
+ // The views using projections with '*' obtain the back pack automatically.
48
+ views_.forEach(view => {
49
+ if (!view.projection || (view.projection.columns && !view.projection.columns.some(col => col === '*'))) {
50
+ csn.extensions.push({
51
+ extend: view.name,
52
+ columns: Object.keys(extension.elements).map(key => {
53
+ return { ref: [key] }
54
+ })
55
+ })
56
+ }
57
+ })
58
+ })
59
+ }
60
+
61
+ const _needsQuotations = t => t instanceof cds.builtin.classes.string || t instanceof cds.builtin.classes.date
62
+
63
+ const _handleDefaults = async (extension, dbEntity, draftEntity) => {
64
+ const ext = Object.keys(extension.elements)
65
+ .filter(key => extension.elements[key].default)
66
+ .map(key => {
67
+ const element = extension.elements[key]
68
+ // .type as ui flex extensions are not linked
69
+ const t = cds.model.definitions[element.type] || cds.builtin.types[element.type]
70
+ const value = t && _needsQuotations(t) ? `"${element.default.val}"` : element.default.val
71
+ return `"${key}":${value}`
72
+ })
73
+
74
+ if (ext.length !== 0) {
75
+ const extStr = ext.join(',')
76
+ const changed = `'{${extStr},' || substr(${EXT_BACK_PACK}, 2, length(${EXT_BACK_PACK})-1)`
77
+ const assign = `${EXT_BACK_PACK} = CASE WHEN ${EXT_BACK_PACK} IS NULL THEN '{${extStr}}' ELSE ${changed} END`
78
+ await UPDATE(dbEntity).with(assign)
79
+ if (draftEntity) await UPDATE(draftEntity).with(assign)
80
+ }
81
+ }
82
+
83
+ const _validateCsn = (csn, req) => {
84
+ if (!csn) req.reject(400, 'Missing extension')
85
+ if (!csn.extensions) return
86
+
87
+ csn.extensions.forEach(extension => {
88
+ if (!extension.extend || !cds.model.definitions[extension.extend]) {
89
+ req.reject(400, 'Invalid extension. Parameter "extend" missing or malformed')
90
+ }
91
+
92
+ if (!extension.elements) {
93
+ req.reject(400, 'Invalid extension. Missing parameter "elements"')
94
+ }
95
+ })
96
+ }
97
+
98
+ const _validateExtensionFields = (csn, req) => {
99
+ if (!csn.extensions) return
100
+
101
+ csn.extensions.forEach(extension => {
102
+ if (extension.elements) {
103
+ Object.keys(extension.elements).forEach(name => {
104
+ if (!/^[A-Za-z]\w*$/.test(name)) {
105
+ req.reject(400, `Invalid extension. Bad element name "${name}"`)
106
+ }
107
+
108
+ if (Object.keys(cds.model.definitions[extension.extend].elements).includes(name)) {
109
+ req.reject(400, `Invalid extension. Element "${name}" already exists`)
110
+ }
111
+ })
112
+ }
113
+ })
114
+ }
115
+
116
+ const _getCompilerError = messages => {
117
+ const defaultMsg = 'Error while compiling extension'
118
+ if (!messages) return defaultMsg
119
+
120
+ for (const msg of messages) {
121
+ if (msg.severity === 'Error') return msg.message
122
+ }
123
+
124
+ return defaultMsg
125
+ }
126
+
127
+ const _validateExtension = async (ext, req) => {
128
+ try {
129
+ const { 'cds_r.ModelProviderService': mps } = cds.services
130
+ const csn = await mps.getCsn(req.tenant, ['*'])
131
+ const extCsn = cds.compile.to.json(ext)
132
+ await cds.compile.to.csn({ 'base.csn': JSON.stringify(csn), 'ext.csn': extCsn })
133
+ } catch (err) {
134
+ console.trace(err) // eslint-disable-line no-console
135
+ req.reject(400, _getCompilerError(err.messages))
136
+ }
137
+ }
138
+
139
+ const _addExtension = async function (req) {
140
+ // validate extension
141
+ const csn = _getCsn(req)
142
+ _validateCsn(csn, req)
143
+ await _validateExtensionFields(csn, req)
144
+ _addViews(csn, cds)
145
+ await _validateExtension(csn, req)
146
+
147
+ // save extension
148
+ const ID = cds.utils.uuid()
149
+ await INSERT.into('cds_r.Extensions').entries([{ ID, csn: JSON.stringify(csn), activated: false }])
150
+
151
+ // defaults
152
+ for (const ext of req.data.extensions) {
153
+ const extension = JSON.parse(ext)
154
+ const draft = _getDraftTable(extension.extend)
155
+ const target = cds.model.definitions[extension.extend]
156
+ const dbEntity = _resolveViews(target).name
157
+ await _handleDefaults(extension, dbEntity, draft)
158
+ }
159
+
160
+ // update event
161
+ await this.redis.emit(channelName(), { tenant: req.tenant || BASE_TENANT })
162
+ }
163
+
164
+ const _activate = async function (req) {
165
+ // get extensions
166
+ const rs = await cds.tx({ tenant: req.tenant }, tx => tx.run(SELECT.from('cds_r.Extensions')))
167
+ rs.forEach(row => {
168
+ row.csn = row.csn.replace(/,"@cds.extension":true/g, '')
169
+ row.activated = true
170
+ })
171
+
172
+ // activate
173
+ const { MultitenancyService: mts } = cds.services
174
+ await mts.activateExtensions(req.data.tenant)
175
+
176
+ // restore extensions
177
+ await INSERT.into('cds_r.Extensions').entries(rs)
178
+
179
+ // update event
180
+ await this.redis.emit(channelName(), { tenant: req.tenant || BASE_TENANT })
181
+ }
182
+
183
+ const _exists = async fileOrDir => {
184
+ try {
185
+ return await fs.promises.stat(fileOrDir)
186
+ } catch (_) {
187
+ return false
188
+ }
189
+ }
190
+
191
+ const _compileProject = async function (extension, req) {
192
+ let csn, root
193
+ try {
194
+ root = await fs.promises.mkdtemp(`${TMP_DIR}${path.sep}extension-`)
195
+ const files = []
196
+ for (const ext of extension) {
197
+ if (ext.filename.indexOf('/') > -1) {
198
+ await fs.promises.mkdir(path.join(root, path.dirname(ext.filename)), { recursive: true })
199
+ }
200
+ const file = path.join(root, ext.filename)
201
+ await fs.promises.writeFile(file, ext.model)
202
+ files.push(file)
203
+ }
204
+ csn = await cds.compile(files, { flavor: 'parsed' })
205
+ if (csn.requires) delete csn.requires
206
+ } catch (err) {
207
+ // req.reject(400, _getCompilerError(err.messages))
208
+ if (err.messages) req.reject(400, _getCompilerError(err.messages))
209
+ else {
210
+ // eslint-disable-next-line no-console
211
+ console.error(err)
212
+ throw err
213
+ }
214
+ } finally {
215
+ if (await _exists(root)) {
216
+ await (fs.promises.rm || fs.promises.rmdir)(root, { recursive: true, force: true })
217
+ }
218
+ }
219
+
220
+ return csn
221
+ }
222
+
223
+ const _activateProject = async function (req) {
224
+ if (req.data.extension.length === 0) return
225
+
226
+ const csn = await _compileProject(req.data.extension, req)
227
+ await _validateExtension(csn, req)
228
+
229
+ // keep only extension to be activated
230
+ const ID = cds.utils.uuid()
231
+ let activated, notActivated
232
+ await cds.tx({ tenant: req.tenant }, async tx => {
233
+ activated = await tx.run(SELECT.from('cds_r.Extensions').where({ activated: true }))
234
+ notActivated = await tx.run(SELECT.from('cds_r.Extensions').where({ activated: false }))
235
+ await tx.run(DELETE.from('cds_r.Extensions').where({ activated: false }))
236
+ await tx.run(INSERT.into('cds_r.Extensions').entries([{ ID, csn: JSON.stringify(csn), activated: false }]))
237
+ })
238
+
239
+ // activate
240
+ const { MultitenancyService: mts } = cds.services
241
+ await mts.activateExtensions(req.data.tenant)
242
+
243
+ // restore extensions
244
+ await cds.tx({ tenant: req.tenant }, async tx => {
245
+ const inserted = await tx.run(SELECT.from('cds_r.Extensions').where({ ID }))
246
+ if (!inserted.length) {
247
+ if (activated.length !== 0) await tx.run(INSERT.into('cds_r.Extensions').entries(activated))
248
+ await tx.run(INSERT.into('cds_r.Extensions').entries([{ ID, csn: JSON.stringify(csn), activated: true }]))
249
+ }
250
+ if (notActivated.length !== 0) await tx.run(INSERT.into('cds_r.Extensions').entries(notActivated))
251
+ })
252
+
253
+ // default values for not activated extensions
254
+ for (const na of notActivated) {
255
+ for (const extension of JSON.parse(na.csn).extensions) {
256
+ const target = cds.model.definitions[extension.extend]
257
+ const dbEntity = _resolveViews(target).name
258
+
259
+ // only db entities
260
+ if (target.name !== dbEntity) continue
261
+
262
+ const draft = _getDraftTable(extension.extend)
263
+ await _handleDefaults(extension, dbEntity, draft)
264
+ }
265
+ }
266
+
267
+ // update event
268
+ await this.redis.emit(channelName(), { tenant: req.tenant || BASE_TENANT })
269
+ }
270
+
271
+ module.exports = async function () {
272
+ this.redis = await cds.connect.to('mtx-messaging')
273
+ this.on('addExtension', _addExtension)
274
+ this.on('activate', _activate)
275
+ this.on('activateProject', _activateProject)
276
+ }
@@ -24,8 +24,9 @@ const getTargetWrite = (target, model) => {
24
24
  }
25
25
 
26
26
  const isExtendedEntity = (entityName, model) => {
27
- // REVISIT: Dass alle unsere und auch custom handlers immer die ensureUnlocalized + ensureNoDraftsSuffix schleife drehen müssen, kann nicht sein
28
27
  const entity = model.definitions[ensureUnlocalized(ensureNoDraftsSuffix(entityName))]
28
+ if (!entity) return false
29
+
29
30
  return entity.elements[EXT_BACK_PACK] || Object.values(entity.elements).some(el => el['@cds.extension'])
30
31
  }
31
32
 
@@ -41,19 +42,33 @@ const _hasExtendedEntityArgs = (args, model) => {
41
42
  })
42
43
  }
43
44
 
45
+ const _hasExtendedExpand = (columns, targetName, model) => {
46
+ for (const col of columns) {
47
+ if (col.ref && col.expand) {
48
+ const targetNameModel = ensureUnlocalized(ensureNoDraftsSuffix(targetName))
49
+ const expTargetName = model.definitions[targetNameModel].elements[col.ref[0]].target
50
+ if (isExtendedEntity(expTargetName, model)) return true
51
+ _hasExtendedExpand(col.expand, expTargetName, model)
52
+ }
53
+ }
54
+ }
55
+
44
56
  const hasExtendedEntity = (req, model) => {
45
57
  if (!req.query.SELECT) return false
46
58
 
59
+ if (req.query.SELECT.columns && req.target && _hasExtendedExpand(req.query.SELECT.columns, req.target.name, model)) {
60
+ return true
61
+ }
62
+
47
63
  if (req.query.SELECT.from.join) {
48
- // join
49
64
  return _hasExtendedEntityArgs(req.query.SELECT.from.args, model)
50
- } else if (req.target.name.SET) {
51
- // union
65
+ }
66
+
67
+ if (req.target.name.SET) {
52
68
  return isExtendedEntity(req.target.name.SET.args[0]._target.name, model)
53
- } else {
54
- // simple select
55
- return isExtendedEntity(req.target.name, model)
56
69
  }
70
+
71
+ return isExtendedEntity(req.target.name, model)
57
72
  }
58
73
 
59
74
  const getExtendedFields = (entityName, model) => {
@@ -12,7 +12,7 @@ const { readAndDeleteKeywords, isActiveEntityRequested, getKeyData } = require('
12
12
  const { isDraftRootEntity } = require('../../fiori/utils/csn')
13
13
  const { getColumns } = require('../../cds-services/services/utils/columns')
14
14
 
15
- const { DRAFT_COLUMNS } = require('../../common/constants/draft')
15
+ const { DRAFT_COLUMNS_MAP } = require('../../common/constants/draft')
16
16
 
17
17
  const _getRootCQN = (context, requestActiveData) => {
18
18
  const keys = filterKeys(context.target.keys)
@@ -30,7 +30,7 @@ const _getExpandSubCqn = (model, parentEntityName, targets, isRoot = true) => {
30
30
 
31
31
  for (const element of Object.values(parentEntity.elements)) {
32
32
  const { name, target, cardinality } = element
33
- if (DRAFT_COLUMNS.includes(name)) {
33
+ if (name in DRAFT_COLUMNS_MAP) {
34
34
  continue
35
35
  }
36
36