@sap/cds 5.7.3 → 5.8.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 +111 -0
- package/app/fiori/routes.js +1 -1
- package/bin/deploy/to-hana/cfUtil.js +251 -138
- package/bin/deploy/to-hana/gitUtil.js +55 -0
- package/bin/deploy/to-hana/hana.js +92 -93
- package/bin/deploy/to-hana/hdiDeployUtil.js +42 -27
- package/bin/deploy/to-hana/index.js +14 -13
- package/bin/mtx/in-cds.js +1 -0
- package/bin/serve.js +1 -1
- package/bin/version.js +1 -0
- package/lib/compile/cdsc.js +0 -6
- package/lib/compile/minify.js +1 -1
- package/lib/compile/resolve.js +1 -1
- package/lib/compile/to/srvinfo.js +1 -1
- package/lib/core/classes.js +21 -1
- package/lib/env/index.js +3 -2
- package/lib/env/requires.js +4 -0
- package/lib/i18n/localize.js +5 -8
- package/lib/index.js +1 -0
- package/lib/log/errors.js +1 -1
- package/lib/ql/SELECT.js +2 -2
- package/lib/req/cds-context.js +1 -1
- package/lib/req/context.js +1 -1
- package/lib/serve/Transaction.js +9 -5
- package/lib/serve/index.js +13 -21
- package/lib/utils/tests.js +90 -66
- package/libx/_runtime/audit/generic/personal/modification.js +0 -8
- package/libx/_runtime/auth/index.js +7 -6
- package/libx/_runtime/auth/strategies/dwc.js +43 -0
- package/libx/_runtime/auth/utils.js +24 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +11 -38
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +12 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +7 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +24 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +43 -38
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +11 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +13 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/boundToCQN.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/deleteToCQN.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/orderByToCQN.js +9 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +17 -30
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +12 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/AbstractEdmStructuredType.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriHelper.js +7 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriTokenizer.js +5 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +19 -47
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +4 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +7 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/CommandExecutor.js +0 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ContextURLFactory.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ErrorJsonSerializer.js +2 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/TrustedResourceJsonSerializer.js +2 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +6 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +18 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +41 -17
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +1 -17
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +80 -21
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +47 -10
- package/libx/_runtime/cds-services/adapter/rest/Rest.js +22 -1
- package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +8 -3
- package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +3 -0
- package/libx/_runtime/cds-services/services/Service.js +1 -1
- package/libx/_runtime/cds-services/services/utils/columns.js +5 -1
- package/libx/_runtime/cds-services/services/utils/compareJson.js +15 -16
- package/libx/_runtime/cds-services/services/utils/differ.js +2 -8
- package/libx/_runtime/common/aspects/Association.js +16 -0
- package/libx/_runtime/common/composition/data.js +28 -37
- package/libx/_runtime/common/composition/delete.js +107 -58
- package/libx/_runtime/common/composition/index.js +3 -3
- package/libx/_runtime/common/composition/insert.js +14 -27
- package/libx/_runtime/common/composition/update.js +39 -34
- package/libx/_runtime/common/error/frontend.js +19 -5
- package/libx/_runtime/common/generic/auth.js +20 -85
- package/libx/_runtime/common/generic/crud.js +22 -1
- package/libx/_runtime/common/i18n/messages.properties +2 -1
- package/libx/_runtime/common/utils/cqn.js +2 -6
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +95 -122
- package/libx/_runtime/common/utils/csn.js +15 -4
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +18 -1
- package/libx/_runtime/common/utils/keys.js +2 -1
- package/libx/_runtime/common/utils/path.js +1 -1
- package/libx/_runtime/common/utils/resolveView.js +12 -4
- package/libx/_runtime/common/utils/rewriteAsterisks.js +27 -13
- package/libx/_runtime/common/utils/search2cqn4sql.js +11 -6
- package/libx/_runtime/common/utils/structured.js +11 -5
- package/libx/_runtime/common/utils/vcap.js +27 -10
- package/libx/_runtime/db/data-conversion/post-processing.js +42 -35
- package/libx/_runtime/db/expand/expand-v2.js +21 -12
- package/libx/_runtime/db/expand/expandCQNToJoin.js +57 -29
- package/libx/_runtime/db/expand/index.js +3 -0
- package/libx/_runtime/db/generic/create.js +0 -10
- package/libx/_runtime/db/generic/index.js +3 -0
- package/libx/_runtime/db/generic/read.js +2 -24
- package/libx/_runtime/db/generic/rewrite.js +1 -3
- package/libx/_runtime/db/generic/update.js +1 -1
- package/libx/_runtime/db/query/delete.js +10 -4
- package/libx/_runtime/db/query/insert.js +3 -4
- package/libx/_runtime/db/query/read.js +4 -1
- package/libx/_runtime/db/query/update.js +5 -5
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +9 -2
- package/libx/_runtime/db/sql-builder/FunctionBuilder.js +3 -0
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -3
- package/libx/_runtime/db/sql-builder/index.js +3 -0
- package/libx/_runtime/db/utils/columns.js +5 -2
- package/libx/_runtime/db/utils/deep.js +6 -8
- package/libx/_runtime/db/utils/generateAliases.js +56 -6
- package/libx/_runtime/fiori/generic/before.js +73 -49
- package/libx/_runtime/fiori/generic/edit.js +14 -18
- package/libx/_runtime/fiori/generic/patch.js +8 -11
- package/libx/_runtime/fiori/generic/read.js +22 -20
- package/libx/_runtime/fiori/generic/readOverDraft.js +1 -4
- package/libx/_runtime/fiori/utils/handler.js +1 -11
- package/libx/_runtime/hana/Service.js +1 -1
- package/libx/_runtime/hana/conversion.js +12 -1
- package/libx/_runtime/hana/execute.js +31 -16
- package/libx/_runtime/hana/localized.js +1 -1
- package/libx/_runtime/hana/search.js +3 -3
- package/libx/_runtime/hana/search2cqn4sql.js +23 -25
- package/libx/_runtime/hana/searchToContains.js +1 -1
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +4 -2
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +0 -1
- package/libx/_runtime/messaging/enterprise-messaging.js +1 -0
- package/libx/_runtime/messaging/file-based.js +3 -1
- package/libx/_runtime/messaging/service.js +16 -7
- package/libx/_runtime/remote/utils/client.js +37 -20
- package/libx/_runtime/remote/utils/data.js +53 -12
- package/libx/_runtime/sqlite/Service.js +1 -1
- package/libx/_runtime/sqlite/conversion.js +10 -0
- package/libx/_runtime/sqlite/localized.js +1 -1
- package/libx/_runtime/types/api.js +2 -2
- package/libx/gql/resolvers/crud/update.js +8 -5
- package/libx/gql/resolvers/parse/ast/enrich.js +1 -0
- package/libx/odata/afterburner.js +29 -6
- package/libx/odata/cqn2odata.js +9 -0
- package/libx/odata/grammar.pegjs +50 -22
- package/libx/odata/index.js +2 -2
- package/libx/odata/parser.js +1 -1
- package/libx/odata/utils.js +2 -2
- package/libx/rest/RestAdapter.js +29 -1
- package/libx/rest/middleware/auth.js +1 -3
- package/libx/rest/middleware/parse.js +1 -0
- package/package.json +1 -1
- package/server.js +1 -1
- package/bin/deploy/to-hana/logger.js +0 -27
- package/bin/deploy/to-hana/runCommand.js +0 -113
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +0 -37
- package/libx/_runtime/common/utils/auth.js +0 -16
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
const cds = require('../../../../cds')
|
|
2
|
+
|
|
3
|
+
const { Readable } = require('stream')
|
|
4
|
+
const { big } = require('@sap/cds-foss')
|
|
5
|
+
|
|
2
6
|
const getTemplate = require('../../../../common/utils/template')
|
|
3
7
|
const templateProcessor = require('../../../../common/utils/templateProcessor')
|
|
4
|
-
const { big } = require('@sap/cds-foss')
|
|
5
8
|
const { omitValue, applyOmitValuesPreference } = require('./omitValues')
|
|
6
9
|
|
|
7
10
|
const METADATA = {
|
|
@@ -23,36 +26,59 @@ const METADATA = {
|
|
|
23
26
|
$mediaEditLink: '*@odata.mediaEditLink',
|
|
24
27
|
$mediaReadLink: '*@odata.mediaReadLink',
|
|
25
28
|
$mediaContentType: '*@odata.mediaContentType',
|
|
29
|
+
$mediaContentDispositionFilename: '*@odata.mediaContentDispositionFilename', // > not supported by okra
|
|
30
|
+
$mediaContentDispositionType: '*@odata.mediaContentDispositionType', // > not supported by okra
|
|
26
31
|
$mediaEtag: '*@odata.mediaEtag'
|
|
27
32
|
}
|
|
28
33
|
|
|
34
|
+
const _getPropertyName = req => {
|
|
35
|
+
const segments = req._.odataReq.getUriInfo().getPathSegments()
|
|
36
|
+
if (segments[segments.length - 1].getKind().match(/\w*\.PROPERTY$/)) {
|
|
37
|
+
return segments[segments.length - 1].getProperty().getName()
|
|
38
|
+
}
|
|
39
|
+
if (
|
|
40
|
+
segments[segments.length - 1].getKind() === 'VALUE' &&
|
|
41
|
+
segments[segments.length - 2].getKind() === 'PRIMITIVE.PROPERTY'
|
|
42
|
+
) {
|
|
43
|
+
return segments[segments.length - 2].getProperty().getName()
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
29
47
|
/**
|
|
30
48
|
* Convert any result to the result object structure, which is expected of odata-v4.
|
|
31
49
|
*
|
|
32
50
|
* @param {*} result
|
|
33
|
-
* @param {*} [
|
|
51
|
+
* @param {*} [req]
|
|
34
52
|
* @returns {string | object}
|
|
35
53
|
*/
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
54
|
+
// eslint-disable-next-line complexity
|
|
55
|
+
const toODataResult = (result, req) => {
|
|
56
|
+
if (result == null) return ''
|
|
57
|
+
|
|
58
|
+
let propertyName, isStream
|
|
59
|
+
if (req) {
|
|
60
|
+
propertyName = _getPropertyName(req)
|
|
61
|
+
isStream =
|
|
62
|
+
req._.odataReq.getUriInfo().getLastSegment().getProperty() &&
|
|
63
|
+
req._.odataReq.getUriInfo().getLastSegment().getProperty().getType().toString() === 'Edm.Stream'
|
|
64
|
+
|
|
65
|
+
const isCollection = !propertyName && req._.odataReq.getUriInfo().getLastSegment().isCollection()
|
|
66
|
+
if (isCollection && !Array.isArray(result)) result = [result]
|
|
67
|
+
else if (!isCollection && Array.isArray(result)) result = result[0]
|
|
49
68
|
}
|
|
50
69
|
|
|
51
|
-
|
|
52
|
-
|
|
70
|
+
let value = result
|
|
71
|
+
if (typeof result === 'object') {
|
|
72
|
+
if ('value' in result && (result.value instanceof Readable || isStream)) value = result.value
|
|
73
|
+
else if (propertyName) value = result[propertyName]
|
|
53
74
|
}
|
|
54
75
|
|
|
76
|
+
const odataResult = { value }
|
|
77
|
+
|
|
55
78
|
if (typeof result === 'object') {
|
|
79
|
+
// backwards compatibility for content-type in stream
|
|
80
|
+
if (result['*@odata.mediaContentType']) result.$mediaContentType = result['*@odata.mediaContentType']
|
|
81
|
+
|
|
56
82
|
for (const key in METADATA) {
|
|
57
83
|
// do not set "@odata.context" as it may be inherited of remote service
|
|
58
84
|
if (key === '$context') {
|
|
@@ -60,6 +86,18 @@ const toODataResult = (result, arg) => {
|
|
|
60
86
|
continue
|
|
61
87
|
}
|
|
62
88
|
|
|
89
|
+
// REVISIT: okra doesn't support content disposition
|
|
90
|
+
if (key === '$mediaContentDispositionFilename' && result[key] && req) {
|
|
91
|
+
const cdt = result.$mediaContentDispositionType || 'attachment'
|
|
92
|
+
req._.odataRes.setHeader('Content-Disposition', `${cdt}; filename="${encodeURIComponent(result[key])}"`)
|
|
93
|
+
delete result[key]
|
|
94
|
+
continue
|
|
95
|
+
}
|
|
96
|
+
if (key === '$mediaContentDispositionType') {
|
|
97
|
+
delete result[key]
|
|
98
|
+
continue
|
|
99
|
+
}
|
|
100
|
+
|
|
63
101
|
if (key in result) {
|
|
64
102
|
odataResult[METADATA[key]] = result[key]
|
|
65
103
|
delete result[key]
|
|
@@ -112,6 +150,13 @@ const addAssociationToRow = (row, foreignKey, foreignKeyElement) => {
|
|
|
112
150
|
delete row[foreignKey]
|
|
113
151
|
}
|
|
114
152
|
|
|
153
|
+
const localizeAfterDraftActivate = (row, key, locale) => {
|
|
154
|
+
if (row.texts && Object.prototype.hasOwnProperty.call(row, key)) {
|
|
155
|
+
const texts = row.texts.filter(t => t.locale === locale)[0]
|
|
156
|
+
if (texts && texts[key]) row[key] = texts[key]
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
115
160
|
const _processCategory = (category, processArgs, req, options, previousResult) => {
|
|
116
161
|
const { row, key, element } = processArgs
|
|
117
162
|
|
|
@@ -136,6 +181,10 @@ const _processCategory = (category, processArgs, req, options, previousResult) =
|
|
|
136
181
|
if (key !== 'DraftAdministrativeData_DraftUUID') delete row[key]
|
|
137
182
|
break
|
|
138
183
|
|
|
184
|
+
case 'localizeAfterDraftActivate':
|
|
185
|
+
localizeAfterDraftActivate(row, key, req.locale)
|
|
186
|
+
break
|
|
187
|
+
|
|
139
188
|
// no default
|
|
140
189
|
}
|
|
141
190
|
}
|
|
@@ -197,16 +246,25 @@ const _pick = options => (element, target, parent) => {
|
|
|
197
246
|
const categories = []
|
|
198
247
|
|
|
199
248
|
if (element['@odata.etag']) categories.push('@odata.etag')
|
|
249
|
+
|
|
200
250
|
if (element.type === 'cds.Decimal') categories.push('@cds.Decimal')
|
|
251
|
+
|
|
201
252
|
categories.push(..._assocs(element, target))
|
|
253
|
+
|
|
202
254
|
if (options.omitValuesPreference) categories.push('@odata.omitValues')
|
|
255
|
+
if (options.event === 'draftActivate' && options.locale && options.locale !== 'en' && element.localized === true) {
|
|
256
|
+
categories.push('localizeAfterDraftActivate')
|
|
257
|
+
}
|
|
258
|
+
|
|
203
259
|
if (categories.length) return { categories }
|
|
204
260
|
}
|
|
205
261
|
|
|
206
|
-
const _getOptions = headers => {
|
|
262
|
+
const _getOptions = ({ headers, locale, event }) => {
|
|
207
263
|
const options = {
|
|
208
264
|
decimals: null,
|
|
209
|
-
omitValuesPreference: null
|
|
265
|
+
omitValuesPreference: null,
|
|
266
|
+
locale,
|
|
267
|
+
event
|
|
210
268
|
}
|
|
211
269
|
|
|
212
270
|
if (!headers) return options
|
|
@@ -231,7 +289,8 @@ const _getOptions = headers => {
|
|
|
231
289
|
const _generateCacheKey = (headers, options) => {
|
|
232
290
|
let key = 'postProcess' // default template cache key for post processing
|
|
233
291
|
if (headers.prefer) key += `:${headers.prefer}`
|
|
234
|
-
if (options.decimals) key += `:
|
|
292
|
+
if (options.decimals) key += `:ExponentialDecimals=true`
|
|
293
|
+
if (options.event === 'draftActivate' && options.locale) key += `:locale=${options.locale}`
|
|
235
294
|
return key
|
|
236
295
|
}
|
|
237
296
|
|
|
@@ -241,7 +300,7 @@ const postProcess = (req, res, service, result, previousResult) => {
|
|
|
241
300
|
|
|
242
301
|
if (!target || !result || !model || !model.definitions[target.name]) return
|
|
243
302
|
|
|
244
|
-
const options = _getOptions(
|
|
303
|
+
const options = _getOptions(req)
|
|
245
304
|
const cacheKey = _generateCacheKey(headers, options)
|
|
246
305
|
const parent = _getParent(model, target.name)
|
|
247
306
|
const template = getTemplate(cacheKey, service, target, { pick: _pick(options) }, parent)
|
|
@@ -5,6 +5,9 @@ const { SELECT } = cds.ql
|
|
|
5
5
|
|
|
6
6
|
const { targetFromPath, isPathToDraft } = require('../../../../common/utils/cqn')
|
|
7
7
|
const { deepCopyArray } = require('../../../../common/utils/copy')
|
|
8
|
+
const { cqn2cqn4sql } = require('../../../../common/utils/cqn2cqn4sql')
|
|
9
|
+
const { ensureDraftsSuffix } = require('../../../../fiori/utils/handler')
|
|
10
|
+
const { removeIsActiveEntityRecursively, isActiveEntityRequested } = require('../../../../fiori/utils/where')
|
|
8
11
|
|
|
9
12
|
const isStreaming = segments => {
|
|
10
13
|
const lastSegment = segments[segments.length - 1]
|
|
@@ -15,10 +18,38 @@ const isStreaming = segments => {
|
|
|
15
18
|
)
|
|
16
19
|
}
|
|
17
20
|
|
|
21
|
+
const _adaptSubSelectsDraft = select => {
|
|
22
|
+
if (select.SELECT.from.ref) {
|
|
23
|
+
const index = select.SELECT.from.ref.length - 1
|
|
24
|
+
select.SELECT.from.ref[index] = ensureDraftsSuffix(select.SELECT.from.ref[index])
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (select.SELECT.where) {
|
|
28
|
+
for (let i = 0; i < select.SELECT.where.length; i++) {
|
|
29
|
+
const element = select.SELECT.where[i]
|
|
30
|
+
if (element.SELECT) {
|
|
31
|
+
_adaptSubSelectsDraft(element)
|
|
32
|
+
} else if (element.xpr) {
|
|
33
|
+
for (const ele of element.xpr.filter(e => e.SELECT)) {
|
|
34
|
+
_adaptSubSelectsDraft(ele)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const adaptStreamCQN = (cqn, isDraft = false) => {
|
|
42
|
+
if (isDraft || !isActiveEntityRequested(cqn.SELECT.where)) {
|
|
43
|
+
_adaptSubSelectsDraft(cqn)
|
|
44
|
+
} else {
|
|
45
|
+
cqn.SELECT.where = removeIsActiveEntityRecursively(cqn.SELECT.where)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
18
49
|
const getStreamProperties = (req, model) => {
|
|
19
50
|
const mediaTypeProperty = Object.values(req.target.elements).find(val => val['@Core.MediaType'])
|
|
20
51
|
|
|
21
|
-
let contentType,
|
|
52
|
+
let contentType, contentDispositionFilename
|
|
22
53
|
const columns = []
|
|
23
54
|
if (typeof mediaTypeProperty['@Core.MediaType'] === 'object') {
|
|
24
55
|
let contentTypeProperty = mediaTypeProperty['@Core.MediaType']['=']
|
|
@@ -46,36 +77,42 @@ const getStreamProperties = (req, model) => {
|
|
|
46
77
|
`@Core.ContentDisposition.Filename in entity "${req.target.name}" points to property "${contentDispositionProperty}" which was renamed or is not part of the projection. You must update the annotation value.`
|
|
47
78
|
)
|
|
48
79
|
} else {
|
|
49
|
-
columns.push({ ref: [contentDispositionProperty], as: '
|
|
80
|
+
columns.push({ ref: [contentDispositionProperty], as: 'contentDispositionFilename' })
|
|
50
81
|
}
|
|
51
82
|
} else {
|
|
52
|
-
|
|
83
|
+
contentDispositionFilename = mediaTypeProperty['@Core.ContentDisposition.Filename']
|
|
53
84
|
}
|
|
54
85
|
}
|
|
86
|
+
const contentDispositionType = mediaTypeProperty['@Core.ContentDisposition.Type']
|
|
55
87
|
|
|
56
88
|
if (columns.length && cds.db && !req.target._hasPersistenceSkip) {
|
|
57
89
|
// used cloned path
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (req.target._isDraftEnabled && isPathToDraft(select.SELECT.from.ref, model))
|
|
61
|
-
select.SELECT.from.ref[0].id = select.SELECT.from.ref[0].id + '_drafts'
|
|
90
|
+
let select = SELECT.one.from({ ref: deepCopyArray(req.query.SELECT.from.ref) }).columns(columns)
|
|
62
91
|
|
|
63
92
|
// new parser has media property as last ref element -> remove
|
|
64
93
|
if (targetFromPath(select.SELECT.from.ref, model).kind === 'element') select.SELECT.from.ref.pop()
|
|
65
94
|
|
|
95
|
+
const pathToDraft = isPathToDraft(select.SELECT.from.ref, model)
|
|
96
|
+
if (req.target._isDraftEnabled && pathToDraft) {
|
|
97
|
+
select = cqn2cqn4sql(select, model)
|
|
98
|
+
adaptStreamCQN(select, pathToDraft)
|
|
99
|
+
}
|
|
100
|
+
|
|
66
101
|
return cds
|
|
67
102
|
.tx(req)
|
|
68
103
|
.run(select)
|
|
69
104
|
.then(res => ({
|
|
70
105
|
contentType: (res && res.contentType) || contentType,
|
|
71
|
-
|
|
106
|
+
contentDispositionFilename: (res && res.contentDispositionFilename) || contentDispositionFilename,
|
|
107
|
+
contentDispositionType
|
|
72
108
|
}))
|
|
73
109
|
}
|
|
74
110
|
|
|
75
|
-
return Promise.resolve({ contentType,
|
|
111
|
+
return Promise.resolve({ contentType, contentDispositionFilename, contentDispositionType })
|
|
76
112
|
}
|
|
77
113
|
|
|
78
114
|
module.exports = {
|
|
79
115
|
isStreaming,
|
|
80
|
-
getStreamProperties
|
|
116
|
+
getStreamProperties,
|
|
117
|
+
adaptStreamCQN
|
|
81
118
|
}
|
|
@@ -12,9 +12,10 @@ const { contentTypeCheck } = require('./utils/header-checks')
|
|
|
12
12
|
const parse = require('./utils/parse-url')
|
|
13
13
|
const { base64toBuffer } = require('./utils/binary')
|
|
14
14
|
|
|
15
|
-
const { UNAUTHORIZED, FORBIDDEN, getRequiresAsArray } = require('../../../
|
|
15
|
+
const { UNAUTHORIZED, FORBIDDEN, getRequiresAsArray } = require('../../../auth/utils')
|
|
16
16
|
|
|
17
17
|
const PPP = { POST: 1, PUT: 1, PATCH: 1 }
|
|
18
|
+
const GH = { GET: 1, HEAD: 1 }
|
|
18
19
|
|
|
19
20
|
let _i18n
|
|
20
21
|
const i18n = (...args) => {
|
|
@@ -93,6 +94,21 @@ class Rest {
|
|
|
93
94
|
throw FORBIDDEN
|
|
94
95
|
}
|
|
95
96
|
|
|
97
|
+
// service root
|
|
98
|
+
if (req.path === '/' && GH[req.method]) {
|
|
99
|
+
if (req.method === 'HEAD') return res.json({})
|
|
100
|
+
else
|
|
101
|
+
return res.json(
|
|
102
|
+
Object.keys(srv.entities).reduce(
|
|
103
|
+
(acc, cur) => {
|
|
104
|
+
acc.entities.push({ name: cur, url: cur })
|
|
105
|
+
return acc
|
|
106
|
+
},
|
|
107
|
+
{ entities: [] }
|
|
108
|
+
)
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
96
112
|
// content-type check, parse url, and base64 to buffer
|
|
97
113
|
try {
|
|
98
114
|
if (PPP[req.method]) contentTypeCheck(req)
|
|
@@ -115,6 +131,11 @@ class Rest {
|
|
|
115
131
|
}
|
|
116
132
|
})
|
|
117
133
|
|
|
134
|
+
// HEAD
|
|
135
|
+
this.router.head('/*', (req, res, next) => {
|
|
136
|
+
read(req, res, next)
|
|
137
|
+
})
|
|
138
|
+
|
|
118
139
|
// GET
|
|
119
140
|
this.router.get('/*', (req, res, next) => {
|
|
120
141
|
// READ or custom operation?
|
|
@@ -28,9 +28,7 @@ module.exports = service => {
|
|
|
28
28
|
|
|
29
29
|
result = await tx.dispatch(req)
|
|
30
30
|
|
|
31
|
-
if (result == null)
|
|
32
|
-
throw getError(404, 'NO_MATCHING_RESOURCE')
|
|
33
|
-
}
|
|
31
|
+
if (result == null) throw getError(404, 'NO_MATCHING_RESOURCE')
|
|
34
32
|
|
|
35
33
|
bufferToBase64(result, segments[0])
|
|
36
34
|
|
|
@@ -41,6 +39,13 @@ module.exports = service => {
|
|
|
41
39
|
await tx.rollback(e).catch(() => {})
|
|
42
40
|
} finally {
|
|
43
41
|
if (err) next(err)
|
|
42
|
+
else if (restReq.method === 'HEAD')
|
|
43
|
+
restRes
|
|
44
|
+
.set({
|
|
45
|
+
'content-type': 'application/json; charset=utf-8',
|
|
46
|
+
'content-length': JSON.stringify(result).length
|
|
47
|
+
})
|
|
48
|
+
.end()
|
|
44
49
|
else restRes.send(toRestResult(result))
|
|
45
50
|
}
|
|
46
51
|
}
|
|
@@ -230,6 +230,9 @@ module.exports = {
|
|
|
230
230
|
POST: (service, req) => {
|
|
231
231
|
return parseCreateOrReadUrl('CREATE', service, req)
|
|
232
232
|
},
|
|
233
|
+
HEAD: (service, req) => {
|
|
234
|
+
return parseCreateOrReadUrl('READ', service, req)
|
|
235
|
+
},
|
|
233
236
|
GET: (service, req) => {
|
|
234
237
|
return parseCreateOrReadUrl('READ', service, req)
|
|
235
238
|
},
|
|
@@ -128,7 +128,7 @@ class ApplicationService extends cds.Service {
|
|
|
128
128
|
// compat
|
|
129
129
|
restoreLink(req)
|
|
130
130
|
if (req.query.SELECT && req.query.SELECT._4odata) {
|
|
131
|
-
q.SELECT
|
|
131
|
+
Object.defineProperty(q.SELECT, '_4odata', { value: req.query.SELECT._4odata })
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
// REVISIT: We need to provide target explicitly because it's cached already within ensure_target
|
|
@@ -17,7 +17,10 @@ const { DRAFT_COLUMNS_UNION } = require('../../../common/constants/draft')
|
|
|
17
17
|
* @param [options.filterVirtual=false]
|
|
18
18
|
* @returns {Array<object>} - array of columns
|
|
19
19
|
*/
|
|
20
|
-
const getColumns = (
|
|
20
|
+
const getColumns = (
|
|
21
|
+
entity,
|
|
22
|
+
{ onlyNames = false, removeIgnore = false, filterDraft = true, filterVirtual = false, keysOnly = false }
|
|
23
|
+
) => {
|
|
21
24
|
const skipDraft = filterDraft && entity._isDraftEnabled
|
|
22
25
|
const columns = []
|
|
23
26
|
const elements = entity.elements
|
|
@@ -28,6 +31,7 @@ const getColumns = (entity, { onlyNames = false, removeIgnore = false, filterDra
|
|
|
28
31
|
if (filterVirtual && element.virtual) continue
|
|
29
32
|
if (removeIgnore && element['@cds.api.ignore']) continue
|
|
30
33
|
if (skipDraft && DRAFT_COLUMNS_UNION.includes(each)) continue
|
|
34
|
+
if (keysOnly && !element.key) continue
|
|
31
35
|
columns.push(onlyNames ? each : element)
|
|
32
36
|
}
|
|
33
37
|
|
|
@@ -285,30 +285,29 @@ const _mergeArrays = (entity, oldValue, newValue) => {
|
|
|
285
285
|
return merged
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
-
const mergeJsonDeep = (entity, oldValue,
|
|
288
|
+
const mergeJsonDeep = (entity, oldValue, value) => {
|
|
289
|
+
// REVISIT readAfterWrite -> commit -> postProcessing
|
|
290
|
+
// Detach result from req.data
|
|
291
|
+
const newValue = value ? Object.assign({}, value) : oldValue // if newValue === undefined
|
|
289
292
|
if (_isObject(oldValue) && _isObject(newValue)) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
if (
|
|
299
|
-
else {
|
|
300
|
-
const target = entity && entity.elements[key] && entity.elements[key]._target
|
|
293
|
+
// append to newValue to keep an order of attributes as might be defined in custom handler
|
|
294
|
+
Object.keys(oldValue).forEach(key => {
|
|
295
|
+
if (!(key in newValue)) {
|
|
296
|
+
Object.assign(newValue, { [key]: oldValue[key] })
|
|
297
|
+
} else {
|
|
298
|
+
const target = entity && entity.elements[key] && entity.elements[key]._target
|
|
299
|
+
if (_isObject(newValue[key])) {
|
|
300
|
+
newValue[key] = mergeJsonDeep(target, oldValue[key], newValue[key])
|
|
301
|
+
} else if (Array.isArray(newValue[key])) {
|
|
301
302
|
if (target) {
|
|
302
|
-
|
|
303
|
+
newValue[key] = _mergeArrays(target, oldValue[key], newValue[key])
|
|
303
304
|
}
|
|
304
305
|
// Can't merge items without target
|
|
305
306
|
}
|
|
306
|
-
} else {
|
|
307
|
-
Object.assign(oldValue, { [key]: newValue[key] })
|
|
308
307
|
}
|
|
309
308
|
})
|
|
310
309
|
}
|
|
311
|
-
return
|
|
310
|
+
return newValue
|
|
312
311
|
}
|
|
313
312
|
|
|
314
313
|
// Signature similar to Object.assign(oldValue, newValue)
|
|
@@ -45,14 +45,8 @@ module.exports = class {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
async _addPartialPersistentState(req) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
req.query,
|
|
51
|
-
req,
|
|
52
|
-
true,
|
|
53
|
-
true,
|
|
54
|
-
this._srv
|
|
55
|
-
)
|
|
48
|
+
// REVISIT: cds.context.model?
|
|
49
|
+
const deepUpdateData = await selectDeepUpdateData(this._srv, this._srv.model, req, true)
|
|
56
50
|
req._.partialPersistentState = deepUpdateData
|
|
57
51
|
}
|
|
58
52
|
|
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
// global.cds is used on purpose here!
|
|
2
2
|
const cds = global.cds
|
|
3
3
|
|
|
4
|
+
// requesting logger without module on purpose!
|
|
5
|
+
const LOG = cds.log()
|
|
6
|
+
|
|
4
7
|
const ODATA_CONTAINED = '@odata.contained'
|
|
5
8
|
|
|
6
9
|
const { isSelfManaged, isBacklink, getAnchor, getBacklink } = require('./utils')
|
|
7
10
|
const { foreignKeyPropagations } = require('../utils/foreignKeyPropagations')
|
|
8
11
|
|
|
12
|
+
const _logDeprecationForODataContained = () => {
|
|
13
|
+
if (!cds._deprecationWarningForODataContained) {
|
|
14
|
+
LOG._warn &&
|
|
15
|
+
LOG.warn(
|
|
16
|
+
'Annotation "@odata.contained" is deprecated and will be removed in an upcoming release. Use compositions instead of associations.'
|
|
17
|
+
)
|
|
18
|
+
cds._deprecationWarningForODataContained = true
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
9
22
|
module.exports = class {
|
|
10
23
|
get _isAssociationStrict() {
|
|
11
24
|
return (
|
|
@@ -15,6 +28,7 @@ module.exports = class {
|
|
|
15
28
|
}
|
|
16
29
|
|
|
17
30
|
get _isAssociationEffective() {
|
|
31
|
+
this[ODATA_CONTAINED] && _logDeprecationForODataContained()
|
|
18
32
|
return (
|
|
19
33
|
this.own('__isAssociationEffective') ||
|
|
20
34
|
this.set(
|
|
@@ -25,6 +39,7 @@ module.exports = class {
|
|
|
25
39
|
}
|
|
26
40
|
|
|
27
41
|
get _isCompositionEffective() {
|
|
42
|
+
this[ODATA_CONTAINED] && _logDeprecationForODataContained()
|
|
28
43
|
return (
|
|
29
44
|
this.own('__isCompositionEffective') ||
|
|
30
45
|
this.set(
|
|
@@ -36,6 +51,7 @@ module.exports = class {
|
|
|
36
51
|
}
|
|
37
52
|
|
|
38
53
|
get _isContained() {
|
|
54
|
+
this[ODATA_CONTAINED] && _logDeprecationForODataContained()
|
|
39
55
|
return (
|
|
40
56
|
this.own('__isContained') ||
|
|
41
57
|
this.set(
|