@sap/cds 5.4.6 → 5.5.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 (228) hide show
  1. package/CHANGELOG.md +208 -2
  2. package/apis/ql.d.ts +17 -15
  3. package/app/index.js +1 -1
  4. package/bin/build/buildTaskEngine.js +26 -42
  5. package/bin/build/buildTaskFactory.js +6 -10
  6. package/bin/build/buildTaskHandler.js +2 -4
  7. package/bin/build/buildTaskProvider.js +3 -1
  8. package/bin/build/buildTaskProviderFactory.js +9 -15
  9. package/bin/build/constants.js +15 -3
  10. package/bin/build/index.js +5 -4
  11. package/bin/build/mtaUtil.js +8 -11
  12. package/bin/build/provider/buildTaskHandlerEdmx.js +63 -6
  13. package/bin/build/provider/buildTaskHandlerInternal.js +2 -34
  14. package/bin/build/provider/buildTaskProviderInternal.js +16 -42
  15. package/bin/build/provider/fiori/index.js +13 -24
  16. package/bin/build/provider/hana/2migration.js +17 -15
  17. package/bin/build/provider/hana/2tabledata.js +52 -48
  18. package/bin/build/provider/hana/index.js +27 -25
  19. package/bin/build/provider/hana/migrationtable.js +91 -67
  20. package/bin/build/provider/java-cf/index.js +14 -24
  21. package/bin/build/provider/mtx/index.js +12 -14
  22. package/bin/build/provider/node-cf/index.js +18 -32
  23. package/bin/cds.js +5 -5
  24. package/bin/serve.js +29 -23
  25. package/bin/version.js +0 -1
  26. package/lib/compile/etc/_localized.js +4 -9
  27. package/lib/compile/for/sql.js +5 -2
  28. package/lib/compile/parse.js +25 -17
  29. package/lib/compile/to/srvinfo.js +2 -1
  30. package/lib/connect/bindings.js +2 -1
  31. package/lib/connect/index.js +48 -49
  32. package/lib/core/classes.js +1 -1
  33. package/lib/core/reflect.js +10 -2
  34. package/lib/deploy.js +26 -23
  35. package/lib/env/defaults.js +13 -6
  36. package/lib/env/index.js +73 -78
  37. package/lib/env/requires.js +38 -19
  38. package/lib/index.js +9 -10
  39. package/lib/lazy.js +2 -2
  40. package/lib/log/index.js +33 -45
  41. package/lib/log/service/index.js +2 -2
  42. package/lib/ql/CREATE.js +14 -9
  43. package/lib/ql/DELETE.js +6 -5
  44. package/lib/ql/DROP.js +12 -9
  45. package/lib/ql/INSERT.js +40 -16
  46. package/lib/ql/Query.js +67 -40
  47. package/lib/ql/SELECT.js +162 -127
  48. package/lib/ql/UPDATE.js +74 -42
  49. package/lib/ql/Whereable.js +77 -87
  50. package/lib/ql/index.js +36 -24
  51. package/lib/ql/parse.js +35 -0
  52. package/lib/req/context.js +44 -8
  53. package/lib/req/locale.js +7 -7
  54. package/lib/serve/Service-api.js +21 -14
  55. package/lib/serve/Service-dispatch.js +28 -12
  56. package/lib/serve/Transaction.js +22 -10
  57. package/lib/serve/index.js +16 -11
  58. package/lib/utils/axios.js +23 -16
  59. package/lib/utils/data.js +35 -0
  60. package/lib/utils/tests.js +27 -18
  61. package/libx/_runtime/audit/generic/personal/access.js +81 -0
  62. package/libx/_runtime/audit/generic/personal/constants.js +4 -0
  63. package/libx/_runtime/audit/generic/personal/index.js +50 -0
  64. package/libx/_runtime/audit/generic/personal/modification.js +138 -0
  65. package/libx/_runtime/audit/generic/personal/utils.js +186 -0
  66. package/libx/_runtime/audit/utils/v2.js +10 -4
  67. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +5 -5
  68. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +6 -7
  69. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +5 -7
  70. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +5 -7
  71. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +2 -3
  72. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +4 -0
  73. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +7 -4
  74. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +59 -8
  75. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +11 -1
  76. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +6 -10
  77. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +3 -46
  78. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +2 -5
  79. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/createToCQN.js +2 -2
  80. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/deleteToCQN.js +4 -3
  81. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -2
  82. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +0 -1
  83. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +1 -1
  84. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/updateToCQN.js +2 -2
  85. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +16 -18
  86. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/EdmEntityType.js +6 -3
  87. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/format/RepresentationKind.js +4 -1
  88. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/core/OdataRequest.js +1 -0
  89. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/SerializerFactory.js +15 -2
  90. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/OperationValidator.js +1 -0
  91. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
  92. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +8 -1
  93. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +6 -1
  94. package/libx/_runtime/cds-services/adapter/odata-v4/utils/omitValues.js +12 -5
  95. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +1 -7
  96. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +7 -7
  97. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +14 -18
  98. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +13 -13
  99. package/libx/_runtime/cds-services/adapter/rest/handlers/create.js +0 -1
  100. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +2 -1
  101. package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +2 -2
  102. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +2 -4
  103. package/libx/_runtime/cds-services/adapter/rest/utils/result.js +4 -2
  104. package/libx/_runtime/cds-services/services/Service.js +40 -5
  105. package/libx/_runtime/cds-services/services/utils/columns.js +13 -7
  106. package/libx/_runtime/cds-services/services/utils/compareJson.js +88 -4
  107. package/libx/_runtime/cds-services/services/utils/differ.js +24 -6
  108. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -2
  109. package/libx/_runtime/common/composition/data.js +44 -55
  110. package/libx/_runtime/common/composition/delete.js +97 -71
  111. package/libx/_runtime/common/composition/index.js +2 -1
  112. package/libx/_runtime/common/composition/insert.js +34 -11
  113. package/libx/_runtime/common/composition/tree.js +119 -92
  114. package/libx/_runtime/common/composition/update.js +4 -1
  115. package/libx/_runtime/common/composition/utils.js +1 -3
  116. package/libx/_runtime/common/constants/draft.js +12 -1
  117. package/libx/_runtime/common/generic/auth.js +6 -22
  118. package/libx/_runtime/common/generic/crud.js +14 -13
  119. package/libx/_runtime/common/generic/input.js +23 -26
  120. package/libx/_runtime/common/generic/put.js +1 -1
  121. package/libx/_runtime/common/generic/sorting.js +16 -16
  122. package/libx/_runtime/common/i18n/index.js +1 -1
  123. package/libx/_runtime/common/i18n/messages.properties +4 -0
  124. package/libx/_runtime/common/utils/backlinks.js +12 -5
  125. package/libx/_runtime/common/utils/cqn.js +6 -1
  126. package/libx/_runtime/common/utils/cqn2cqn4sql.js +102 -101
  127. package/libx/_runtime/common/utils/csn.js +47 -4
  128. package/libx/_runtime/common/utils/data.js +0 -37
  129. package/libx/_runtime/common/utils/enrichWithKeysFromWhere.js +1 -1
  130. package/libx/_runtime/common/utils/entityFromCqn.js +7 -24
  131. package/libx/_runtime/common/utils/foreignKeyPropagations.js +39 -7
  132. package/libx/_runtime/common/utils/generateOnCond.js +11 -12
  133. package/libx/_runtime/common/utils/onlyKeysRemain.js +10 -0
  134. package/libx/_runtime/common/utils/path.js +35 -0
  135. package/libx/_runtime/common/utils/postProcessing.js +86 -0
  136. package/libx/_runtime/common/utils/quotingStyles.js +37 -26
  137. package/libx/_runtime/common/utils/resolveView.js +223 -171
  138. package/libx/_runtime/common/utils/rewriteAsterisk.js +46 -26
  139. package/libx/_runtime/common/utils/structured.js +6 -12
  140. package/libx/_runtime/common/utils/template.js +10 -5
  141. package/libx/_runtime/common/utils/templateDelimiter.js +1 -0
  142. package/libx/_runtime/common/utils/templateProcessor.js +22 -30
  143. package/libx/_runtime/common/utils/union.js +31 -0
  144. package/libx/_runtime/common/utils/unionCqnTemplate.js +184 -0
  145. package/libx/_runtime/db/Service.js +1 -1
  146. package/libx/_runtime/db/data-conversion/timestamp.js +2 -9
  147. package/libx/_runtime/db/expand/expandCQNToJoin.js +204 -297
  148. package/libx/_runtime/db/expand/index.js +3 -3
  149. package/libx/_runtime/db/expand/rawToExpanded.js +36 -7
  150. package/libx/_runtime/db/generic/index.js +1 -1
  151. package/libx/_runtime/db/generic/input.js +5 -7
  152. package/libx/_runtime/db/generic/integrity.js +1 -1
  153. package/libx/_runtime/db/generic/rewrite.js +2 -10
  154. package/libx/_runtime/db/generic/update.js +13 -5
  155. package/libx/_runtime/db/generic/virtual.js +22 -58
  156. package/libx/_runtime/db/query/delete.js +7 -4
  157. package/libx/_runtime/db/query/insert.js +6 -4
  158. package/libx/_runtime/db/query/read.js +13 -20
  159. package/libx/_runtime/db/query/run.js +4 -1
  160. package/libx/_runtime/db/query/update.js +5 -4
  161. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +35 -2
  162. package/libx/_runtime/db/sql-builder/FunctionBuilder.js +17 -2
  163. package/libx/_runtime/db/sql-builder/InsertBuilder.js +6 -5
  164. package/libx/_runtime/db/sql-builder/ReferenceBuilder.js +10 -0
  165. package/libx/_runtime/db/sql-builder/SelectBuilder.js +35 -24
  166. package/libx/_runtime/db/sql-builder/UpdateBuilder.js +14 -4
  167. package/libx/_runtime/db/sql-builder/arrayed.js +4 -0
  168. package/libx/_runtime/db/utils/deep.js +8 -0
  169. package/libx/_runtime/db/utils/generateAliases.js +2 -1
  170. package/libx/_runtime/fiori/generic/activate.js +19 -15
  171. package/libx/_runtime/fiori/generic/before.js +3 -11
  172. package/libx/_runtime/fiori/generic/cancel.js +1 -1
  173. package/libx/_runtime/fiori/generic/delete.js +3 -1
  174. package/libx/_runtime/fiori/generic/edit.js +12 -2
  175. package/libx/_runtime/fiori/generic/new.js +5 -5
  176. package/libx/_runtime/fiori/generic/patch.js +0 -18
  177. package/libx/_runtime/fiori/generic/read.js +241 -189
  178. package/libx/_runtime/fiori/utils/delete.js +36 -7
  179. package/libx/_runtime/fiori/utils/handler.js +43 -44
  180. package/libx/_runtime/fiori/utils/where.js +30 -15
  181. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +4 -6
  182. package/libx/_runtime/hana/execute.js +2 -2
  183. package/libx/_runtime/hana/localized.js +4 -4
  184. package/libx/_runtime/hana/pool.js +29 -14
  185. package/libx/_runtime/hana/search2cqn4sql.js +2 -1
  186. package/libx/_runtime/hana/searchToContains.js +18 -14
  187. package/libx/_runtime/index.js +0 -5
  188. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +13 -5
  189. package/libx/_runtime/messaging/common-utils/naming-conventions.js +4 -1
  190. package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +31 -19
  191. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +1 -2
  192. package/libx/_runtime/messaging/enterprise-messaging.js +6 -4
  193. package/libx/_runtime/messaging/service.js +7 -6
  194. package/libx/_runtime/odata/cqn2odata.js +110 -43
  195. package/libx/_runtime/odata/index.js +26 -48
  196. package/libx/_runtime/odata/odata2cqn.js +1 -6154
  197. package/libx/_runtime/odata/odata2cqn.pegjs +559 -0
  198. package/libx/_runtime/odata/readToCqn.js +94 -64
  199. package/libx/_runtime/remote/Service.js +74 -21
  200. package/libx/_runtime/remote/cqn2odata/index.js +1 -5
  201. package/libx/_runtime/remote/utils/client.js +24 -101
  202. package/libx/_runtime/remote/utils/dataConversion.js +27 -12
  203. package/libx/_runtime/sqlite/Service.js +3 -5
  204. package/libx/_runtime/sqlite/execute.js +23 -24
  205. package/libx/_runtime/sqlite/localized.js +12 -7
  206. package/libx/_runtime/types/api.js +10 -0
  207. package/package.json +1 -1
  208. package/server.js +16 -2
  209. package/lib/ql/grammar.pegjs +0 -208
  210. package/lib/ql/parser.js +0 -1
  211. package/lib/ql/rt/DELETE.js +0 -29
  212. package/lib/ql/rt/INSERT.js +0 -23
  213. package/lib/ql/rt/Query.js +0 -84
  214. package/lib/ql/rt/SELECT.js +0 -174
  215. package/lib/ql/rt/UPDATE.js +0 -119
  216. package/lib/ql/rt/_helpers.js +0 -91
  217. package/lib/ql/rt/index.js +0 -32
  218. package/libx/_runtime/audit/generic/personal.js +0 -260
  219. package/libx/_runtime/cds-services/statements/BaseStatement.js +0 -72
  220. package/libx/_runtime/cds-services/statements/Create.js +0 -57
  221. package/libx/_runtime/cds-services/statements/Delete.js +0 -33
  222. package/libx/_runtime/cds-services/statements/Drop.js +0 -42
  223. package/libx/_runtime/cds-services/statements/Insert.js +0 -201
  224. package/libx/_runtime/cds-services/statements/Select.js +0 -826
  225. package/libx/_runtime/cds-services/statements/Update.js +0 -181
  226. package/libx/_runtime/cds-services/statements/Where.js +0 -726
  227. package/libx/_runtime/cds-services/statements/index.js +0 -25
  228. package/libx/_runtime/common/generic/resolve-mock.js +0 -9
@@ -27,11 +27,8 @@ const _createNavGroupBy = pathSegments => {
27
27
  let name = pathSegments[0].getNavigationProperty().getName()
28
28
  for (let i = 1; i < pathSegments.length; i++) {
29
29
  name += pathSegments[i].getProperty()
30
- ? '.{' + pathSegments[i].getProperty().getName()
31
- : '.{' + pathSegments[i].getNavigationProperty().getName()
32
- }
33
- for (let i = 1; i < pathSegments.length; i++) {
34
- name += '}'
30
+ ? '.' + pathSegments[i].getProperty().getName()
31
+ : '.' + pathSegments[i].getNavigationProperty().getName()
35
32
  }
36
33
  return name
37
34
  }
@@ -1,5 +1,5 @@
1
- // REVISIT: should be cds.ql
2
- const { INSERT } = require('../../../statements')
1
+ const cds = require('../../../../cds')
2
+ const { INSERT } = cds.ql
3
3
 
4
4
  const { getFeatureNotSupportedError } = require('../../../util/errors')
5
5
  const { convertUrlPathToCqn } = require('./utils')
@@ -1,13 +1,14 @@
1
- // REVISIT: should be cds.ql
2
- const { UPDATE, DELETE } = require('../../../statements')
1
+ const cds = require('../../../../cds')
2
+ const { UPDATE, DELETE } = cds.ql
3
3
 
4
4
  const { getFeatureNotSupportedError } = require('../../../util/errors')
5
5
  const { convertUrlPathToCqn } = require('./utils')
6
6
 
7
- const { ENTITY, NAVIGATION_TO_ONE } = require('../okra/odata-server').uri.UriResource.ResourceKind
7
+ const { ENTITY, NAVIGATION_TO_ONE, SINGLETON } = require('../okra/odata-server').uri.UriResource.ResourceKind
8
8
 
9
9
  const SUPPORTED_KINDS = {
10
10
  [ENTITY]: 1,
11
+ [SINGLETON]: 1,
11
12
  [NAVIGATION_TO_ONE]: 1
12
13
  }
13
14
 
@@ -191,10 +191,9 @@ const _getItemCQN = (model, name, navigationProperty, expandItem) => {
191
191
  orderByToCQN(item, orderBy)
192
192
  }
193
193
 
194
- // REVISIT: expand on hana (window function) breaks without setting dummy limit
195
194
  const top = expandItem.getOption(QueryOptions.TOP)
196
195
  if (navigationProperty.isCollection())
197
- addLimit(item, top != null ? top : Number.MAX_SAFE_INTEGER, expandItem.getOption(QueryOptions.SKIP) || 0)
196
+ addLimit(item, top != null ? top : null, expandItem.getOption(QueryOptions.SKIP) || 0)
198
197
 
199
198
  _filter(item, expandItem.getOption(QueryOptions.FILTER))
200
199
 
@@ -25,7 +25,6 @@ const deleteToCQN = require('./deleteToCQN')
25
25
  */
26
26
  module.exports = (component, service, target, data, odataReq, upsert) => {
27
27
  const odata2cqn = cds.env.features.odata_new_parser
28
- if (odata2cqn && !cds.odata) cds.odata = require('../../../../odata')
29
28
 
30
29
  switch (component) {
31
30
  case DATA_CREATE_HANDLER:
@@ -70,7 +70,7 @@ const enhanceCqnWithSubSelects = (cqn, pathSegments, csn, SELECT) => {
70
70
  let currentCqn
71
71
 
72
72
  if (isLastElement) {
73
- cqn._from(entityName, tableAlias)
73
+ cqn.SELECT.from = { ref: [entityName], as: tableAlias }
74
74
  _addKeysToWhereIfNeeded(cqn, keys, tableAlias)
75
75
  } else {
76
76
  currentCqn = SELECT.from(`${entityName} as ${tableAlias}`, [1])
@@ -1,5 +1,5 @@
1
- // REVISIT: should be cds.ql
2
- const { UPDATE } = require('../../../statements')
1
+ const cds = require('../../../../cds')
2
+ const { UPDATE } = cds.ql
3
3
 
4
4
  const { getFeatureNotSupportedError } = require('../../../util/errors')
5
5
  const { isStreaming } = require('../utils/stream')
@@ -79,34 +79,32 @@ const isSameArray = (arr1, arr2) => {
79
79
  return arr1.length === arr2.length && arr1.every((element, index) => element === arr2[index])
80
80
  }
81
81
 
82
- const _getStructKeys = (elements, prefix) => {
82
+ const _getStructKeys = (key, prefix, joinStructured) => {
83
83
  const structKeys = []
84
- for (const key in elements) {
85
- if (elements[key]._isStructured) {
86
- structKeys.push(..._getStructKeys(elements[key].elements, `${prefix}_${elements[key].name}`))
84
+ for (const keyName in key.elements) {
85
+ const keyElement = key.elements[keyName]
86
+ if (keyElement._isStructured) {
87
+ structKeys.push(..._getStructKeys(keyElement, [...prefix, keyName], joinStructured))
87
88
  continue
88
89
  }
89
- if (elements.isAssociation || elements.isComposition) continue
90
-
91
- structKeys.push(`${prefix}_${key}`)
90
+ if (keyElement.isAssociation) continue
91
+ const newKey = joinStructured ? [...prefix, keyName].join('_') : [...prefix, keyName]
92
+ structKeys.push(newKey)
92
93
  }
93
-
94
94
  return structKeys
95
95
  }
96
96
 
97
- const getAllKeys = entity => {
97
+ const getAllKeys = (entity, joinStructured = true) => {
98
98
  const allKeys = []
99
-
100
- for (const key in entity.keys) {
101
- if (entity.keys[key].isAssociation || entity.keys[key].isComposition) continue
102
-
103
- if (entity.keys[key]._isStructured) {
104
- allKeys.push(..._getStructKeys(entity.keys[key].elements, entity.keys[key].name))
105
- continue
99
+ if (entity && entity.elements) {
100
+ // in elements because of aspects
101
+ for (const keyName in entity.elements) {
102
+ const key = entity.elements[keyName]
103
+ if (!key.key || key.isAssociation || key.isComposition) continue
104
+ if (key._isStructured) allKeys.push(..._getStructKeys(key, [keyName], joinStructured))
105
+ else allKeys.push(keyName)
106
106
  }
107
- allKeys.push(entity.keys[key].name)
108
107
  }
109
-
110
108
  return allKeys
111
109
  }
112
110
 
@@ -107,9 +107,12 @@ class EdmEntityType extends AbstractEdmStructuredType {
107
107
  * @returns {boolean} whether this entity type is a media entity type
108
108
  */
109
109
  hasStream () {
110
- if (this.csdlStructuredType.hasStream) return this.csdlStructuredType.hasStream
111
-
112
- return this.getBaseType() ? this.getBaseType().hasStream() : false
110
+ if (this.csdlStructuredType.hasStream) return true
111
+ if (this.getBaseType() && this.getBaseType().hasStream()) return true
112
+ const streamProp = this.csdlStructuredType &&
113
+ this.csdlStructuredType.properties &&
114
+ this.csdlStructuredType.properties.find(prop => prop.type && prop.type.namespace + '.' + prop.type.name === 'Edm.Stream')
115
+ return !!streamProp
113
116
  }
114
117
  }
115
118
 
@@ -162,7 +162,10 @@ class RepresentationKind {
162
162
  case ResourceKind.COUNT:
163
163
  return RepresentationKind.Kinds.COUNT
164
164
  case ResourceKind.VALUE:
165
- if (uriInfo.getFinalEdmType().getKind() === EdmTypeKind.ENTITY) return null
165
+ if (uriInfo.getFinalEdmType().getKind() === EdmTypeKind.ENTITY) {
166
+ uriInfo.getLastSegment()._isStreamByDollarValue = true
167
+ return RepresentationKind.Kinds.BINARY
168
+ }
166
169
  return uriInfo.getFinalEdmType() === EdmPrimitiveTypeKind.Binary
167
170
  ? RepresentationKind.Kinds.BINARY
168
171
  : RepresentationKind.Kinds.PRIMITIVE_VALUE
@@ -65,6 +65,7 @@ class OdataRequest {
65
65
 
66
66
  this._deepInsertExpand = []
67
67
  this._hasDelta = false
68
+ this._inRequestUrl = inRequest.url
68
69
  }
69
70
 
70
71
  /**
@@ -425,8 +425,21 @@ class SerializerFactory {
425
425
  const uriInfo = context.getRequest().getUriInfo()
426
426
  let type = uriInfo.getFinalEdmType()
427
427
  if (type.getKind() === EdmTypeKind.DEFINITION) type = type.getUnderlyingType()
428
- const segment = uriInfo.getLastSegment(type === EdmPrimitiveTypeKind.Stream ? 0 : -1)
429
- const propertyOrReturnType = segment.getProperty() || (segment.getFunction() || segment.getAction()).getReturnType()
428
+
429
+ let propertyOrReturnType
430
+ if (uriInfo.getLastSegment()._isStreamByDollarValue) {
431
+ for (const [propertyName, property] of uriInfo.getLastSegment(-1).getEdmType().getOwnProperties()) {
432
+ if (property.getType() === EdmPrimitiveTypeKind.Stream) {
433
+ propertyOrReturnType = property
434
+ break
435
+ }
436
+ }
437
+ type = EdmPrimitiveTypeKind.Stream
438
+ } else {
439
+ const segment = uriInfo.getLastSegment(type === EdmPrimitiveTypeKind.Stream ? 0 : -1)
440
+ propertyOrReturnType = segment.getProperty() || (segment.getFunction() || segment.getAction()).getReturnType()
441
+ }
442
+
430
443
  let value = data.value
431
444
 
432
445
  if (value === null || value === undefined) {
@@ -44,6 +44,7 @@ const whiteList = new Map()
44
44
  .set(HttpMethods.GET, true)
45
45
  .set(HttpMethods.PUT, true)
46
46
  .set(HttpMethods.PATCH, true)
47
+ .set(HttpMethods.DELETE, true)
47
48
  )
48
49
  .set(
49
50
  ResourceKinds.NAVIGATION_TO_ONE,
@@ -3,7 +3,7 @@ const Dispatcher = require('./Dispatcher')
3
3
  const cds = require('../../../cds')
4
4
 
5
5
  const to = service => {
6
- const edm = cds.compile.to.edm(service.model, { service: service.options.service })
6
+ const edm = cds.compile.to.edm(service.model, { service: service.definition.name })
7
7
  const odata = new OData(edm, service.model, service.options)
8
8
 
9
9
  odata.addCDSServiceToChannel(service)
@@ -97,7 +97,11 @@ function _entityOrTypeName(navSourceSegment) {
97
97
  }
98
98
 
99
99
  // TODO do it similar for both cases below?
100
- return (navSourceSegment.getEntitySet() ? navSourceSegment.getEntitySet() : navSourceSegment.getNavigationProperty())
100
+ return (
101
+ navSourceSegment.getEntitySet()
102
+ ? navSourceSegment.getEntitySet()
103
+ : navSourceSegment.getNavigationProperty() || navSourceSegment.getSingleton()
104
+ )
101
105
  .getEntityType()
102
106
  .getFullQualifiedName()
103
107
  }
@@ -207,6 +211,9 @@ const getData = (component, odataReq, service) => {
207
211
  }
208
212
 
209
213
  if (component === DATA_DELETE_HANDLER || component === DATA_READ_HANDLER) {
214
+ if (component === DATA_DELETE_HANDLER && lastSegment.getKind() === 'PRIMITIVE.PROPERTY') {
215
+ return Object.assign(keyValues, { [lastSegment.getProperty().getName()]: null })
216
+ }
210
217
  return keyValues
211
218
  }
212
219
 
@@ -3,7 +3,7 @@ const { SELECT } = cds.ql
3
3
  const { isCustomOperation } = require('./request')
4
4
  const expandToCQN = require('../odata-to-cqn/expandToCQN')
5
5
  const QueryOptions = require('../okra/odata-server').QueryOptions
6
- const { COMPLEX_PROPERTY } = require('../okra/odata-server').uri.UriResource.ResourceKind
6
+ const { COMPLEX_PROPERTY, PRIMITIVE_PROPERTY } = require('../okra/odata-server').uri.UriResource.ResourceKind
7
7
 
8
8
  const _selectForFunction = (selectColumns, result, opReturnType) => {
9
9
  if (!Array.isArray(result)) result = [result]
@@ -104,6 +104,11 @@ const resolveStructuredName = (pathSegments, index, nameArr = []) => {
104
104
  const prop = pathSegments[index].getProperty()
105
105
  nameArr.unshift(prop.getName())
106
106
  return resolveStructuredName(pathSegments, --index, nameArr)
107
+ } else if (
108
+ pathSegments[index].getKind() === PRIMITIVE_PROPERTY &&
109
+ pathSegments[index - 1].getKind() === COMPLEX_PROPERTY
110
+ ) {
111
+ return resolveStructuredName(pathSegments, --index, nameArr)
107
112
  }
108
113
 
109
114
  return nameArr
@@ -10,7 +10,7 @@ const hasOmitValuesPreference = (preferHeader, preference) => {
10
10
  * omitted from the response payload.
11
11
  *
12
12
  * @param {import('../okra/odata-server/core/OdataResponse')} response
13
- * @param {Map | null} omitValuesPreference
13
+ * @param {Map<string, boolean> | null} omitValuesPreference
14
14
  */
15
15
  const applyOmitValuesPreference = (response, omitValuesPreference) => {
16
16
  if (!omitValuesPreference || omitValuesPreference.size === 0 || !response) return
@@ -40,20 +40,27 @@ const applyOmitValuesPreference = (response, omitValuesPreference) => {
40
40
  *
41
41
  * **REVISIT**: The handling of delta payloads and instance annotations is not currently
42
42
  * implemented, as the runtime does not yet support those features.
43
- * In the future, you might want to use the `pathSegments` argument for the implementation
44
- * of delta payloads.
43
+ * In the future, you might want to use the `processArgs.pathSegments` argument for the
44
+ * implementation of delta payloads.
45
+ *
46
+ * @param {import('../../../../types/api').templateProcessorProcessFnArgs} processArgs
47
+ * @param {import('../../../../cds-services/adapter/odata-v4/ODataRequest')} request
48
+ * @param {Map<string, boolean> | null} omitValuesPreference
49
+ * @param {object | undefined} previousRow
45
50
  */
46
- const omitValue = (request, omitValuesPreference, previousRow, row, key, element, plain, isRowRoot, pathSegments) => {
51
+ const omitValue = (processArgs, request, omitValuesPreference, previousRow) => {
47
52
  const preferHeader = request.headers.prefer
48
53
  if (!preferHeader || !omitValuesPreference) return
49
54
 
55
+ const { row, key, element, isRoot } = processArgs
50
56
  const defaultValue = element.default && element.default.val ? element.default.val : null
51
57
  const responseValue = row[key]
52
58
  const isDefaultValue = responseValue === defaultValue
53
59
 
54
60
  if (isDefaultValue) {
55
61
  // Don't omit the response value if the request is an update operation and it has changed it
56
- if (request.event === 'UPDATE') {
62
+ // REVISIT: We don't have access to deep previous rows, therefore we always ignore default values in deep operations
63
+ if (request.event === 'UPDATE' && isRoot) {
57
64
  if (previousRow === undefined) return
58
65
  if (responseValue !== previousRow[key]) return
59
66
  }
@@ -3,35 +3,29 @@ const { SELECT } = cds.ql
3
3
 
4
4
  const { getDeepSelect } = require('../../../services/utils/handlerUtils')
5
5
  const { DRAFT_COLUMNS } = require('../../../../common/constants/draft')
6
- const { getVirtuals, getVirtualFromTarget, postProcessVirtuals } = require('../../../../db/generic/virtual')
7
6
  const { filterKeys } = require('../../../../fiori/utils/handler')
8
7
 
9
8
  const _getColumns = target => {
10
9
  const columns = []
11
10
  for (const k in target.elements) {
12
- if (!target.elements[k].isAssociation && !target.elements[k].virtual && !DRAFT_COLUMNS.includes(k)) columns.push(k)
11
+ if (!target.elements[k].isAssociation && !DRAFT_COLUMNS.includes(k)) columns.push(k)
13
12
  }
14
13
  return columns
15
14
  }
16
15
 
17
16
  module.exports = async (req, srv) => {
18
17
  let deepSelect
19
- let virtuals = []
20
18
  if (req.event === 'draftActivate') {
21
19
  const where = filterKeys(req.target.keys).reduce((w, k) => {
22
20
  w[k] = req.data[k]
23
21
  return w
24
22
  }, {})
25
23
  deepSelect = SELECT.from(req.target).columns(_getColumns(req.target)).where(where)
26
- virtuals = getVirtualFromTarget(req)
27
24
  } else if (req.event === 'UPDATE') {
28
25
  deepSelect = SELECT.from(req.query.UPDATE.entity, _getColumns(req.target))
29
- virtuals = getVirtualFromTarget(req)
30
26
  } else {
31
27
  deepSelect = getDeepSelect(req)
32
- virtuals = getVirtuals({ query: deepSelect, target: req.target }, srv.model)
33
28
  }
34
29
  const result = await cds.tx(req).run(deepSelect)
35
- postProcessVirtuals(virtuals, result)
36
30
  return result
37
31
  }
@@ -33,15 +33,15 @@ const isCustomOperation = (pathSegments, includingBound = true) => {
33
33
  * Validate resource path length and autoexposed entities.
34
34
  * It will throw an error in case the maximum is exceeded or top entity is autoexposed.
35
35
  *
36
- * @param {object} req odata request
37
- * @param {object} options odata configuration options
38
- * @param model
36
+ * @param {object} odataReq
37
+ * @param {object} service
39
38
  */
40
- const validateResourcePath = (req, options, model) => {
41
- const segment = req.getUriInfo().getPathSegments()[0]
39
+ const validateResourcePath = (odataReq, service) => {
40
+ const segment = odataReq.getUriInfo().getPathSegments()[0]
42
41
  if (segment.getKind() === ENTITY || segment.getKind() === ENTITY_COLLECTION) {
43
42
  const name = segment.getEntitySet().getName()
44
- const entity = model.definitions[`${options.service}.${name}`]
43
+ // REVISIT: This validation is violating all principles of locality
44
+ const entity = service.model.definitions[`${service.definition.name}.${name}`]
45
45
  if (!entity) return
46
46
 
47
47
  /*
@@ -51,7 +51,7 @@ const validateResourcePath = (req, options, model) => {
51
51
  */
52
52
  if (entity._isDraftEnabled) return
53
53
  if (entity['@cds.autoexposed']) {
54
- if (entity['@cds.autoexpose'] && req.getIncomingRequest().method === 'GET') {
54
+ if (entity['@cds.autoexpose'] && odataReq.getIncomingRequest().method === 'GET') {
55
55
  // combination GET && @cds.autoexposed && @cds.autoexpose is OK
56
56
  return
57
57
  }
@@ -71,14 +71,6 @@ const toODataResult = (result, arg) => {
71
71
  return odataResult
72
72
  }
73
73
 
74
- const getVirtualsFromResult = ({ elements }, result) => {
75
- const virtuals = {}
76
- for (const k in elements) {
77
- if (elements[k].virtual) virtuals[k] = result[k]
78
- }
79
- return virtuals
80
- }
81
-
82
74
  const addEtags = (row, key) => {
83
75
  row['*@odata.etag'] = row[key]
84
76
  }
@@ -121,8 +113,14 @@ const addAssociationToRow = (row, foreignKey, foreignKeyElement) => {
121
113
  delete row[foreignKey]
122
114
  }
123
115
 
124
- const _processCategory = (category, row, key, element, options) => {
116
+ const _processCategory = (category, processArgs, req, options, previousResult) => {
117
+ const { row, key, element } = processArgs
118
+
125
119
  switch (category) {
120
+ case '@odata.omitValues':
121
+ omitValue(processArgs, req, options.omitValuesPreference, previousResult)
122
+ break
123
+
126
124
  case '@odata.etag':
127
125
  addEtags(row, key)
128
126
  break
@@ -143,16 +141,13 @@ const _processCategory = (category, row, key, element, options) => {
143
141
  }
144
142
  }
145
143
 
146
- const _processorFn = (req, previousResult, options) => (row, key, element, plain, isRowRoot, pathSegments) => {
144
+ const _processorFn = (req, previousResult, options) => processArgs => {
145
+ const { row, key, plain } = processArgs
147
146
  if (typeof row !== 'object' || !Object.prototype.hasOwnProperty.call(row, key)) return
148
-
149
- omitValue(req, options.omitValuesPreference, previousResult, row, key, element, plain, isRowRoot, pathSegments)
150
-
151
147
  const categories = plain.categories
152
- if (!categories) return
153
148
 
154
149
  for (const category of categories) {
155
- _processCategory(category, row, key, element, options)
150
+ _processCategory(category, processArgs, req, options, previousResult)
156
151
  }
157
152
  }
158
153
 
@@ -207,9 +202,8 @@ const _pick = options => (element, target, parent) => {
207
202
  if (element['@odata.etag']) categories.push('@odata.etag')
208
203
  if (element.type === 'cds.Decimal') categories.push('@cds.Decimal')
209
204
  categories.push(..._assocs(element, target, parent))
210
-
205
+ if (options.omitValuesPreference) categories.push('@odata.omitValues')
211
206
  if (categories.length) return { categories }
212
- if (options.omitValuesPreference) return true
213
207
  }
214
208
 
215
209
  const _getPostProcessOptions = headers => {
@@ -223,11 +217,13 @@ const _getPostProcessOptions = headers => {
223
217
  // REVISIT: options ie headers are not used in _pick since its cached
224
218
  // consequently, all decimals are always picked even w/o these headers
225
219
  const acceptHeader = headers.accept
220
+
226
221
  if (acceptHeader && acceptHeader.includes('IEEE754Compatible=true')) {
227
222
  options.decimals = { exponential: acceptHeader.includes('ExponentialDecimals=true') }
228
223
  }
229
224
 
230
225
  const preferHeader = headers.prefer
226
+
231
227
  if (preferHeader && preferHeader.includes('omit-values=')) {
232
228
  options.omitValuesPreference = new Map()
233
229
  }
@@ -252,6 +248,7 @@ const postProcess = (req, res, service, result, previousResult) => {
252
248
 
253
249
  // process each row
254
250
  const processFn = _processorFn(req, previousResult, options)
251
+
255
252
  for (const row of rows) {
256
253
  const args = {
257
254
  processFn,
@@ -270,6 +267,5 @@ const postProcess = (req, res, service, result, previousResult) => {
270
267
 
271
268
  module.exports = {
272
269
  toODataResult,
273
- getVirtualsFromResult,
274
270
  postProcess
275
271
  }
@@ -18,7 +18,9 @@ const _getProperties = async (properties, req) => {
18
18
  const cqn = cqn2cqn4sql(SELECT.one(req.query.SELECT.from), req._model).columns(properties)
19
19
 
20
20
  // REVISIT: renaming of media type property (e.g., mimeType as MimeType in AFC) not reflected in @Core.MediaType
21
- if (req.target.query && !req.target.drafts) cqn.SELECT.from.ref[0] = req.target.query._target.name
21
+ if (req.target.query && req.target.query._target && !req.target.drafts) {
22
+ cqn.SELECT.from.ref[0] = req.target.query._target.name
23
+ }
22
24
 
23
25
  adaptStreamCQN(cqn)
24
26
 
@@ -45,26 +47,24 @@ const getStreamProperties = async (segments, srv, req) => {
45
47
  // REVISIT: we need to read direcly from db, which might not be there!
46
48
  if (!cds.db) return {}
47
49
 
48
- const propertyName = segments[segments.length - 1].getProperty().getName()
49
-
50
50
  let contentType, entityName, namespace, contentDisposition
51
- if (segments[segments.length - 2].getKind() === 'ENTITY') {
52
- entityName = segments[segments.length - 2].getEntitySet().getName()
53
- namespace = segments[segments.length - 2].getEdmType().getFullQualifiedName().namespace
54
- } else if (segments[segments.length - 2].getKind() === 'NAVIGATION.TO.ONE') {
55
- entityName = segments[segments.length - 2].getTarget().getName()
56
- namespace = segments[segments.length - 2].getTarget().getEntityType().getFullQualifiedName().namespace
51
+ const previous = segments[segments.length - 2]
52
+ if (previous.getKind() === 'ENTITY') {
53
+ entityName = previous.getEntitySet().getName()
54
+ namespace = previous.getEdmType().getFullQualifiedName().namespace
55
+ } else if (previous.getKind() === 'NAVIGATION.TO.ONE') {
56
+ entityName = previous.getTarget().getName()
57
+ namespace = previous.getTarget().getEntityType().getFullQualifiedName().namespace
57
58
  }
58
59
 
59
60
  if (entityName) {
60
61
  const entityDefinition = findCsnTargetFor(entityName, srv.model, namespace)
61
62
  if (entityDefinition._hasPersistenceSkip) return {}
62
63
 
63
- contentType = entityDefinition.elements[`${propertyName}`]['@Core.MediaType']
64
+ const mediaProperty = Object.values(entityDefinition.elements).find(ele => ele['@Core.MediaType'])
65
+ contentType = mediaProperty['@Core.MediaType']
64
66
  // @Core.ContentDisposition.Filename is correct, but we released with @Core.ContentDisposition, so we should stay compatible
65
- contentDisposition =
66
- entityDefinition.elements[`${propertyName}`]['@Core.ContentDisposition.Filename'] ||
67
- entityDefinition.elements[`${propertyName}`]['@Core.ContentDisposition']
67
+ contentDisposition = mediaProperty['@Core.ContentDisposition.Filename'] || mediaProperty['@Core.ContentDisposition']
68
68
  // contentDisposition can be only dynamic
69
69
  if (typeof contentType === 'object' || contentDisposition) {
70
70
  const properties = _getDynamicProperties(contentType, contentDisposition)
@@ -54,7 +54,6 @@ module.exports = service => {
54
54
  try {
55
55
  const reqs = data.map(d => new RestRequest(parsed, d, restReq, restRes, service))
56
56
  result = await Promise.all(reqs.map(req => tx.dispatch(req)))
57
- result = result.map(res => res[0])
58
57
 
59
58
  _transformToComplex(result, data)
60
59
  bufferToBase64(result, target)
@@ -5,6 +5,7 @@ const RestRequest = require('../RestRequest')
5
5
  const getData = require('../utils/data')
6
6
  const { validateReturnType } = require('../utils/validation-checks')
7
7
  const { bufferToBase64 } = require('../utils/binary')
8
+ const { toRestResult } = require('../utils/result')
8
9
 
9
10
  const _convertCustomOperationReturnValue = (returns, result) => {
10
11
  if (returns.items) {
@@ -56,7 +57,7 @@ module.exports = service => {
56
57
  else {
57
58
  // only set status if not yet modified
58
59
  if (restRes.statusCode === 200 && status) restRes.status(status)
59
- restRes.send(body)
60
+ restRes.send(toRestResult(body))
60
61
  }
61
62
  }
62
63
  }
@@ -22,10 +22,10 @@ module.exports = service => {
22
22
  const tx = service.tx({ user: restReq.user, req: restReq, _model: service.model })
23
23
  cds.context = tx
24
24
 
25
- const req = new RestRequest(parsed, data, restReq, restRes, service)
26
-
27
25
  let result, err, commit
28
26
  try {
27
+ const req = new RestRequest(parsed, data, restReq, restRes, service)
28
+
29
29
  result = await tx.dispatch(req)
30
30
 
31
31
  if (result == null) {
@@ -2,8 +2,7 @@ const cds = require('../../../../cds')
2
2
 
3
3
  const { _newReadToCQN } = require('../../../../odata/readToCqn')
4
4
 
5
- // REVISIT: should be cds.ql
6
- const { INSERT, SELECT, UPDATE, DELETE } = require('../../../statements')
5
+ const { INSERT, SELECT, UPDATE, DELETE } = cds.ql
7
6
 
8
7
  const { createCqlString } = require('./utils')
9
8
  const { getColumns } = require('../../../services/utils/columns')
@@ -38,7 +37,6 @@ const _readToCQN = ({ isCollection, segments }, target, restReq) => {
38
37
  */
39
38
  module.exports = (parsed, data, restReq, service) => {
40
39
  const odata2cqn = cds.env.features.rest_new_parser
41
- if (odata2cqn && !cds.odata) cds.odata = require('../../../../odata')
42
40
 
43
41
  const { event, segments, target } = parsed
44
42
 
@@ -47,7 +45,7 @@ module.exports = (parsed, data, restReq, service) => {
47
45
 
48
46
  switch (event) {
49
47
  case 'CREATE':
50
- return Array.isArray(data) ? data.map(d => INSERT.into(target).entries(d)) : [INSERT.into(target).entries(data)]
48
+ return INSERT.into(target).entries(data)
51
49
  case 'READ':
52
50
  return odata2cqn ? _newReadToCQN(service, target, restReq) : _readToCQN(parsed, target, restReq)
53
51
  case 'UPDATE':
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Convert any result to the result object structure, which is expected of rest.
2
+ * Convert any result to the format expected of rest.
3
3
  *
4
4
  * @param {*} result
5
- * @returns {string | object}
5
+ * @returns {string | object | boolean}
6
6
  */
7
7
  const toRestResult = result => {
8
8
  let restResult = result
@@ -13,6 +13,8 @@ const toRestResult = result => {
13
13
  count: result.$count,
14
14
  value: result
15
15
  }
16
+ } else if (typeof result === 'number') {
17
+ restResult = result.toString()
16
18
  }
17
19
 
18
20
  return restResult