@sap/cds 7.5.2 → 7.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.
- package/CHANGELOG.md +79 -22
- package/app/index.js +1 -1
- package/lib/auth/index.js +3 -0
- package/lib/compile/extend.js +9 -4
- package/lib/compile/for/lean_drafts.js +3 -4
- package/lib/compile/load.js +11 -15
- package/lib/compile/minify.js +2 -4
- package/lib/compile/to/sql.js +6 -4
- package/lib/compile/to/srvinfo.js +25 -3
- package/lib/compile/to/yaml.js +1 -1
- package/lib/dbs/cds-deploy.js +7 -13
- package/lib/env/defaults.js +1 -10
- package/lib/env/schemas/cds-package.js +27 -0
- package/lib/env/schemas/cds-rc.js +693 -0
- package/lib/env/schemas/index.js +6 -4
- package/lib/i18n/localize.js +15 -1
- package/lib/index.js +40 -47
- package/lib/log/cds-error.js +6 -0
- package/lib/ql/Query.js +2 -1
- package/lib/ql/cds-ql.js +1 -2
- package/lib/ql/infer.js +0 -2
- package/lib/req/request.js +3 -6
- package/lib/srv/middlewares/trace.js +2 -2
- package/lib/srv/protocols/hcql.js +44 -30
- package/lib/srv/protocols/http.js +60 -0
- package/lib/srv/protocols/index.js +0 -7
- package/lib/srv/protocols/odata-v4.js +8 -2
- package/lib/srv/srv-api.js +129 -62
- package/lib/srv/srv-handlers.js +0 -1
- package/lib/srv/srv-models.js +1 -0
- package/lib/utils/cds-test.js +1 -1
- package/lib/utils/cds-utils.js +26 -0
- package/lib/utils/check-version.js +10 -13
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +22 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +3 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +89 -21
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/boundToCQN.js +4 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +1 -24
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/updateToCQN.js +1 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/ApplyParser.js +3 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/TrustedResourceJsonSerializer.js +7 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +0 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +2 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +17 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +22 -2
- package/libx/_runtime/cds-services/services/utils/columns.js +1 -2
- package/libx/_runtime/common/aspects/Association.js +17 -9
- package/libx/_runtime/common/generic/crud.js +13 -22
- package/libx/_runtime/common/generic/etag.js +1 -1
- package/libx/_runtime/common/generic/input.js +9 -1
- package/libx/_runtime/common/generic/paging.js +3 -3
- package/libx/_runtime/common/generic/sorting.js +25 -15
- package/libx/_runtime/common/generic/stream.js +2 -16
- package/libx/_runtime/common/utils/copy.js +5 -0
- package/libx/_runtime/common/utils/cqn.js +1 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +4 -3
- package/libx/_runtime/common/utils/csn.js +0 -49
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +5 -5
- package/libx/_runtime/common/utils/generateOnCond.js +50 -25
- package/libx/_runtime/common/utils/resolveView.js +5 -44
- package/libx/_runtime/common/utils/rewriteAsterisks.js +17 -4
- package/libx/_runtime/common/utils/stream.js +16 -15
- package/libx/_runtime/common/utils/streamProp.js +25 -22
- package/libx/_runtime/db/Service.js +27 -8
- package/libx/_runtime/db/generic/input.js +6 -1
- package/libx/_runtime/db/generic/rewrite.js +3 -2
- package/libx/_runtime/db/query/read.js +15 -5
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +0 -11
- package/libx/_runtime/db/utils/columns.js +1 -0
- package/libx/_runtime/db/utils/stream.js +41 -0
- package/libx/_runtime/fiori/generic/read.js +2 -1
- package/libx/_runtime/fiori/generic/readOverDraft.js +1 -1
- package/libx/_runtime/fiori/lean-draft.js +216 -59
- package/libx/_runtime/hana/Service.js +1 -1
- package/libx/_runtime/hana/execute.js +53 -14
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +34 -15
- package/libx/_runtime/remote/Service.js +2 -1
- package/libx/_runtime/remote/utils/client.js +1 -1
- package/libx/_runtime/sqlite/Service.js +1 -1
- package/libx/_runtime/sqlite/execute.js +17 -5
- package/libx/odata/afterburner.js +58 -19
- package/libx/odata/cqn2odata.js +6 -8
- package/libx/odata/create.js +44 -0
- package/libx/odata/delete.js +25 -0
- package/libx/odata/error.js +8 -3
- package/libx/odata/metadata.js +6 -8
- package/libx/odata/service-document.js +1 -1
- package/libx/odata/update.js +110 -0
- package/libx/odata/utils.js +9 -6
- package/libx/outbox/index.js +48 -78
- package/libx/rest/RestAdapter.js +0 -3
- package/package.json +1 -1
- package/lib/env/schemas/cds-package.json +0 -17
- package/lib/env/schemas/cds-rc.json +0 -740
- package/lib/ql/STREAM.js +0 -90
|
@@ -44,7 +44,6 @@ const { isStreaming } = require('../utils/stream')
|
|
|
44
44
|
const { getPageSize } = require('../../../../common/generic/paging')
|
|
45
45
|
const { isAsteriskColumn } = require('../../../../common/utils/rewriteAsterisks')
|
|
46
46
|
const { handleStreamProperties } = require('../../../../common/utils/streamProp')
|
|
47
|
-
const { isNewStream } = require('../../../../common/utils/stream')
|
|
48
47
|
|
|
49
48
|
const { ensureUnlocalized } = require('../../../../fiori/utils/handler')
|
|
50
49
|
|
|
@@ -316,28 +315,6 @@ const readToCQN = (service, target, odataReq) => {
|
|
|
316
315
|
const segments = uriInfo.getPathSegments()
|
|
317
316
|
isPathSupported(SUPPORTED_SEGMENT_KINDS, segments)
|
|
318
317
|
|
|
319
|
-
if (isNewStream()) {
|
|
320
|
-
let propertyParam
|
|
321
|
-
if (isStreaming(segments)) {
|
|
322
|
-
propertyParam = getPropertyParam(segments)
|
|
323
|
-
} else if (segments[segments.length - 1]._isStreamByDollarValue) {
|
|
324
|
-
// REVISIT: Issue with multiple streaming properties ? Also in read.js
|
|
325
|
-
for (const k in target.elements) {
|
|
326
|
-
if (target.elements[k]['@Core.MediaType']) {
|
|
327
|
-
propertyParam = { ref: [k] }
|
|
328
|
-
break
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
if (propertyParam) {
|
|
334
|
-
const isView = target.params && Object.keys(target.params).length > 0
|
|
335
|
-
return STREAM.from(isView ? convertUrlPathToViewCqn(segments) : convertUrlPathToCqn(segments, service)).column(
|
|
336
|
-
propertyParam.ref[0]
|
|
337
|
-
)
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
318
|
const queryOptions = odataReq.getQueryOptions()
|
|
342
319
|
const entity = service.model.definitions[ensureUnlocalized(target.name)]
|
|
343
320
|
const propertyParam = getPropertyParam(segments)
|
|
@@ -375,7 +352,7 @@ const readToCQN = (service, target, odataReq) => {
|
|
|
375
352
|
// keep target as input because of localized view
|
|
376
353
|
const cqn = SELECT.from(isView ? convertUrlPathToViewCqn(segments) : convertUrlPathToCqn(segments, service), select)
|
|
377
354
|
if (!cqn.SELECT.columns) cqn.SELECT.columns = ['*']
|
|
378
|
-
if (!streaming) handleStreamProperties(target, cqn, service.model
|
|
355
|
+
if (!streaming) handleStreamProperties(target, cqn.SELECT.columns, service.model)
|
|
379
356
|
|
|
380
357
|
const isCount =
|
|
381
358
|
isCollectionOrToMany &&
|
|
@@ -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
|
|
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())
|
|
@@ -336,6 +336,13 @@ class TrustedResourceJsonSerializer {
|
|
|
336
336
|
const propertyValue = data[entityProp]
|
|
337
337
|
const isCollection = Array.isArray(propertyValue)
|
|
338
338
|
const edmProperty = type.getStructuralProperty(entityProp)
|
|
339
|
+
|
|
340
|
+
// enterprise search result? -> simple return what was provided
|
|
341
|
+
if (type._fqn?.name === 'sap_esh_SearchResult') {
|
|
342
|
+
result[entityProp] = propertyValue
|
|
343
|
+
continue
|
|
344
|
+
}
|
|
345
|
+
|
|
339
346
|
if (edmProperty) {
|
|
340
347
|
let propertyType = edmProperty.getType()
|
|
341
348
|
|
|
@@ -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
|
-
|
|
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 (
|
|
119
|
-
|
|
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)
|
|
@@ -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 =
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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 (
|
|
107
|
-
|
|
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 (
|
|
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
|
|
9
|
-
const MAX = cds.env.query?.limit?.max
|
|
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 ===
|
|
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
|
-
|
|
58
|
-
|
|
76
|
+
// add default sort to root query
|
|
77
|
+
if (select.orderBy) _addDefaultSortOrder(req, select)
|
|
59
78
|
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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'
|
|
37
|
+
this.before(['PATCH', 'UPDATE'], '*', addContentType)
|
|
52
38
|
})
|
|
@@ -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(
|
|
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')
|
|
24
|
-
|
|
22
|
+
if (onEl !== 'and') {
|
|
23
|
+
currArr.push(onEl)
|
|
24
|
+
} else {
|
|
25
25
|
currArr = []
|
|
26
26
|
}
|
|
27
27
|
}
|