@sap/cds 5.5.4 → 5.6.2

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 (210) hide show
  1. package/CHANGELOG.md +138 -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/to/sql.js +22 -2
  12. package/lib/connect/bindings.js +2 -1
  13. package/lib/core/reflect.js +4 -1
  14. package/lib/env/index.js +175 -41
  15. package/lib/env/requires.js +16 -1
  16. package/lib/i18n/localize.js +33 -5
  17. package/lib/index.js +3 -3
  18. package/lib/log/format/kibana.js +6 -2
  19. package/lib/ql/Query.js +1 -0
  20. package/lib/ql/SELECT.js +15 -8
  21. package/lib/ql/Whereable.js +5 -0
  22. package/lib/req/context.js +13 -5
  23. package/lib/serve/Service-dispatch.js +8 -1
  24. package/lib/serve/Service-methods.js +1 -1
  25. package/lib/utils/axios.js +7 -0
  26. package/lib/utils/data.js +1 -1
  27. package/lib/utils/tests.js +1 -1
  28. package/libx/_runtime/audit/Service.js +18 -18
  29. package/libx/_runtime/audit/generic/personal/access.js +1 -1
  30. package/libx/_runtime/audit/generic/personal/modification.js +3 -2
  31. package/libx/_runtime/audit/generic/personal/utils.js +23 -63
  32. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +4 -0
  33. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +37 -35
  34. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +3 -1
  35. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +5 -5
  36. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +13 -7
  37. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +84 -34
  38. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +10 -4
  39. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +9 -3
  40. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +8 -6
  41. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -3
  42. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +13 -11
  43. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +11 -95
  44. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ResourcePathParser.js +17 -11
  45. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
  46. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/SetResponseHeadersCommand.js +1 -0
  47. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +6 -2
  48. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +3 -34
  49. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +3 -3
  50. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +64 -31
  51. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +10 -5
  52. package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +1 -1
  53. package/libx/_runtime/cds-services/adapter/rest/handlers/update.js +1 -1
  54. package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +1 -3
  55. package/libx/_runtime/cds-services/adapter/rest/utils/validation-checks.js +20 -21
  56. package/libx/_runtime/cds-services/services/utils/columns.js +6 -1
  57. package/libx/_runtime/cds-services/services/utils/compareJson.js +1 -8
  58. package/libx/_runtime/cds-services/services/utils/differ.js +7 -26
  59. package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -4
  60. package/libx/_runtime/cds-services/util/assert.js +29 -13
  61. package/libx/_runtime/cds.js +2 -1
  62. package/libx/_runtime/common/aspects/Association.js +72 -0
  63. package/libx/_runtime/common/aspects/any.js +8 -45
  64. package/libx/_runtime/common/aspects/entity.js +0 -1
  65. package/libx/_runtime/common/aspects/relation.js +40 -0
  66. package/libx/_runtime/common/aspects/utils.js +73 -1
  67. package/libx/_runtime/common/auth/strategies/utils/uaa.js +10 -14
  68. package/libx/_runtime/common/composition/data.js +3 -2
  69. package/libx/_runtime/common/composition/delete.js +3 -1
  70. package/libx/_runtime/common/composition/tree.js +23 -18
  71. package/libx/_runtime/common/composition/update.js +6 -1
  72. package/libx/_runtime/common/composition/utils.js +34 -8
  73. package/libx/_runtime/common/error/frontend.js +6 -1
  74. package/libx/_runtime/common/generic/auth.js +15 -13
  75. package/libx/_runtime/common/generic/crud.js +2 -2
  76. package/libx/_runtime/common/generic/etag.js +11 -8
  77. package/libx/_runtime/common/generic/input.js +3 -3
  78. package/libx/_runtime/common/generic/paging.js +9 -5
  79. package/libx/_runtime/common/generic/put.js +3 -2
  80. package/libx/_runtime/common/generic/sorting.js +3 -3
  81. package/libx/_runtime/common/generic/temporal.js +3 -3
  82. package/libx/_runtime/common/utils/cqn.js +20 -1
  83. package/libx/_runtime/common/utils/cqn2cqn4sql.js +125 -139
  84. package/libx/_runtime/common/utils/csn.js +50 -52
  85. package/libx/_runtime/common/utils/foreignKeyPropagations.js +41 -176
  86. package/libx/_runtime/common/utils/generateOnCond.js +40 -70
  87. package/libx/_runtime/common/utils/{enrichWithKeysFromWhere.js → keys.js} +29 -28
  88. package/libx/_runtime/common/utils/postProcessing.js +3 -0
  89. package/libx/_runtime/common/utils/propagateForeignKeys.js +84 -0
  90. package/libx/_runtime/common/utils/resolveStructured.js +1 -1
  91. package/libx/_runtime/common/utils/resolveView.js +7 -5
  92. package/libx/_runtime/common/utils/rewriteAsterisks.js +94 -0
  93. package/libx/_runtime/common/utils/search2cqn4sql.js +9 -8
  94. package/libx/_runtime/common/utils/template.js +54 -46
  95. package/libx/_runtime/db/Service.js +9 -2
  96. package/libx/_runtime/db/expand/expandCQNToJoin.js +54 -33
  97. package/libx/_runtime/db/expand/rawToExpanded.js +2 -1
  98. package/libx/_runtime/db/generic/arrayed.js +13 -28
  99. package/libx/_runtime/db/generic/create.js +1 -0
  100. package/libx/_runtime/db/generic/input.js +7 -11
  101. package/libx/_runtime/db/generic/integrity.js +2 -2
  102. package/libx/_runtime/db/generic/rewrite.js +2 -5
  103. package/libx/_runtime/db/generic/update.js +1 -0
  104. package/libx/_runtime/db/query/read.js +9 -4
  105. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +6 -0
  106. package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -2
  107. package/libx/_runtime/db/sql-builder/annotations.js +1 -0
  108. package/libx/_runtime/db/utils/columns.js +14 -43
  109. package/libx/_runtime/db/utils/deep.js +5 -7
  110. package/libx/_runtime/fiori/generic/activate.js +3 -2
  111. package/libx/_runtime/fiori/generic/before.js +2 -2
  112. package/libx/_runtime/fiori/generic/cancel.js +3 -2
  113. package/libx/_runtime/fiori/generic/delete.js +3 -2
  114. package/libx/_runtime/fiori/generic/edit.js +3 -3
  115. package/libx/_runtime/fiori/generic/new.js +2 -2
  116. package/libx/_runtime/fiori/generic/patch.js +2 -2
  117. package/libx/_runtime/fiori/generic/prepare.js +2 -2
  118. package/libx/_runtime/fiori/generic/read.js +17 -63
  119. package/libx/_runtime/fiori/generic/readOverDraft.js +4 -4
  120. package/libx/_runtime/fiori/uiflex/extensibility/index.cds +15 -0
  121. package/libx/_runtime/fiori/uiflex/extensibility/index.js +148 -0
  122. package/libx/_runtime/fiori/uiflex/handler/transformREAD.js +119 -0
  123. package/libx/_runtime/fiori/uiflex/handler/transformRESULT.js +43 -0
  124. package/libx/_runtime/fiori/uiflex/handler/transformWRITE.js +62 -0
  125. package/libx/_runtime/fiori/uiflex/index.js +35 -0
  126. package/libx/_runtime/fiori/uiflex/utils.js +78 -0
  127. package/libx/_runtime/fiori/utils/handler.js +3 -13
  128. package/libx/_runtime/fiori/utils/where.js +6 -1
  129. package/libx/_runtime/hana/pool.js +12 -11
  130. package/libx/_runtime/hana/search2cqn4sql.js +34 -43
  131. package/libx/_runtime/hana/searchToContains.js +3 -3
  132. package/libx/_runtime/index.js +5 -2
  133. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +1 -1
  134. package/libx/_runtime/messaging/common-utils/AMQPClient.js +16 -3
  135. package/libx/_runtime/messaging/common-utils/connections.js +11 -14
  136. package/libx/_runtime/messaging/common-utils/naming-conventions.js +1 -1
  137. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -1
  138. package/libx/_runtime/messaging/message-queuing.js +18 -0
  139. package/libx/_runtime/remote/Service.js +20 -4
  140. package/libx/_runtime/remote/utils/client-types.d.ts +7 -0
  141. package/libx/_runtime/remote/utils/client.js +117 -23
  142. package/libx/_runtime/sqlite/Service.js +2 -2
  143. package/libx/_runtime/sqlite/convertAssocToOneManaged.js +1 -3
  144. package/libx/gql/GraphQLAdapter.js +33 -0
  145. package/libx/gql/constants/adapter.js +69 -0
  146. package/libx/gql/constants/cds.js +18 -0
  147. package/libx/gql/constants/graphql.js +33 -0
  148. package/libx/gql/resolvers/crud/create.js +15 -0
  149. package/libx/gql/resolvers/crud/delete.js +24 -0
  150. package/libx/gql/resolvers/crud/index.js +6 -0
  151. package/libx/gql/resolvers/crud/read.js +25 -0
  152. package/libx/gql/resolvers/crud/update.js +31 -0
  153. package/libx/gql/resolvers/crud/utils/index.js +36 -0
  154. package/libx/gql/resolvers/field.js +5 -0
  155. package/libx/gql/resolvers/index.js +7 -0
  156. package/libx/gql/resolvers/mutation.js +23 -0
  157. package/libx/gql/resolvers/parse/ast/enrich.js +51 -0
  158. package/libx/gql/resolvers/parse/ast/fragment.js +11 -0
  159. package/libx/gql/resolvers/parse/ast/fromObject.js +39 -0
  160. package/libx/gql/resolvers/parse/ast/index.js +3 -0
  161. package/libx/gql/resolvers/parse/ast/meta.js +4 -0
  162. package/libx/gql/resolvers/parse/ast/variable.js +7 -0
  163. package/libx/gql/resolvers/parse/ast2cqn/columns.js +42 -0
  164. package/libx/gql/resolvers/parse/ast2cqn/entries.js +31 -0
  165. package/libx/gql/resolvers/parse/ast2cqn/index.js +8 -0
  166. package/libx/gql/resolvers/parse/ast2cqn/limit.js +6 -0
  167. package/libx/gql/resolvers/parse/ast2cqn/orderBy.js +24 -0
  168. package/libx/gql/resolvers/parse/ast2cqn/utils/index.js +3 -0
  169. package/libx/gql/resolvers/parse/ast2cqn/where.js +70 -0
  170. package/libx/gql/resolvers/parse/utils/index.js +8 -0
  171. package/libx/gql/resolvers/query.js +13 -0
  172. package/libx/gql/resolvers/root.js +34 -0
  173. package/libx/gql/schema/generate.js +18 -0
  174. package/libx/gql/schema/index.js +5 -0
  175. package/libx/gql/schema/mutation.js +76 -0
  176. package/libx/gql/schema/query.js +108 -0
  177. package/libx/gql/schema/typeDefMap.js +45 -0
  178. package/libx/gql/schema/utils/index.js +54 -0
  179. package/libx/gql/utils/index.js +12 -0
  180. package/libx/{_runtime/odata/cqn2odata.js → odata/cqn2odata/index.js} +39 -100
  181. package/libx/odata/index.js +80 -0
  182. package/libx/odata/odata2cqn/afterburner.js +170 -0
  183. package/libx/{_runtime/odata/odata2cqn.pegjs → odata/odata2cqn/grammar.pegjs} +102 -123
  184. package/libx/odata/odata2cqn/index.js +3 -0
  185. package/libx/odata/odata2cqn/parser.js +1 -0
  186. package/libx/odata/utils/index.js +64 -0
  187. package/libx/rest/RestAdapter.js +101 -0
  188. package/libx/rest/RestRequest.js +30 -0
  189. package/libx/rest/index.js +3 -0
  190. package/libx/rest/middleware/auth.js +22 -0
  191. package/libx/rest/middleware/content.js +15 -0
  192. package/libx/rest/middleware/create.js +40 -0
  193. package/libx/rest/middleware/delete.js +20 -0
  194. package/libx/rest/middleware/error.js +56 -0
  195. package/libx/rest/middleware/operation.js +39 -0
  196. package/libx/rest/middleware/parse.js +90 -0
  197. package/libx/rest/middleware/read.js +29 -0
  198. package/libx/rest/middleware/update.js +42 -0
  199. package/libx/rest/utils/data.js +65 -0
  200. package/package.json +4 -1
  201. package/server.js +29 -7
  202. package/libx/_runtime/cds-services/services/utils/diff.js +0 -53
  203. package/libx/_runtime/cds-services/util/auditlog.js +0 -247
  204. package/libx/_runtime/cds-services/util/xsenv.js +0 -51
  205. package/libx/_runtime/common/utils/backlinks.js +0 -83
  206. package/libx/_runtime/common/utils/rewriteAsterisk.js +0 -72
  207. package/libx/_runtime/odata/index.js +0 -55
  208. package/libx/_runtime/odata/odata2cqn.js +0 -1
  209. package/libx/_runtime/odata/readToCqn.js +0 -129
  210. package/libx/_runtime/remote/cqn2odata/index.js +0 -2
@@ -9,12 +9,18 @@ const {
9
9
  const { getSapMessages } = require('../../../../common/error/frontend')
10
10
  const { validateResourcePath } = require('../utils/request')
11
11
  const { isReturnMinimal } = require('../utils/handlerUtils')
12
- const { foreignKeyPropagations } = require('../../../../common/utils/foreignKeyPropagations')
13
12
  const readAfterWrite = require('../utils/readAfterWrite')
14
- const { toODataResult, postProcess } = require('../utils/result')
13
+ const { toODataResult, postProcess, postProcessMinimal } = require('../utils/result')
15
14
  const { hasOmitValuesPreference } = require('../utils/omitValues')
16
15
  const { mergeJson } = require('../../../services/utils/compareJson')
17
16
 
17
+ /*
18
+ const { isStreaming } = require('../utils/stream')
19
+ const { findCsnTargetFor } = require('../../../../common/utils/csn')
20
+ const { isActiveEntityRequested, removeIsActiveEntityRecursively } = require('../../../../fiori/utils/where')
21
+ const { ensureDraftsSuffix } = require('../../../../fiori/utils/handler')
22
+ */
23
+
18
24
  const _isUpsertAllowed = target => {
19
25
  return !(cds.env.runtime && cds.env.runtime.allow_upsert === false) && !(target && target._isDraftEnabled)
20
26
  }
@@ -36,53 +42,41 @@ const _infoForeignKeyInParent = (req, odataReq, odataRes, tx) => {
36
42
  }
37
43
 
38
44
  const navID = typeof nav === 'string' ? nav : nav.id
39
- const elementNav = tx.model.definitions[parent].elements[navID]
45
+ const navElement = tx.model.definitions[parent].elements[navID]
40
46
 
41
47
  // not a containment
42
- if (!elementNav['@odata.contained']) {
48
+ if (!navElement['@odata.contained']) {
43
49
  return info
44
50
  }
45
51
 
46
52
  const where = req.query.INSERT.into.ref[0].where
47
- return { parent, nav, where }
48
- }
49
-
50
- const _getParentKey = (parentKeyObj, parentKey, childKey, req) => {
51
- let parentKeyVal, parentUpdateRequired
52
-
53
- if (parentKeyObj.length !== 0 && parentKeyObj[0][parentKey] !== null) {
54
- parentKeyVal = parentKeyObj[0][parentKey]
55
- } else if (req.target.keys[childKey].type === 'cds.UUID') {
56
- parentUpdateRequired = true
57
- parentKeyVal = cds.utils.uuid()
58
- } else {
59
- throw new Error('Only keys of type UUID can be generated: ' + childKey)
60
- }
61
-
62
- return { parentKeyVal, parentUpdateRequired }
53
+ return { parent, navElement, where }
63
54
  }
64
55
 
65
56
  const _create = async (req, odataReq, odataRes, tx) => {
66
57
  let result
67
58
 
68
- const { parent, nav, where } = _infoForeignKeyInParent(req, odataReq, odataRes, tx)
69
- if (parent && nav && where) {
70
- const onKeys = foreignKeyPropagations(tx.model.definitions[parent].elements[nav])
71
- const parentKeys = onKeys.map(key => key.parentFieldName)
59
+ const { parent, navElement, where } = _infoForeignKeyInParent(req, odataReq, odataRes, tx)
60
+ if (parent && navElement && where) {
61
+ const onKeys = navElement._foreignKeys
62
+ const parentKeys = onKeys.filter(key => key.parentElement).map(key => key.parentElement.name)
72
63
  const parentKeyObj = await tx.run(SELECT.from(parent).columns(parentKeys).where(where))
73
64
 
74
65
  const parentUpdateObj = {}
75
66
  onKeys.forEach(key => {
76
- const { parentKeyVal, parentUpdateRequired } = _getParentKey(
77
- parentKeyObj,
78
- key.parentFieldName,
79
- key.childFieldName,
80
- req
81
- )
82
- odataReq.getBody()[key.childFieldName] = parentKeyVal
67
+ let parentKeyVal, parentUpdateRequired
68
+ if (parentKeyObj.length !== 0 && parentKeyObj[0][key.parentElement.name] !== null) {
69
+ parentKeyVal = parentKeyObj[0][key.parentElement.name]
70
+ } else if (key.childElement.type === 'cds.UUID' && key.childElement.key) {
71
+ parentUpdateRequired = true
72
+ parentKeyVal = cds.utils.uuid()
73
+ } else {
74
+ throw new Error('Only keys of type UUID can be generated: ' + key.childFieldName)
75
+ }
76
+ odataReq.getBody()[key.childElement.name] = parentKeyVal
83
77
 
84
78
  if (parentUpdateRequired) {
85
- parentUpdateObj[key.parentFieldName] = parentKeyVal
79
+ parentUpdateObj[key.parentElement.name] = parentKeyVal
86
80
  }
87
81
  })
88
82
 
@@ -107,7 +101,7 @@ const _updateThenCreate = async (req, odataReq, odataRes, tx) => {
107
101
  try {
108
102
  result = await tx.dispatch(req)
109
103
  } catch (e) {
110
- if (e.code === 404 && _isUpsertAllowed(req.target)) {
104
+ if ((e.code === 404 || e.status === 404 || e.statusCode === 404) && _isUpsertAllowed(req.target)) {
111
105
  // REVISIT: remove error (and child?) from tx.context? -> would require a unique req.id
112
106
  ;[result, req] = await _create(req, odataReq, odataRes, tx)
113
107
  } else {
@@ -127,6 +121,58 @@ const _readAfterWriteAndVirtuals = async (req, service, result) => {
127
121
  const _shouldReadPreviousResult = req =>
128
122
  req.event === 'UPDATE' && !isReturnMinimal(req) && hasOmitValuesPreference(req.headers.prefer, 'defaults')
129
123
 
124
+ /*
125
+ const _getEntity = (segments, model) => {
126
+ let entityName, namespace
127
+ const previous = segments[segments.length - 2]
128
+ if (previous.getKind() === 'ENTITY') {
129
+ entityName = previous.getEntitySet().getName()
130
+ namespace = previous.getEdmType().getFullQualifiedName().namespace
131
+ } else if (previous.getKind() === 'NAVIGATION.TO.ONE') {
132
+ entityName = previous.getTarget().getName()
133
+ namespace = previous.getTarget().getEntityType().getFullQualifiedName().namespace
134
+ }
135
+
136
+ if (entityName) {
137
+ return findCsnTargetFor(entityName, model, namespace)
138
+ }
139
+ }
140
+
141
+ const _getMediaType = entity => {
142
+ if (entity._hasPersistenceSkip) return
143
+
144
+ return Object.values(entity.elements).find(ele => ele['@Core.IsMediaType'])
145
+ }
146
+
147
+ const _getMediaTypeCQN = (mediaType, contentType, entity, req) => {
148
+ const where = req.query.UPDATE.entity.ref[0].where
149
+ const isActive = isActiveEntityRequested(where)
150
+ const data = {}
151
+ data[mediaType.name] = contentType
152
+ const cqn = UPDATE(entity).set(data)
153
+ cqn.UPDATE.where = removeIsActiveEntityRecursively(where)
154
+ if (!isActive) {
155
+ cqn.UPDATE.entity = ensureDraftsSuffix(entity.name)
156
+ }
157
+
158
+ return cqn
159
+ }
160
+
161
+ const _handleMediaType = async (odataReq, model, tx, req) => {
162
+ const segments = odataReq.getUriInfo().getPathSegments()
163
+ const contentType = odataReq._inRequest.headers['content-type']
164
+ if (isStreaming(segments) && contentType) {
165
+ const entity = _getEntity(segments, model)
166
+ if (entity && !entity['@cds.persistence.skip']) {
167
+ const mediaType = _getMediaType(entity)
168
+ if (mediaType) {
169
+ await tx.run(_getMediaTypeCQN(mediaType, contentType, entity, req))
170
+ }
171
+ }
172
+ }
173
+ }
174
+ */
175
+
130
176
  /**
131
177
  * The handler that will be registered with odata-v4.
132
178
  *
@@ -155,8 +201,10 @@ const update = service => {
155
201
 
156
202
  let result, err, commit
157
203
  try {
158
- let previousResult
204
+ // // REVISIT: should be handled somewhere else
205
+ // await _handleMediaType(odataReq, service.model, tx, req)
159
206
 
207
+ let previousResult
160
208
  if (_shouldReadPreviousResult(req)) {
161
209
  previousResult = await _readAfterWriteAndVirtuals(req, service, result)
162
210
  }
@@ -171,6 +219,8 @@ const update = service => {
171
219
  }
172
220
 
173
221
  postProcess(req, odataRes, service, result, previousResult)
222
+ } else {
223
+ postProcessMinimal(req, result)
174
224
  }
175
225
 
176
226
  if (changeset) {
@@ -42,10 +42,16 @@ class ExpressionToCQN {
42
42
  case EdmPrimitiveTypeKind.Double:
43
43
  return { val: parseFloat(value) }
44
44
  case EdmPrimitiveTypeKind.DateTimeOffset: {
45
- let val = new Date(value).toISOString()
46
- // cut off ms if cds.DateTime
47
- if (expression._cdsType === 'cds.DateTime') val = val.replace(/\.\d\d\dZ$/, 'Z')
48
- return { val }
45
+ try {
46
+ let val = new Date(value).toISOString()
47
+ // cut off ms if cds.DateTime
48
+ if (expression._cdsType === 'cds.DateTime') val = val.replace(/\.\d\d\dZ$/, 'Z')
49
+ return { val }
50
+ } catch (e) {
51
+ throw Object.assign(new Error(`The type 'Edm.DateTimeOffset' is not compatible with '${value}'`), {
52
+ status: 400
53
+ })
54
+ }
49
55
  }
50
56
  default:
51
57
  return { val: value }
@@ -195,17 +195,23 @@ const applyToCQN = (transformations, entity, model) => {
195
195
  _handleTransformation(transformation, entity, res)
196
196
  }
197
197
  for (const item of transformation.getGroupByItems()) {
198
- if (item.getPathSegments()[0].getKind() === 'COMPLEX.PROPERTY') {
198
+ const pathSegment = item.getPathSegments().length > 0 && item.getPathSegments()[0]
199
+ if (!pathSegment) {
200
+ throw getFeatureNotSupportedError(
201
+ 'Transformation "groupby" with query option $apply does not support this request'
202
+ )
203
+ }
204
+ if (pathSegment.getKind() === 'COMPLEX.PROPERTY') {
199
205
  throw getFeatureNotSupportedError(
200
206
  'Transformation "groupby" with query option $apply does not support complex properties'
201
207
  )
202
208
  // TODO support annotations Groupable
203
209
  // Odata spec: http://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/cs01/odata-data-aggregation-ext-v4.0-cs01.html#_Toc378326318
204
210
  // res.groupBy.push(_complexProperty(item.getPathSegments()))
205
- } else if (item.getPathSegments()[0].getProperty()) {
211
+ } else if (pathSegment.getProperty()) {
206
212
  const name = item.getPathSegments()[0].getProperty().getName()
207
213
  res.groupBy.push(name)
208
- } else if (item.getPathSegments()[0].getNavigationProperty()) {
214
+ } else if (pathSegment.getNavigationProperty()) {
209
215
  res.groupBy.push(_createNavGroupBy(item.getPathSegments()))
210
216
  }
211
217
  }
@@ -98,7 +98,7 @@ const _getInnerSelect = expandItem => {
98
98
  * @returns {Array}
99
99
  * @private
100
100
  */
101
- const _getSelectedElements = (expandItem, targetType, relatedEntity) => {
101
+ const _getSelectedElements = (expandItem, targetType, relatedEntity, options) => {
102
102
  if (cds.env.effective.odata.proxies || cds.env.effective.odata.xrefs) {
103
103
  // proxy target?
104
104
  let proxy = true
@@ -113,7 +113,9 @@ const _getSelectedElements = (expandItem, targetType, relatedEntity) => {
113
113
  let innerSelectItems = _getInnerSelect(expandItem)
114
114
 
115
115
  if (innerSelectItems.length === 0 || innerSelectItems.some(item => item.isAll())) {
116
- return _getColumnsFromTargetType(targetType, relatedEntity, true)
116
+ // REVISIT: Remove once we clean up our draft handling
117
+ if (options && options.rewriteAsterisks) return _getColumnsFromTargetType(targetType, relatedEntity, true)
118
+ return ['*']
117
119
  }
118
120
 
119
121
  // remove navigations from select clause
@@ -166,7 +168,7 @@ const _filter = (item, expression) => {
166
168
  item.where = SELECT.from('a').where(expressionToCQN.parse(expression)).SELECT.where
167
169
  }
168
170
 
169
- const _getItemCQN = (model, name, navigationProperty, expandItem) => {
171
+ const _getItemCQN = (model, name, navigationProperty, expandItem, options) => {
170
172
  _notSupported(expandItem)
171
173
 
172
174
  const targetType = navigationProperty.getEntityType()
@@ -176,7 +178,7 @@ const _getItemCQN = (model, name, navigationProperty, expandItem) => {
176
178
  const relatedEntity = findCsnTargetFor(entityName, model, namespace)
177
179
  const item = {
178
180
  ref: name, // ['structured', 'nested_', nestedAssocToOne] if expand on structured
179
- expand: _getSelectedElements(expandItem, targetType, relatedEntity)
181
+ expand: _getSelectedElements(expandItem, targetType, relatedEntity, options)
180
182
  }
181
183
 
182
184
  item.expand.push(..._getInnerExpandItems(model, expandItem, targetType))
@@ -222,7 +224,7 @@ const _name = expandItem =>
222
224
  * @param type
223
225
  * @returns {Array}
224
226
  */
225
- const expandToCQN = (model, expandItems, type) => {
227
+ const expandToCQN = (model, expandItems, type, options) => {
226
228
  const allElements = []
227
229
  const isAll = expandItems.some(item => item.isAll())
228
230
 
@@ -230,7 +232,7 @@ const expandToCQN = (model, expandItems, type) => {
230
232
  const expandItem = _getExpandItem(isAll, expandItems, name)
231
233
 
232
234
  if (isAll || expandItem) {
233
- allElements.push(_getItemCQN(model, [name], navigationProperty, expandItem))
235
+ allElements.push(_getItemCQN(model, [name], navigationProperty, expandItem, options))
234
236
  }
235
237
  }
236
238
 
@@ -1,7 +1,5 @@
1
1
  const cds = require('../../../../cds')
2
2
 
3
- const { _newReadToCQN } = require('../../../../odata/readToCqn')
4
-
5
3
  const {
6
4
  Components: { DATA_CREATE_HANDLER, DATA_DELETE_HANDLER, DATA_READ_HANDLER, DATA_UPDATE_HANDLER }
7
5
  } = require('../okra/odata-server')
@@ -32,7 +30,7 @@ module.exports = (component, service, target, data, odataReq, upsert) => {
32
30
  case DATA_DELETE_HANDLER:
33
31
  return deleteToCQN(service, odataReq)
34
32
  case DATA_READ_HANDLER:
35
- return odata2cqn ? _newReadToCQN(service, target, odataReq) : readToCQN(service, target, odataReq)
33
+ return odata2cqn ? cds.odata.parse(odataReq, { service }) : readToCQN(service, target, odataReq)
36
34
  case DATA_UPDATE_HANDLER:
37
35
  return updateToCQN(service, data, odataReq)
38
36
  case 'BOUND.ACTION':
@@ -2,9 +2,8 @@ const cds = require('../../../../cds')
2
2
  const { SELECT } = cds.ql
3
3
 
4
4
  const QueryOptions = require('../okra/odata-server').QueryOptions
5
- const { getColumns } = require('../../../services/utils/columns')
6
5
  const { isNavigation, isPathSupported } = require('./selectHelper')
7
- const { isViewWithParams, validationQuery } = require('./selectHelper')
6
+ const { isViewWithParams, getValidationQuery } = require('./selectHelper')
8
7
  const { ensureUnlocalized } = require('../../../../fiori/utils/handler')
9
8
  const ExpressionToCQN = require('./ExpressionToCQN')
10
9
  const orderByToCQN = require('./orderByToCQN')
@@ -16,6 +15,7 @@ const { resolveStructuredName } = require('../utils/handlerUtils')
16
15
  const { isStreaming } = require('../utils/stream')
17
16
  const { convertUrlPathToCqn, getAllKeys } = require('./utils')
18
17
  const { getMaxPageSize } = require('../../../../common/utils/page')
18
+ const { isAsteriskColumn } = require('../../../../common/utils/rewriteAsterisks')
19
19
 
20
20
  const {
21
21
  COUNT,
@@ -200,8 +200,10 @@ const _cleanupForApply = (apply, cqn) => {
200
200
  const selectColumns = cqn.SELECT.columns.map(c => c.as || (c.ref && c.ref[c.ref.length - 1]))
201
201
  if (cqn.SELECT.orderBy) {
202
202
  // include path expressions
203
- const newOrderBy = cqn.SELECT.orderBy.filter(o => _containsSelectedColumn(o, selectColumns))
204
- cqn.SELECT.orderBy = newOrderBy
203
+ if (!cqn.SELECT.columns.some(c => isAsteriskColumn(c))) {
204
+ const newOrderBy = cqn.SELECT.orderBy.filter(o => _containsSelectedColumn(o, selectColumns))
205
+ cqn.SELECT.orderBy = newOrderBy
206
+ }
205
207
  }
206
208
 
207
209
  if (!cqn.SELECT.orderBy || !cqn.SELECT.orderBy.length) {
@@ -229,16 +231,16 @@ const _checkViewWithParamCall = (isView, segments, kind, name) => {
229
231
  }
230
232
  }
231
233
 
232
- const enhanceCqnForNavigation = (segments, isView, cqn, service, SELECT, kind) => {
234
+ const addValidationQueryIfRequired = (segments, isView, cqn, service, kind) => {
233
235
  if (isNavigation(segments) && !isView && (kind === NAVIGATION_TO_MANY || kind === NAVIGATION_TO_ONE)) {
234
- cqn._validationQuery = validationQuery(segments, service.model, SELECT)
236
+ cqn._validationQuery = getValidationQuery(cqn.SELECT.from.ref, service.model)
235
237
  cqn._validationQuery.__navToManyWithKeys =
236
238
  kind === NAVIGATION_TO_ONE && segments[segments.length - 1].getKeyPredicates().length !== 0
237
239
  }
238
240
  }
239
241
 
240
242
  const _addKeysToSelectIfNoStreaming = (entity, select, streaming) => {
241
- // might also be signleton w/o keys
243
+ // might also be singleton w/o keys
242
244
  if (!streaming && entity.keys) {
243
245
  for (const k of Object.values(entity.keys)) {
244
246
  // REVISIT: !select.includes(k.name) needed?
@@ -370,9 +372,7 @@ const readToCQN = (service, target, odataReq) => {
370
372
  }
371
373
 
372
374
  if (select.length === 0) {
373
- select.push(
374
- ...getColumns(entity, { onlyNames: true, removeIgnore: true, filterDraft: false }).map(col => ({ ref: [col] }))
375
- )
375
+ select.push('*')
376
376
  }
377
377
 
378
378
  if (expand.length) {
@@ -388,7 +388,7 @@ const readToCQN = (service, target, odataReq) => {
388
388
 
389
389
  // keep target as input because of localized view
390
390
  const cqn = SELECT.from(isView ? _convertUrlPathToViewCqn(segments) : convertUrlPathToCqn(segments, service), select)
391
- enhanceCqnForNavigation(segments, isView, cqn, service, SELECT, kind)
391
+ addValidationQueryIfRequired(segments, isView, cqn, service, kind)
392
392
 
393
393
  if (Object.keys(apply).length) {
394
394
  _extendCqnWithApply(cqn, apply, entity)
@@ -409,6 +409,8 @@ const readToCQN = (service, target, odataReq) => {
409
409
  }
410
410
 
411
411
  _cleanupForApply(apply, cqn)
412
+ // just like in new parser
413
+ if (cqn.SELECT.columns.length === 1 && cqn.SELECT.columns[0] === '*') delete cqn.SELECT.columns
412
414
  return cqn
413
415
  }
414
416
 
@@ -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 === '') {
@@ -75,6 +75,7 @@ class SetResponseHeadersCommand extends Command {
75
75
  ? lastSegment.getTarget() && lastSegment.getTarget().isConcurrent()
76
76
  : this._request.getConcurrentResource()) &&
77
77
  representationKind !== RepresentationKinds.ENTITY_COLLECTION &&
78
+ representationKind !== RepresentationKinds.COUNT &&
78
79
  representationKind !== RepresentationKinds.REFERENCE &&
79
80
  representationKind !== RepresentationKinds.REFERENCE_COLLECTION
80
81
  ) {
@@ -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()