@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
@@ -7,6 +7,7 @@ const replaceManagedData = require('../utils/dollar')
7
7
  const { deepCopyArray } = require('../utils/copy')
8
8
 
9
9
  const onlyKeysRemain = require('../utils/onlyKeysRemain')
10
+ const { getColumns } = require('../../cds-services/services/utils/columns')
10
11
 
11
12
  const _targetEntityDoesNotExist = async req => {
12
13
  const { query } = req
@@ -29,12 +30,13 @@ const _processorFn = req => {
29
30
  const { event, user, timestamp } = req
30
31
  const ts = new Date(timestamp).toISOString()
31
32
 
32
- return ({ row, key, plain }) => {
33
- const categories = plain.categories
34
-
35
- for (const category of categories) {
36
- if (category === '@cds.on.update' || (event === 'CREATE' && category === '@cds.on.insert')) {
33
+ return ({ row, key, plain, isRoot }) => {
34
+ for (const category of plain.categories) {
35
+ if (event === 'CREATE' && category === '@cds.on.insert') {
37
36
  replaceManagedData(row, key, user, ts)
37
+ } else if (category === '@cds.on.update') {
38
+ if (isRoot) replaceManagedData(row, key, user, ts)
39
+ else if (row[key] === '$user' || row[key] === '$now') delete row[key]
38
40
  }
39
41
  }
40
42
  }
@@ -92,16 +94,18 @@ module.exports = cds.service.impl(function () {
92
94
  if (res.length === 0) req.reject(404)
93
95
  }
94
96
 
97
+ // REVISIT: remove block with cds^6 (i.e., update_managed_properties is always true)
95
98
  // no changes, no op (otherwise, @cds.on.update gets new values), but we need to check existence
96
- if (req.event === 'UPDATE' && onlyKeysRemain(req)) {
99
+ if (cds.env.features.update_managed_properties === false && req.event === 'UPDATE' && onlyKeysRemain(req)) {
97
100
  if (await _targetEntityDoesNotExist(req)) req.reject(404)
98
101
  result = req.data
99
102
  }
100
103
 
101
- if (req.event === 'DELETE' && req.target._isSingleton) {
102
- if (!req.target['@odata.singleton.nullable']) req.reject(400, 'SINGLETON_NOT_NULLABLE')
103
-
104
- const singleton = await cds.tx(req).run(SELECT.one(req.target))
104
+ if (req.event in { DELETE: 1, UPDATE: 1 } && req.target && req.target._isSingleton) {
105
+ if (req.event === 'DELETE' && !req.target['@odata.singleton.nullable']) req.reject(400, 'SINGLETON_NOT_NULLABLE')
106
+ const keyColumns = getColumns(req.target, { onlyNames: true, keysOnly: true })
107
+ const selectSingleton = SELECT.one(req.target).columns(keyColumns)
108
+ const singleton = await cds.tx(req).run(selectSingleton)
105
109
  if (!singleton) req.reject(404)
106
110
  req.query.where(singleton)
107
111
  }
@@ -55,7 +55,7 @@ const _handler = async function (req) {
55
55
  }
56
56
 
57
57
  // generate new etag, if UUID
58
- if (C_U_[req.event] && etagElement.type === 'cds.UUID') {
58
+ if (C_U_[req.event] && etagElement.isUUID) {
59
59
  req.data[etagElement.name] = cds.utils.uuid()
60
60
  }
61
61
  }
@@ -34,9 +34,8 @@ const getSimpleCategory = category => {
34
34
  }
35
35
 
36
36
  const rowKeysGenerator = eventName => {
37
+ if (eventName === 'UPDATE') return
37
38
  return (keyNames, row, template) => {
38
- if (eventName === 'UPDATE') return
39
-
40
39
  for (const keyName of keyNames) {
41
40
  if (Object.prototype.hasOwnProperty.call(row, keyName)) {
42
41
  continue
@@ -59,7 +58,10 @@ const _isDraftCoreComputed = (req, element, event) =>
59
58
  element['@Core.Computed'] &&
60
59
  !((event === 'CREATE' && element['@cds.on.insert']) || element['@cds.on.update'])
61
60
 
62
- const _getMediaTypeProperty = element => element['@Core.MediaType'] && element['@Core.MediaType']['=']
61
+ const _isStreamingProperty = (elements, row, property) =>
62
+ Object.values(elements).some(
63
+ element => element['@Core.MediaType'] && element['@Core.MediaType']['='] === property && row[element.name]
64
+ )
63
65
 
64
66
  const _getMediaTypeValue = req =>
65
67
  req._.req &&
@@ -101,15 +103,15 @@ const _processCategory = ({ row, key, category, isRoot, event, value, req, eleme
101
103
  }
102
104
 
103
105
  // set media type from content-type header if streaming
104
- const mtProperty = _getMediaTypeProperty(element)
106
+ const isStreaming = _isStreamingProperty(element.parent.elements, row, key)
105
107
  const mtValue = _getMediaTypeValue(req)
106
- if (category === 'stream' && row[key] && mtProperty && mtValue) row[mtProperty] = mtValue
108
+ if (category === 'stream' && isStreaming && mtValue) row[key] = mtValue
107
109
  }
108
110
 
109
111
  const processorFn = (errors, req) => {
110
112
  const { event } = req
111
113
 
112
- return ({ row, key, element, plain, isRoot, pathSegments }) => {
114
+ return ({ row, key, element, plain, isRoot, path }) => {
113
115
  const categories = plain.categories
114
116
  // ugly pointer passing for sonar
115
117
  const value = { mandatory: false, val: row && row[key] }
@@ -123,7 +125,7 @@ const processorFn = (errors, req) => {
123
125
  }
124
126
 
125
127
  // REVISIT: Convert checkInputConstraints to template mechanism
126
- checkInputConstraints({ element, value: value.val, errors, pathSegments, event })
128
+ checkInputConstraints({ element, value: value.val, errors, path, event })
127
129
  }
128
130
  }
129
131
 
@@ -153,11 +155,11 @@ const _pick = element => {
153
155
  categories.push('associationEffective')
154
156
  }
155
157
 
156
- if (element.key && !DRAFT_COLUMNS_MAP[element.name] && element.type === 'cds.UUID') {
158
+ if (element.key && !DRAFT_COLUMNS_MAP[element.name] && element.isUUID) {
157
159
  categories.push('uuid')
158
160
  }
159
161
 
160
- if (element['@Core.MediaType']) categories.push('stream')
162
+ if (element['@Core.IsMediaType']) categories.push('stream')
161
163
 
162
164
  if (categories.length) return { categories }
163
165
  }
@@ -188,38 +190,34 @@ function _handler(req) {
188
190
  if (template.elements.size === 0) return
189
191
 
190
192
  const errors = []
193
+ const args = {
194
+ processFn: processorFn(errors, req),
195
+ template,
196
+ pathOptions: {
197
+ rowKeysGenerator: rowKeysGenerator(req.event),
198
+ includeKeyValues: true,
199
+ path: []
200
+ }
201
+ }
202
+ if (_isBoundAction(req)) {
203
+ const pathSegment = _getBoundActionBindingParameter(req)
204
+ const keys = req._ && req._.params && req._.params[0]
205
+ if (pathSegment) {
206
+ args.pathOptions.path.push({ key: pathSegment, url: pathSegment })
207
+ }
208
+
209
+ if (keys && 'IsActiveEntity' in keys) {
210
+ args.pathOptions.extraKeys = { IsActiveEntity: keys.IsActiveEntity }
211
+ }
212
+ }
213
+
191
214
  const data = getDataFromCQN(req.query) // REVISIT: req.data should point into req.query
192
215
 
193
216
  enrichDataWithKeysFromWhere(data, req, this)
194
217
 
195
218
  const arrayData = Array.isArray(data) ? data : [data]
196
219
  for (const row of arrayData) {
197
- let pathSegments
198
- let extraKeys
199
-
200
- if (_isBoundAction(req)) {
201
- const pathSegment = _getBoundActionBindingParameter(req)
202
- const keys = req._ && req._.params && req._.params[0]
203
- pathSegments = pathSegment ? [pathSegment] : []
204
-
205
- if (keys && 'IsActiveEntity' in keys) {
206
- extraKeys = { IsActiveEntity: keys.IsActiveEntity }
207
- }
208
- }
209
-
210
- const args = {
211
- processFn: processorFn(errors, req),
212
- row,
213
- template,
214
- pathOptions: {
215
- extraKeys,
216
- rowKeysGenerator: rowKeysGenerator(req.event),
217
- segments: pathSegments,
218
- includeKeyValues: true
219
- }
220
- }
221
-
222
- templateProcessor(args)
220
+ templateProcessor(Object.assign(args, { row }))
223
221
  }
224
222
 
225
223
  setDataFromCQN(req) // REVISIT: req.data should point into req.query
@@ -257,6 +255,7 @@ const _processActionFunctionRow = (row, param, key, errors, event, service) => {
257
255
  const _processActionFunction = (row, eventParams, errors, event, service) => {
258
256
  for (const key in eventParams) {
259
257
  let param = eventParams[key]
258
+ // .type of action/function behaves different to .type of other csn elements
260
259
  const _type = param.type
261
260
  if (!_type && param.items) param = param.items
262
261
  _processActionFunctionRow(row, param, key, errors, event, service)
@@ -292,6 +291,7 @@ function _actionFunctionHandler(req) {
292
291
  // REVISIT: find better solution, maybe compiler?
293
292
  // resolve enums like format, range, etc.
294
293
  for (const param of Object.values(eventParams)) {
294
+ // .type of action/function behaves different to .type of other csn elements
295
295
  const _type = param.type && this.model && this.model.definitions[param.type]
296
296
  if (_type) {
297
297
  param.enum = _type.enum
@@ -1,8 +1,7 @@
1
1
  const cds = require('../../cds')
2
2
  const LOG = cds.log('app')
3
3
  const { getAllKeys } = require('../../cds-services/adapter/odata-v4/odata-to-cqn/utils')
4
-
5
- const DRAFT_COLUMNS = ['IsActiveEntity', 'HasDraftEntity', 'HasActiveEntity']
4
+ const { DRAFT_COLUMNS_MAP } = require('../../common/constants/draft')
6
5
 
7
6
  const _getStaticOrders = req => {
8
7
  const { target: entity, query } = req
@@ -20,7 +19,7 @@ const _getStaticOrders = req => {
20
19
  if (cds.env.features.implicit_sorting !== false && (req.target._isSingleton || query.SELECT.limit)) {
21
20
  const keys = getAllKeys(entity, true)
22
21
  for (const key of keys) {
23
- if (!DRAFT_COLUMNS.includes(key) && !defaultOrders.some(o => o.by['='] === key)) {
22
+ if (!(key in DRAFT_COLUMNS_MAP) && !defaultOrders.some(o => o.by['='] === key)) {
24
23
  ordersFromKeys.push({ by: { '=': key } })
25
24
  }
26
25
  }
@@ -21,14 +21,14 @@ const _getTimeDelta = (target, queryOption) => {
21
21
 
22
22
  if (
23
23
  _isDate(queryOption) ||
24
- Object.values(target.elements).some(el => el['@cds.valid.from'] && el.type === 'cds.Date')
24
+ Object.values(target.elements).some(el => el['@cds.valid.from'] && el._type === 'cds.Date')
25
25
  ) {
26
26
  return 1000 * 60 * 60 * 24
27
27
  }
28
28
 
29
29
  if (
30
30
  _isTimestamp(queryOption) &&
31
- Object.values(target.elements).some(el => el['@cds.valid.from'] && el.type === 'cds.Timestamp')
31
+ Object.values(target.elements).some(el => el['@cds.valid.from'] && el._type === 'cds.Timestamp')
32
32
  ) {
33
33
  return 1
34
34
  }
@@ -2,7 +2,6 @@ const fs = require('fs')
2
2
  const path = require('path')
3
3
 
4
4
  const cds = require('../../cds')
5
- const LOG = cds.log('app')
6
5
 
7
6
  const dirs = (cds.env.i18n && cds.env.i18n.folders) || []
8
7
 
@@ -50,36 +49,8 @@ function init(locale, file) {
50
49
  if (!file) file = findFile(locale)
51
50
  if (!file) return
52
51
 
53
- let raw
54
- try {
55
- raw = fs.readFileSync(file, 'utf-8')
56
- } catch (e) {
57
- if (LOG._warn) {
58
- e.message = `Unable to load file "${file}" for locale "${locale}" due to error: ` + e.message
59
- LOG.warn(e)
60
- }
61
- return
62
- }
63
-
64
- try {
65
- const pairs = raw
66
- .replace(/\r/g, '')
67
- .split(/\n/)
68
- .map(ele => ele.trim())
69
- .filter(ele => ele && !ele.startsWith('#'))
70
- .map(ele => {
71
- const del = ele.indexOf('=')
72
- return [ele.slice(0, del), ele.slice(del + 1)].map(ele => ele.trim())
73
- })
74
- for (const [key, value] of pairs) {
75
- i18ns[locale][key] = value
76
- }
77
- } catch (e) {
78
- if (LOG._warn) {
79
- e.message = `Unable to process file "${file}" for locale "${locale}" due to error: ` + e.message
80
- LOG.warn(e)
81
- }
82
- }
52
+ const props = cds.load.properties(file)
53
+ i18ns[locale] = props
83
54
  }
84
55
 
85
56
  init('default', path.join(__dirname, 'messages.properties'))
@@ -71,7 +71,7 @@ ENTITY_IS_INSERT_ONLY=Entity "{0}" is insert-only
71
71
  ENTITY_IS_READ_ONLY=Entity "{0}" is read-only
72
72
  ENTITY_IS_NOT_CRUD=Entity "{0}" is not {1}
73
73
  ENTITY_IS_NOT_CRUD_VIA_NAVIGATION=Entity "{0}" is not {1} via association "{2}"
74
- ENTITY_IS_AUTOEXPOSED=Entity "{0}" is not explicitely exposed as part of the service
74
+ ENTITY_IS_AUTOEXPOSED=Entity "{0}" is not explicitly exposed as part of the service
75
75
  EXPAND_IS_RESTRICTED=Navigation property "{0}" is not allowed for expand operation
76
76
  EXPAND_COUNT_UNSUPPORTED="$count" is not supported for expand operation
77
77
  ORDERBY_LAMBDA_UNSUPPORTED="$orderby" does not support lambda
@@ -0,0 +1,21 @@
1
+ /*
2
+ * addition for feature toggles
3
+ */
4
+ module.exports = cds => {
5
+ if (!cds.requires.toggles) return (req, res, next) => next()
6
+
7
+ return (req, res, next) => {
8
+ // inject features from dwc header
9
+ const fth = req.headers['dwc-product-configuration']
10
+ if (fth) {
11
+ const { features } = JSON.parse(Buffer.from(fth, 'base64').toString('utf-8'))
12
+ req.features = features
13
+ .filter(f => f.enabled)
14
+ .map(f => f.name)
15
+ .sort((a, b) => a.localeCompare(b))
16
+ Object.freeze(req.features)
17
+ }
18
+
19
+ next()
20
+ }
21
+ }
@@ -29,7 +29,16 @@ const deepCopyObject = obj => {
29
29
  return clone
30
30
  }
31
31
 
32
+ const deepCopy = data => {
33
+ if (Array.isArray(data)) {
34
+ return deepCopyArray(data)
35
+ }
36
+
37
+ return deepCopyObject(data)
38
+ }
39
+
32
40
  module.exports = {
33
41
  deepCopyObject,
34
- deepCopyArray
42
+ deepCopyArray,
43
+ deepCopy
35
44
  }
@@ -1,9 +1,11 @@
1
+ /* eslint-disable complexity */
2
+
1
3
  const cds = require('../../cds')
2
4
  const { SELECT, INSERT, DELETE, UPDATE } = cds.ql
3
5
  const Query = require('../../../../lib/ql/Query')
4
6
 
5
7
  const { resolveView } = require('./resolveView')
6
- const { ensureNoDraftsSuffix } = require('./draft')
8
+ const { ensureNoDraftsSuffix, getDraftColumnsCQNForDraft } = require('./draft')
7
9
  const { flattenStructuredSelect } = require('./structured')
8
10
  const search2cqn4sql = require('./search2cqn4sql')
9
11
  const { getEntityNameFromCQN } = require('./entityFromCqn')
@@ -14,8 +16,12 @@ const { addToWhere } = require('../../common/utils/cqn')
14
16
  const { removeIsActiveEntityRecursively } = require('../../fiori/utils/where')
15
17
  const { addRefToWhereIfNecessary } = require('../../../odata/afterburner')
16
18
  const { addAliasToExpression, PARENT_ALIAS, FOREIGN_ALIAS } = require('../../db/utils/generateAliases')
19
+ const { getColumns } = require('../../cds-services/services/utils/columns')
17
20
 
18
- const OPERATIONS = ['=', '>', '<', '!=', '<>', '>=', '<=', 'like', 'between', 'in', 'not in']
21
+ const OPERATIONS_MAP = ['=', '>', '<', '!=', '<>', '>=', '<=', 'like', 'between', 'in', 'not in'].reduce((acc, cur) => {
22
+ acc[cur] = 1
23
+ return acc
24
+ }, {})
19
25
 
20
26
  const _elementFromRef = (name, entity) => {
21
27
  if (!entity) return
@@ -104,7 +110,7 @@ const convertPathExpressionToWhere = (fromClause, model, options) => {
104
110
  }
105
111
  }
106
112
 
107
- const _convertPathExpressionForInsertOrDelete = (intoClause, model) => {
113
+ const _convertPathExpressionForInsert = (intoClause, model) => {
108
114
  // .into is plain string or csn entity
109
115
  if (typeof intoClause === 'string' || intoClause.name) {
110
116
  return intoClause
@@ -177,10 +183,7 @@ const _getWindColumns = (columns, groupBy, bottomTop) => {
177
183
  return [].concat(columns, _getWindowXpr(groupBy, bottomTop))
178
184
  }
179
185
 
180
- const _convertCountNavigation = (SELECT, model) => {
181
- const entityName = SELECT.from.ref[0].id || SELECT.from.ref[0]
182
- const entity = model.definitions[entityName]
183
-
186
+ const _convertCountNavigation = (SELECT, target) => {
184
187
  const newWhere = []
185
188
  for (let i = 0; i < SELECT.where.length; i++) {
186
189
  const element = SELECT.where[i]
@@ -192,8 +195,8 @@ const _convertCountNavigation = (SELECT, model) => {
192
195
 
193
196
  const navigations = element.args[0].ref
194
197
  const navigationName = navigations[0].id || navigations[0]
195
- if (entity.elements[navigationName]) {
196
- let currentEntity = entity
198
+ if (target.elements[navigationName]) {
199
+ let currentEntity = target
197
200
  let topQuery
198
201
  let lastQuery
199
202
 
@@ -344,7 +347,7 @@ const _getWhereExistsSubSelect = (queryTarget, outerAlias, innerAlias, ref, mode
344
347
  }
345
348
 
346
349
  subSelect.where(queryTarget._relations[navName].join(innerAlias, outerAlias))
347
- if (cds.env.effective.odata.structs) {
350
+ if (cds.env.effective.odata.structs || cds.env.features.ucsn_struct_conversion) {
348
351
  flattenStructuredSelect(subSelect, model)
349
352
  }
350
353
  subSelect.columns([{ val: 1 }])
@@ -459,7 +462,7 @@ const convertWhereExists = (query, model, options, currentTarget) => {
459
462
  }
460
463
 
461
464
  if (element.xpr) {
462
- convertWhereExists({ ...query, where: element.xpr }, model, options) // > recursing into nested {xpr}
465
+ convertWhereExists({ ...query, where: element.xpr }, model, options, currentTarget) // > recursing into nested {xpr}
463
466
  } else if (element === 'exists' && where[i + 1].ref) {
464
467
  if (query.from) {
465
468
  query.from.as = outerAlias
@@ -551,7 +554,7 @@ const _convertOrderByIfSkip = (orderByCQN, index) => {
551
554
 
552
555
  const _convertWhereIfSkip = (whereCQN, index) => {
553
556
  whereCQN.splice(index, 1, '1 = 1')
554
- OPERATIONS.includes(whereCQN[index + 1]) ? whereCQN.splice(index + 1, 2) : whereCQN.splice(index - 2, 2)
557
+ whereCQN[index + 1] in OPERATIONS_MAP ? whereCQN.splice(index + 1, 2) : whereCQN.splice(index - 2, 2)
555
558
  }
556
559
 
557
560
  const _convertOrderByOrWhereCQN = (orderByOrWhereCQN, target, model, alias, processFn) => {
@@ -564,7 +567,7 @@ const _convertOrderByOrWhereCQN = (orderByOrWhereCQN, target, model, alias, proc
564
567
 
565
568
  const _convertOrderByOrWhereIfSkip = (cqn, target, model) => {
566
569
  const alias = cqn.SELECT.from.as
567
- if (cqn.SELECT.orderBy && cqn.SELECT.orderBy.length > 1) {
570
+ if (cqn.SELECT.orderBy && cqn.SELECT.orderBy.length) {
568
571
  _convertOrderByOrWhereCQN(cqn.SELECT.orderBy, target, model, alias, _convertOrderByIfSkip)
569
572
  }
570
573
 
@@ -659,7 +662,11 @@ const _convertPathExpression = (SELECT, model, options = {}) => {
659
662
  // Okra always wants to have the key values, remove once we relax this requirement
660
663
  if (model.definitions[target] && model.definitions[target].keys) {
661
664
  SELECT.columns = Object.keys(model.definitions[target].keys)
662
- .filter(k => !model.definitions[target].keys[k].isAssociation)
665
+ .filter(
666
+ k =>
667
+ !model.definitions[target].keys[k].isAssociation &&
668
+ !columns.find(element => element.ref && element.ref[element.ref.length - 1] === k)
669
+ )
663
670
  .map(k => ({ ref: [k] }))
664
671
  } else SELECT.columns = []
665
672
  }
@@ -679,6 +686,33 @@ const _convertPathExpression = (SELECT, model, options = {}) => {
679
686
  }
680
687
  }
681
688
 
689
+ const _convertToOneEqNullInFilter = (query, target) => {
690
+ // we do not handle join or union
691
+ if (!target) return
692
+
693
+ for (let i = 0; i < query.where.length; i++) {
694
+ const w = query.where[i]
695
+ const w2 = query.where[i + 2]
696
+ if (!w2 || !w.ref || w2.val !== null) {
697
+ continue
698
+ }
699
+ const element = target.elements[w.ref[w.ref.length - 1]]
700
+ if (element && element.is2one && !element.on) {
701
+ const foreignKeys = Object.values(element.parent.elements).filter(e => e._foreignKey4 === element.name)
702
+ const replacedKeys = foreignKeys.reduce((arr, e, idx) => {
703
+ arr.push({ ref: [...w.ref.slice(0, w.ref.length - 1), e.name] }, query.where[i + 1], query.where[i + 2])
704
+ if (idx < foreignKeys.length - 1) {
705
+ arr.push('and')
706
+ }
707
+ return arr
708
+ }, [])
709
+
710
+ query.where.splice(i, 3, '(', ...replacedKeys, ')')
711
+ i += replacedKeys.length + 2
712
+ }
713
+ }
714
+ }
715
+
682
716
  // eslint-disable-next-line complexity
683
717
  const _convertSelect = (query, model, _options) => {
684
718
  const options = Object.assign(
@@ -688,6 +722,7 @@ const _convertSelect = (query, model, _options) => {
688
722
  },
689
723
  _options
690
724
  )
725
+
691
726
  // ensure query is ql enabled
692
727
  if (!(query instanceof Query)) Object.setPrototypeOf(query, Object.getPrototypeOf(SELECT()))
693
728
  if (query.SELECT.from && query.SELECT.from.SELECT) {
@@ -709,8 +744,14 @@ const _convertSelect = (query, model, _options) => {
709
744
 
710
745
  _convertPathExpression(query.SELECT, model, options)
711
746
  rewriteAsterisks(query, model, options)
712
- if (query.SELECT.where && _isCountNavigation(query.SELECT.where)) {
713
- _convertCountNavigation(query.SELECT, model)
747
+ if (query.SELECT.where) {
748
+ const entityName =
749
+ (query.SELECT.from.ref && (query.SELECT.from.ref[0].id || query.SELECT.from.ref[0])) || query.SELECT.from
750
+ const target = model.definitions[entityName]
751
+ if (_isCountNavigation(query.SELECT.where)) {
752
+ _convertCountNavigation(query.SELECT, target)
753
+ }
754
+ _convertToOneEqNullInFilter(query.SELECT, target)
714
755
  }
715
756
 
716
757
  // extract where clause if it is in column expand ref
@@ -727,7 +768,7 @@ const _convertSelect = (query, model, _options) => {
727
768
  search2cqn4sql(query, model, { ...query._searchOptions, ...{ entityName, alias } })
728
769
  }
729
770
 
730
- if (query.SELECT.columns && cds.env.effective.odata.structs) {
771
+ if (query.SELECT.columns && (cds.env.effective.odata.structs || cds.env.features.ucsn_struct_conversion)) {
731
772
  flattenStructuredSelect(query, model)
732
773
  }
733
774
 
@@ -736,30 +777,43 @@ const _convertSelect = (query, model, _options) => {
736
777
  _createWindowCQN(query.SELECT, model)
737
778
  }
738
779
 
780
+ // best-effort ensure columns
781
+ if (options._4db && !query.SELECT.columns) {
782
+ let target = query._target
783
+ if (target && target._unresolved && typeof target.name === 'string') {
784
+ target = model.definitions[ensureNoDraftsSuffix(target.name)] || target
785
+ }
786
+ if (target && !target._unresolved) {
787
+ const cols = getColumns(target, { onlyNames: true })
788
+ query.columns(cols)
789
+ if (target._isDraftEnabled && query._target._unresolved) {
790
+ query.SELECT.columns.push(...getDraftColumnsCQNForDraft(target))
791
+ query.SELECT.columns.push({ ref: ['DraftAdministrativeData_DraftUUID'] })
792
+ }
793
+ }
794
+ }
795
+
739
796
  return query
740
797
  }
741
798
 
742
799
  const _convertInsert = (query, model, options) => {
743
800
  // resolve path expression
744
- const resolvedIntoClause = _convertPathExpressionForInsertOrDelete(query.INSERT.into, model)
801
+ const resolvedIntoClause = _convertPathExpressionForInsert(query.INSERT.into, model)
745
802
 
746
803
  // overwrite only .into, foreign keys are already set
747
- const insert = INSERT.into(resolvedIntoClause)
804
+ // 'a' added as placeholder since its overwritten by Object.assign below
805
+ const insert = INSERT.into('a')
748
806
 
749
807
  // REVISIT flatten structured types, currently its done in SQL builder
750
808
 
751
809
  // We add all previous properties ot the newly created query.
752
810
  // Reason is to not lose the query API functionality
753
- Object.assign(insert.INSERT, query.INSERT, { into: resolvedIntoClause })
754
-
755
- const targetName = insert.INSERT.into.name || insert.INSERT.into
811
+ Object.assign(insert.INSERT, query.INSERT, { into: { ref: [resolvedIntoClause], as: query.INSERT.into.as } })
756
812
 
757
- const target = model.definitions[targetName]
813
+ const target = model.definitions[resolvedIntoClause]
758
814
  if (!target) return insert
759
815
 
760
- const resolvedView = resolveView(insert, model, cds.db)
761
-
762
- return resolvedView
816
+ return resolveView(insert, model, cds.db)
763
817
  }
764
818
 
765
819
  function _modifyNavigationInWhere(whereClause, target) {
@@ -783,6 +837,11 @@ function _modifyNavigationInWhere(whereClause, target) {
783
837
  const _plainDelete = (cqn, model) => {
784
838
  const name = cqn.DELETE.from.name || (cqn.DELETE.from.ref && cqn.DELETE.from.ref[0]) || cqn.DELETE.from
785
839
  const target = model.definitions[name]
840
+
841
+ if (cqn.DELETE.where) {
842
+ _convertToOneEqNullInFilter(cqn.DELETE, target)
843
+ }
844
+
786
845
  if (!target) return cqn
787
846
 
788
847
  return resolveView(cqn, model, cds.db)
@@ -804,9 +863,13 @@ const _convertDelete = (query, model, options) => {
804
863
 
805
864
  if (alias) deleet.DELETE.from = { ref: [target], as: alias }
806
865
  if (where) deleet.where(where)
807
- if (query.DELETE.where) deleet.where(addAliasToExpression(query.DELETE.where, alias))
808
866
 
809
867
  const targetEntity = model.definitions[target]
868
+ if (query.DELETE.where) {
869
+ deleet.where(addAliasToExpression(query.DELETE.where, alias))
870
+ _convertToOneEqNullInFilter(deleet.DELETE, targetEntity)
871
+ }
872
+
810
873
  if (!targetEntity) return deleet
811
874
 
812
875
  return resolveView(deleet, model, cds.db)
@@ -815,6 +878,11 @@ const _convertDelete = (query, model, options) => {
815
878
  function _plainUpdate(cqn, model) {
816
879
  const name = cqn.UPDATE.entity.name || (cqn.UPDATE.entity.ref && cqn.UPDATE.entity.ref[0]) || cqn.UPDATE.entity
817
880
  const target = model.definitions[name]
881
+
882
+ if (cqn.UPDATE.where) {
883
+ _convertToOneEqNullInFilter(cqn.UPDATE, target)
884
+ }
885
+
818
886
  if (!target) return cqn
819
887
 
820
888
  return resolveView(cqn, model, cds.db)
@@ -841,9 +909,12 @@ const _convertUpdate = (query, model, options) => {
841
909
 
842
910
  if (alias) update.UPDATE.entity = { ref: [target], as: alias }
843
911
  if (where) update.where(where)
844
- if (query.UPDATE.where) update.where(addAliasToExpression(query.UPDATE.where, alias))
845
-
846
912
  const targetEntity = model.definitions[target]
913
+ if (query.UPDATE.where) {
914
+ update.where(addAliasToExpression(query.UPDATE.where, alias))
915
+ _convertToOneEqNullInFilter(update.UPDATE, targetEntity)
916
+ }
917
+
847
918
  if (!targetEntity) return update
848
919
 
849
920
  return resolveView(update, model, cds.db)