@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
@@ -0,0 +1,109 @@
1
+ const cds = require('../../_runtime/cds')
2
+ const getTemplate = require('../../_runtime/common/utils/template')
3
+ const templateProcessor = require('../../_runtime/common/utils/templateProcessor')
4
+ const IS_PROXY = Symbol('flat2structProxy')
5
+
6
+ const proxifyIfFlattened = (definition, payload) => {
7
+ if (!definition || !definition._flat2struct || payload == null || payload[IS_PROXY]) return payload
8
+ return Object.setPrototypeOf(
9
+ payload,
10
+ new Proxy(
11
+ {},
12
+ {
13
+ get: function (_, k, cur) {
14
+ if (k === IS_PROXY) return true
15
+ if (!definition._flat2struct[k]) return Reflect.get(...arguments)
16
+ const segments = definition._flat2struct[k]
17
+ for (let i = 0; i < segments.length - 1; i++) {
18
+ cur = cur[segments[i]]
19
+ if (!cur) return cur
20
+ }
21
+ return cur[segments[segments.length - 1]]
22
+ },
23
+ set: function (_, k, v, o) {
24
+ let cur = o
25
+ if (definition._flat2struct[k]) {
26
+ const segments = definition._flat2struct[k]
27
+ for (let i = 0; i < segments.length - 1; i++) {
28
+ if (!cur[segments[i]]) {
29
+ cur[segments[i]] = {}
30
+ }
31
+ cur = cur[segments[i]]
32
+ }
33
+ cur[segments[segments.length - 1]] = v
34
+ } else if (k === IS_PROXY) {
35
+ // do nothing
36
+ } else {
37
+ Reflect.set(...arguments)
38
+ }
39
+ return o
40
+ }
41
+ }
42
+ )
43
+ )
44
+ }
45
+
46
+ const _picker = element => {
47
+ if (Array.isArray(element)) return { category: 'flat leaf' }
48
+ if (element.isAssociation) return { category: 'node' }
49
+ }
50
+
51
+ const _processor = ({ row, key, plain: { category }, element }) => {
52
+ if (!(key in row)) return
53
+ if (category === 'node') {
54
+ row[key] = Array.isArray(row[key])
55
+ ? row[key].map(data => proxifyIfFlattened(element._target, data))
56
+ : proxifyIfFlattened(element._target, row[key])
57
+ } else if (category === 'flat leaf') {
58
+ const data = row[key]
59
+ delete row[key]
60
+ row[key] = data
61
+ }
62
+ }
63
+
64
+ const _cleanup = (row, definition, cleanupNull) => {
65
+ if (!row || !definition) return
66
+ const elements = definition.elements || definition.params
67
+ for (const key of Object.keys(row)) {
68
+ const element = elements[key]
69
+ if (!element) {
70
+ if (!definition['@open']) delete row[key]
71
+ continue
72
+ }
73
+ if (!row[key]) continue
74
+ if (element.isAssociation) {
75
+ if (element.is2many) {
76
+ for (const r of row[key]) {
77
+ _cleanup(r, element._target, cleanupNull)
78
+ }
79
+ } else {
80
+ _cleanup(row[key], element._target, cleanupNull)
81
+ }
82
+ } else if (element.elements) {
83
+ _cleanup(row[key], element, cleanupNull)
84
+ if (cleanupNull && Object.values(row[key]).every(v => v == null)) row[key] = null
85
+ }
86
+ }
87
+ }
88
+
89
+ function convertStructured(service, definition, data, { cleanupNull = false } = {}) {
90
+ if (!definition) return
91
+ // REVISIT check `structs` mode only for now as uCSN is not yet available
92
+ const flatAccess = cds.env.features.compat_flat_access
93
+ const template = getTemplate('universal-input', service, definition, { pick: _picker, flatAccess })
94
+ const arrayData = Array.isArray(data) ? data : [data]
95
+ if (template && template.elements.size) {
96
+ for (let i = 0; i < arrayData.length; i++) {
97
+ const row = proxifyIfFlattened(definition, arrayData[i])
98
+ templateProcessor({ processFn: _processor, row, template, pathOptions: { path: [] } })
99
+ }
100
+ }
101
+ for (const row of arrayData) {
102
+ _cleanup(row, definition, cleanupNull)
103
+ }
104
+ }
105
+
106
+ module.exports = {
107
+ convertStructured,
108
+ proxifyIfFlattened
109
+ }
@@ -1,3 +1,5 @@
1
+ const cds = require('../../../../lib')
2
+
1
3
  const { ARGUMENT } = require('../../constants/adapter')
2
4
  const { getArgumentByName, astToEntries } = require('../parse/ast2cqn')
3
5
  const { entriesStructureToEntityStructure } = require('./utils')
@@ -9,7 +11,10 @@ module.exports = async (service, entityFQN, selection) => {
9
11
  const entries = entriesStructureToEntityStructure(service, entityFQN, astToEntries(input))
10
12
  query.entries(entries)
11
13
 
12
- const result = await service.tx(tx => tx.run(query))
14
+ const result = await service.tx(tx => {
15
+ cds.context = tx
16
+ return tx.run(query)
17
+ })
13
18
 
14
19
  return Array.isArray(result) ? result : [result]
15
20
  }
@@ -1,3 +1,5 @@
1
+ const cds = require('../../../../lib')
2
+
1
3
  const { ARGUMENT } = require('../../constants/adapter')
2
4
  const { getArgumentByName, astToWhere } = require('../parse/ast2cqn')
3
5
 
@@ -11,7 +13,10 @@ module.exports = async (service, entityFQN, selection) => {
11
13
 
12
14
  let result
13
15
  try {
14
- result = await service.tx(tx => tx.run(query))
16
+ result = await service.tx(tx => {
17
+ cds.context = tx
18
+ return tx.run(query)
19
+ })
15
20
  } catch (e) {
16
21
  if (e.code === 404) {
17
22
  result = 0
@@ -1,3 +1,5 @@
1
+ const cds = require('../../../../lib')
2
+
1
3
  const { ARGUMENT } = require('../../constants/adapter')
2
4
  const { getArgumentByName, astToColumns, astToWhere, astToOrderBy, astToLimit } = require('../parse/ast2cqn')
3
5
 
@@ -21,5 +23,8 @@ module.exports = async (service, entityFQN, selection) => {
21
23
  query.limit(astToLimit(top, skip))
22
24
  }
23
25
 
24
- return await service.tx(tx => tx.run(query))
26
+ return await service.tx(tx => {
27
+ cds.context = tx
28
+ return tx.run(query)
29
+ })
25
30
  }
@@ -1,3 +1,5 @@
1
+ const cds = require('../../../../lib')
2
+
1
3
  const { ARGUMENT } = require('../../constants/adapter')
2
4
  const { getArgumentByName, astToColumns, astToWhere, astToEntries } = require('../parse/ast2cqn')
3
5
  const { entriesStructureToEntityStructure } = require('./utils')
@@ -24,8 +26,14 @@ module.exports = async (service, entityFQN, selection) => {
24
26
 
25
27
  let resultBeforeUpdate
26
28
  const result = await service.tx(async tx => {
29
+ cds.context = tx
27
30
  // read needs to be done before the update, otherwise the where clause might become invalid (case that properties in where clause are updated by the mutation)
28
- resultBeforeUpdate = await service.tx(tx => tx.run(queryBeforeUpdate))
31
+ resultBeforeUpdate = await tx.run(queryBeforeUpdate)
32
+
33
+ if (resultBeforeUpdate.length === 0) {
34
+ return []
35
+ }
36
+
29
37
  return tx.run(query)
30
38
  })
31
39
 
@@ -14,7 +14,9 @@ const astToColumns = selections => {
14
14
  }
15
15
 
16
16
  if (selection.selectionSet && selection.selectionSet.selections) {
17
- column.expand = astToColumns(selection.selectionSet.selections)
17
+ const columns = astToColumns(selection.selectionSet.selections)
18
+ // columns is empty if only __typename was selected (which was filtered out in the enriched AST)
19
+ column.expand = columns.length > 0 ? columns : ['*']
18
20
  }
19
21
 
20
22
  const filter = getArgumentByName(selection.arguments, ARGUMENT.FILTER)
@@ -30,8 +30,8 @@ const servicesToTypeDefMap = services => {
30
30
  // TODO structured types
31
31
  continue
32
32
  } else {
33
- if (CDS_TO_GRAPHQL_TYPES[ele.type]) {
34
- def[ele.name] = CDS_TO_GRAPHQL_TYPES[ele.type]
33
+ if (CDS_TO_GRAPHQL_TYPES[ele._type]) {
34
+ def[ele.name] = CDS_TO_GRAPHQL_TYPES[ele._type]
35
35
  }
36
36
  // TODO aspects
37
37
  }
@@ -3,10 +3,20 @@ const cds = require('../_runtime/cds')
3
3
  const { where2obj } = require('../_runtime/common/utils/cqn')
4
4
  const { findCsnTargetFor } = require('../_runtime/common/utils/csn')
5
5
 
6
- const _addKeysDeep = (keys, keysCollector) => {
6
+ const _addKeysDeep = (keys, keysCollector, ignoreManagedBacklinks) => {
7
7
  for (const keyName in keys) {
8
8
  const key = keys[keyName]
9
- if (key.type === 'cds.Association' || key['@odata.foreignKey4'] === 'up_') continue
9
+ const foreignKey = key._foreignKey4
10
+ if (key.isAssociation || foreignKey === 'up_' || key['@cds.api.ignore'] === true) continue
11
+
12
+ if (ignoreManagedBacklinks && foreignKey) {
13
+ const navigationElement = keys[foreignKey]
14
+ if (!navigationElement.on && navigationElement._isBacklink) {
15
+ // skip navigation elements that are backlinks
16
+ continue
17
+ }
18
+ }
19
+
10
20
  if ('elements' in key) {
11
21
  _addKeysDeep(key.elements, keysCollector)
12
22
  continue
@@ -15,10 +25,10 @@ const _addKeysDeep = (keys, keysCollector) => {
15
25
  }
16
26
  }
17
27
 
18
- function _keysOf(entity) {
28
+ function _keysOf(entity, ignoreManagedBacklinks) {
19
29
  if (!entity || !entity.keys) return
20
30
  const keysCollector = []
21
- _addKeysDeep(entity.keys, keysCollector)
31
+ _addKeysDeep(entity.keys, keysCollector, ignoreManagedBacklinks)
22
32
  return keysCollector
23
33
  }
24
34
 
@@ -31,18 +41,47 @@ function _getDefinition(definition, name, namespace) {
31
41
  )
32
42
  }
33
43
 
44
+ function _resolveAliasInParams(params, entity) {
45
+ if (!entity._alias2ref) return
46
+ const paramKeys = Object.keys(params)
47
+ for (const paramKey of paramKeys) {
48
+ if (entity._alias2ref[paramKey]) {
49
+ params[entity._alias2ref[paramKey].join('_')] = params[paramKey]
50
+ params[paramKey] = undefined
51
+ }
52
+ }
53
+ }
54
+
34
55
  function _resolveAliasInWhere(where, entity) {
35
56
  if (!entity._alias2ref) return
36
- for (let i = 0; i < where.length; i++) {
37
- if (!where[i].ref || where[i].ref.length > 1 || entity.keys[where[i].ref[0]]) continue
38
- where[i].ref = entity._alias2ref[where[i].ref[0]] || where[i].ref
57
+ for (const w of where) {
58
+ if (!w.ref || w.ref.length > 1 || entity.keys[w.ref[0]]) continue
59
+ w.ref = entity._alias2ref[w.ref[0]] || w.ref
60
+ }
61
+ }
62
+
63
+ function _addDefaultParams(ref, view) {
64
+ const params = view.params
65
+ const defaults = params && Object.values(params).filter(p => p.default)
66
+ if (defaults && defaults.length > 0) {
67
+ if (!ref.where) ref.where = []
68
+ for (const def of defaults) {
69
+ if (ref.where.find(e => e.ref && e.ref[0] === def.name)) {
70
+ continue
71
+ }
72
+ if (ref.where.length > 0) ref.where.push('and')
73
+ ref.where.push({ ref: [def.name] }, '=', { val: def.default.val })
74
+ }
39
75
  }
40
76
  }
41
77
 
42
78
  // case: single key without name, e.g., Foo(1)
43
79
  function addRefToWhereIfNecessary(where, entity) {
44
80
  if (!where || where.length !== 1) return 0
45
- const keys = _keysOf(entity)
81
+
82
+ const isView = !!entity.params
83
+
84
+ const keys = isView ? Object.keys(entity.params) : _keysOf(entity)
46
85
  if (keys.length !== 1) return 0
47
86
  where.unshift(...[{ ref: [keys[0]] }, '='])
48
87
  return 1
@@ -64,13 +103,14 @@ function _processSegments(cqn, model, namespace) {
64
103
 
65
104
  if (incompleteKeys) {
66
105
  // > key
67
- keys = keys || _keysOf(current)
68
- const key = keys[keyCount++]
106
+ keys = keys || _keysOf(current, !cds.env.features.rest_new_adapter && !cds.env.features.rest_new_parser) // if odata skip backlinks as key as they are used from structure
107
+ let key = keys[keyCount++]
69
108
  one = true
70
109
  const element = current.elements[key]
71
110
  let base = ref[i - keyCount]
72
111
  if (!base.id) base = { id: base, where: [] }
73
112
  if (base.where.length) base.where.push('and')
113
+
74
114
  if (ref[i].id) {
75
115
  // > fix case key value parsed to collection with filter
76
116
  const val = `${ref[i].id}(${Object.keys(params)
@@ -78,7 +118,7 @@ function _processSegments(cqn, model, namespace) {
78
118
  .join(',')})`
79
119
  base.where.push({ ref: [key] }, '=', { val })
80
120
  } else {
81
- base.where.push({ ref: [key] }, '=', { val: element.type === 'cds.Integer' ? Number(seg) : seg })
121
+ base.where.push({ ref: [key] }, '=', { val: element._type === 'cds.Integer' ? Number(seg) : seg })
82
122
  }
83
123
  ref[i] = null
84
124
  ref[i - keyCount] = base
@@ -95,13 +135,40 @@ function _processSegments(cqn, model, namespace) {
95
135
  // REVISIT: 404 or 400?
96
136
  if (!current) cds.error(`Invalid resource path "${path}"`, { code: 404 })
97
137
 
98
- if (current.kind === 'entity') {
138
+ if (current.params && current.kind === 'entity') {
139
+ // > View with params
140
+ if (ref[i].where) {
141
+ keyCount += addRefToWhereIfNecessary(ref[i].where, current)
142
+ _resolveAliasInWhere(ref[i].where, current)
143
+ _resolveAliasInParams(params, current)
144
+ }
145
+
146
+ _addDefaultParams(ref[i], current)
147
+ if ((!params || !Object.keys(params).length) && ref[i].where) params = where2obj(ref[i].where)
148
+
149
+ _checkAllKeysProvided(params, current)
150
+
151
+ ref[i].args = {}
152
+ for (let j = 0; j < ref[i].where.length; j++) {
153
+ const w = ref[i].where[j]
154
+ if (w === 'and' || !w.ref) continue
155
+ ref[i].args[w.ref[0]] = ref[i].where[j + 2]
156
+ j += 2
157
+ }
158
+ ref[i].where = undefined
159
+ if (ref[i + 1] !== 'Set') {
160
+ // /Set is missing
161
+ throw new Error(`Incorrect call to a view with parameter "${current.name}"`)
162
+ }
163
+ ref[++i] = null
164
+ } else if (current.kind === 'entity') {
99
165
  // > entity
100
166
  one = !!(ref[i].where || current._isSingleton)
101
167
  incompleteKeys = ref[i].where ? false : i === ref.length - 1 || one ? false : true
102
168
  if (ref[i].where) {
103
169
  keyCount += addRefToWhereIfNecessary(ref[i].where, current)
104
170
  _resolveAliasInWhere(ref[i].where, current)
171
+ _resolveAliasInParams(params, current)
105
172
  // in case of Foo(1), params will be {} (before addRefToWhereIfNecessary was called)
106
173
  if (!Object.keys(params).length) params = where2obj(ref[i].where)
107
174
  _checkAllKeysProvided(params, current)
@@ -114,7 +181,7 @@ function _processSegments(cqn, model, namespace) {
114
181
  }
115
182
  ref[i] = { operation: current.name }
116
183
  if (params) ref[i].args = params
117
- if (current.returns && current.returns.type) one = true
184
+ if (current.returns && current.returns._type) one = true
118
185
  } else if (current.isAssociation) {
119
186
  // > navigation
120
187
  one = !!(current.is2one || ref[i].where)
@@ -161,11 +228,38 @@ function _processSegments(cqn, model, namespace) {
161
228
  const _resolveFrom = from => (from.SELECT ? _resolveFrom(from.SELECT.from) : from)
162
229
 
163
230
  const _checkAllKeysProvided = (params, entity) => {
164
- const keysOfEntity = _keysOf(entity)
231
+ let keysOfEntity
232
+ const isView = !!entity.params
233
+ if (isView) {
234
+ // view with params
235
+ if (params === undefined) {
236
+ throw new Error(`Incorrect call to a view with parameter "${entity.name}"`)
237
+ } else if (Object.keys(params).length === 0) {
238
+ throw new Error('KEY_EXPECTED')
239
+ }
240
+
241
+ keysOfEntity = Object.keys(entity.params)
242
+ } else {
243
+ keysOfEntity = _keysOf(entity)
244
+ }
245
+
165
246
  if (!keysOfEntity) return
166
247
  for (const keyOfEntity of keysOfEntity) {
167
- if (!(keyOfEntity in params))
168
- throw Object.assign(new Error(`Key "${keyOfEntity}" is missing for entity "${entity.name}"`), { status: 400 })
248
+ if (!(keyOfEntity in params)) {
249
+ if (isView && entity.params[keyOfEntity].default) {
250
+ // will be added later?
251
+ continue
252
+ }
253
+
254
+ throw Object.assign(
255
+ new Error(
256
+ `${isView ? 'Parameter' : 'Key'} "${keyOfEntity}" is missing for ${isView ? 'view' : 'entity'} "${
257
+ entity.name
258
+ }"`
259
+ ),
260
+ { status: 400 }
261
+ )
262
+ }
169
263
  }
170
264
  }
171
265
 
@@ -184,7 +184,15 @@
184
184
  / rv:$("$ref"/"$value") {return !TECHNICAL_OPTS.includes(rv) && {from: {ref: [rv]}}}
185
185
  / head:(
186
186
  (identifier filter:(OPEN CLOSE/OPEN args CLOSE)? !segment) / val:segment{return [val]}
187
- ) tail:( '/' p:path {return p} )? {
187
+ )? tail:((s:"/" {return s;}) path?)? {
188
+ tail = tail && tail[1]
189
+ if (!head && !tail) {
190
+ return {from: {ref: ['']}}
191
+ } else if (!head && tail && tail.from) {
192
+ tail.from.ref.unshift('')
193
+ return tail
194
+ }
195
+
188
196
  const [id, filter] = head
189
197
  // minimal: val also as path segment
190
198
  const ref = []
@@ -0,0 +1,39 @@
1
+ const cds = require('../../lib')
2
+
3
+ module.exports = (component, service, target, data, odataReq, upsert) => {
4
+ let query = cds.odata.parse(odataReq, { service })
5
+
6
+ const _target = query.SELECT && query.SELECT.from
7
+
8
+ const {
9
+ SELECT: { one }
10
+ } = query
11
+
12
+ switch (component) {
13
+ case 'CREATE':
14
+ // create
15
+ // error in cases like `POST Books(1)` i.e. `POST` with navigation to single entity
16
+ if (one && !upsert) cds.error('POST not allowed on entity', { code: 400 })
17
+ return INSERT.into(_target).entries(data)
18
+ case 'DELETE':
19
+ if (!one) cds.error('DELETE not allowed on collection', { code: 400 })
20
+
21
+ // eslint-disable-next-line no-case-declarations
22
+ const last = _target.ref && _target.ref[_target.ref.length - 1]
23
+ if (target.elements[last]) {
24
+ // delete simple property
25
+ const ref = { ref: _target.ref.slice(0, _target.ref.length - 1) }
26
+ return UPDATE(ref).data({ [_target.ref[_target.ref.length - 1]]: null })
27
+ } else {
28
+ return DELETE.from(_target)
29
+ }
30
+ case 'UPDATE':
31
+ // eslint-disable-next-line no-throw-literal
32
+ if (!one) throw { statusCode: 400, code: '400', message: `INVALID_${odataReq.getMethod()}` }
33
+ return UPDATE(_target).data(data)
34
+ case 'READ':
35
+ return query
36
+ default:
37
+ return {}
38
+ }
39
+ }