@sap/cds 5.5.3 → 5.6.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 (227) hide show
  1. package/CHANGELOG.md +134 -1
  2. package/apis/services.d.ts +27 -1
  3. package/app/index.js +22 -11
  4. package/bin/build/buildTaskFactory.js +1 -1
  5. package/bin/build/provider/buildTaskProviderInternal.js +1 -1
  6. package/bin/build/provider/fiori/index.js +1 -1
  7. package/bin/build/provider/hana/2migration.js +8 -7
  8. package/bin/build/provider/java-cf/index.js +1 -1
  9. package/bin/deploy/to-hana/hana.js +1 -17
  10. package/common.cds +8 -0
  11. package/lib/compile/index.js +1 -1
  12. package/lib/compile/to/sql.js +22 -2
  13. package/lib/connect/bindings.js +2 -1
  14. package/lib/connect/index.js +1 -1
  15. package/lib/core/infer.js +1 -1
  16. package/lib/core/reflect.js +3 -1
  17. package/lib/env/index.js +175 -41
  18. package/lib/env/requires.js +24 -3
  19. package/lib/i18n/localize.js +33 -5
  20. package/lib/index.js +7 -6
  21. package/lib/log/format/kibana.js +6 -2
  22. package/lib/ql/DELETE.js +1 -1
  23. package/lib/ql/INSERT.js +1 -1
  24. package/lib/ql/Query.js +13 -10
  25. package/lib/ql/SELECT.js +15 -8
  26. package/lib/ql/UPDATE.js +1 -1
  27. package/lib/ql/Whereable.js +5 -0
  28. package/lib/req/context.js +87 -37
  29. package/lib/req/{impl.js → request.js} +1 -1
  30. package/lib/req/{res.js → response.js} +0 -0
  31. package/lib/serve/Service-api.js +1 -1
  32. package/lib/serve/Service-dispatch.js +12 -2
  33. package/lib/serve/Service-handlers.js +21 -7
  34. package/lib/serve/Service-methods.js +1 -1
  35. package/lib/serve/Transaction.js +7 -6
  36. package/lib/serve/index.js +1 -1
  37. package/lib/utils/axios.js +7 -0
  38. package/lib/utils/data.js +1 -1
  39. package/libx/_runtime/audit/Service.js +18 -18
  40. package/libx/_runtime/audit/generic/personal/access.js +1 -1
  41. package/libx/_runtime/audit/generic/personal/modification.js +3 -2
  42. package/libx/_runtime/audit/generic/personal/utils.js +23 -63
  43. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +6 -0
  44. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +37 -35
  45. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -1
  46. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +3 -1
  47. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +5 -5
  48. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +1 -1
  49. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -7
  50. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +84 -34
  51. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +10 -4
  52. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +9 -3
  53. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +8 -6
  54. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -3
  55. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +13 -11
  56. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +11 -95
  57. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +17 -11
  58. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
  59. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +6 -2
  60. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +3 -34
  61. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -3
  62. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +48 -18
  63. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +10 -5
  64. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +1 -1
  65. package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +1 -1
  66. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +1 -3
  67. package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +9 -2
  68. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +20 -21
  69. package/libx/_runtime/cds-services/services/utils/columns.js +6 -1
  70. package/libx/_runtime/cds-services/services/utils/compareJson.js +1 -8
  71. package/libx/_runtime/cds-services/services/utils/differ.js +7 -26
  72. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -4
  73. package/libx/_runtime/cds-services/util/assert.js +29 -13
  74. package/libx/_runtime/cds.js +2 -1
  75. package/libx/_runtime/common/aspects/Association.js +72 -0
  76. package/libx/_runtime/common/aspects/any.js +8 -45
  77. package/libx/_runtime/common/aspects/entity.js +0 -1
  78. package/libx/_runtime/common/aspects/relation.js +40 -0
  79. package/libx/_runtime/common/aspects/utils.js +73 -1
  80. package/libx/_runtime/common/auth/strategies/utils/uaa.js +10 -14
  81. package/libx/_runtime/common/composition/data.js +3 -2
  82. package/libx/_runtime/common/composition/delete.js +3 -1
  83. package/libx/_runtime/common/composition/tree.js +23 -18
  84. package/libx/_runtime/common/composition/utils.js +34 -8
  85. package/libx/_runtime/common/error/frontend.js +6 -1
  86. package/libx/_runtime/common/generic/auth.js +15 -13
  87. package/libx/_runtime/common/generic/crud.js +2 -2
  88. package/libx/_runtime/common/generic/etag.js +11 -8
  89. package/libx/_runtime/common/generic/input.js +3 -3
  90. package/libx/_runtime/common/generic/paging.js +9 -5
  91. package/libx/_runtime/common/generic/put.js +3 -2
  92. package/libx/_runtime/common/generic/sorting.js +3 -3
  93. package/libx/_runtime/common/generic/temporal.js +3 -3
  94. package/libx/_runtime/common/toggles/alpha.js +1 -1
  95. package/libx/_runtime/common/utils/cqn.js +20 -1
  96. package/libx/_runtime/common/utils/cqn2cqn4sql.js +125 -139
  97. package/libx/_runtime/common/utils/csn.js +50 -52
  98. package/libx/_runtime/common/utils/foreignKeyPropagations.js +41 -176
  99. package/libx/_runtime/common/utils/generateOnCond.js +40 -70
  100. package/libx/_runtime/common/utils/{enrichWithKeysFromWhere.js → keys.js} +29 -28
  101. package/libx/_runtime/common/utils/postProcessing.js +3 -0
  102. package/libx/_runtime/common/utils/propagateForeignKeys.js +84 -0
  103. package/libx/_runtime/common/utils/resolveStructured.js +1 -1
  104. package/libx/_runtime/common/utils/resolveView.js +19 -9
  105. package/libx/_runtime/common/utils/rewriteAsterisks.js +94 -0
  106. package/libx/_runtime/common/utils/search2cqn4sql.js +9 -8
  107. package/libx/_runtime/common/utils/template.js +54 -46
  108. package/libx/_runtime/db/Service.js +9 -2
  109. package/libx/_runtime/db/expand/expandCQNToJoin.js +10 -24
  110. package/libx/_runtime/db/expand/rawToExpanded.js +2 -1
  111. package/libx/_runtime/db/generic/create.js +1 -0
  112. package/libx/_runtime/db/generic/input.js +7 -11
  113. package/libx/_runtime/db/generic/integrity.js +2 -2
  114. package/libx/_runtime/db/generic/rewrite.js +2 -5
  115. package/libx/_runtime/db/generic/update.js +1 -0
  116. package/libx/_runtime/db/query/read.js +10 -5
  117. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +6 -0
  118. package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -2
  119. package/libx/_runtime/db/sql-builder/annotations.js +1 -0
  120. package/libx/_runtime/db/utils/columns.js +14 -43
  121. package/libx/_runtime/db/utils/deep.js +5 -7
  122. package/libx/_runtime/fiori/generic/activate.js +3 -2
  123. package/libx/_runtime/fiori/generic/before.js +2 -2
  124. package/libx/_runtime/fiori/generic/cancel.js +3 -2
  125. package/libx/_runtime/fiori/generic/delete.js +3 -2
  126. package/libx/_runtime/fiori/generic/edit.js +2 -2
  127. package/libx/_runtime/fiori/generic/new.js +2 -2
  128. package/libx/_runtime/fiori/generic/patch.js +2 -2
  129. package/libx/_runtime/fiori/generic/prepare.js +2 -2
  130. package/libx/_runtime/fiori/generic/read.js +17 -63
  131. package/libx/_runtime/fiori/generic/readOverDraft.js +4 -4
  132. package/libx/_runtime/fiori/uiflex/extensibility/index.cds +15 -0
  133. package/libx/_runtime/fiori/uiflex/extensibility/index.js +148 -0
  134. package/libx/_runtime/fiori/uiflex/handler/transformREAD.js +119 -0
  135. package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +43 -0
  136. package/libx/_runtime/fiori/uiflex/handler/transformWRITE.js +62 -0
  137. package/libx/_runtime/fiori/uiflex/index.js +35 -0
  138. package/libx/_runtime/fiori/uiflex/utils.js +78 -0
  139. package/libx/_runtime/fiori/utils/handler.js +3 -13
  140. package/libx/_runtime/fiori/utils/where.js +6 -1
  141. package/libx/_runtime/hana/Service.js +5 -2
  142. package/libx/_runtime/hana/execute.js +1 -1
  143. package/libx/_runtime/hana/pool.js +12 -11
  144. package/libx/_runtime/hana/search2cqn4sql.js +34 -43
  145. package/libx/_runtime/hana/searchToContains.js +3 -3
  146. package/libx/_runtime/index.js +5 -2
  147. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  148. package/libx/_runtime/messaging/common-utils/AMQPClient.js +16 -3
  149. package/libx/_runtime/messaging/common-utils/connections.js +11 -14
  150. package/libx/_runtime/messaging/common-utils/naming-conventions.js +1 -1
  151. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -1
  152. package/libx/_runtime/messaging/enterprise-messaging.js +1 -1
  153. package/libx/_runtime/messaging/message-queuing.js +18 -0
  154. package/libx/_runtime/remote/Service.js +14 -2
  155. package/libx/_runtime/remote/utils/client-types.d.ts +7 -0
  156. package/libx/_runtime/remote/utils/client.js +117 -23
  157. package/libx/_runtime/sqlite/Service.js +4 -3
  158. package/libx/_runtime/sqlite/convertAssocToOneManaged.js +1 -3
  159. package/libx/_runtime/sqlite/execute.js +1 -1
  160. package/libx/gql/GraphQLAdapter.js +33 -0
  161. package/libx/gql/constants/adapter.js +69 -0
  162. package/libx/gql/constants/cds.js +18 -0
  163. package/libx/gql/constants/graphql.js +33 -0
  164. package/libx/gql/resolvers/crud/create.js +15 -0
  165. package/libx/gql/resolvers/crud/delete.js +24 -0
  166. package/libx/gql/resolvers/crud/index.js +6 -0
  167. package/libx/gql/resolvers/crud/read.js +25 -0
  168. package/libx/gql/resolvers/crud/update.js +31 -0
  169. package/libx/gql/resolvers/crud/utils/index.js +36 -0
  170. package/libx/gql/resolvers/field.js +5 -0
  171. package/libx/gql/resolvers/index.js +7 -0
  172. package/libx/gql/resolvers/mutation.js +23 -0
  173. package/libx/gql/resolvers/parse/ast/enrich.js +51 -0
  174. package/libx/gql/resolvers/parse/ast/fragment.js +11 -0
  175. package/libx/gql/resolvers/parse/ast/fromObject.js +39 -0
  176. package/libx/gql/resolvers/parse/ast/index.js +3 -0
  177. package/libx/gql/resolvers/parse/ast/meta.js +4 -0
  178. package/libx/gql/resolvers/parse/ast/variable.js +7 -0
  179. package/libx/gql/resolvers/parse/ast2cqn/columns.js +42 -0
  180. package/libx/gql/resolvers/parse/ast2cqn/entries.js +31 -0
  181. package/libx/gql/resolvers/parse/ast2cqn/index.js +8 -0
  182. package/libx/gql/resolvers/parse/ast2cqn/limit.js +6 -0
  183. package/libx/gql/resolvers/parse/ast2cqn/orderBy.js +24 -0
  184. package/libx/gql/resolvers/parse/ast2cqn/utils/index.js +3 -0
  185. package/libx/gql/resolvers/parse/ast2cqn/where.js +70 -0
  186. package/libx/gql/resolvers/parse/utils/index.js +8 -0
  187. package/libx/gql/resolvers/query.js +13 -0
  188. package/libx/gql/resolvers/root.js +34 -0
  189. package/libx/gql/schema/generate.js +18 -0
  190. package/libx/gql/schema/index.js +5 -0
  191. package/libx/gql/schema/mutation.js +76 -0
  192. package/libx/gql/schema/query.js +108 -0
  193. package/libx/gql/schema/typeDefMap.js +45 -0
  194. package/libx/gql/schema/utils/index.js +54 -0
  195. package/libx/gql/utils/index.js +12 -0
  196. package/libx/{_runtime/odata/cqn2odata.js → odata/cqn2odata/index.js} +39 -100
  197. package/libx/odata/index.js +80 -0
  198. package/libx/odata/odata2cqn/afterburner.js +170 -0
  199. package/libx/{_runtime/odata/odata2cqn.pegjs → odata/odata2cqn/grammar.pegjs} +102 -123
  200. package/libx/odata/odata2cqn/index.js +3 -0
  201. package/libx/odata/odata2cqn/parser.js +1 -0
  202. package/libx/odata/utils/index.js +64 -0
  203. package/libx/rest/RestAdapter.js +101 -0
  204. package/libx/rest/RestRequest.js +30 -0
  205. package/libx/rest/index.js +3 -0
  206. package/libx/rest/middleware/auth.js +22 -0
  207. package/libx/rest/middleware/content.js +15 -0
  208. package/libx/rest/middleware/create.js +40 -0
  209. package/libx/rest/middleware/delete.js +20 -0
  210. package/libx/rest/middleware/error.js +56 -0
  211. package/libx/rest/middleware/operation.js +39 -0
  212. package/libx/rest/middleware/parse.js +90 -0
  213. package/libx/rest/middleware/read.js +29 -0
  214. package/libx/rest/middleware/update.js +42 -0
  215. package/libx/rest/utils/data.js +65 -0
  216. package/package.json +4 -1
  217. package/server.js +42 -29
  218. package/lib/req/cls.js +0 -39
  219. package/libx/_runtime/cds-services/services/utils/diff.js +0 -53
  220. package/libx/_runtime/cds-services/util/auditlog.js +0 -247
  221. package/libx/_runtime/cds-services/util/xsenv.js +0 -51
  222. package/libx/_runtime/common/utils/backlinks.js +0 -83
  223. package/libx/_runtime/common/utils/rewriteAsterisk.js +0 -72
  224. package/libx/_runtime/odata/index.js +0 -55
  225. package/libx/_runtime/odata/odata2cqn.js +0 -1
  226. package/libx/_runtime/odata/readToCqn.js +0 -129
  227. package/libx/_runtime/remote/cqn2odata/index.js +0 -2
@@ -1,5 +1,7 @@
1
1
  const { getFeatureNotSupportedError } = require('../../../util/errors')
2
- const { getOnCond } = require('../../../../common/utils/generateOnCond')
2
+ const { deepCopyArray } = require('../../../../common/utils/copy')
3
+ const cds = require('../../../../cds')
4
+ const { cqn2cqn4sql } = require('../../../../common/utils/cqn2cqn4sql')
3
5
 
4
6
  const isNavigation = pathSegments => {
5
7
  return pathSegments.length > 1 && pathSegments[1].getKind().startsWith('NAVIGATION')
@@ -9,99 +11,14 @@ const isViewWithParams = target => {
9
11
  return target.params && Object.keys(target.params).length > 0
10
12
  }
11
13
 
12
- const _entityNameFromSegment = segment => {
13
- if (segment.getKind() === 'SINGLETON') {
14
- return segment.getSingleton().getEntityType().getFullQualifiedName().toString()
15
- }
16
-
17
- if (segment.getKind() === 'ENTITY') {
18
- return segment.getEntitySet().getEntityType().getFullQualifiedName().toString()
19
- }
20
-
21
- return segment.getNavigationProperty().getEntityType().getFullQualifiedName().toString()
22
- }
23
-
24
- const _keysFromSegment = segment => {
25
- if (segment.getKeyPredicates().length > 0) {
26
- const keys = {}
27
-
28
- for (const keyPredicate of segment.getKeyPredicates()) {
29
- const key = keyPredicate.getEdmRef().getName().replace(/\//g, '.')
30
- keys[key] = keyPredicate.getText()
31
- }
32
-
33
- return keys
34
- }
35
- }
36
-
37
- const _addKeysToWhereIfNeeded = (cqn, keys, tableAlias) => {
38
- if (keys) {
39
- for (const key in keys) {
40
- cqn.where([{ ref: [`${tableAlias}`, `${key}`] }, '=', { val: keys[key] }])
41
- }
42
- }
43
- }
44
-
45
- const _addOnCondToWhere = (cqn, entity, tableAlias, identifier, csn) => {
46
- const onConditionOptions = {
47
- associationNames: entity.current,
48
- csn: csn,
49
- aliases: {
50
- select: tableAlias,
51
- join: identifier
52
- }
53
- }
54
-
55
- const onCond = getOnCond(csn.definitions[entity.previous].elements[entity.current], onConditionOptions)
56
- cqn.where(onCond)
57
- }
58
-
59
- const enhanceCqnWithSubSelects = (cqn, pathSegments, csn, SELECT) => {
60
- let previousCqn, previousEntityName
61
-
62
- // if .../property or .../$count requested, must be ignored when building query
63
- const segments = pathSegments.filter(s => s.getProperty() === null && s.getKind() !== 'COUNT')
64
-
65
- for (let i = 0; i < segments.length; i++) {
66
- const isLastElement = i === segments.length - 1
67
- const tableAlias = `T${i}`
68
- const entityName = _entityNameFromSegment(segments[i])
69
- const keys = _keysFromSegment(segments[i])
70
- let currentCqn
71
-
72
- if (isLastElement) {
73
- cqn.SELECT.from = { ref: [entityName], as: tableAlias }
74
- _addKeysToWhereIfNeeded(cqn, keys, tableAlias)
75
- } else {
76
- currentCqn = SELECT.from(`${entityName} as ${tableAlias}`, [1])
77
- _addKeysToWhereIfNeeded(currentCqn, keys, tableAlias)
78
- }
79
-
80
- if (previousCqn) {
81
- _addOnCondToWhere(
82
- previousCqn,
83
- { current: segments[i].getNavigationProperty().getName(), previous: previousEntityName },
84
- tableAlias,
85
- `T${i - 1}`,
86
- csn
87
- )
88
-
89
- if (isLastElement) {
90
- cqn.where(['exists', previousCqn])
91
- } else {
92
- currentCqn.where(['exists', previousCqn])
93
- }
94
- }
95
-
96
- previousCqn = currentCqn
97
- previousEntityName = entityName
98
- }
99
- }
14
+ const getValidationQuery = (ref, model) => {
15
+ const refQuery = deepCopyArray(ref.slice(0, ref.length - 1))
16
+ const cqn = cds.ql.SELECT.from({ ref: refQuery }).columns({
17
+ val: 1,
18
+ as: 'validationQuery'
19
+ })
100
20
 
101
- const validationQuery = (pathSegments, csn, SELECT) => {
102
- const cqn = SELECT.from('placeholder')
103
- enhanceCqnWithSubSelects(cqn, pathSegments.slice(0, pathSegments.length - 1), csn, SELECT)
104
- return cqn
21
+ return cqn2cqn4sql(cqn, model)
105
22
  }
106
23
 
107
24
  const isPathSupported = (supported, pathSegments) => {
@@ -113,9 +30,8 @@ const isPathSupported = (supported, pathSegments) => {
113
30
  }
114
31
 
115
32
  module.exports = {
116
- enhanceCqnWithSubSelects,
117
33
  isNavigation,
118
34
  isViewWithParams,
119
35
  isPathSupported,
120
- validationQuery
36
+ getValidationQuery
121
37
  }
@@ -41,11 +41,11 @@ class ResourcePathParser {
41
41
  let tokenizer = new UriTokenizer(uriPathSegments[0])
42
42
 
43
43
  tokenizer.requireNext(TokenKind.ODataIdentifier)
44
- const currentToken = tokenizer.getText()
45
44
 
45
+ const currentToken = tokenizer.getText()
46
46
  let currentResource = new UriResource()
47
-
48
47
  let edmResult = this._edmContainer.getEntitySet(currentToken)
48
+
49
49
  if (edmResult) {
50
50
  currentResource
51
51
  .setKind(UriResource.ResourceKind.ENTITY_COLLECTION)
@@ -53,11 +53,12 @@ class ResourcePathParser {
53
53
  .setEntitySet(edmResult)
54
54
 
55
55
  this._target = edmResult
56
-
57
- return result.concat(this._parseCollectionNavigation(uriPathSegments, currentResource, tokenizer))
56
+ const uriResources = this._parseCollectionNavigation(uriPathSegments, currentResource, tokenizer)
57
+ return result.concat(uriResources)
58
58
  }
59
59
 
60
60
  edmResult = this._edmContainer.getSingleton(currentToken)
61
+
61
62
  if (edmResult) {
62
63
  currentResource
63
64
  .setKind(UriResource.ResourceKind.SINGLETON)
@@ -65,14 +66,13 @@ class ResourcePathParser {
65
66
  .setIsCollection(false)
66
67
 
67
68
  this._target = edmResult
68
-
69
69
  tokenizer.requireNext(TokenKind.EOF)
70
70
  uriPathSegments.shift()
71
-
72
71
  return result.concat(this._parseSingleNavigation(uriPathSegments, currentResource))
73
72
  }
74
73
 
75
74
  edmResult = this._edmContainer.getActionImport(currentToken)
75
+
76
76
  if (edmResult) {
77
77
  const unboundAction = edmResult.getUnboundAction()
78
78
 
@@ -91,6 +91,7 @@ class ResourcePathParser {
91
91
  }
92
92
 
93
93
  edmResult = this._edmContainer.getFunctionImport(currentToken)
94
+
94
95
  if (edmResult) {
95
96
  const functions = edmResult.getUnboundFunctions()
96
97
  const returnType = functions[0].getReturnType()
@@ -123,9 +124,9 @@ class ResourcePathParser {
123
124
  if (tokenizer.next(TokenKind.OPEN)) {
124
125
  throw new UriSyntaxError(UriSyntaxError.Message.FUNCTION_IMPORT_EOF, edmResult.getName())
125
126
  }
127
+
126
128
  tokenizer.requireNext(TokenKind.EOF)
127
129
  uriPathSegments.shift()
128
-
129
130
  return result.concat(this._parseSingleNavigation(uriPathSegments, currentResource))
130
131
  }
131
132
 
@@ -133,6 +134,7 @@ class ResourcePathParser {
133
134
  if (tokenizer.next(TokenKind.OPEN)) {
134
135
  throw new UriSyntaxError(UriSyntaxError.Message.FUNCTION_IMPORT_EOF, edmResult.getName())
135
136
  }
137
+
136
138
  tokenizer.requireNext(TokenKind.EOF)
137
139
  uriPathSegments.shift()
138
140
 
@@ -148,6 +150,7 @@ class ResourcePathParser {
148
150
  ? this._parseComplexPath(uriPathSegments, currentResource)
149
151
  : this._parsePrimitivePath(uriPathSegments, currentResource)
150
152
  }
153
+
151
154
  return functionRest ? result.concat(currentResource, functionRest) : result.concat(currentResource)
152
155
  }
153
156
 
@@ -211,7 +214,8 @@ class ResourcePathParser {
211
214
  }
212
215
  }
213
216
 
214
- return result.concat(this._parseCollectionNavPath(uriPathSegments, currentResource, tokenizer))
217
+ const uriResources = this._parseCollectionNavPath(uriPathSegments, currentResource, tokenizer)
218
+ return result.concat(uriResources)
215
219
  }
216
220
 
217
221
  /**
@@ -259,11 +263,9 @@ class ResourcePathParser {
259
263
  if (tokenizer.next(TokenKind.CLOSE)) throw new UriSyntaxError(UriSyntaxError.Message.KEY_EXPECTED)
260
264
 
261
265
  const edmType = currentResource.getEdmType()
262
-
263
266
  const keyPredicates = new KeyPredicateParser(this._edm, this._aliases).parse(currentResource, edmType, tokenizer)
264
267
 
265
268
  tokenizer.requireNext(TokenKind.CLOSE)
266
-
267
269
  currentResource.setKeyPredicates(keyPredicates).setIsCollection(false)
268
270
 
269
271
  if (currentResource.getKind() === UriResource.ResourceKind.ENTITY_COLLECTION) {
@@ -383,15 +385,19 @@ class ResourcePathParser {
383
385
  TokenKind.VALUE,
384
386
  UriResource.ResourceKind.VALUE
385
387
  )
388
+
386
389
  if (valueResource) {
387
390
  const currentType = currentResource.getEdmType()
391
+
388
392
  if (currentType.hasStream()) {
389
393
  return result.concat(valueResource)
390
394
  }
395
+
391
396
  throw new UriSyntaxError(UriSyntaxError.Message.PREVIOUS_TYPE_HAS_NO_MEDIA, currentType.getName())
392
397
  }
393
398
 
394
- return result.concat(this._parsePropertyPath(uriPathSegments, currentResource, tokenizer))
399
+ const uriResources = this._parsePropertyPath(uriPathSegments, currentResource, tokenizer)
400
+ return result.concat(uriResources)
395
401
  }
396
402
 
397
403
  /**
@@ -138,7 +138,8 @@ class UriParser {
138
138
  }
139
139
  }
140
140
 
141
- uriInfo.setPathSegments(this._parseRelativeUri(uriPathSegments, uriInfo.getAliases()))
141
+ const uriResources = this._parseRelativeUri(uriPathSegments, uriInfo.getAliases())
142
+ uriInfo.setPathSegments(uriResources)
142
143
 
143
144
  let currentUriSegment = uriPathSegments.shift()
144
145
  if (currentUriSegment || currentUriSegment === '') {
@@ -1,11 +1,15 @@
1
+ const cds = require('../../../cds')
2
+
1
3
  const OData = require('./OData')
2
4
  const Dispatcher = require('./Dispatcher')
3
- const cds = require('../../../cds')
5
+
6
+ const { alias2ref } = require('../../../common/utils/csn')
4
7
 
5
8
  const to = service => {
6
9
  const edm = cds.compile.to.edm(service.model, { service: service.definition.name })
7
- const odata = new OData(edm, service.model, service.options)
10
+ alias2ref(service, edm)
8
11
 
12
+ const odata = new OData(edm, service.model, service.options)
9
13
  odata.addCDSServiceToChannel(service)
10
14
 
11
15
  return new Dispatcher(odata).getService()
@@ -1,10 +1,7 @@
1
- const cds = require('../../../../cds')
2
-
3
1
  const {
4
2
  Components: { DATA_DELETE_HANDLER, DATA_READ_HANDLER, DATA_CREATE_HANDLER, DATA_UPDATE_HANDLER }
5
3
  } = require('../okra/odata-server')
6
4
 
7
- const { getOnCond } = require('../../../../common/utils/generateOnCond')
8
5
  const { findCsnTargetFor } = require('../../../../common/utils/csn')
9
6
  const { isStreaming } = require('./stream')
10
7
  const { deepCopyObject, deepCopyArray } = require('../../../../common/utils/copy')
@@ -106,27 +103,6 @@ function _entityOrTypeName(navSourceSegment) {
106
103
  .getFullQualifiedName()
107
104
  }
108
105
 
109
- const _getNavigationInfoX4 = (navProperty, navSourceSegment, csn) => {
110
- const { name, namespace } = _entityOrTypeName(navSourceSegment)
111
- const def = findCsnTargetFor(name, csn, namespace)
112
-
113
- const navigationName = navProperty.getName()
114
- const navigationDefinition = def.elements[navigationName]
115
- return { navigationName, navigationDefinition }
116
- }
117
-
118
- const _getNavigationInfo = (navProperty, navSourceSegment, csn) => {
119
- if (cds.env.effective.odata.proxies) {
120
- return _getNavigationInfoX4(navProperty, navSourceSegment, csn)
121
- }
122
-
123
- const { name, namespace } = _entityOrTypeName(navSourceSegment)
124
- const def = findCsnTargetFor(name, csn, namespace)
125
- const navigationName = navProperty.getName()
126
- const navigationDefinition = def.elements[navigationName]
127
- return { navigationName, navigationDefinition }
128
- }
129
-
130
106
  const _addForeignKeys = (service, req, data) => {
131
107
  const pathSegments = req.getUriInfo().getPathSegments()
132
108
  // retrieve keys/values from the path segment representing the navigation source
@@ -149,16 +125,9 @@ const _addForeignKeys = (service, req, data) => {
149
125
  }
150
126
  }
151
127
  } else {
152
- const { navigationName, navigationDefinition } = _getNavigationInfo(navProperty, navSourceSegment, service.model)
153
- const onConditionOptions = {
154
- associationNames: navigationName,
155
- csn: service.model,
156
- aliases: {
157
- select: 'target',
158
- join: 'source'
159
- }
160
- }
161
- const onCondition = getOnCond(navigationDefinition, onConditionOptions)
128
+ const { name, namespace } = _entityOrTypeName(navSourceSegment)
129
+ const def = findCsnTargetFor(name, service.model, namespace)
130
+ const onCondition = def._relations[navProperty.getName()].join('target', 'source')
162
131
  _addKeysToData(navSourceKeyValues, onCondition, data)
163
132
  }
164
133
  }
@@ -23,14 +23,14 @@ const _selectForFunction = (selectColumns, result, opReturnType) => {
23
23
 
24
24
  const { ensureDraftsSuffix, isDraftActivateAction } = require('../../../../fiori/utils/handler')
25
25
 
26
- const _expand = (model, uriInfo) => {
26
+ const _expand = (model, uriInfo, options) => {
27
27
  const expand = uriInfo.getQueryOption(QueryOptions.EXPAND)
28
28
 
29
29
  if (!expand || expand.length === 0) {
30
30
  return []
31
31
  }
32
32
 
33
- return expandToCQN(model, expand, uriInfo.getFinalEdmType())
33
+ return expandToCQN(model, expand, uriInfo.getFinalEdmType(), options)
34
34
  }
35
35
 
36
36
  const _expandForFunction = async (uriInfo, result, req, srv, opReturnType) => {
@@ -52,7 +52,7 @@ const _expandForFunction = async (uriInfo, result, req, srv, opReturnType) => {
52
52
  selectQuery.where(key, '=', row[key])
53
53
  }
54
54
 
55
- const expandCqn = _expand(srv.model, uriInfo)
55
+ const expandCqn = _expand(srv.model, uriInfo, { rewriteAsterisks: true })
56
56
  selectQuery.columns(expandCqn)
57
57
 
58
58
  const res = await cds.tx(req).run(selectQuery)
@@ -2,7 +2,6 @@ const cds = require('../../../../cds')
2
2
  const getTemplate = require('../../../../common/utils/template')
3
3
  const templateProcessor = require('../../../../common/utils/templateProcessor')
4
4
  const { big } = require('@sap/cds-foss')
5
- const { isBacklink } = require('../../../../common/utils/backlinks')
6
5
  const { omitValue, applyOmitValuesPreference } = require('./omitValues')
7
6
 
8
7
  const METADATA = {
@@ -157,30 +156,28 @@ const _getParent = (model, name) => {
157
156
  if (target && target.elements) {
158
157
  for (const elementName in target.elements) {
159
158
  const element = target.elements[elementName]
160
- const assoc = element.target && model.definitions[element.target]
161
- if (assoc && element._isAssociationStrict && isBacklink(element, assoc, true)) return assoc
159
+ if (element._anchor && element._anchor._isContained) return element._anchor
162
160
  }
163
161
  }
164
162
 
165
163
  return null
166
164
  }
167
165
 
168
- const _isUpAssoc = (element, parent) =>
169
- element &&
170
- cds.env.effective.odata.containment &&
171
- /^up_(_up_)*$/.test(element.name) &&
172
- _isContainedOrBackLink(element, parent)
166
+ const _isUpAssoc = element => element && /^up_(_up_)*$/.test(element.name) && _isContainedOrBackLink(element)
173
167
 
174
- const _isContainedOrBackLink = (element, parent) =>
175
- element && element.isAssociation && element.keys && (element._isContained || isBacklink(element, parent, true))
168
+ const _isContainedOrBackLink = element =>
169
+ element &&
170
+ element.isAssociation &&
171
+ element.keys &&
172
+ (element._isContained || (element._anchor && element._anchor._isContained))
176
173
 
177
- const _assocs = (element, target, parent) => {
174
+ const _assocs = (element, target) => {
178
175
  const assocName = element['@odata.foreignKey4']
179
176
  const assoc = assocName && target.elements[assocName]
180
177
 
181
178
  if (cds.env.effective.odata.refs) {
182
179
  // expand assoc keys except of up_ backlinks
183
- if (element['@odata.foreignKey4'] && !_isUpAssoc(assoc, parent)) {
180
+ if (element['@odata.foreignKey4'] && !_isUpAssoc(assoc)) {
184
181
  return ['@odata.foreignKey4']
185
182
  }
186
183
 
@@ -189,7 +186,7 @@ const _assocs = (element, target, parent) => {
189
186
  }
190
187
  }
191
188
 
192
- if (_isContainedOrBackLink(assoc, parent)) {
189
+ if (_isContainedOrBackLink(assoc)) {
193
190
  return ['@cleanup']
194
191
  }
195
192
 
@@ -201,12 +198,12 @@ const _pick = options => (element, target, parent) => {
201
198
 
202
199
  if (element['@odata.etag']) categories.push('@odata.etag')
203
200
  if (element.type === 'cds.Decimal') categories.push('@cds.Decimal')
204
- categories.push(..._assocs(element, target, parent))
201
+ categories.push(..._assocs(element, target))
205
202
  if (options.omitValuesPreference) categories.push('@odata.omitValues')
206
203
  if (categories.length) return { categories }
207
204
  }
208
205
 
209
- const _getPostProcessOptions = headers => {
206
+ const _getOptions = headers => {
210
207
  const options = {
211
208
  decimals: null,
212
209
  omitValuesPreference: null
@@ -231,14 +228,23 @@ const _getPostProcessOptions = headers => {
231
228
  return options
232
229
  }
233
230
 
231
+ const _generateCacheKey = (headers, options) => {
232
+ let key = 'postProcess' // default template cache key for post processing
233
+ if (headers.prefer) key += `:${headers.prefer}`
234
+ if (options.decimals) key += `:exponentialDecimals=true`
235
+ return key
236
+ }
237
+
234
238
  const postProcess = (req, res, service, result, previousResult) => {
235
239
  const { model } = service
236
240
  const { headers, target } = req
237
241
 
238
242
  if (!target || !result || !model || !model.definitions[target.name]) return
239
243
 
240
- const options = _getPostProcessOptions(headers)
241
- const template = getTemplate('postProcess', service, target, { pick: _pick(options) }, _getParent(model, target.name))
244
+ const options = _getOptions(headers)
245
+ const cacheKey = _generateCacheKey(headers, options)
246
+ const parent = _getParent(model, target.name)
247
+ const template = getTemplate(cacheKey, service, target, { pick: _pick(options) }, parent)
242
248
 
243
249
  if (template.elements.size === 0) return
244
250
 
@@ -265,7 +271,31 @@ const postProcess = (req, res, service, result, previousResult) => {
265
271
  applyOmitValuesPreference(res, options.omitValuesPreference)
266
272
  }
267
273
 
274
+ const postProcessMinimal = (req, result) => {
275
+ const { target } = req
276
+
277
+ if (!target || !result) return
278
+
279
+ const etagElement = Object.values(target.elements).find(el => el['@odata.etag'])
280
+
281
+ if (!etagElement) return
282
+
283
+ const etag = etagElement.name
284
+
285
+ // normalize result to rows
286
+ result = result.value && Object.keys(result).filter(k => !k.match(/^\W/)).length === 1 ? result.value : result
287
+ const rows = Array.isArray(result) ? result : [result]
288
+
289
+ // process each row
290
+ for (const row of rows) {
291
+ if (typeof row !== 'object' || !Object.prototype.hasOwnProperty.call(row, etag)) return
292
+
293
+ addEtags(row, etag)
294
+ }
295
+ }
296
+
268
297
  module.exports = {
269
298
  toODataResult,
270
- postProcess
299
+ postProcess,
300
+ postProcessMinimal
271
301
  }
@@ -1,7 +1,8 @@
1
1
  const cds = require('../../../../cds')
2
2
  const { SELECT } = cds.ql
3
- const { adaptStreamCQN } = require('../../../../fiori/utils/handler')
4
- const cqn2cqn4sql = require('../../../../common/utils/cqn2cqn4sql')
3
+ const { isActiveEntityRequested } = require('../../../../fiori/utils/where')
4
+ const { ensureDraftsSuffix } = require('../../../../fiori/utils/handler')
5
+ const { cqn2cqn4sql } = require('../../../../common/utils/cqn2cqn4sql')
5
6
  const { findCsnTargetFor } = require('../../../../common/utils/csn')
6
7
 
7
8
  const isStreaming = segments => {
@@ -14,6 +15,8 @@ const isStreaming = segments => {
14
15
  }
15
16
 
16
17
  const _getProperties = async (properties, req) => {
18
+ const isActiveRequested = isActiveEntityRequested(req.query.SELECT.from.ref[0].where)
19
+
17
20
  // REVISIT DRAFT HANDLING: cqn2cqn4sql should not happen here, but adaptStreamCQN relies on exists clause
18
21
  const cqn = cqn2cqn4sql(SELECT.one(req.query.SELECT.from), req._model).columns(properties)
19
22
 
@@ -22,7 +25,9 @@ const _getProperties = async (properties, req) => {
22
25
  cqn.SELECT.from.ref[0] = req.target.query._target.name
23
26
  }
24
27
 
25
- adaptStreamCQN(cqn)
28
+ if (!isActiveRequested) {
29
+ cqn.SELECT.from.ref[0] = ensureDraftsSuffix(cqn.SELECT.from.ref[0])
30
+ }
26
31
 
27
32
  try {
28
33
  return await cds.tx(req).run(cqn)
@@ -44,7 +49,7 @@ const _getDynamicProperties = (contentType, contentDisposition) => {
44
49
  }
45
50
 
46
51
  const getStreamProperties = async (segments, srv, req) => {
47
- // REVISIT: we need to read direcly from db, which might not be there!
52
+ // REVISIT: we need to read directly from db, which might not be there!
48
53
  if (!cds.db) return {}
49
54
 
50
55
  let contentType, entityName, namespace, contentDisposition
@@ -52,7 +57,7 @@ const getStreamProperties = async (segments, srv, req) => {
52
57
  if (previous.getKind() === 'ENTITY') {
53
58
  entityName = previous.getEntitySet().getName()
54
59
  namespace = previous.getEdmType().getFullQualifiedName().namespace
55
- } else if (previous.getKind() === 'NAVIGATION.TO.ONE') {
60
+ } else if (previous.getKind() === 'NAVIGATION.TO.ONE' && previous.getTarget()) {
56
61
  entityName = previous.getTarget().getName()
57
62
  namespace = previous.getTarget().getEntityType().getFullQualifiedName().namespace
58
63
  }
@@ -39,7 +39,7 @@ module.exports = service => {
39
39
  if (!operation.returns) {
40
40
  status = 204
41
41
  } else {
42
- validateReturnType(req, operation, result)
42
+ validateReturnType(operation, result)
43
43
  bufferToBase64(result, segments[0])
44
44
  body = _convertCustomOperationReturnValue(operation.returns, result)
45
45
  }
@@ -23,7 +23,7 @@ const _updateThenCreate = async (parsed, restReq, restRes, tx) => {
23
23
  req = new RestRequest(parsed, _getData(parsed, restReq), restReq, restRes, tx)
24
24
  result = await tx.dispatch(req)
25
25
  } catch (e) {
26
- if (e.code === 404 && UPSERT_ALLOWED) {
26
+ if ((e.code === 404 || e.status === 404 || e.statusCode === 404) && UPSERT_ALLOWED) {
27
27
  // REVISIT: remove error (and child?) from tx.context? -> would require a unique req.id
28
28
 
29
29
  parsed.event = 'CREATE'
@@ -1,7 +1,5 @@
1
1
  const cds = require('../../../../cds')
2
2
 
3
- const { _newReadToCQN } = require('../../../../odata/readToCqn')
4
-
5
3
  const { INSERT, SELECT, UPDATE, DELETE } = cds.ql
6
4
 
7
5
  const { createCqlString } = require('./utils')
@@ -47,7 +45,7 @@ module.exports = (parsed, data, restReq, service) => {
47
45
  case 'CREATE':
48
46
  return INSERT.into(target).entries(data)
49
47
  case 'READ':
50
- return odata2cqn ? _newReadToCQN(service, target, restReq) : _readToCQN(parsed, target, restReq)
48
+ return odata2cqn ? cds.odata.parse(restReq, { service }) : _readToCQN(parsed, target, restReq)
51
49
  case 'UPDATE':
52
50
  return UPDATE(createCqlString(target, key, value)).data(data)
53
51
  case 'DELETE':
@@ -105,7 +105,10 @@ const _validateAndConvertParamValues = (csnElement, params = {}) => {
105
105
  const _getLastEntity = segments => {
106
106
  let last
107
107
  for (let i = segments.length - 1; i >= 0; i--) {
108
- if (segments[i].kind === 'entity') {
108
+ if (segments[i].target) {
109
+ last = segments[i].target
110
+ break
111
+ } else if (segments[i].kind === 'entity') {
109
112
  last = segments[i]
110
113
  break
111
114
  }
@@ -187,6 +190,7 @@ const parseCreateOrReadUrl = (event, service, req) => {
187
190
  }
188
191
 
189
192
  _setConvenienceProperties(parsed)
193
+ if (typeof parsed.target === 'string') parsed.target = service.model.definitions[parsed.target]
190
194
 
191
195
  return parsed
192
196
  }
@@ -212,7 +216,10 @@ const parseUpdateOrDeleteUrl = (event, service, req) => {
212
216
  segments.push(parts[1])
213
217
  }
214
218
 
215
- return { event, segments, target: _getLastEntity(segments) }
219
+ let target = _getLastEntity(segments)
220
+ if (typeof target === 'string') target = service.model.definitions[target]
221
+
222
+ return { event, segments, target }
216
223
  }
217
224
 
218
225
  module.exports = {