@sap/cds 5.8.3 → 5.9.1

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 (246) hide show
  1. package/CHANGELOG.md +193 -77
  2. package/app/fiori/preview.js +16 -11
  3. package/app/fiori/routes.js +15 -8
  4. package/app/index.js +1 -1
  5. package/bin/build/buildTaskFactory.js +3 -3
  6. package/bin/build/buildTaskProviderFactory.js +1 -1
  7. package/bin/build/constants.js +1 -1
  8. package/bin/build/provider/buildTaskHandlerEdmx.js +12 -7
  9. package/bin/build/provider/buildTaskHandlerInternal.js +1 -1
  10. package/bin/build/provider/buildTaskProviderInternal.js +8 -2
  11. package/bin/build/provider/hana/2migration.js +27 -24
  12. package/bin/build/provider/hana/index.js +17 -18
  13. package/bin/build/provider/hana/migrationtable.js +9 -10
  14. package/bin/build/provider/java-cf/index.js +4 -5
  15. package/bin/build/provider/node-cf/index.js +99 -6
  16. package/bin/cds.js +17 -18
  17. package/bin/deploy/to-hana/cfUtil.js +16 -19
  18. package/bin/deploy/to-hana/hana.js +7 -24
  19. package/bin/deploy/to-hana/hdiDeployUtil.js +8 -4
  20. package/bin/mtx/in-cds.js +2 -2
  21. package/bin/serve.js +10 -3
  22. package/bin/utils/modules.js +7 -0
  23. package/bin/version.js +56 -3
  24. package/lib/compile/cdsc.js +7 -2
  25. package/lib/compile/etc/_localized.js +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/index.js +3 -0
  31. package/lib/compile/minify.js +16 -2
  32. package/lib/compile/parse.js +2 -2
  33. package/lib/compile/resolve.js +35 -18
  34. package/lib/compile/to/json.js +3 -1
  35. package/lib/compile/to/sql.js +2 -2
  36. package/lib/compile/to/srvinfo.js +4 -2
  37. package/lib/connect/index.js +1 -1
  38. package/lib/core/entities.js +15 -14
  39. package/lib/core/index.js +39 -36
  40. package/lib/core/reflect.js +4 -2
  41. package/lib/deploy.js +114 -127
  42. package/lib/env/defaults.js +1 -0
  43. package/lib/env/index.js +165 -165
  44. package/lib/env/presets.js +1 -0
  45. package/lib/env/requires.js +120 -49
  46. package/lib/index.js +1 -0
  47. package/lib/log/format/kibana.js +2 -2
  48. package/lib/ql/SELECT.js +10 -0
  49. package/lib/ql/parse.js +1 -0
  50. package/lib/req/cds-context.js +4 -1
  51. package/lib/req/context.js +50 -56
  52. package/lib/req/event.js +1 -6
  53. package/lib/req/locale.js +6 -5
  54. package/lib/req/request.js +2 -0
  55. package/lib/req/user.js +7 -5
  56. package/lib/serve/Service-api.js +10 -7
  57. package/lib/serve/Service-dispatch.js +9 -11
  58. package/lib/serve/Service-methods.js +30 -41
  59. package/lib/serve/Transaction.js +10 -7
  60. package/lib/serve/adapters.js +7 -5
  61. package/lib/serve/index.js +24 -12
  62. package/lib/utils/data.js +1 -1
  63. package/lib/utils/index.js +27 -30
  64. package/lib/utils/resources/index.js +101 -0
  65. package/lib/utils/resources/tar.js +71 -0
  66. package/lib/utils/resources/utils.js +11 -0
  67. package/libx/_runtime/audit/Service.js +36 -39
  68. package/libx/_runtime/audit/generic/personal/access.js +3 -4
  69. package/libx/_runtime/audit/generic/personal/modification.js +3 -4
  70. package/libx/_runtime/audit/utils/v2.js +1 -2
  71. package/libx/_runtime/auth/index.js +126 -84
  72. package/libx/_runtime/auth/strategies/JWT.js +12 -19
  73. package/libx/_runtime/auth/strategies/dummy.js +1 -5
  74. package/libx/_runtime/auth/strategies/dwc.js +11 -9
  75. package/libx/_runtime/auth/strategies/mock.js +0 -4
  76. package/libx/_runtime/auth/strategies/{utils/xssec.js → xssecUtils.js} +7 -4
  77. package/libx/_runtime/auth/strategies/xsuaa.js +12 -19
  78. package/libx/_runtime/auth/utils.js +22 -1
  79. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +104 -98
  80. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +2 -2
  81. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -1
  82. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -1
  83. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/language.js +2 -8
  84. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +4 -29
  85. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +2 -1
  86. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +3 -2
  87. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +2 -2
  88. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +4 -6
  89. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +24 -21
  90. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +8 -2
  91. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +2 -0
  92. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/DispatcherCommand.js +2 -6
  93. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -12
  94. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +33 -9
  95. package/libx/_runtime/cds-services/adapter/odata-v4/utils/dispatcherUtils.js +51 -0
  96. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +2 -2
  97. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +10 -3
  98. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +9 -11
  99. package/libx/_runtime/cds-services/adapter/rest/RestRequest.js +6 -3
  100. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +4 -2
  101. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/utils.js +1 -1
  102. package/libx/_runtime/cds-services/adapter/rest/utils/binary.js +1 -1
  103. package/libx/_runtime/cds-services/adapter/rest/utils/key-value-utils.js +2 -3
  104. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +6 -4
  105. package/libx/_runtime/cds-services/adapter/rest/utils/result.js +1 -0
  106. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +8 -5
  107. package/libx/_runtime/cds-services/services/Service.js +40 -0
  108. package/libx/_runtime/cds-services/services/utils/columns.js +4 -3
  109. package/libx/_runtime/cds-services/services/utils/compareJson.js +4 -4
  110. package/libx/_runtime/cds-services/services/utils/differ.js +3 -3
  111. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +4 -4
  112. package/libx/_runtime/cds-services/services/utils/restrictions.js +78 -0
  113. package/libx/_runtime/cds-services/util/assert.js +20 -14
  114. package/libx/_runtime/cds.js +9 -1
  115. package/libx/_runtime/common/aspects/any.js +5 -0
  116. package/libx/_runtime/common/aspects/entity.js +25 -7
  117. package/libx/_runtime/common/aspects/utils.js +2 -2
  118. package/libx/_runtime/common/composition/data.js +6 -0
  119. package/libx/_runtime/common/composition/insert.js +3 -2
  120. package/libx/_runtime/common/composition/tree.js +4 -10
  121. package/libx/_runtime/common/composition/update.js +4 -4
  122. package/libx/_runtime/common/constants/draft.js +29 -26
  123. package/libx/_runtime/common/error/constants.js +2 -2
  124. package/libx/_runtime/common/error/frontend.js +7 -15
  125. package/libx/_runtime/common/generic/auth/capabilities.js +59 -0
  126. package/libx/_runtime/common/generic/auth/constants.js +20 -0
  127. package/libx/_runtime/common/generic/auth/expand.js +54 -0
  128. package/libx/_runtime/common/generic/auth/index.js +32 -0
  129. package/libx/_runtime/common/generic/auth/insertOnly.js +15 -0
  130. package/libx/_runtime/common/generic/auth/readOnly.js +26 -0
  131. package/libx/_runtime/common/generic/auth/requires.js +34 -0
  132. package/libx/_runtime/common/generic/auth/restrict.js +296 -0
  133. package/libx/_runtime/common/generic/auth/utils.js +213 -0
  134. package/libx/_runtime/common/generic/crud.js +14 -10
  135. package/libx/_runtime/common/generic/etag.js +1 -1
  136. package/libx/_runtime/common/generic/input.js +35 -35
  137. package/libx/_runtime/common/generic/sorting.js +2 -3
  138. package/libx/_runtime/common/generic/temporal.js +2 -2
  139. package/libx/_runtime/common/i18n/messages.properties +1 -1
  140. package/libx/_runtime/common/toggles/handler.js +21 -0
  141. package/libx/_runtime/common/utils/copy.js +10 -1
  142. package/libx/_runtime/common/utils/cqn2cqn4sql.js +100 -29
  143. package/libx/_runtime/common/utils/csn.js +63 -1
  144. package/libx/_runtime/common/utils/dollar.js +10 -1
  145. package/libx/_runtime/common/utils/draft.js +46 -7
  146. package/libx/_runtime/common/utils/entityFromCqn.js +13 -9
  147. package/libx/_runtime/common/utils/extensibilityUtils.js +18 -0
  148. package/libx/_runtime/common/utils/foreignKeyPropagations.js +88 -104
  149. package/libx/_runtime/common/utils/generateOnCond.js +5 -2
  150. package/libx/_runtime/common/utils/quotingStyles.js +2 -0
  151. package/libx/_runtime/common/utils/resolveStructured.js +25 -9
  152. package/libx/_runtime/common/utils/resolveView.js +4 -1
  153. package/libx/_runtime/common/utils/rewriteAsterisks.js +3 -16
  154. package/libx/_runtime/common/utils/structured.js +33 -37
  155. package/libx/_runtime/common/utils/template.js +17 -8
  156. package/libx/_runtime/common/utils/templateProcessor.js +28 -28
  157. package/libx/_runtime/db/data-conversion/post-processing.js +118 -412
  158. package/libx/_runtime/db/expand/expandCQNToJoin.js +45 -41
  159. package/libx/_runtime/db/expand/rawToExpanded.js +29 -8
  160. package/libx/_runtime/db/generic/index.js +1 -3
  161. package/libx/_runtime/db/generic/input.js +5 -10
  162. package/libx/_runtime/db/generic/rewrite.js +5 -2
  163. package/libx/_runtime/db/generic/structured.js +2 -2
  164. package/libx/_runtime/db/query/delete.js +2 -2
  165. package/libx/_runtime/db/query/insert.js +1 -1
  166. package/libx/_runtime/db/query/update.js +9 -14
  167. package/libx/_runtime/db/sql-builder/CreateBuilder.js +4 -3
  168. package/libx/_runtime/db/sql-builder/FunctionBuilder.js +8 -8
  169. package/libx/_runtime/db/sql-builder/InsertBuilder.js +14 -1
  170. package/libx/_runtime/db/sql-builder/SelectBuilder.js +3 -2
  171. package/libx/_runtime/db/sql-builder/dataTypes.js +3 -3
  172. package/libx/_runtime/db/utils/columns.js +3 -3
  173. package/libx/_runtime/db/utils/normalizeTimeData.js +2 -2
  174. package/libx/_runtime/db/utils/propagateForeignKeys.js +6 -2
  175. package/libx/_runtime/extensibility/mps/index.js +5 -0
  176. package/libx/_runtime/extensibility/mps/service.js +111 -0
  177. package/libx/_runtime/extensibility/mps/tar.js +42 -0
  178. package/libx/_runtime/extensibility/mps/utils.js +11 -0
  179. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformREAD.js +0 -0
  180. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformRESULT.js +17 -5
  181. package/libx/_runtime/{fiori → extensibility}/uiflex/handler/transformWRITE.js +1 -0
  182. package/libx/_runtime/extensibility/uiflex/index.js +54 -0
  183. package/libx/_runtime/extensibility/uiflex/service.js +276 -0
  184. package/libx/_runtime/{fiori → extensibility}/uiflex/utils.js +22 -7
  185. package/libx/_runtime/fiori/generic/activate.js +2 -2
  186. package/libx/_runtime/fiori/generic/before.js +4 -4
  187. package/libx/_runtime/fiori/generic/new.js +3 -3
  188. package/libx/_runtime/fiori/generic/patch.js +1 -1
  189. package/libx/_runtime/fiori/generic/read.js +58 -66
  190. package/libx/_runtime/fiori/generic/readOverDraft.js +71 -16
  191. package/libx/_runtime/fiori/utils/handler.js +6 -13
  192. package/libx/_runtime/fiori/utils/where.js +6 -5
  193. package/libx/_runtime/hana/Service.js +4 -10
  194. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +1 -1
  195. package/libx/_runtime/hana/driver.js +2 -2
  196. package/libx/_runtime/hana/execute.js +27 -74
  197. package/libx/_runtime/hana/pool.js +1 -1
  198. package/libx/_runtime/hana/streaming.js +2 -1
  199. package/libx/_runtime/index.js +6 -6
  200. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +5 -21
  201. package/libx/_runtime/messaging/Outbox.js +2 -2
  202. package/libx/_runtime/messaging/common-utils/AMQPClient.js +4 -14
  203. package/libx/_runtime/messaging/common-utils/connections.js +5 -7
  204. package/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +30 -0
  205. package/libx/_runtime/messaging/enterprise-messaging-shared.js +2 -1
  206. package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +36 -30
  207. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +19 -12
  208. package/libx/_runtime/messaging/enterprise-messaging.js +8 -8
  209. package/libx/_runtime/messaging/file-based.js +5 -5
  210. package/libx/_runtime/messaging/message-queuing.js +14 -12
  211. package/libx/_runtime/messaging/outbox/utils.js +18 -19
  212. package/libx/_runtime/messaging/redis-messaging.js +91 -0
  213. package/libx/_runtime/messaging/service.js +8 -6
  214. package/libx/_runtime/remote/Service.js +44 -8
  215. package/libx/_runtime/remote/utils/client.js +24 -19
  216. package/libx/_runtime/remote/utils/data.js +11 -11
  217. package/libx/_runtime/sqlite/Service.js +6 -9
  218. package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +5 -2
  219. package/libx/_runtime/types/api.js +10 -2
  220. package/libx/common/utils/ucsn.js +109 -0
  221. package/libx/gql/resolvers/crud/update.js +5 -0
  222. package/libx/gql/resolvers/parse/ast2cqn/columns.js +3 -1
  223. package/libx/gql/schema/typeDefMap.js +2 -2
  224. package/libx/odata/afterburner.js +110 -16
  225. package/libx/odata/cqn2odata.js +24 -27
  226. package/libx/odata/grammar.pegjs +9 -1
  227. package/libx/odata/parseToCqn.js +39 -0
  228. package/libx/odata/parser.js +1 -1
  229. package/libx/rest/RestAdapter.js +9 -1
  230. package/libx/rest/middleware/input.js +54 -0
  231. package/libx/rest/middleware/operation.js +14 -1
  232. package/libx/rest/middleware/parse.js +11 -7
  233. package/package.json +2 -2
  234. package/server.js +34 -19
  235. package/srv/audit-log.cds +2 -2
  236. package/srv/flex.cds +8 -2
  237. package/srv/flex.js +1 -1
  238. package/srv/mps.cds +23 -0
  239. package/srv/mps.js +1 -0
  240. package/libx/_runtime/auth/strategies/utils/uaa.js +0 -21
  241. package/libx/_runtime/common/generic/auth.js +0 -874
  242. package/libx/_runtime/common/toggles/alpha.js +0 -43
  243. package/libx/_runtime/db/generic/arrayed.js +0 -33
  244. package/libx/_runtime/fiori/uiflex/index.js +0 -35
  245. package/libx/_runtime/fiori/uiflex/service.js +0 -150
  246. 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
+ }
@@ -29,6 +29,11 @@ module.exports = async (service, entityFQN, selection) => {
29
29
  cds.context = tx
30
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)
31
31
  resultBeforeUpdate = await tx.run(queryBeforeUpdate)
32
+
33
+ if (resultBeforeUpdate.length === 0) {
34
+ return []
35
+ }
36
+
32
37
  return tx.run(query)
33
38
  })
34
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
 
@@ -83,14 +83,10 @@ function _args(args) {
83
83
 
84
84
  if (hasValidProps(cur, 'func', 'args')) {
85
85
  res.push(`${cur.func}(${_args(cur.args)})`)
86
- }
87
-
88
- if (hasValidProps(cur, 'ref')) {
89
- res.push(cur.ref.join('/'))
90
- }
91
-
92
- if (hasValidProps(cur, 'val')) {
93
- res.push(formatVal(cur.val))
86
+ } else if (hasValidProps(cur, 'ref')) {
87
+ res.push(_format(cur))
88
+ } else if (hasValidProps(cur, 'val')) {
89
+ res.push(_format(cur))
94
90
  }
95
91
  }
96
92
 
@@ -98,13 +94,13 @@ function _args(args) {
98
94
  }
99
95
 
100
96
  const _in = (column, /* in */ collection, target, kind, isLambda) => {
101
- const ref = isLambda ? [LAMBDA_VARIABLE, ...column.ref].join('/') : column.ref.join('/')
97
+ const ref = _format(column, null, target, kind, isLambda)
102
98
  // { val: [ 1, 2, 3 ] } or { list: [ { val: 1}, { val: 2}, { val: 3} ] }
103
99
  const values = collection.val || collection.list
104
100
  if (values && values.length) {
105
101
  // REVISIT: what about OData `in` operator?
106
- const expressions = values.map(value => `${ref} eq ${_format(value, ref, target, kind, isLambda)}`)
107
- return expressions.join(' or ')
102
+ const expressions = values.map(value => `${ref}%20eq%20${_format(value, ref, target, kind, isLambda)}`)
103
+ return expressions.join('%20or%20')
108
104
  }
109
105
  }
110
106
 
@@ -119,9 +115,10 @@ const _odataV2Func = (func, args) => {
119
115
  }
120
116
 
121
117
  const _format = (cur, element, target, kind, isLambda) => {
122
- if (typeof cur !== 'object') return formatVal(cur, element, target, kind)
123
- if (hasValidProps(cur, 'ref')) return isLambda ? [LAMBDA_VARIABLE, ...cur.ref].join('/') : cur.ref.join('/')
124
- if (hasValidProps(cur, 'val')) return formatVal(cur.val, element, target, kind)
118
+ if (typeof cur !== 'object') return encodeURIComponent(formatVal(cur, element, target, kind))
119
+ if (hasValidProps(cur, 'ref'))
120
+ return encodeURIComponent(isLambda ? [LAMBDA_VARIABLE, ...cur.ref].join('/') : cur.ref.join('/'))
121
+ if (hasValidProps(cur, 'val')) return encodeURIComponent(formatVal(cur.val, element, target, kind))
125
122
  if (hasValidProps(cur, 'xpr')) return `(${_xpr(cur.xpr, target, kind, isLambda)})`
126
123
  // REVISIT: How to detect the types for all functions?
127
124
  if (hasValidProps(cur, 'func', 'args')) {
@@ -156,7 +153,7 @@ function _xpr(expr, target, kind, isLambda) {
156
153
  } else if (isOrIsNotValue) {
157
154
  // REVISIT: "is" only used for null values?
158
155
  const operator = isOrIsNotValue[1] /* 'is not' */ ? 'ne' : 'eq'
159
- res.push(...[operator, formatVal(isOrIsNotValue[2])])
156
+ res.push(...[operator, _format({ val: isOrIsNotValue[2] })])
160
157
  } else if (cur === 'between') {
161
158
  // ref gt low.val and ref lt high.val
162
159
  const between = [expr[i - 1], 'gt', expr[i + 1], 'and', expr[i - 1], 'lt', expr[i + 3]]
@@ -188,7 +185,7 @@ function _xpr(expr, target, kind, isLambda) {
188
185
  }
189
186
  }
190
187
 
191
- return res.join(' ')
188
+ return res.join('%20')
192
189
  }
193
190
 
194
191
  const _keysOfWhere = (where, kind, target) => {
@@ -202,11 +199,11 @@ const _keysOfWhere = (where, kind, target) => {
202
199
  const res = []
203
200
  for (const cur of where) {
204
201
  if (hasValidProps(cur, 'ref')) {
205
- res.push(cur.ref.join('/'))
202
+ res.push(_format(cur))
206
203
  } else if (hasValidProps(cur, 'val')) {
207
204
  // find previous ref
208
205
  const element = res[res.length - 2]
209
- res.push(formatVal(cur.val, element, target, kind))
206
+ res.push(_format(cur, element, target, kind))
210
207
  } else if (cur === 'and') {
211
208
  res.push(',')
212
209
  } else {
@@ -267,15 +264,15 @@ const _parseColumnsV2 = (columns, prefix = []) => {
267
264
 
268
265
  if (hasValidProps(column, 'expand')) {
269
266
  const parsed = _parseColumnsV2(column.expand, [refName])
270
- expand.push(refName, ...parsed.expand)
267
+ expand.push(encodeURIComponent(refName), ...parsed.expand)
271
268
  select.push(...parsed.select)
272
269
  } else {
273
- select.push(refName)
270
+ select.push(encodeURIComponent(refName))
274
271
  }
275
272
  }
276
273
 
277
274
  if (column === '*') {
278
- select.push(`${prefix.join('/')}/*`)
275
+ select.push(encodeURIComponent(`${prefix.join('/')}/*`))
279
276
  }
280
277
  }
281
278
 
@@ -288,7 +285,7 @@ const _parseColumns = columns => {
288
285
 
289
286
  for (const column of columns) {
290
287
  if (hasValidProps(column, 'ref')) {
291
- let refName = column.ref.join('/')
288
+ let refName = _format(column)
292
289
  if (hasValidProps(column, 'expand')) {
293
290
  // REVISIT: incomplete, see test Foo?$expand=invoices($count=true;$expand=item($search="some"))
294
291
  if (!columns.some(c => !c.expand)) select.push(refName)
@@ -350,16 +347,16 @@ function $orderBy(orderBy) {
350
347
 
351
348
  for (const cur of orderBy) {
352
349
  if (hasValidProps(cur, 'ref', 'sort')) {
353
- res.push(cur.ref.join('/') + ' ' + cur.sort)
350
+ res.push(_format(cur) + '%20' + cur.sort)
354
351
  continue
355
352
  }
356
353
 
357
354
  if (hasValidProps(cur, 'ref')) {
358
- res.push(cur.ref.join('/'))
355
+ res.push(_format(cur))
359
356
  }
360
357
 
361
358
  if (hasValidProps(cur, 'func', 'sort')) {
362
- res.push(`${cur.func}(${_args(cur.args)})` + ' ' + cur.sort)
359
+ res.push(`${cur.func}(${_args(cur.args)})` + '%20' + cur.sort)
363
360
  continue
364
361
  }
365
362
 
@@ -382,7 +379,7 @@ function parseSearch(search) {
382
379
 
383
380
  if (hasValidProps(cur, 'val')) {
384
381
  // search term must not be formatted
385
- res.push(`"${cur.val}"`)
382
+ res.push(`"${encodeURIComponent(cur.val)}"`)
386
383
  }
387
384
 
388
385
  if (typeof cur === 'string') {
@@ -398,7 +395,7 @@ function parseSearch(search) {
398
395
  }
399
396
 
400
397
  function $search(search, kind) {
401
- const expr = parseSearch(search).join(' ').replace('( ', '(').replace(' )', ')')
398
+ const expr = parseSearch(search).join('%20').replace('(%20', '(').replace('%20)', ')')
402
399
 
403
400
  if (expr) {
404
401
  // odata-v2 may support custom query option "search"
@@ -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
+ }