@sap/cds 5.8.2 → 5.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) hide show
  1. package/CHANGELOG.md +214 -78
  2. package/app/fiori/preview.js +16 -11
  3. package/app/fiori/routes.js +3 -0
  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 +20 -17
  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 +12 -5
  22. package/bin/utils/modules.js +7 -0
  23. package/bin/version.js +56 -3
  24. package/lib/compile/cdsc.js +26 -3
  25. package/lib/compile/etc/_localized.js +36 -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/for/odata.js +1 -1
  31. package/lib/compile/index.js +3 -0
  32. package/lib/compile/minify.js +16 -2
  33. package/lib/compile/parse.js +2 -2
  34. package/lib/compile/resolve.js +35 -18
  35. package/lib/compile/to/json.js +3 -1
  36. package/lib/compile/to/sql.js +2 -2
  37. package/lib/compile/to/srvinfo.js +4 -2
  38. package/lib/connect/index.js +1 -1
  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 +120 -49
  47. package/lib/index.js +1 -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 +7 -5
  62. package/lib/serve/index.js +24 -12
  63. package/lib/utils/data.js +1 -1
  64. package/lib/utils/index.js +27 -30
  65. package/lib/utils/resources/index.js +101 -0
  66. package/lib/utils/resources/tar.js +71 -0
  67. package/lib/utils/resources/utils.js +11 -0
  68. package/libx/_runtime/audit/Service.js +36 -39
  69. package/libx/_runtime/audit/generic/personal/access.js +3 -4
  70. package/libx/_runtime/audit/generic/personal/modification.js +3 -4
  71. package/libx/_runtime/audit/utils/v2.js +1 -2
  72. package/libx/_runtime/auth/index.js +126 -84
  73. package/libx/_runtime/auth/strategies/JWT.js +12 -19
  74. package/libx/_runtime/auth/strategies/dummy.js +1 -5
  75. package/libx/_runtime/auth/strategies/dwc.js +11 -9
  76. package/libx/_runtime/auth/strategies/mock.js +0 -4
  77. package/libx/_runtime/auth/strategies/{utils/xssec.js → xssecUtils.js} +7 -4
  78. package/libx/_runtime/auth/strategies/xsuaa.js +12 -19
  79. package/libx/_runtime/auth/utils.js +22 -1
  80. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +104 -98
  81. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +2 -2
  82. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -1
  83. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +13 -0
  84. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/language.js +2 -8
  85. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +4 -29
  86. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +2 -1
  87. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +3 -2
  88. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +2 -2
  89. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +4 -6
  90. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +24 -21
  91. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +8 -2
  92. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriHelper.js +1 -1
  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/deserializer/ResourceJsonDeserializer.js +5 -6
  95. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/DispatcherCommand.js +2 -6
  96. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/UriHelper.js +4 -1
  97. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -12
  98. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +33 -9
  99. package/libx/_runtime/cds-services/adapter/odata-v4/utils/dispatcherUtils.js +50 -0
  100. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +2 -2
  101. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +10 -3
  102. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +9 -11
  103. package/libx/_runtime/cds-services/adapter/rest/RestRequest.js +6 -3
  104. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +4 -2
  105. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/utils.js +1 -1
  106. package/libx/_runtime/cds-services/adapter/rest/utils/binary.js +1 -1
  107. package/libx/_runtime/cds-services/adapter/rest/utils/key-value-utils.js +2 -3
  108. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +6 -4
  109. package/libx/_runtime/cds-services/adapter/rest/utils/result.js +1 -0
  110. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +8 -5
  111. package/libx/_runtime/cds-services/services/Service.js +40 -0
  112. package/libx/_runtime/cds-services/services/utils/columns.js +4 -3
  113. package/libx/_runtime/cds-services/services/utils/compareJson.js +4 -4
  114. package/libx/_runtime/cds-services/services/utils/differ.js +3 -3
  115. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +4 -4
  116. package/libx/_runtime/cds-services/services/utils/restrictions.js +78 -0
  117. package/libx/_runtime/cds-services/util/assert.js +20 -14
  118. package/libx/_runtime/cds.js +9 -1
  119. package/libx/_runtime/common/aspects/any.js +5 -0
  120. package/libx/_runtime/common/aspects/entity.js +25 -7
  121. package/libx/_runtime/common/aspects/utils.js +2 -2
  122. package/libx/_runtime/common/composition/data.js +6 -0
  123. package/libx/_runtime/common/composition/insert.js +3 -2
  124. package/libx/_runtime/common/composition/tree.js +4 -10
  125. package/libx/_runtime/common/composition/update.js +4 -4
  126. package/libx/_runtime/common/constants/draft.js +29 -26
  127. package/libx/_runtime/common/error/constants.js +2 -2
  128. package/libx/_runtime/common/error/frontend.js +7 -15
  129. package/libx/_runtime/common/generic/auth/capabilities.js +59 -0
  130. package/libx/_runtime/common/generic/auth/constants.js +20 -0
  131. package/libx/_runtime/common/generic/auth/expand.js +54 -0
  132. package/libx/_runtime/common/generic/auth/index.js +32 -0
  133. package/libx/_runtime/common/generic/auth/insertOnly.js +15 -0
  134. package/libx/_runtime/common/generic/auth/readOnly.js +26 -0
  135. package/libx/_runtime/common/generic/auth/requires.js +34 -0
  136. package/libx/_runtime/common/generic/auth/restrict.js +296 -0
  137. package/libx/_runtime/common/generic/auth/utils.js +213 -0
  138. package/libx/_runtime/common/generic/crud.js +14 -10
  139. package/libx/_runtime/common/generic/etag.js +1 -1
  140. package/libx/_runtime/common/generic/input.js +35 -35
  141. package/libx/_runtime/common/generic/sorting.js +2 -3
  142. package/libx/_runtime/common/generic/temporal.js +2 -2
  143. package/libx/_runtime/common/i18n/index.js +2 -31
  144. package/libx/_runtime/common/i18n/messages.properties +1 -1
  145. package/libx/_runtime/common/toggles/handler.js +21 -0
  146. package/libx/_runtime/common/utils/copy.js +10 -1
  147. package/libx/_runtime/common/utils/cqn2cqn4sql.js +100 -29
  148. package/libx/_runtime/common/utils/csn.js +63 -1
  149. package/libx/_runtime/common/utils/dollar.js +10 -1
  150. package/libx/_runtime/common/utils/draft.js +46 -7
  151. package/libx/_runtime/common/utils/entityFromCqn.js +13 -9
  152. package/libx/_runtime/common/utils/extensibilityUtils.js +18 -0
  153. package/libx/_runtime/common/utils/foreignKeyPropagations.js +88 -104
  154. package/libx/_runtime/common/utils/generateOnCond.js +9 -6
  155. package/libx/_runtime/common/utils/quotingStyles.js +2 -0
  156. package/libx/_runtime/common/utils/resolveStructured.js +25 -9
  157. package/libx/_runtime/common/utils/resolveView.js +4 -1
  158. package/libx/_runtime/common/utils/rewriteAsterisks.js +3 -16
  159. package/libx/_runtime/common/utils/structured.js +33 -37
  160. package/libx/_runtime/common/utils/template.js +17 -8
  161. package/libx/_runtime/common/utils/templateProcessor.js +28 -28
  162. package/libx/_runtime/db/data-conversion/post-processing.js +118 -417
  163. package/libx/_runtime/db/expand/expandCQNToJoin.js +45 -41
  164. package/libx/_runtime/db/expand/rawToExpanded.js +29 -8
  165. package/libx/_runtime/db/generic/index.js +1 -3
  166. package/libx/_runtime/db/generic/input.js +5 -10
  167. package/libx/_runtime/db/generic/rewrite.js +5 -2
  168. package/libx/_runtime/db/generic/structured.js +2 -2
  169. package/libx/_runtime/db/query/delete.js +2 -2
  170. package/libx/_runtime/db/query/insert.js +1 -1
  171. package/libx/_runtime/db/query/update.js +9 -14
  172. package/libx/_runtime/db/sql-builder/CreateBuilder.js +4 -3
  173. package/libx/_runtime/db/sql-builder/InsertBuilder.js +14 -1
  174. package/libx/_runtime/db/sql-builder/SelectBuilder.js +3 -2
  175. package/libx/_runtime/db/sql-builder/dataTypes.js +3 -3
  176. package/libx/_runtime/db/utils/columns.js +3 -3
  177. package/libx/_runtime/db/utils/normalizeTimeData.js +2 -2
  178. package/libx/_runtime/db/utils/propagateForeignKeys.js +6 -2
  179. package/libx/_runtime/extensibility/mps/index.js +5 -0
  180. package/libx/_runtime/extensibility/mps/service.js +111 -0
  181. package/libx/_runtime/extensibility/mps/tar.js +42 -0
  182. package/libx/_runtime/extensibility/mps/utils.js +11 -0
  183. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformREAD.js +0 -0
  184. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformRESULT.js +17 -5
  185. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformWRITE.js +1 -0
  186. package/libx/_runtime/extensibility/uiflex/index.js +54 -0
  187. package/libx/_runtime/extensibility/uiflex/service.js +276 -0
  188. package/libx/_runtime/{fiori → extensibility}/uiflex/utils.js +22 -7
  189. package/libx/_runtime/fiori/generic/activate.js +2 -2
  190. package/libx/_runtime/fiori/generic/before.js +4 -4
  191. package/libx/_runtime/fiori/generic/new.js +3 -3
  192. package/libx/_runtime/fiori/generic/patch.js +1 -1
  193. package/libx/_runtime/fiori/generic/read.js +58 -66
  194. package/libx/_runtime/fiori/generic/readOverDraft.js +71 -16
  195. package/libx/_runtime/fiori/utils/handler.js +6 -13
  196. package/libx/_runtime/fiori/utils/where.js +6 -5
  197. package/libx/_runtime/hana/Service.js +4 -10
  198. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +2 -2
  199. package/libx/_runtime/hana/driver.js +2 -2
  200. package/libx/_runtime/hana/execute.js +29 -75
  201. package/libx/_runtime/hana/pool.js +1 -1
  202. package/libx/_runtime/hana/streaming.js +2 -1
  203. package/libx/_runtime/index.js +6 -6
  204. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +5 -21
  205. package/libx/_runtime/messaging/Outbox.js +2 -2
  206. package/libx/_runtime/messaging/common-utils/AMQPClient.js +4 -14
  207. package/libx/_runtime/messaging/common-utils/connections.js +5 -7
  208. package/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +30 -0
  209. package/libx/_runtime/messaging/enterprise-messaging-shared.js +2 -1
  210. package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +36 -30
  211. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +19 -12
  212. package/libx/_runtime/messaging/enterprise-messaging.js +8 -8
  213. package/libx/_runtime/messaging/file-based.js +5 -5
  214. package/libx/_runtime/messaging/message-queuing.js +14 -12
  215. package/libx/_runtime/messaging/outbox/utils.js +18 -19
  216. package/libx/_runtime/messaging/redis-messaging.js +91 -0
  217. package/libx/_runtime/messaging/service.js +8 -6
  218. package/libx/_runtime/remote/Service.js +44 -8
  219. package/libx/_runtime/remote/utils/client.js +25 -13
  220. package/libx/_runtime/remote/utils/data.js +11 -11
  221. package/libx/_runtime/sqlite/Service.js +6 -9
  222. package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +5 -2
  223. package/libx/_runtime/types/api.js +10 -2
  224. package/libx/common/utils/ucsn.js +109 -0
  225. package/libx/gql/resolvers/crud/create.js +6 -1
  226. package/libx/gql/resolvers/crud/delete.js +6 -1
  227. package/libx/gql/resolvers/crud/read.js +6 -1
  228. package/libx/gql/resolvers/crud/update.js +9 -1
  229. package/libx/gql/resolvers/parse/ast2cqn/columns.js +3 -1
  230. package/libx/gql/schema/typeDefMap.js +2 -2
  231. package/libx/odata/afterburner.js +110 -16
  232. package/libx/odata/grammar.pegjs +9 -1
  233. package/libx/odata/parseToCqn.js +39 -0
  234. package/libx/odata/parser.js +1 -1
  235. package/libx/rest/RestAdapter.js +9 -1
  236. package/libx/rest/middleware/input.js +54 -0
  237. package/libx/rest/middleware/operation.js +14 -1
  238. package/libx/rest/middleware/parse.js +11 -7
  239. package/package.json +1 -1
  240. package/server.js +34 -19
  241. package/srv/audit-log.cds +2 -2
  242. package/srv/flex.cds +8 -2
  243. package/srv/flex.js +1 -1
  244. package/srv/mps.cds +23 -0
  245. package/srv/mps.js +1 -0
  246. package/libx/_runtime/auth/strategies/utils/uaa.js +0 -21
  247. package/libx/_runtime/common/generic/auth.js +0 -874
  248. package/libx/_runtime/common/toggles/alpha.js +0 -43
  249. package/libx/_runtime/db/generic/arrayed.js +0 -33
  250. package/libx/_runtime/fiori/uiflex/index.js +0 -35
  251. package/libx/_runtime/fiori/uiflex/service.js +0 -150
  252. package/libx/rest/utils/data.js +0 -60
@@ -1,50 +1,7 @@
1
- const cds = require('../../cds')
2
-
3
- const { ensureUnlocalized, ensureNoDraftsSuffix } = require('../../common/utils/draft')
4
-
5
- /**
6
- * Check if the value is a function or reference to private function.
7
- * Return if any of the cases are true.
8
- *
9
- * @param {string|Function} fn -
10
- * @returns {Function}
11
- * @throws Error if no valid parameter fn provided
12
- * @private
13
- */
14
- const _getMethod = fn => {
15
- if (typeof fn === 'function') {
16
- return fn
17
- }
18
-
19
- throw new Error(`Method "${fn}" does not exist.`)
20
- }
21
-
22
- /**
23
- * Get the entity name from CQN and check if it exists at CSN.
24
- *
25
- * @param {object} csn - Reflected CSN
26
- * @param {string} from - Entity name
27
- * @returns {string|undefined}
28
- * @private
29
- */
30
- const _getEntityName = (csn, from) => {
31
- if (!from.ref || typeof csn !== 'object') {
32
- return
33
- }
34
-
35
- let entityName = from.ref[0].id ? from.ref[0].id : from.ref[0]
36
-
37
- if (csn.definitions && csn.definitions[entityName]) {
38
- return entityName
39
- }
40
-
41
- // REVISIT: Find better solution
42
- entityName = entityName.replace(/_drafts$/i, '')
43
-
44
- if (csn.definitions && csn.definitions[entityName]) {
45
- return entityName
46
- }
47
- }
1
+ const { getEntityNameFromCQN, traverseFroms } = require('../../common/utils/entityFromCqn')
2
+ const { ensureNoDraftsSuffix } = require('../../common/utils/draft')
3
+ const { proxifyIfFlattened } = require('../../../common/utils/ucsn')
4
+ const cds = require('../../../../lib')
48
5
 
49
6
  const _refs = (refs, as) => {
50
7
  const arr = []
@@ -90,114 +47,135 @@ const _extractRefs = (from, as) => {
90
47
  return [ref]
91
48
  }
92
49
 
93
- // REVISIT: check why we need includeAlias for some draft cases (check AFC tests)
94
- const _addMapperFunction = (elements, toService, key, type, alias, includeAlias) => {
95
- if (!toService.has(type)) {
96
- return
97
- }
98
-
99
- const convertFunction = _getMethod(toService.get(type))
100
-
101
- // ambiguous cases will lead to SQL syntax errors anyway, so no need for a check
102
- elements.set(key, convertFunction)
103
- if (includeAlias && alias) {
104
- elements.set(`${alias}_${key}`, convertFunction)
50
+ const _getCastFunction = ({ type }) => {
51
+ switch (type) {
52
+ case 'cds.Boolean':
53
+ return Boolean
54
+ case 'cds.Integer':
55
+ return Number
56
+ default:
57
+ return String
105
58
  }
106
59
  }
107
60
 
108
- const _filterUnique = (value, index, arr) => {
109
- return arr.indexOf(value) === index
110
- }
111
-
112
- const _addMapperFunction4struct = (elements, toService, parent, struct, alias, includeAlias) => {
113
- for (const k in struct.elements) {
114
- const current = struct.elements[k]
115
- if (current._isStructured) {
116
- _addMapperFunction4struct(elements, toService, `${parent}_${k}`, current, alias, includeAlias)
117
- } else {
118
- _addMapperFunction(elements, toService, `${parent}_${k}`, current.type, alias, includeAlias)
119
- }
61
+ const _structElement = (definition, structPath) => {
62
+ if (definition.elements) {
63
+ return _structElement(definition.elements[structPath[0]], structPath.slice(1))
120
64
  }
121
- }
122
65
 
123
- /**
124
- * Get a map of all possible elements and their mappers.
125
- *
126
- * @param {Map} toService - Mapping instructions for data conversions based on CDS data types
127
- * @param {object} csn - Reflected CSN
128
- * @param {object} cqn - CQN that is used to query the DB.
129
- * @returns {Map<any, any>}
130
- * @private
131
- */
132
- const _getElementCombinations = (toService, csn, cqn, includeAlias = false) => {
133
- const elements = new Map()
66
+ return definition
67
+ }
134
68
 
135
- // Get list of available and relevant entities as defined by from
136
- for (const from of _extractRefs(cqn.SELECT.from).filter(_filterUnique)) {
137
- const entityName = _getEntityName(csn, from)
69
+ const _getNestedElement = (entity, key) => {
70
+ if (entity.elements[key]) return entity.elements[key]
138
71
 
139
- if (!entityName) {
140
- continue
141
- }
72
+ const structPath = entity._flat2struct[key]
73
+ if (!structPath) return
142
74
 
143
- const entity = csn.definitions[ensureUnlocalized(entityName)]
144
- for (const key in entity.elements) {
145
- const element = entity.elements[key]
146
- if (element._isStructured) {
147
- _addMapperFunction4struct(elements, toService, key, element, from.as, includeAlias)
148
- } else {
149
- if ('SET' in cqn.SELECT.from) {
150
- _addMapperFunction(elements, toService, key, element.type, cqn.SELECT.from.as, includeAlias)
151
- } else {
152
- _addMapperFunction(elements, toService, key, element.type, from.as, includeAlias)
153
- }
154
- }
155
- }
156
- }
75
+ return _structElement(entity, structPath)
76
+ }
157
77
 
158
- return elements
78
+ const _structure = csnEntity => (value, key, row, unaliasedKey) => {
79
+ proxifyIfFlattened(csnEntity, row)
80
+ const effectiveKey = unaliasedKey || key
81
+ delete row[effectiveKey]
82
+ row[effectiveKey] = value
159
83
  }
160
84
 
161
- const _getCastFunction = ({ type }) => {
162
- switch (type) {
163
- case 'cds.Boolean':
164
- return Boolean
165
- case 'cds.Integer':
166
- return Number
167
- default:
168
- return String
85
+ const _addConverter = (mapper, name, converter) => {
86
+ if (mapper.has(name)) {
87
+ const oldConverter = mapper.get(name)
88
+ mapper.set(name, (val, key, row, unaliasedKey) => {
89
+ oldConverter(val, key, row, unaliasedKey)
90
+ converter(row[unaliasedKey || key], key, row, unaliasedKey)
91
+ })
92
+ } else {
93
+ mapper.set(name, converter)
169
94
  }
170
95
  }
171
96
 
172
97
  /**
173
98
  * Get a map of to be converted elements and their conversion functions.
174
99
  *
175
- * @param {Map} toService - Mapping instructions for data conversions based on CDS data types
100
+ * @param {Map} conversionMap - Mapping instructions for data conversions based on CDS data types
176
101
  * @param {object} csn - Reflected CSN
177
102
  * @param {object} cqn - CQN that is used to query the DB.
178
103
  * @returns {Map<any, any>}
179
104
  * @private
180
105
  */
181
- const _getMapperForListedElements = (toService, csn, cqn) => {
182
- const elements = _getElementCombinations(toService, csn, cqn, true)
106
+ // eslint-disable-next-line complexity
107
+ const _getMapperForListedElements = (conversionMap, csn, cqn) => {
183
108
  const mapper = new Map()
184
109
 
185
- for (const element of cqn.SELECT.columns) {
186
- if (element.ref) {
187
- const identifier = element.ref.length === 1 ? element.ref[0] : undefined
188
- const name = element.as ? element.as : identifier
110
+ const alias2entity = {}
111
+ if (cqn.SELECT.from.args) {
112
+ for (const each of cqn.SELECT.from.args) {
113
+ const { entityName, alias } = getEntityNameFromCQN(each)
114
+ let as = each.as || alias
115
+ if (as) as = ensureNoDraftsSuffix(as) // > in comp2one, the alias gets suffixed with "_drafts"
116
+ alias2entity[as] =
117
+ csn.definitions[ensureNoDraftsSuffix(entityName)] || csn.definitions[entityName.replace(/\.drafts$/, '')]
118
+ }
119
+ // additionally, for deeply nested args, find pairs of single-element ref + as and build alias to entity mapping
120
+ const ref2as = []
121
+ traverseFroms(cqn.SELECT.from, from => ref2as.push({ ref: from.ref, as: from.as }))
122
+ for (const each of ref2as) {
123
+ alias2entity[each.as] =
124
+ csn.definitions[ensureNoDraftsSuffix(each.ref[0])] || csn.definitions[each.ref[0].replace(/\.drafts$/, '')]
125
+ }
126
+ } else {
127
+ const { entityName } = getEntityNameFromCQN(cqn)
128
+ alias2entity[0] = csn.definitions[ensureNoDraftsSuffix(entityName)]
129
+ }
130
+
131
+ for (const col of cqn.SELECT.columns) {
132
+ if (col.cast) {
133
+ const name = col.as ? col.as : col.ref[col.ref.length - 1]
134
+ _addConverter(mapper, name, (val, key, row, unaliasedKey) => {
135
+ row[unaliasedKey || key] = _getCastFunction(col.cast)(val)
136
+ })
137
+ continue
138
+ }
139
+ if (col.ref) {
140
+ const name = col.ref[col.ref.length - 1]
141
+
142
+ const entity =
143
+ col.ref.length > 1
144
+ ? alias2entity[col.ref[0]] || alias2entity[0]
145
+ : alias2entity[0] || alias2entity[getEntityNameFromCQN(cqn).alias]
146
+ if (!entity) {
147
+ // REVISIT: should not be necessary, but HasActiveEntity is not always prefixed with alias
148
+ if (col.ref.length === 1 && col.ref[0] === 'HasActiveEntity')
149
+ _addConverter(mapper, col.as ? col.as : name, (val, key, row, unaliasedKey) => {
150
+ row[unaliasedKey || key] = conversionMap.get('cds.Boolean')(val)
151
+ })
152
+ continue
153
+ }
154
+
155
+ const element = _getNestedElement(entity, name)
156
+ if (!element) continue
157
+
158
+ // REVISIT: _type is undefined for cds.hana.CLOB etc.
159
+ const type = element._type || element.type
160
+ if (conversionMap.has(type)) {
161
+ _addConverter(mapper, col.as ? col.as : name, (val, key, row, unaliasedKey) => {
162
+ row[unaliasedKey || key] = conversionMap.get(type)(val)
163
+ })
164
+ } else if (element.items) {
165
+ _addConverter(mapper, col.as ? col.as : name, (val, key, row, unaliasedKey) => {
166
+ const effectiveKey = unaliasedKey || key
167
+ row[effectiveKey] = JSON.parse(val)
168
+ }) // > arrayed elements
169
+ }
189
170
 
190
- if (element.cast) {
191
- mapper.set(name, _getCastFunction(element.cast))
192
- } else if (elements.has(name)) {
193
- mapper.set(name, elements.get(name))
194
- } else if (elements.has(identifier)) {
195
- mapper.set(name, elements.get(identifier))
196
- } else if (elements.has(element.ref.join('_'))) {
197
- mapper.set(name || element.ref[element.ref.length - 1], elements.get(element.ref.join('_')))
171
+ if (
172
+ cds.env.features.ucsn_struct_conversion &&
173
+ element.parent &&
174
+ element.parent._isStructured &&
175
+ !element._isStructured
176
+ ) {
177
+ _addConverter(mapper, col.as ? col.as : name, _structure(entity))
198
178
  }
199
- } else if (element.as && element.cast) {
200
- mapper.set(element.as, _getCastFunction(element.cast))
201
179
  }
202
180
  }
203
181
 
@@ -207,143 +185,20 @@ const _getMapperForListedElements = (toService, csn, cqn) => {
207
185
  /**
208
186
  * Based on CSN and CQN get a map on how to map the result.
209
187
  *
210
- * @param {Map} toService - Mapping instructions for data conversions based on CDS data types
188
+ * @param {Map} conversionMap - Mapping instructions for data conversions based on CDS data types
211
189
  * @param {object} csn - Reflected CSN
212
190
  * @param {object} cqn - CQN that is used to query the DB.
213
191
  * @returns {Map<any, any>}
214
192
  * @private
215
193
  */
216
- const getPostProcessMapper = (toService, csn = {}, cqn = {}) => {
217
- // No mapper defined or irrelevant as no READ request
218
- if (!Object.prototype.hasOwnProperty.call(cqn, 'SELECT')) {
219
- return new Map()
220
- }
221
-
222
- if (Array.isArray(cqn.SELECT.columns) && cqn.SELECT.columns.length !== 0 && !cqn.SELECT.columns.includes('*')) {
223
- return _getMapperForListedElements(toService, csn, cqn)
224
- }
225
-
226
- // No element/column specified
227
- return _getElementCombinations(toService, csn, cqn)
228
- }
229
-
230
- const _getCombineStructureConvert = (structure, columnName, propName, fn) => {
231
- const length = structure.length
232
-
233
- return row => {
234
- if (row[columnName] === undefined) {
235
- return
236
- }
237
-
238
- if (!row[structure[0]]) {
239
- row[structure[0]] = {}
240
- }
241
-
242
- let subObj = row[structure[0]]
243
-
244
- for (let i = 1; i < length; i++) {
245
- subObj = subObj[structure[i]] = {}
246
- }
247
-
248
- subObj[propName] = fn ? fn(row[columnName]) : row[columnName]
249
-
250
- delete row[columnName]
251
- }
252
- }
253
-
254
- const _getCombineRenameConvert = (columnName, propName, fn) => {
255
- return row => {
256
- if (row[columnName] === undefined) {
257
- return
258
- }
259
-
260
- row[propName] = fn ? fn(row[columnName]) : row[columnName]
261
- delete row[columnName]
262
- }
194
+ const getPostProcessMapper = (conversionMap, csn = {}, cqn = {}) => {
195
+ return cqn.SELECT.columns ? _getMapperForListedElements(conversionMap, csn, cqn) : new Map()
263
196
  }
264
197
 
265
- const _getConvert = (columnName, fn) => {
266
- return row => {
267
- row[columnName] = fn(row[columnName])
268
- }
269
- }
270
-
271
- const _getRemoveMapper = (mapper, propName) => {
272
- if (mapper) {
273
- const fn = mapper.get(propName)
274
- mapper.delete(propName)
275
-
276
- return fn
277
- }
278
- }
279
-
280
- const _propertyMapper = (dataMapper, propertyMapper, objStructMapper, mapper) => {
281
- if (!propertyMapper) {
282
- return
283
- }
284
-
285
- for (const [columnName, propName] of propertyMapper.entries()) {
286
- const fn = _getRemoveMapper(dataMapper, propName)
287
- const structure = _getRemoveMapper(objStructMapper, propName)
288
-
289
- mapper.push(
290
- structure
291
- ? _getCombineStructureConvert(structure, columnName, propName, fn)
292
- : _getCombineRenameConvert(columnName, propName, fn)
293
- )
294
- }
295
- }
296
-
297
- const _objStructMapper = (dataMapper, propertyMapper, objStructMapper, mapper) => {
298
- if (!objStructMapper) {
299
- return
300
- }
301
-
302
- for (const [propName, structure] of objStructMapper.entries()) {
303
- mapper.push(_getCombineStructureConvert(structure, propName, propName, _getRemoveMapper(dataMapper, propName)))
304
- }
305
- }
306
-
307
- const _dataMapper = (dataMapper, propertyMapper, objStructMapper, mapper) => {
308
- if (!dataMapper) {
309
- return
310
- }
311
-
312
- for (const [columnName, converter] of dataMapper.entries()) {
313
- mapper.push(_getConvert(columnName, converter))
314
- }
315
- }
316
-
317
- /**
318
- * Generate the mapper per row up front, so that we do not have to iterate over possibly three mappers
319
- *
320
- * @param dataMapper
321
- * @param propertyMapper
322
- * @param objStructMapper
323
- * @returns {Array}
324
- * @private
325
- */
326
- const _combineMappers = (dataMapper, propertyMapper, objStructMapper) => {
327
- const mapper = []
328
-
329
- // Technical names + optionally structure and/or type conversions
330
- _propertyMapper(dataMapper, propertyMapper, objStructMapper, mapper)
331
-
332
- // Deep structures + optionally type conversions
333
- _objStructMapper(dataMapper, propertyMapper, objStructMapper, mapper)
334
-
335
- // type conversion
336
- _dataMapper(dataMapper, propertyMapper, objStructMapper, mapper)
337
-
338
- return mapper
339
- }
340
-
341
- const _processRow = (mapper, mapperCount, row) => {
342
- // REVISIT: when is this the case?
198
+ const _processRow = (mapper, row) => {
343
199
  if (!row) return
344
-
345
- for (let i = 0; i < mapperCount; i++) {
346
- mapper[i](row)
200
+ for (const [columnName, converter] of mapper.entries()) {
201
+ converter(row[columnName], columnName, row)
347
202
  }
348
203
  }
349
204
 
@@ -351,179 +206,25 @@ const _processRow = (mapper, mapperCount, row) => {
351
206
  * Post process the result as given by the db driver.
352
207
  *
353
208
  * @param {*} result - The result as returned by the db driver.
354
- * @param {Map} dataMapper - Instructions, how to transform.
209
+ * @param {Map} mapper - Instructions, how to transform.
355
210
  * @param {Map} propertyMapper - Instructions, how to rename properties.
356
211
  * @param {Map} objStructMapper - Instructions, how to rename properties.
357
212
  * @returns {*}
358
213
  * @private
359
214
  */
360
- const postProcess = (result, dataMapper, propertyMapper, objStructMapper) => {
361
- const mapper = _combineMappers(dataMapper, propertyMapper, objStructMapper)
362
- const mapperCount = mapper.length
363
-
364
- if (mapperCount === 0) {
365
- return result
366
- }
215
+ const postProcess = (result, mapper = new Map()) => {
216
+ if (mapper.size === 0) return result
367
217
 
368
- if (Array.isArray(result)) {
369
- for (let i = 0, length = result.length; i < length; i++) {
370
- _processRow(mapper, mapperCount, result[i])
371
- }
372
- } else {
373
- _processRow(mapper, mapperCount, result)
374
- }
218
+ if (Array.isArray(result)) for (const each of result) _processRow(mapper, each)
219
+ else _processRow(mapper, result)
375
220
 
376
221
  return result
377
222
  }
378
223
 
379
- const _checkExpressionsAmbiguousNaming = (csn, entity, element) => {
380
- if (!entity.as || !Array.isArray(element.ref)) {
381
- return
382
- }
383
-
384
- if (
385
- entity.as === element.ref[0] &&
386
- csn.definitions[entity.ref[0]] &&
387
- csn.definitions[entity.ref[0]].elements[element.ref[1]] &&
388
- csn.definitions[entity.ref[0]].elements[element.ref[0]] &&
389
- csn.definitions[entity.ref[0]].elements[element.ref[0]].isAssociation
390
- ) {
391
- throw new Error(`Ambiguous entity property and alias name: "${entity.as}"`)
392
- }
393
- }
394
-
395
- const _checkColumnsAmbiguousNaming = (csn, entity, columns) => {
396
- for (const element of columns) {
397
- _checkExpressionsAmbiguousNaming(csn, entity, element)
398
- }
399
- }
400
-
401
- const _checkJoinAmbiguousNaming = (csn, select) => {
402
- for (const subSelect of select.from.args) {
403
- if (Array.isArray(select.columns)) {
404
- _checkColumnsAmbiguousNaming(csn, subSelect, select.columns)
405
- }
406
- }
407
- }
408
-
409
- const _checkSelectAmbiguousNaming = (csn, select) => {
410
- if (Array.isArray(select.columns)) {
411
- _checkColumnsAmbiguousNaming(csn, select.from, select.columns)
412
- }
413
- }
414
-
415
- const _checkRecursiveSelectAmbiguousNaming = (csn, select) => {
416
- if (select.from.SELECT) {
417
- _checkRecursiveSelectAmbiguousNaming(csn, select.from.SELECT)
418
- } else if (select.from.join) {
419
- _checkJoinAmbiguousNaming(csn, select)
420
- } else if (select.from.as) {
421
- // Check innermost select statement
422
- _checkSelectAmbiguousNaming(csn, select)
423
- }
424
- }
425
-
426
- const getStructMapper = (csn, cqn, propertyMapper) => {
427
- if (csn && cqn.SELECT) {
428
- _checkRecursiveSelectAmbiguousNaming(csn, cqn.SELECT)
429
- }
430
-
431
- // REVISIT still needed?
432
-
433
- return new Map()
434
- }
435
-
436
- const _addToMap = (map, key) => {
437
- const allUpperKey = key.toUpperCase()
438
-
439
- if (key === allUpperKey) {
440
- return
441
- }
442
-
443
- map.set(allUpperKey, key)
444
- }
445
-
446
- const _addColumn = (map, from, element) => {
447
- _addToMap(map, element)
448
-
449
- if (from.ref[0] && from.ref[0].id) {
450
- _addToMap(map, `${from.ref[0].id}.${element}`)
451
- } else {
452
- if (from.ref) {
453
- _addToMap(map, `${from.ref.join('.')}.${element}`)
454
- }
455
- }
456
-
457
- if (from.as) {
458
- _addToMap(map, `${from.as}.${element}`)
459
- }
460
- }
461
-
462
- const _addAssociationToMap = (map, from, element) => {
463
- const assocs = element.keys ? element.keys.map(key => `${element.name}_${key.ref[0]}`) : []
464
- for (const assocName of assocs) {
465
- _addColumn(map, from, assocName)
466
- }
467
- }
468
-
469
- const _addComplexTypeToMap = (map, from, name, element) => {
470
- for (const complexName in element.elements) {
471
- _addColumn(map, from, `${name}_${complexName}`)
472
- }
473
- }
474
-
475
- const _getKeyMapForAllElements = (csn, cqn) => {
476
- const map = new Map()
477
-
478
- // Get list of available and relevant entities as defined by from
479
- for (const from of _extractRefs(cqn.SELECT.from)) {
480
- const entityName = _getEntityName(csn, from)
481
-
482
- if (!entityName) {
483
- continue
484
- }
485
-
486
- for (const name in csn.definitions[entityName].elements) {
487
- const element = csn.definitions[entityName].elements[name]
488
-
489
- if (element.isAssociation) {
490
- _addAssociationToMap(map, from, element)
491
- } else if (element._isStructured) {
492
- _addComplexTypeToMap(map, from, name, element)
493
- } else {
494
- _addColumn(map, from, name)
495
- }
496
- }
497
- }
498
-
499
- return map
500
- }
501
-
502
- /**
503
- * Provide a map with to be renamed properties.
504
- *
505
- * @param {object} csn - Reflected CSN
506
- * @param {object} cqn - CQN that is used to query the DB.
507
- * @returns {Map<any, any>}
508
- */
509
- const getPropertyMapper = (csn, cqn) => {
510
- if (
511
- cds.env.sql.names === 'plain' &&
512
- cqn.SELECT &&
513
- (cqn.SELECT.columns === undefined || (cqn.SELECT.columns && cqn.SELECT.columns.includes('*')))
514
- ) {
515
- return _getKeyMapForAllElements(csn, cqn)
516
- }
517
-
518
- return new Map()
519
- }
520
-
521
224
  /*
522
225
  * this module is required by cds-pg. -> in case of incompatible changes, we should let them know.
523
226
  */
524
227
  module.exports = {
525
- getPropertyMapper,
526
228
  getPostProcessMapper,
527
- getStructMapper,
528
229
  postProcess
529
230
  }