@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,4 +1,5 @@
1
1
  const cds = require('../../cds')
2
+ const resolveStructured = require('../../common/utils/resolveStructured')
2
3
 
3
4
  const { ensureNoDraftsSuffix } = require('./draft')
4
5
 
@@ -140,6 +141,7 @@ const findCsnTargetFor = (edmName, model, namespace) => {
140
141
  // probably, a combination of '_' and '.', resolving
141
142
  const finding = _findRootEntity(model, edmName, namespace)
142
143
  target = finding.target
144
+
143
145
  // something left in navigation path => x4 navigation
144
146
  // resolving within found entity
145
147
  if (target && finding.left > 0) {
@@ -151,10 +153,12 @@ const findCsnTargetFor = (edmName, model, namespace) => {
151
153
  }
152
154
  }
153
155
  }
156
+
154
157
  // remember edm <-> csn
155
158
  if (target) {
156
159
  mapping[edmName] = target
157
160
  }
161
+
158
162
  return mapping[edmName]
159
163
  }
160
164
 
@@ -191,7 +195,40 @@ const isRootEntity = (definitions, entityName) => {
191
195
  return true
192
196
  }
193
197
 
198
+ function _alias2RefRest(service) {
199
+ for (const each of Object.values(service.entities)) {
200
+ each._alias2ref = {}
201
+ const keys = each.keys
202
+ for (const key in keys) {
203
+ if (keys[key].elements) {
204
+ const structKeys = resolveStructured({ structName: key, structProperties: [] }, keys[key].elements, false, true)
205
+ for (const structKey of structKeys) {
206
+ if (each._alias2ref[structKey.key] != null) {
207
+ // key clash, aliasing not possible
208
+ each._alias2ref = {}
209
+ return
210
+ }
211
+ each._alias2ref[structKey.key] = structKey.resolved
212
+ }
213
+ }
214
+ }
215
+ }
216
+ }
217
+
218
+ const prefixForStruct = element => {
219
+ const prefixes = []
220
+ let parent = element.parent
221
+ while (parent && parent.kind !== 'entity') {
222
+ prefixes.push(parent.name)
223
+ parent = parent.parent
224
+ }
225
+ return prefixes.length ? prefixes.reverse().join('_') + '_' : ''
226
+ }
227
+
194
228
  function alias2ref(service, edm) {
229
+ if (!edm) {
230
+ return _alias2RefRest(service)
231
+ }
195
232
  const defs = edm[service.definition.name]
196
233
  for (const each of Object.values(service.entities)) {
197
234
  const def = defs[each.name.replace(service.definition.name + '.', '').replace(/\./g, '_')]
@@ -205,6 +242,29 @@ function alias2ref(service, edm) {
205
242
  }
206
243
  }
207
244
 
245
+ function getDraftTreeRoot(entity, model) {
246
+ if (entity.own('__draftTreeRoot')) return entity.__draftTreeRoot
247
+
248
+ let parent
249
+ let current = entity
250
+ while (current && !current['@Common.DraftRoot.ActivationAction']) {
251
+ const parents = []
252
+ for (const k in model.definitions) {
253
+ const e = model.definitions[k]
254
+ if (e.kind !== 'entity' || !e.compositions) continue
255
+ for (const c in e.compositions) if (e.compositions[c].target === current.name) parents.push(e)
256
+ }
257
+ if (parents.length > 1 && parents.some(p => p !== parents[0])) {
258
+ // > unable to determine single parent
259
+ parent = undefined
260
+ break
261
+ }
262
+ current = parent = parents[0]
263
+ }
264
+
265
+ return entity.set('__draftTreeRoot', parent)
266
+ }
267
+
208
268
  module.exports = {
209
269
  getEtagElement,
210
270
  findCsnTargetFor,
@@ -212,5 +272,7 @@ module.exports = {
212
272
  isRootEntity,
213
273
  getDataSubject,
214
274
  alias2ref,
215
- getComp2oneParents
275
+ getComp2oneParents,
276
+ prefixForStruct,
277
+ getDraftTreeRoot
216
278
  }
@@ -11,7 +11,16 @@ module.exports = (entryOrRow, keyOrIndex, user, timestamp) => {
11
11
  else if (entryOrRow[keyOrIndex] === '$now') entryOrRow[keyOrIndex] = timestamp
12
12
  else if (entryOrRow[keyOrIndex] === '$uuid') entryOrRow[keyOrIndex] = cds.utils.uuid()
13
13
  else if (typeof entryOrRow[keyOrIndex] === 'string') {
14
+ // NOTE: with xsuaa, user attributes are always arrays
14
15
  const attr = entryOrRow[keyOrIndex].match(/^\$user\.(.*)/)
15
- if (attr && attr.length > 1) entryOrRow[keyOrIndex] = (user.attr && user.attr[attr[1]]) || null
16
+ if (attr && attr.length > 1) {
17
+ const val = (user.attr && user.attr[attr[1]]) || null
18
+ if (Array.isArray(val)) {
19
+ if (val.length > 1) entryOrRow[keyOrIndex] = JSON.stringify(val)
20
+ else entryOrRow[keyOrIndex] = val.length > 0 ? val[0] : null
21
+ } else {
22
+ entryOrRow[keyOrIndex] = val
23
+ }
24
+ }
16
25
  }
17
26
  }
@@ -18,18 +18,57 @@ const ensureUnlocalized = table => {
18
18
  return _table
19
19
  }
20
20
 
21
- const ensureDraftsSuffix = name => {
22
- if (name.endsWith('_drafts')) {
23
- return name
24
- }
21
+ const ensureDraftsSuffix = name => (name.endsWith('_drafts') ? name : `${ensureUnlocalized(name)}_drafts`)
22
+
23
+ const ensureNoDraftsSuffix = name => name.replace(/_drafts$/g, '')
25
24
 
26
- return `${ensureUnlocalized(name)}_drafts`
25
+ const getDraftColumnsCQNForActive = target => {
26
+ const draftName = ensureDraftsSuffix(target.name)
27
+ const subSelect = SELECT.from(draftName).columns([1])
28
+ for (const key in target.keys) {
29
+ if (key !== 'IsActiveEntity') subSelect.where([{ ref: [target.name, key] }, '=', { ref: [draftName, key] }])
30
+ }
31
+ return [
32
+ { val: true, as: 'IsActiveEntity', cast: { type: 'cds.Boolean' } },
33
+ { val: false, as: 'HasActiveEntity', cast: { type: 'cds.Boolean' } },
34
+ {
35
+ xpr: ['case', 'when', 'exists', subSelect, 'then', 'true', 'else', 'false', 'end'],
36
+ as: 'HasDraftEntity',
37
+ cast: { type: 'cds.Boolean' }
38
+ }
39
+ ]
27
40
  }
28
41
 
29
- const ensureNoDraftsSuffix = name => name.replace(/_drafts$/g, '')
42
+ const getDraftColumnsCQNForDraft = target => {
43
+ /*
44
+ * NOTE: the following with xpr could be used to detect if there really is an active or not, but that breaks tests
45
+ */
46
+ // const activeName = ensureNoDraftsSuffix(target.name)
47
+ // const subSelect = SELECT.from(activeName).columns([1])
48
+ // for (const key in target.keys) {
49
+ // if (key !== 'IsActiveEntity') subSelect.where([{ ref: [target.name, key] }, '=', { ref: [activeName, key] }])
50
+ // }
51
+ // return [
52
+ // { val: false, as: 'IsActiveEntity', cast: { type: 'cds.Boolean' } },
53
+ // {
54
+ // xpr: ['case', 'when', 'exists', subSelect, 'then', 'true', 'else', 'false', 'end'],
55
+ // as: 'HasActiveEntity',
56
+ // cast: { type: 'cds.Boolean' }
57
+ // },
58
+ // { val: false, as: 'HasDraftEntity', cast: { type: 'cds.Boolean' } }
59
+ // ]
60
+
61
+ return [
62
+ { val: false, as: 'IsActiveEntity', cast: { type: 'cds.Boolean' } },
63
+ { ref: ['HasActiveEntity'], cast: { type: 'cds.Boolean' } },
64
+ { val: false, as: 'HasDraftEntity', cast: { type: 'cds.Boolean' } }
65
+ ]
66
+ }
30
67
 
31
68
  module.exports = {
32
69
  ensureUnlocalized,
33
70
  ensureDraftsSuffix,
34
- ensureNoDraftsSuffix
71
+ ensureNoDraftsSuffix,
72
+ getDraftColumnsCQNForActive,
73
+ getDraftColumnsCQNForDraft
35
74
  }
@@ -1,23 +1,26 @@
1
1
  const { ensureNoDraftsSuffix } = require('../../common/utils/draft')
2
2
 
3
- const getEntityNameFromCQN = cqn => {
3
+ const traverseFroms = (cqn, cb) => {
4
4
  while (cqn.SELECT) cqn = cqn.SELECT.from
5
5
 
6
6
  // Do the most likely first -> {ref}
7
7
  if (cqn.ref) {
8
- return { entityName: cqn.ref[0].id || cqn.ref[0], alias: cqn.as }
8
+ return cb(cqn)
9
9
  }
10
10
 
11
- // TODO cleanup
12
- // REVISIT infer should do this for req.target
13
- // REVISIT2 No, req.target doesn't make sense for joins
14
11
  if (cqn.SET) {
15
- return cqn.SET.args.map(getEntityNameFromCQN).find(n => n.entityName !== 'DRAFT.DraftAdministrativeData')
12
+ return cqn.SET.args.map(a => traverseFroms(a, cb))
16
13
  }
14
+
17
15
  if (cqn.join) {
18
- return cqn.args.map(getEntityNameFromCQN).find(n => n.entityName !== 'DRAFT.DraftAdministrativeData')
16
+ return cqn.args.map(a => traverseFroms(a, cb))
19
17
  }
20
- return {}
18
+ }
19
+
20
+ const getEntityNameFromCQN = cqn => {
21
+ const res = []
22
+ traverseFroms(cqn, from => res.push({ entityName: from.ref[0].id || from.ref[0], alias: from.as }))
23
+ return res.length === 1 ? res[0] : res.find(n => n.entityName !== 'DRAFT.DraftAdministrativeData') || {}
21
24
  }
22
25
 
23
26
  // Note: This also works for the common draft scenarios
@@ -31,5 +34,6 @@ const getEntityFromCQN = (req, service) => {
31
34
 
32
35
  module.exports = {
33
36
  getEntityFromCQN,
34
- getEntityNameFromCQN
37
+ getEntityNameFromCQN,
38
+ traverseFroms
35
39
  }
@@ -0,0 +1,18 @@
1
+ const optionsApp = require('./vcap.js')
2
+
3
+ const BASE_TENANT = 'anonymous'
4
+
5
+ const channelName = () => {
6
+ // REVISIT: Why so complicated?
7
+
8
+ const appName = optionsApp.appName || 'CAP'
9
+ const appID = optionsApp.appID || '00000000'
10
+ const shrunkAppID = appID.substring(0, 4)
11
+
12
+ return `${appName}/${shrunkAppID}`
13
+ }
14
+
15
+ module.exports = {
16
+ BASE_TENANT,
17
+ channelName
18
+ }
@@ -60,8 +60,7 @@ const _foreignKeyPropagationsFromToManyOn = (element, childFieldName) => {
60
60
  const foreignKeys = _foreignKeysForTarget(element, childFieldName)
61
61
  // REVISIT foreignKeys is empty if we have deep operations where a sub element is annotated with persistence skip
62
62
  if (foreignKeys && foreignKeys.length) {
63
- const parentKeys = _parentKeys(element)
64
- return _resolvedKeys(parentKeys, foreignKeys, true)
63
+ return _resolvedKeys(foreignKeys, true)
65
64
  }
66
65
  return []
67
66
  }
@@ -118,62 +117,11 @@ const _foreignKeyPropagationsFromOn = element => {
118
117
  return foreignKeyPropagations
119
118
  }
120
119
 
121
- /*
122
- * recursive resolvedKeys for a structured element
123
- * returns how many indexes can be skipped in the outer loop
124
- * example:
125
- * foo: {
126
- * bar: {
127
- * moo: Integer;
128
- * shu: Integer;
129
- * };
130
- * baz: Integer;
131
- * };
132
- * -> foo_bar_moo, foo_bar_shu, foo_baz
133
- * -> processed three instead of one from outer loop perspective
134
- */
135
- const _resolve4struct = (others, struct, fkps, fillChild, childIsStruct, i) => {
136
- let j = 0
137
-
138
- for (const k in struct.elements) {
139
- const other = others[i + j]
140
- const current = struct.elements[k]
141
-
142
- if (current._isStructured) {
143
- // call recursive and increment skip
144
- j += _resolve4struct(others, current, fkps, fillChild, childIsStruct, i + j)
145
- } else if (current.isAssociation) {
146
- continue
147
- } else {
148
- // calc prefix
149
- let prefix = struct.name
150
- let cur = struct.parent
151
- while (cur._isStructured) {
152
- prefix = cur.name + '_' + prefix
153
- cur = cur.parent
154
- }
155
-
156
- // push propagation
157
- fkps.push({
158
- childElement: childIsStruct ? current : other,
159
- parentElement: childIsStruct ? other : current,
160
- fillChild,
161
- prefix,
162
- deep: !fillChild && _resolveTargetForeignKey(childIsStruct ? current : other)
163
- })
164
-
165
- // increment skip
166
- j++
167
- }
168
- }
169
-
170
- return j
171
- }
172
-
173
120
  const _resolveTargetForeignKey = targetKey => {
174
- const targetName = targetKey['@odata.foreignKey4']
121
+ const targetName = targetKey._foreignKey4
175
122
  if (!targetName) return
176
- const _foreignKeyProps = foreignKeyPropagations(targetKey.parent.elements[targetName])
123
+ const parentElements = targetKey.parent.elements
124
+ const _foreignKeyProps = foreignKeyPropagations(parentElements[targetName])
177
125
  const propagation = _foreignKeyProps.find(_fkp => _fkp.parentElement && targetKey.name === _fkp.parentElement.name)
178
126
  return { targetName, propagation }
179
127
  }
@@ -184,39 +132,22 @@ const _resolveColumnsFromQuery = query => {
184
132
  return []
185
133
  }
186
134
 
187
- const _resolvedKeys = (foreignKeys, targetKeys, fillChild) => {
188
- const foreignKeyPropagations = []
135
+ const _resolvedKeys = (keys, fillChild) => {
136
+ const foreignKeys = fillChild ? keys.map(fk => Object.getPrototypeOf(fk)) : keys
137
+ const targetKeys = fillChild ? keys : keys.map(fk => Object.getPrototypeOf(fk))
189
138
 
139
+ const foreignKeyPropagations = []
190
140
  for (let i = 0; i < foreignKeys.length; i++) {
191
141
  const fk = foreignKeys[i]
192
- let tk
193
-
194
- /*
195
- * REVISIT: poor man's look-up of target key with fallback to targetKeys[i]
196
- * Look at elements, then try to find it in query and resolve recursively until you have the full path.
197
- * Once you have the full path, you can find it in the target entity.
198
- * NOTE: There can be projections upon projections and renamings in every projection. -> not yet covered!!!
199
- */
200
- const tkCol = _resolveColumnsFromQuery(targetKeys[i].parent.query).find(
201
- c => c.ref && `${fk['@odata.foreignKey4']}_${c.ref.join('_')}` === fk.name
202
- )
203
- tk = tkCol && targetKeys.find(tk => tk.name === (tkCol.as ? tkCol.as : tkCol.ref.join('_')))
204
- // with composition of aspects, the lookup fails -> we need this final fallback
205
- if (!tk) tk = targetKeys[i]
206
-
207
- if (fk._isStructured) {
208
- i += _resolve4struct(targetKeys, fk, foreignKeyPropagations, fillChild, false, i)
209
- } else if (tk._isStructured) {
210
- i += _resolve4struct(foreignKeys, tk, foreignKeyPropagations, fillChild, true, i)
211
- } else {
212
- foreignKeyPropagations.push({
213
- fillChild,
214
- parentElement: fk,
215
- childElement: tk,
216
- // needed only for child -> parent propagation since template loops in other direction
217
- deep: !fillChild && _resolveTargetForeignKey(tk)
218
- })
142
+ const tk = targetKeys[i]
143
+ const propagation = {
144
+ fillChild,
145
+ parentElement: fk,
146
+ childElement: tk,
147
+ // needed only for child -> parent propagation since template loops in other direction
148
+ deep: !fillChild && _resolveTargetForeignKey(tk)
219
149
  }
150
+ foreignKeyPropagations.push(propagation)
220
151
  }
221
152
 
222
153
  return foreignKeyPropagations
@@ -229,10 +160,7 @@ const foreignKeyPropagations = element => {
229
160
  if (element.is2one) {
230
161
  if (!element.on) {
231
162
  const foreignKeys = _foreignKeys(element)
232
- if (foreignKeys) {
233
- const targetKeys = _targetKeys(element)
234
- return _resolvedKeys(foreignKeys, targetKeys)
235
- }
163
+ if (foreignKeys) return _resolvedKeys(foreignKeys, false)
236
164
  } else {
237
165
  // It's a link through a backlink
238
166
  return _foreignKeyPropagationsFromOn(element)
@@ -241,29 +169,85 @@ const foreignKeyPropagations = element => {
241
169
  return []
242
170
  }
243
171
 
244
- const _foreignKeys = csnElement => {
245
- return Object.values(csnElement.parent.elements).filter(element => element['@odata.foreignKey4'] === csnElement.name)
172
+ // REVISIT: Flattening shouldn't be necessary in the future.
173
+ // It's better to deal with structures instead, but
174
+ // that would require changing a lot of code.
175
+ const _foreignKeys = element => {
176
+ const foreignKeys = element.foreignKeys
177
+ const path = [element.name]
178
+ const parent = element.parent
179
+ const result = []
180
+ _addToForeignKeysRec(foreignKeys, path, parent, result)
181
+ return result
246
182
  }
247
183
 
248
- const _foreignKeysForTarget = (csnElement, name) => {
249
- return Object.values(csnElement._target.elements).filter(
250
- element =>
251
- !element.isAssociation && !element.isComposition && element['@odata.foreignKey4'] === (name || csnElement.name)
184
+ /*
185
+ * REVISIT: poor man's look-up of target key
186
+ * Look at elements, then try to find it in query and resolve recursively until you have the full path.
187
+ * Once you have the full path, you can find it in the target entity.
188
+ * NOTE: There can be projections upon projections and renamings in every projection. -> not yet covered!!!
189
+ */
190
+ const _poorMansLookup = (el, name, foreignKeySource) => {
191
+ // REVISIT: Dirty hack
192
+ const tkCol = _resolveColumnsFromQuery(el.parent.query).find(
193
+ c => c.ref && `${foreignKeySource}_${c.ref.join('_')}` === name
252
194
  )
195
+ return tkCol && Object.values(el.parent.elements).find(tk => tk.name === (tkCol.as ? tkCol.as : tkCol.ref.join('_')))
253
196
  }
254
197
 
255
- const _targetKeys = csnElement => {
256
- return Object.values(csnElement._target.keys).filter(
257
- element => !element.isAssociation && !element.isComposition && element.name !== 'IsActiveEntity'
258
- )
198
+ const _createForeignKey = (name, el, parent, foreignKeySource) => {
199
+ const tk = _poorMansLookup(el, name, foreignKeySource)
200
+ const navigationCsn = parent.elements[foreignKeySource]
201
+ const foreignKeyCsn = Object.assign(Object.create(tk || el), { parent, name, foreignKeySource })
202
+ // annotation propagation is not possible via prototyping
203
+ for (const key of Object.keys(navigationCsn).filter(key => key.startsWith('@')))
204
+ foreignKeyCsn[key] = navigationCsn[key]
205
+ return foreignKeyCsn
259
206
  }
260
207
 
261
- const _parentKeys = csnElement => {
262
- return Object.values(csnElement.parent.keys).filter(
263
- element => !element.isAssociation && !element.isComposition && element.name !== 'IsActiveEntity'
264
- )
208
+ const _addToForeignKeysRec = (elements, path, parent, result) => {
209
+ for (const elName in elements) {
210
+ const el = elements[elName]
211
+ const foreignKeySource = path[0]
212
+ const newPath = [...path, elName]
213
+ if (el.isAssociation) {
214
+ const foreignKeysOftarget = _foreignKeys(el)
215
+ for (const fk of foreignKeysOftarget) {
216
+ const name = [...path, fk.name].join('_')
217
+ if (result.some(x => x.name === name)) return
218
+ const foreignKeyCsn = _createForeignKey(name, fk, parent, foreignKeySource)
219
+ result.push(foreignKeyCsn)
220
+ }
221
+ } else if (!el.elements) {
222
+ const name = newPath.join('_')
223
+ if (result.some(x => x.name === name)) return
224
+ const foreignKeyCsn = _createForeignKey(name, el, parent, foreignKeySource)
225
+ result.push(foreignKeyCsn)
226
+ } else _addToForeignKeysRec(el.elements, newPath, parent, result)
227
+ }
228
+ }
229
+
230
+ const _foreignKeysForTarget = (csnElement, name) => {
231
+ const target = csnElement._target.elements[name || csnElement.name]
232
+ return _foreignKeys(target)
233
+ }
234
+
235
+ const foreignKey4 = element => {
236
+ if (!element || !element.parent) return
237
+ const parentElements = element.parent.elements
238
+ for (const assoc of Object.keys(parentElements)
239
+ .map(n => parentElements[n])
240
+ .filter(e => e.isAssociation)) {
241
+ const foreignKeys = _foreignKeys(assoc)
242
+ if (!foreignKeys.length) continue
243
+ const target = foreignKeys.find(fk => fk.name === element.name)
244
+ if (target) {
245
+ return target && target.foreignKeySource
246
+ }
247
+ }
265
248
  }
266
249
 
267
250
  module.exports = {
268
- foreignKeyPropagations
251
+ foreignKeyPropagations,
252
+ foreignKey4
269
253
  }
@@ -1,12 +1,13 @@
1
+ const { prefixForStruct } = require('./csn')
2
+
1
3
  const _toRef = (alias, column) => {
2
4
  if (Array.isArray(column)) column = column.join('_')
3
5
  return { ref: alias ? [alias, column] : [column] }
4
6
  }
5
7
 
6
8
  const _adaptRefs = (onCond, path, { select, join }) => {
7
- const adaptedOnCondition = onCond.map(el => {
9
+ const _adaptEl = el => {
8
10
  const ref = el.ref
9
-
10
11
  if (ref) {
11
12
  if (ref[0] === path.join('_') && ref[1]) {
12
13
  return _toRef(select, ref.slice(1))
@@ -18,12 +19,13 @@ const _adaptRefs = (onCond, path, { select, join }) => {
18
19
  }
19
20
 
20
21
  return _toRef(join, ref.slice(0))
22
+ } else if (el.xpr) {
23
+ return { xpr: el.xpr.map(_adaptEl) }
21
24
  }
22
25
 
23
26
  return el
24
- })
25
-
26
- return adaptedOnCondition
27
+ }
28
+ return onCond.map(_adaptEl)
27
29
  }
28
30
 
29
31
  const _args = (csnElement, path, aliases) => {
@@ -73,7 +75,8 @@ const _foreignToOn = (csnElement, path, { select, join }) => {
73
75
  if (on.length !== 0) {
74
76
  on.push('and')
75
77
  }
76
- const ref1 = _toRef(select, key.prefix ? `${key.prefix}_${key.childElement.name}` : key.childElement.name)
78
+ const prefixChild = prefixForStruct(key.childElement)
79
+ const ref1 = _toRef(select, prefixChild + key.childElement.name)
77
80
  const structPrefix = path.length > 1 ? path.slice(0, -1) : []
78
81
  const ref2 = _toRef(join, [...structPrefix, key.parentElement.name])
79
82
  on.push(ref1, '=', ref2)
@@ -32,6 +32,8 @@ module.exports = {
32
32
  _reserved = _COMPILER_RESERVED[_dialect] || _DEFAULT_RESERVED
33
33
  }
34
34
 
35
+ if (typeof s !== 'string') throw new Error(`string expected but ${typeof s} received`)
36
+
35
37
  // * or already quoted?
36
38
  if (s === '*' || _isQuoted(s)) return s
37
39
 
@@ -1,4 +1,4 @@
1
- const _flattenProps = (subElement, structName, structProperties, structElement, asRef) => {
1
+ const _flattenProps = (subElement, structName, structProperties, structElement, asRef, withKey) => {
2
2
  if (subElement.elements) {
3
3
  return _resolveStructured(
4
4
  {
@@ -6,34 +6,50 @@ const _flattenProps = (subElement, structName, structProperties, structElement,
6
6
  structProperties: structProperties.slice(1)
7
7
  },
8
8
  subElement.elements,
9
- asRef
9
+ asRef,
10
+ withKey
10
11
  )
11
12
  } else if (subElement.isAssociation) {
12
13
  if (structProperties.length && subElement.is2one && !subElement.on) {
13
- return asRef
14
- ? [{ ref: [`${structName}_${structProperties.join('_')}`] }]
15
- : [`${structName}_${structProperties.join('_')}`]
14
+ const flattenedName = `${structName}_${structProperties.join('_')}`
15
+ if (withKey) {
16
+ return [{ key: structElement, resolved: [flattenedName] }]
17
+ }
18
+
19
+ return asRef ? [{ ref: [flattenedName] }] : [flattenedName]
16
20
  }
17
21
 
18
22
  return []
19
23
  }
20
- return asRef ? [{ ref: [`${structName}_${structElement}`] }] : [`${structName}_${structElement}`]
24
+ const flattenedName = `${structName}_${structElement}`
25
+ if (withKey) {
26
+ return [{ key: structElement, resolved: [flattenedName] }]
27
+ }
28
+
29
+ return asRef ? [{ ref: [flattenedName] }] : [flattenedName]
21
30
  }
22
31
 
23
- const _resolveStructured = ({ structName, structProperties }, subElements, asRef = true) => {
32
+ const _resolveStructured = ({ structName, structProperties }, subElements, asRef = true, withKey = false) => {
24
33
  if (!subElements) {
25
34
  return []
26
35
  }
27
36
 
28
37
  // only add from structProperties
29
38
  if (structProperties.length) {
30
- return _flattenProps(subElements[structProperties[0]], structName, structProperties, structProperties[0], asRef)
39
+ return _flattenProps(
40
+ subElements[structProperties[0]],
41
+ structName,
42
+ structProperties,
43
+ structProperties[0],
44
+ asRef,
45
+ withKey
46
+ )
31
47
  }
32
48
 
33
49
  const flattenedElements = []
34
50
  for (const structElement in subElements) {
35
51
  flattenedElements.push(
36
- ..._flattenProps(subElements[structElement], structName, structProperties, structElement, asRef)
52
+ ..._flattenProps(subElements[structElement], structName, structProperties, structElement, asRef, withKey)
37
53
  )
38
54
  }
39
55
  return flattenedElements
@@ -346,7 +346,10 @@ const _newSelect = (query, transitions, service) => {
346
346
  }
347
347
  if (!newSelect.columns && targetTransition.mapping.size) newSelect.columns = _initialColumns(targetTransition)
348
348
  if (newSelect.columns) {
349
- rewriteAsterisks({ SELECT: query.SELECT }, service.model, { _4db: service instanceof cds.DatabaseService })
349
+ rewriteAsterisks({ SELECT: query.SELECT }, service.model, {
350
+ _4db: service instanceof cds.DatabaseService,
351
+ target: targetTransition.queryTarget
352
+ })
350
353
  newSelect.columns = _newColumns(newSelect.columns, targetTransition, service, service.kind !== 'app-service')
351
354
  }
352
355
  if (newSelect.having) newSelect.having = _newColumns(newSelect.having, targetTransition)
@@ -1,6 +1,6 @@
1
1
  const { getNavigationIfStruct } = require('./structured')
2
2
  const getColumns = require('../../db/utils/columns')
3
- const { ensureDraftsSuffix, ensureNoDraftsSuffix } = require('./draft')
3
+ const { ensureNoDraftsSuffix, getDraftColumnsCQNForActive } = require('./draft')
4
4
  const { getEntityNameFromCQN } = require('./entityFromCqn')
5
5
 
6
6
  const isAsteriskColumn = col => col === '*' || (col.ref && col.ref[0] === '*' && !col.expand)
@@ -14,20 +14,7 @@ const _isDuplicate = newColumn => column => {
14
14
 
15
15
  const _cqlDraftColumns = target => {
16
16
  if (target.name.endsWith('DraftAdministrativeData')) return []
17
- const draftName = ensureDraftsSuffix(target.name)
18
- const subSelect = SELECT.from(draftName).columns([1])
19
- for (const key in target.keys) {
20
- if (key !== 'IsActiveEntity') subSelect.where([{ ref: [target.name, key] }, '=', { ref: [draftName, key] }])
21
- }
22
- return [
23
- { val: true, as: 'IsActiveEntity', cast: { type: 'cds.Boolean' } },
24
- { val: false, as: 'HasActiveEntity', cast: { type: 'cds.Boolean' } },
25
- {
26
- xpr: ['case', 'when', 'exists', subSelect, 'then', 'true', 'else', 'false', 'end'],
27
- as: 'HasDraftEntity',
28
- cast: { type: 'cds.Boolean' }
29
- }
30
- ]
17
+ return getDraftColumnsCQNForActive(target)
31
18
  }
32
19
 
33
20
  const _expandColumn = (column, target, _4db) => {
@@ -135,7 +122,7 @@ const rewriteAsterisks = (query, model, options) => {
135
122
  return
136
123
  }
137
124
 
138
- const target = _targetOfQueryIfNotDraft(query, model)
125
+ const target = options.target || _targetOfQueryIfNotDraft(query, model)
139
126
  if (!target) return
140
127
 
141
128
  // REVISIT: Also support JOINs/SETs here