@sap/cds 7.5.3 → 7.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 (101) hide show
  1. package/CHANGELOG.md +79 -21
  2. package/app/index.js +6 -17
  3. package/lib/auth/index.js +3 -0
  4. package/lib/compile/extend.js +9 -4
  5. package/lib/compile/for/lean_drafts.js +3 -4
  6. package/lib/compile/load.js +11 -15
  7. package/lib/compile/minify.js +2 -4
  8. package/lib/compile/to/sql.js +6 -4
  9. package/lib/compile/to/yaml.js +1 -1
  10. package/lib/dbs/cds-deploy.js +7 -13
  11. package/lib/env/defaults.js +1 -10
  12. package/lib/env/schemas/cds-package.js +27 -0
  13. package/lib/env/schemas/cds-rc.js +693 -0
  14. package/lib/env/schemas/index.js +6 -4
  15. package/lib/index.js +40 -47
  16. package/lib/log/cds-error.js +6 -0
  17. package/lib/log/format/aspects/als.js +1 -0
  18. package/lib/log/format/json.js +5 -1
  19. package/lib/ql/Query.js +2 -1
  20. package/lib/ql/cds-ql.js +1 -2
  21. package/lib/ql/infer.js +0 -2
  22. package/lib/req/cds-context.js +1 -1
  23. package/lib/req/request.js +3 -6
  24. package/lib/srv/middlewares/trace.js +2 -2
  25. package/lib/srv/protocols/hcql.js +44 -30
  26. package/lib/srv/protocols/http.js +60 -0
  27. package/lib/srv/protocols/index.js +0 -7
  28. package/lib/srv/protocols/odata-v4.js +8 -2
  29. package/lib/srv/srv-api.js +129 -62
  30. package/lib/srv/srv-handlers.js +0 -1
  31. package/lib/srv/srv-models.js +1 -0
  32. package/lib/utils/cds-utils.js +26 -0
  33. package/lib/utils/check-version.js +10 -13
  34. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +22 -6
  35. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +3 -4
  36. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +89 -21
  37. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/boundToCQN.js +4 -2
  38. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +1 -24
  39. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/updateToCQN.js +1 -7
  40. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ApplyParser.js +3 -3
  41. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/batch/BatchProcessor.js +1 -1
  42. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/http/HttpHeaderReader.js +1 -1
  43. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/TrustedResourceJsonSerializer.js +6 -0
  44. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +0 -5
  45. package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +2 -0
  46. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +17 -1
  47. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +22 -2
  48. package/libx/_runtime/cds-services/services/utils/columns.js +1 -2
  49. package/libx/_runtime/common/aspects/Association.js +17 -9
  50. package/libx/_runtime/common/generic/crud.js +13 -22
  51. package/libx/_runtime/common/generic/etag.js +1 -1
  52. package/libx/_runtime/common/generic/input.js +9 -1
  53. package/libx/_runtime/common/generic/paging.js +3 -3
  54. package/libx/_runtime/common/generic/sorting.js +25 -15
  55. package/libx/_runtime/common/generic/stream.js +2 -16
  56. package/libx/_runtime/common/i18n/messages.properties +3 -0
  57. package/libx/_runtime/common/utils/copy.js +5 -0
  58. package/libx/_runtime/common/utils/cqn.js +1 -1
  59. package/libx/_runtime/common/utils/cqn2cqn4sql.js +4 -3
  60. package/libx/_runtime/common/utils/csn.js +0 -49
  61. package/libx/_runtime/common/utils/foreignKeyPropagations.js +5 -5
  62. package/libx/_runtime/common/utils/generateOnCond.js +50 -25
  63. package/libx/_runtime/common/utils/resolveView.js +5 -35
  64. package/libx/_runtime/common/utils/rewriteAsterisks.js +17 -4
  65. package/libx/_runtime/common/utils/stream.js +16 -15
  66. package/libx/_runtime/common/utils/streamProp.js +25 -22
  67. package/libx/_runtime/db/Service.js +27 -8
  68. package/libx/_runtime/db/generic/input.js +6 -1
  69. package/libx/_runtime/db/generic/rewrite.js +3 -2
  70. package/libx/_runtime/db/query/read.js +15 -5
  71. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +0 -11
  72. package/libx/_runtime/db/utils/columns.js +1 -0
  73. package/libx/_runtime/db/utils/stream.js +41 -0
  74. package/libx/_runtime/fiori/generic/read.js +2 -1
  75. package/libx/_runtime/fiori/generic/readOverDraft.js +1 -1
  76. package/libx/_runtime/fiori/lean-draft.js +209 -55
  77. package/libx/_runtime/hana/Service.js +1 -1
  78. package/libx/_runtime/hana/execute.js +53 -14
  79. package/libx/_runtime/messaging/AMQPWebhookMessaging.js +2 -1
  80. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +34 -15
  81. package/libx/_runtime/messaging/file-based.js +4 -3
  82. package/libx/_runtime/messaging/redis-messaging.js +2 -1
  83. package/libx/_runtime/remote/Service.js +2 -1
  84. package/libx/_runtime/remote/utils/client.js +1 -1
  85. package/libx/_runtime/sqlite/Service.js +1 -1
  86. package/libx/_runtime/sqlite/execute.js +17 -5
  87. package/libx/odata/afterburner.js +58 -19
  88. package/libx/odata/cqn2odata.js +6 -8
  89. package/libx/odata/create.js +44 -0
  90. package/libx/odata/delete.js +25 -0
  91. package/libx/odata/error.js +8 -3
  92. package/libx/odata/metadata.js +6 -8
  93. package/libx/odata/service-document.js +1 -1
  94. package/libx/odata/update.js +110 -0
  95. package/libx/odata/utils.js +9 -6
  96. package/libx/outbox/index.js +74 -89
  97. package/libx/rest/RestAdapter.js +0 -3
  98. package/package.json +1 -1
  99. package/lib/env/schemas/cds-package.json +0 -17
  100. package/lib/env/schemas/cds-rc.json +0 -740
  101. package/lib/ql/STREAM.js +0 -90
@@ -3,8 +3,7 @@ const { UPDATE } = cds.ql
3
3
 
4
4
  const { getFeatureNotSupportedError } = require('../../../util/errors')
5
5
  const { isStreaming } = require('../utils/stream')
6
- const { convertUrlPathToCqn, getPropertyParam } = require('./utils')
7
- const { isNewStream } = require('../../../../common/utils/stream')
6
+ const { convertUrlPathToCqn } = require('./utils')
8
7
 
9
8
  const { ENTITY, NAVIGATION_TO_ONE, PRIMITIVE_PROPERTY, SINGLETON } =
10
9
  require('../okra/odata-server').uri.UriResource.ResourceKind
@@ -29,11 +28,6 @@ const updateToCQN = (service, data, odataReq) => {
29
28
  const segment = segments[segments.length - 1]
30
29
  const streaming = isStreaming(segments)
31
30
 
32
- if (isNewStream() && streaming) {
33
- const col = getPropertyParam(segments).ref[0]
34
- return STREAM.into(convertUrlPathToCqn(segments, service)).data(data[col]).column(col)
35
- }
36
-
37
31
  if (SUPPORTED_KINDS[segment.getKind()] || streaming) {
38
32
  return UPDATE(convertUrlPathToCqn(segments, service)).data(data)
39
33
  }
@@ -700,10 +700,10 @@ class ApplyParser {
700
700
  }
701
701
  pathSegments.push(this._createPropertyResource(property))
702
702
  }
703
- if (pathSegments[pathSegments.length - 1].isCollection()) {
704
- throw new UriQueryOptionSemanticError(UriQueryOptionSemanticError.Message.COLLECTION)
705
- }
706
703
  if (!pathSegments.length) this._tokenizer.requireNext(TokenKind.ODataIdentifier) // for the error message
704
+ if (pathSegments[pathSegments.length - 1]?.isCollection()) {
705
+ throw new UriQueryOptionSemanticError(UriQueryOptionSemanticError.Message.COLLECTION)
706
+ }
707
707
  // TODO: Generalize to more than one segment and to other than structural properties.
708
708
  if (pathSegments.length === 1 && pathSegments[0].getProperty()) {
709
709
  referencedType.protectProperty(pathSegments[0].getProperty().getName())
@@ -36,7 +36,7 @@ class BatchProcessor {
36
36
  * @returns {Promise} the overall result
37
37
  */
38
38
  process () {
39
- if(cds.env.odata.batch_limit < this._batchContext.getRequestList().length) return Promise.reject({statusCode: 429, message: 'Batch request contains too many requests'})
39
+ if(cds.env.odata.batch_limit < this._batchContext.getRequestList().length) return Promise.reject({statusCode: 429, code: 'BATCH_TOO_MANY_REQ'})
40
40
  const request = this._batchContext.getRequest()
41
41
  const componentManager = request.getService().getComponentManager()
42
42
 
@@ -9,7 +9,7 @@ const HeaderInfo = require('./HeaderInfo')
9
9
  const AcceptTypeInfo = require('../format/AcceptTypeInfo')
10
10
  const CharsetInfo = require('../format/CharsetInfo')
11
11
 
12
- const Q_PATTERN = new RegExp('^(?:(?:0(?:\\.\\d{0,3})?)|(?:1(?:\\.0{0,3})?))$')
12
+ const Q_PATTERN = new RegExp('^(?:(?:0(?:\\.\\d{0,3})?)|(?:1(?:\\.0{0,3})?)|(?:0?\\.\\d{0,3})|\\bq=\\.\\d{0,3}\\b)$')
13
13
 
14
14
  /**
15
15
  * Reads header values as defined in RFCs 7231, 7230, 7240, and 5234.
@@ -303,6 +303,11 @@ class TrustedResourceJsonSerializer {
303
303
  * @private
304
304
  */
305
305
  _serializeStructure (result, type, data, expandItems, odataPath, structurePath) {
306
+ // enterprise search result? -> simply return what was provided
307
+ if (type._fqn?.name === 'sap_esh_SearchResult') {
308
+ return Object.assign(result, data)
309
+ }
310
+
306
311
  for (const entityProp in data) {
307
312
  // draft-enabled entities in odata v2 have own property DraftAdministrativeData_DraftUUID -> preserve if in data
308
313
  if (entityProp === 'DraftAdministrativeData_DraftUUID') {
@@ -336,6 +341,7 @@ class TrustedResourceJsonSerializer {
336
341
  const propertyValue = data[entityProp]
337
342
  const isCollection = Array.isArray(propertyValue)
338
343
  const edmProperty = type.getStructuralProperty(entityProp)
344
+
339
345
  if (edmProperty) {
340
346
  let propertyType = edmProperty.getType()
341
347
 
@@ -1,8 +1,6 @@
1
1
  const cds = require('../../../cds')
2
2
  const OData = require('./OData')
3
3
 
4
- const { alias2ref } = require('../../../common/utils/csn')
5
-
6
4
  /**
7
5
  * This is the express handler for a specific OData endpoint.
8
6
  * Note: the same service can be served at different endpoints.
@@ -14,9 +12,6 @@ module.exports = srv => {
14
12
 
15
13
  function OkraAdapter(srv, model = srv.model) {
16
14
  const edm = cds.compile.to.edm(model, { service: srv.definition?.name || srv.name })
17
-
18
- alias2ref(srv, edm)
19
-
20
15
  return new OData(edm, model, srv.options).addCDSServiceToChannel(srv)
21
16
  }
22
17
 
@@ -89,7 +89,9 @@ const _getExpandColumn = (data, element) => {
89
89
  const _getColumns = (target, data, prefix = []) => {
90
90
  const columns = []
91
91
  for (const each in target.elements) {
92
+ if (target.elements[each]['@cds.api.ignore']) continue
92
93
  if (each in DRAFT_COLUMNS_MAP) continue
94
+ if (!cds.env.features.stream_compat && target.elements[each].type === 'cds.LargeBinary') continue
93
95
  const element = target.elements[each]
94
96
  if (element.elements && data[each]) {
95
97
  prefix.push(element.name)
@@ -199,7 +199,23 @@ const _getCanonicalUrl = (path, target, model) => {
199
199
  const toManySegment =
200
200
  path.length > 1 && Array.isArray(path[path.length - 1].where) && path[path.length - 1].where.length && path.pop()
201
201
  if (target.params) path.push('Set')
202
- const odataUrl = cds.odata.urlify({ SELECT: { from: { ref: path } } }, { model, kind: 'odata' })
202
+ // construct path with only innermost refs for @odata.context
203
+ const _path = []
204
+ for (const seg of path) {
205
+ if (typeof seg === 'string') _path.push(seg)
206
+ else {
207
+ const _seg = { ...seg }
208
+ if (_seg.where) {
209
+ _seg.where = []
210
+ for (const ele of seg.where) {
211
+ if (ele.ref && ele.ref.length > 1) _seg.where.push({ ref: [ele.ref[ele.ref.length - 1]] })
212
+ else _seg.where.push(ele)
213
+ }
214
+ }
215
+ _path.push(_seg)
216
+ }
217
+ }
218
+ const odataUrl = cds.odata.urlify({ SELECT: { from: { ref: _path } } }, { model, kind: 'odata' })
203
219
  let contextPath = odataUrl.path && odataUrl.path.match(/^([^?]*)\??/)[1]
204
220
  if (toManySegment) {
205
221
  contextPath += `/${toManySegment.id}`
@@ -88,6 +88,8 @@ const _cleanupMetadata = (odataResult, result, req) => {
88
88
  * @param {*} [req]
89
89
  * @returns {string | object}
90
90
  */
91
+ // REVISIT: complexity
92
+ // eslint-disable-next-line complexity
91
93
  const toODataResult = (result, req) => {
92
94
  if (result == null) return ''
93
95
 
@@ -115,8 +117,26 @@ const toODataResult = (result, req) => {
115
117
 
116
118
  let value = result
117
119
  if (typeof result === 'object') {
118
- if ('value' in result && (result.value instanceof Readable || isStream)) value = result.value
119
- else if (propertyName) value = result[propertyName]
120
+ if (cds.env.features.stream_compat) {
121
+ // REVISIT: test implicit streaming with null value
122
+ if ('value' in result && (result.value instanceof Readable || isStream)) value = result.value
123
+ else if (propertyName) value = result[propertyName]
124
+ } else {
125
+ if (propertyName && result[propertyName] !== undefined) {
126
+ value = result[propertyName]
127
+ }
128
+ // implicit streaming
129
+ else if (req._.odataReq.getUriInfo().getLastSegment().getKind() === 'VALUE') {
130
+ const property = Object.values(req.target.elements).find(
131
+ el => el.type === 'cds.LargeBinary' && result[el.name] !== undefined
132
+ )
133
+ value = property && result[property.name]
134
+ }
135
+ // result.value can be obtained from custom handlers
136
+ else if (isStream && result.value !== undefined) {
137
+ value = result.value
138
+ }
139
+ }
120
140
  }
121
141
 
122
142
  const odataResult = _cleanupMetadata({ value }, result, req)
@@ -1,6 +1,5 @@
1
1
  const cds = require('../../../cds')
2
- // requesting logger without module on purpose!
3
- const LOG = cds.log()
2
+
4
3
  const { DRAFT_COLUMNS_UNION_MAP } = require('../../../common/constants/draft')
5
4
  const DEFAULT_SEARCHABLE_TYPE = 'cds.String'
6
5
 
@@ -4,15 +4,23 @@ const { foreignKeyPropagations } = require('../utils/foreignKeyPropagations')
4
4
 
5
5
  const _hasJoinCondition = e => e.isAssociation && e.on && e.on.length > 2
6
6
 
7
- const _isSelfRef = e => e.ref && e.ref[0] === '$self'
8
-
9
- const _getBacklinkName = on => {
10
- const i = on.findIndex(_isSelfRef)
11
- if (i === -1) return
12
- let ref
13
- if (on[i + 1] && on[i + 1] === '=') ref = on[i + 2].ref
14
- if (on[i - 1] && on[i - 1] === '=') ref = on[i - 2].ref
15
- return ref && ref[ref.length - 1]
7
+ const _isSelfRef = e => (e.xpr ? e.xpr.find(_isSelfRef) : e.ref && e.ref[0] === '$self')
8
+
9
+ const _getBacklinkName = xpr => {
10
+ for (let i = 0; i < xpr.length; i++) {
11
+ const element = xpr[i]
12
+ if (element.xpr) {
13
+ const selfComparison = _getBacklinkName(element.xpr)
14
+ if (selfComparison) return selfComparison
15
+ }
16
+
17
+ if (element.ref?.[0] === '$self') {
18
+ let ref
19
+ if (xpr[i + 1] && xpr[i + 1] === '=') ref = xpr[i + 2].ref
20
+ if (xpr[i - 1] && xpr[i - 1] === '=') ref = xpr[i - 2].ref
21
+ if (ref) return ref[ref.length - 1]
22
+ }
23
+ }
16
24
  }
17
25
 
18
26
  const isSelfManaged = e => {
@@ -82,6 +82,13 @@ exports.impl = cds.service.impl(function () {
82
82
  }
83
83
 
84
84
  if (result == null && req._etagValidationType === 'if-match') req.reject(412)
85
+
86
+ if (cds.env.features.stream_compat) {
87
+ if (result !== undefined && req.query?._streaming && (result === null || result.pipe)) {
88
+ return { value: result }
89
+ }
90
+ }
91
+
85
92
  return result
86
93
  }
87
94
 
@@ -94,7 +101,7 @@ exports.impl = cds.service.impl(function () {
94
101
  // -> affected rows === 0 -> no change or not exists?
95
102
  if (req.event === 'UPDATE' && result === 0 && !req._authChecked) {
96
103
  if (req._etagValidationType) req.reject(412)
97
- if (await _targetEntityDoesNotExist(req)) req.reject(404)
104
+ if (await _targetEntityDoesNotExist(req)) req.reject(404) // REVISIT: add a reasonable error message
98
105
  }
99
106
 
100
107
  // flag to trigger read after write in protocol adapter
@@ -103,27 +110,11 @@ exports.impl = cds.service.impl(function () {
103
110
  })
104
111
 
105
112
  this.after('READ', '*', async function ([result], req) {
106
- if (req.query?._streaming) {
107
- await enhanceStreamResult(req, req.query, result, this.model)
108
- }
109
- })
110
-
111
- this.on('STREAM', '*', async function (req) {
112
- if (typeof req.query !== 'string' && req.target && req.target._hasPersistenceSkip) {
113
- throw getError({
114
- code: 501,
115
- message: `Entity "${req.target.name}" is annotated with "@cds.persistence.skip" and cannot be served generically.`
116
- })
117
- }
118
-
119
- if (req.query.STREAM.from) {
120
- return { value: await cds.tx(req).run(req.query, req.data) }
121
- } else {
122
- return await cds.tx(req).run(req.query, req.data)
113
+ if (!result) return
114
+ if (cds.env.features.stream_compat) {
115
+ if (req.query?._streaming) {
116
+ await enhanceStreamResult(req, req.query, result, this.model)
117
+ }
123
118
  }
124
119
  })
125
-
126
- this.after(['STREAM'], '*', async function ([result], req) {
127
- if (req.query.STREAM.from) await enhanceStreamResult(req, req.query, result, this.model)
128
- })
129
120
  })
@@ -77,7 +77,7 @@ const commonGenericValidateETag = async function (req) {
77
77
  if (req.protocol !== 'odata-v4') return
78
78
 
79
79
  // automatically add etag columns if not already there
80
- if (req.query.SELECT) addEtagColumns(req.query.SELECT.columns, req.target)
80
+ if (req.query.SELECT && !req.query._streaming) addEtagColumns(req.query.SELECT.columns, req.target)
81
81
 
82
82
  // querying a collection?
83
83
  if (req.event === 'READ' && !req.query.SELECT.one) return
@@ -116,6 +116,9 @@ const _processCategory = (req, category, value, elementInfo, assertMap) => {
116
116
  // Always take over the values from active entities
117
117
  if (cds.env.fiori?.lean_draft && req.context?.event === 'EDIT') return
118
118
 
119
+ // Read-only valus are already deleted before `NEW` (and they can be set in a `NEW` handler!)
120
+ if (cds.env.fiori?.lean_draft && event === 'CREATE' && req.context?.event === 'NEW' && req.target.isDraft) return
121
+
119
122
  delete row[key]
120
123
  value.val = undefined
121
124
  return
@@ -132,7 +135,12 @@ const _processCategory = (req, category, value, elementInfo, assertMap) => {
132
135
  }
133
136
 
134
137
  // generate UUIDs
135
- if (category === 'uuid' && !value.val && ((event !== 'UPDATE' && event !== 'PATCH') || !isRoot)) {
138
+ if (
139
+ category === 'uuid' &&
140
+ !value.val &&
141
+ ((event !== 'UPDATE' && event !== 'PATCH') || !isRoot) &&
142
+ !element.parent.elements[element._foreignKey4]?._isAssociationStrict
143
+ ) {
136
144
  value.val = row[key] = cds.utils.uuid()
137
145
  }
138
146
 
@@ -5,8 +5,8 @@ module.exports = exports = cds.service.impl(function () {
5
5
  this.before('READ', '*', commonGenericPaging)
6
6
  })
7
7
 
8
- const DEFAULT = cds.env.query?.limit?.default || 1000
9
- const MAX = cds.env.query?.limit?.max || 1000
8
+ const DEFAULT = cds.env.query?.limit?.default ?? 1000
9
+ const MAX = cds.env.query?.limit?.max ?? 1000
10
10
  const _cached = Symbol('@cds.query.limit')
11
11
 
12
12
  const getPageSize = def => {
@@ -35,7 +35,7 @@ const commonGenericPaging = function (req) {
35
35
  }
36
36
 
37
37
  const _addPaging = function ({ SELECT }, target) {
38
- if (SELECT.limit === false) return
38
+ if (SELECT.limit === null) return
39
39
  const { rows } = SELECT.limit || (SELECT.limit = {})
40
40
  const conf = getPageSize(target)
41
41
  SELECT.limit.rows = {
@@ -35,6 +35,26 @@ const _getStaticOrders = req => {
35
35
  return [...defaultOrders, ...ordersFromKeys]
36
36
  }
37
37
 
38
+ const _addDefaultSortOrder = (req, select) => {
39
+ // "static orders" = the orders not from the query options
40
+ let staticOrders = _getStaticOrders(req)
41
+
42
+ // remove defaultOrder if not part of group by
43
+ const groupBy = select?.groupBy || select?.from?.SELECT?.groupBy
44
+
45
+ if (groupBy?.length > 0) staticOrders = staticOrders.filter(d => groupBy.some(e => e.ref[0] === d.by['=']))
46
+
47
+ if (!staticOrders.length) return
48
+
49
+ if (select?.from?.SELECT?.groupBy?.length > 0) select = select.from.SELECT
50
+ select.orderBy = select.orderBy ?? []
51
+ select.orderBy.push(
52
+ ...staticOrders
53
+ .filter(d => !select.orderBy.find(o => o.ref && o.ref.join('_') === d.by['=']))
54
+ .map(d => ({ ref: [d.by['=']], sort: d.desc ? 'desc' : 'asc' }))
55
+ )
56
+ }
57
+
38
58
  /**
39
59
  * 1. query options --> already set in req.query
40
60
  * 2. orders from view || @cds.default.order/@odata.default.order
@@ -52,24 +72,14 @@ const commonGenericSorting = function (req) {
52
72
  return
53
73
  }
54
74
 
55
- // apply default sort to bottom-most sub-query
56
75
  if (select.from && select.from.SELECT) {
57
- while (select.from.SELECT) select = select.from.SELECT
58
- }
76
+ // add default sort to root query
77
+ if (select.orderBy) _addDefaultSortOrder(req, select)
59
78
 
60
- // "static orders" = the orders not from the query options
61
- let staticOrders = _getStaticOrders(req)
62
-
63
- // remove defaultOrder if not part of group by
64
- if (select.groupBy && select.groupBy.length > 0) {
65
- staticOrders = staticOrders.filter(d => select.groupBy.find(e => e.ref[0] === d.by['=']))
79
+ // apply default sort to bottom-most sub-query
80
+ while (select.from.SELECT) select = select.from.SELECT
66
81
  }
67
- if (!staticOrders.length) return
68
- ;(select.orderBy || (select.orderBy = [])).push(
69
- ...staticOrders
70
- .filter(d => !select.orderBy.find(o => o.ref && o.ref.join('_') === d.by['=']))
71
- .map(d => ({ ref: [d.by['=']], sort: d.desc ? 'desc' : 'asc' }))
72
- )
82
+ _addDefaultSortOrder(req, select)
73
83
  }
74
84
 
75
85
  /**
@@ -1,5 +1,3 @@
1
- const { isNewStream } = require('../utils/stream')
2
-
3
1
  const cds = require('../../cds')
4
2
 
5
3
  const _getStreamingProperties = elements => {
@@ -27,26 +25,14 @@ function _addContentType(req, mtValue) {
27
25
  if (streamProp) req.data[streamProp.type] = mtValue
28
26
  }
29
27
 
30
- async function _addContentTypeStream(req, mtValue) {
31
- if (req.query.UPDATE || req.query.STREAM?.from) return
32
- if (req.target._hasPersistenceSkip) return
33
- const streamProp = _getStreamingProperties(req.target.elements).find(prop => req.query.STREAM.column === prop.stream)
34
- // REVISIT: move this update to after handler and add etag
35
- if (streamProp) await UPDATE.entity(req.query.STREAM.into).data({ [streamProp.type]: mtValue })
36
- }
37
-
38
28
  async function addContentType(req) {
39
29
  if (!req.query || !req.target) return
40
30
  const mtValue = _getMediaTypeValue()
41
31
  if (!mtValue) return
42
32
 
43
- if (isNewStream()) {
44
- _addContentTypeStream(req, mtValue)
45
- } else {
46
- _addContentType(req, mtValue)
47
- }
33
+ _addContentType(req, mtValue)
48
34
  }
49
35
 
50
36
  module.exports = cds.service.impl(function () {
51
- this.before(['PATCH', 'UPDATE', 'STREAM'], '*', addContentType)
37
+ this.before(['PATCH', 'UPDATE'], '*', addContentType)
52
38
  })
@@ -81,6 +81,9 @@ INVALID_PATCH=PATCH is only allowed on a specific resource
81
81
  INVALID_DELETE=DELETE is only supported on a specific resource
82
82
  CRUD_VIA_NAVIGATION_NOT_SUPPORTED=CRUD via navigations is not yet supported
83
83
 
84
+ # OData protocol adapter
85
+ BATCH_TOO_MANY_REQ=Batch request contains too many requests
86
+
84
87
  # draft
85
88
  DRAFT_ALREADY_EXISTS=A draft for this entity already exists
86
89
  DRAFT_NOT_EXISTING=No draft for this entity exists
@@ -1,3 +1,5 @@
1
+ const { Readable } = require('stream')
2
+
1
3
  const _deepCopy = arg => {
2
4
  if (Buffer.isBuffer(arg)) {
3
5
  return Buffer.from(arg)
@@ -5,6 +7,9 @@ const _deepCopy = arg => {
5
7
  if (Array.isArray(arg)) {
6
8
  return deepCopyArray(arg)
7
9
  }
10
+ if (arg instanceof Readable) {
11
+ return arg
12
+ }
8
13
  if (typeof arg === 'object') {
9
14
  return deepCopyObject(arg)
10
15
  }
@@ -37,7 +37,7 @@ function where2obj(where, target = null, data = {}) {
37
37
  // optional validation if target is passed
38
38
  if (target) {
39
39
  const colEl = target.elements[colName]
40
- if (!colEl || !colEl.key) continue
40
+ if (!colEl || !(colEl.key || colEl.__foreignKey4)) continue
41
41
  }
42
42
  const opWhere = where[i + 1]
43
43
  const valWhere = where[i + 2]
@@ -15,7 +15,6 @@ const { removeIsActiveEntityRecursively } = require('../../fiori/utils/where')
15
15
  const { addRefToWhereIfNecessary } = require('../../../odata/afterburner')
16
16
  const { addAliasToExpression, PARENT_ALIAS, FOREIGN_ALIAS } = require('../../db/utils/generateAliases')
17
17
  const { getColumns } = require('../../cds-services/services/utils/columns')
18
- const { handleStreamProperties } = require('./streamProp')
19
18
 
20
19
  const _elementFromRef = (name, entity) => {
21
20
  if (!entity) return
@@ -717,7 +716,10 @@ const _convertToOneEqNullInFilter = (query, target) => {
717
716
  // eslint-disable-next-line complexity
718
717
  const _convertSelect = (query, model, _options) => {
719
718
  const _4db = _options.service?.isDatabaseService
720
- const options = Object.assign({ _4db, isStreaming: query._streaming, localized: query.SELECT.localized }, _options)
719
+ const options = Object.assign(
720
+ { _4db, isStreaming: cds.env.features.stream_compat && query._streaming, localized: query.SELECT.localized },
721
+ _options
722
+ )
721
723
 
722
724
  // ensure query is ql enabled
723
725
  if (!(query instanceof Query)) Object.setPrototypeOf(query, Object.getPrototypeOf(SELECT()))
@@ -743,7 +745,6 @@ const _convertSelect = (query, model, _options) => {
743
745
  const entity =
744
746
  (query.SELECT.from.ref && (query.SELECT.from.ref[0].id || query.SELECT.from.ref[0])) || query.SELECT.from
745
747
  const target = entity && model.definitions[entity]
746
- handleStreamProperties(target, query, model)
747
748
 
748
749
  if (query.SELECT.where) {
749
750
  if (_isCountNavigation(query.SELECT.where)) {
@@ -1,7 +1,5 @@
1
1
  const cds = require('../../cds')
2
2
 
3
- const resolveStructured = require('../../common/utils/resolveStructured')
4
-
5
3
  const getEtagElement = entity => {
6
4
  return Object.values(entity.elements).find(element => element['@odata.etag'])
7
5
  }
@@ -116,25 +114,6 @@ const getElementDeep = (entity, ref) => {
116
114
  return current
117
115
  }
118
116
 
119
- const _setAlias2ref = entity => {
120
- const _ref2alias = {}
121
- const _alias2ref = {}
122
- const keys = entity.keys
123
- for (const key in keys) {
124
- const structKeys = resolveStructured({ element: keys[key], structProperties: [] }, false, true)
125
- for (const structKey of structKeys) {
126
- if (_alias2ref[structKey.key] != null) {
127
- // key clash, aliasing not possible
128
- return entity
129
- }
130
- _alias2ref[structKey.key] = structKey.resolved
131
- _ref2alias[structKey.resolved.join('/')] = key
132
- }
133
- }
134
- entity._alias2ref = Object.defineProperty(_alias2ref, '__2alias', { value: _ref2alias, configurable: true })
135
- return entity
136
- }
137
-
138
117
  const prefixForStruct = element => {
139
118
  const prefixes = []
140
119
  let parent = element.parent
@@ -145,33 +124,6 @@ const prefixForStruct = element => {
145
124
  return prefixes.length ? prefixes.reverse().join('_') + '_' : ''
146
125
  }
147
126
 
148
- /*
149
- * REVISIT:
150
- * - which scenarios require this?
151
- * - does it still work when serving multiple protocols?
152
- * - can we cache it?
153
- */
154
- function alias2ref(service, edm) {
155
- if (!edm) {
156
- // REST
157
- for (const each of service.entities) _setAlias2ref(each)
158
- } else {
159
- // OData
160
- const defs = edm[service.definition.name]
161
- for (const each of service.entities) {
162
- const def = defs[each.name.replace(service.definition.name + '.', '').replace(/\./g, '_')]
163
- if (!def || !def.$Key || def.$Key.every(ele => typeof ele === 'string')) continue
164
- each._alias2ref = Object.defineProperty({}, '__2alias', { value: {}, configurable: true })
165
- for (const mapping of def.$Key.filter(ele => typeof ele !== 'string')) {
166
- for (const [key, value] of Object.entries(mapping)) {
167
- each._alias2ref[key] = value.split('/')
168
- each._alias2ref.__2alias[value] = key
169
- }
170
- }
171
- }
172
- }
173
- }
174
-
175
127
  function getDraftTreeRoot(entity, model) {
176
128
  if (entity.own('__draftTreeRoot')) return entity.__draftTreeRoot
177
129
 
@@ -204,7 +156,6 @@ module.exports = {
204
156
  getEtagElement,
205
157
  findCsnTargetFor,
206
158
  getElementDeep,
207
- alias2ref,
208
159
  getComp2oneParents,
209
160
  prefixForStruct,
210
161
  getDraftTreeRoot,
@@ -11,17 +11,17 @@ const _sub = (newOn, subOns = []) => {
11
11
  subOns.push([])
12
12
  return subOns
13
13
  }
14
+
14
15
  if (onEl.xpr) {
15
16
  _sub(onEl.xpr, subOns)
16
- // after xpr there usually should be and/or
17
- i++
18
17
  continue
19
18
  }
20
- if (currArr.length === 0) {
19
+ if (currArr.length === 0 && onEl !== 'and') {
21
20
  subOns.push(currArr)
22
21
  }
23
- if (onEl !== 'and') currArr.push(onEl)
24
- else {
22
+ if (onEl !== 'and') {
23
+ currArr.push(onEl)
24
+ } else {
25
25
  currArr = []
26
26
  }
27
27
  }