@sap/cds 7.6.4 → 7.7.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 +39 -1
- package/_i18n/i18n.properties +3 -0
- package/app/index.js +14 -8
- package/bin/serve.js +51 -19
- package/common.cds +16 -0
- package/lib/auth/ias-auth.js +2 -2
- package/lib/auth/index.js +1 -1
- package/lib/auth/jwt-auth.js +1 -1
- package/lib/compile/cdsc.js +23 -11
- package/lib/compile/for/nodejs.js +2 -2
- package/lib/compile/for/odata.js +4 -0
- package/lib/compile/load.js +7 -2
- package/lib/compile/to/sql.js +3 -0
- package/lib/dbs/cds-deploy.js +197 -220
- package/lib/env/defaults.js +2 -1
- package/lib/index.js +8 -2
- package/lib/linked/types.js +1 -0
- package/lib/log/format/json.js +4 -1
- package/lib/plugins.js +2 -2
- package/lib/ql/SELECT.js +8 -8
- package/lib/req/context.js +22 -13
- package/lib/req/request.js +10 -4
- package/lib/srv/cds-connect.js +9 -3
- package/lib/srv/cds-serve.js +5 -3
- package/lib/srv/middlewares/ctx-model.js +1 -1
- package/lib/srv/protocols/odata-v4.js +38 -9
- package/lib/srv/srv-api.js +98 -140
- package/lib/srv/srv-models.js +2 -2
- package/lib/srv/srv-tx.js +1 -0
- package/lib/utils/cds-utils.js +32 -23
- package/lib/utils/data.js +1 -1
- package/lib/utils/tar.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +1 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +0 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +18 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +7 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/index.js +5 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +71 -25
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +10 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +6 -1
- package/libx/_runtime/cds-services/util/assert.js +50 -240
- package/libx/_runtime/cds.js +5 -0
- package/libx/_runtime/common/aspects/any.js +53 -45
- package/libx/_runtime/common/generic/input.js +14 -10
- package/libx/_runtime/common/generic/paging.js +1 -1
- package/libx/_runtime/common/utils/cqn.js +1 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +1 -1
- package/libx/_runtime/common/utils/keys.js +1 -1
- package/libx/_runtime/common/utils/quotingStyles.js +1 -1
- package/libx/_runtime/common/utils/resolveStructured.js +4 -1
- package/libx/_runtime/common/utils/rewriteAsterisks.js +5 -12
- package/libx/_runtime/common/utils/stream.js +2 -16
- package/libx/_runtime/common/utils/streamProp.js +16 -6
- package/libx/_runtime/common/utils/ucsn.js +1 -0
- package/libx/_runtime/db/expand/expandCQNToJoin.js +1 -1
- package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
- package/libx/_runtime/db/utils/columns.js +6 -1
- package/libx/_runtime/fiori/generic/activate.js +11 -3
- package/libx/_runtime/fiori/generic/edit.js +8 -2
- package/libx/_runtime/fiori/lean-draft.js +94 -30
- package/libx/_runtime/hana/execute.js +2 -5
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +12 -22
- package/libx/_runtime/messaging/service.js +6 -2
- package/libx/common/assert/index.js +232 -0
- package/libx/common/assert/type.js +109 -0
- package/libx/common/assert/utils.js +125 -0
- package/libx/common/assert/validation.js +109 -0
- package/libx/odata/index.js +5 -5
- package/libx/odata/middleware/create.js +83 -0
- package/libx/odata/middleware/delete.js +38 -0
- package/libx/odata/middleware/error.js +8 -0
- package/libx/odata/{metadata.js → middleware/metadata.js} +8 -6
- package/libx/odata/middleware/operation.js +78 -0
- package/libx/odata/middleware/parse.js +11 -0
- package/libx/odata/{read.js → middleware/read.js} +42 -20
- package/libx/odata/{service-document.js → middleware/service-document.js} +2 -1
- package/libx/odata/middleware/stream.js +237 -0
- package/libx/odata/middleware/update.js +165 -0
- package/libx/odata/{afterburner.js → parse/afterburner.js} +79 -29
- package/libx/odata/{cqn2odata.js → parse/cqn2odata.js} +5 -3
- package/libx/odata/{parseToCqn.js → parse/parseToCqn.js} +3 -6
- package/libx/odata/{utils.js → utils/index.js} +95 -9
- package/libx/outbox/index.js +2 -1
- package/libx/rest/RestAdapter.js +0 -1
- package/libx/rest/middleware/operation.js +6 -4
- package/libx/rest/middleware/parse.js +20 -2
- package/package.json +1 -1
- package/server.js +43 -71
- package/libx/odata/create.js +0 -44
- package/libx/odata/delete.js +0 -25
- package/libx/odata/error.js +0 -12
- package/libx/odata/update.js +0 -110
- /package/libx/odata/{grammar.peggy → parse/grammar.peggy} +0 -0
- /package/libx/odata/{parser.js → parse/parser.js} +0 -0
- /package/libx/odata/{result.js → utils/result.js} +0 -0
package/lib/utils/tar.js
CHANGED
|
@@ -11,7 +11,7 @@ const _resolve = (...x) => path.resolve (cds.root,...x)
|
|
|
11
11
|
// tar does not work properly on Windows (by npm/jest tests) w/o this change
|
|
12
12
|
const win = path => {
|
|
13
13
|
if (!path) return path
|
|
14
|
-
if (typeof path === 'string') return path.replace('C:', '//localhost/c$')
|
|
14
|
+
if (typeof path === 'string') return path.replace('C:', '//localhost/c$').replace(/\\+/g, '/')
|
|
15
15
|
if (Array.isArray(path)) return path.map(el => win(el))
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -31,9 +31,7 @@ const metadata = service => {
|
|
|
31
31
|
// REVISIT: remove check later
|
|
32
32
|
if (mpSupportsEmptyLocale()) {
|
|
33
33
|
// If no extensibility nor fts, do not provide model to mtxs
|
|
34
|
-
const modelNeeded =
|
|
35
|
-
cds.env.requires.extensibility ||
|
|
36
|
-
(cds.env.requires.toggles && Object.keys(cds.context.features || {}).length)
|
|
34
|
+
const modelNeeded = cds.env.requires.extensibility || cds.context.features?.given
|
|
37
35
|
edmx = await mps.getEdmx({ tenant, model: modelNeeded && service.model, service: service.definition.name })
|
|
38
36
|
const extBundle = cds.env.requires.extensibility && (await mps.getI18n({ tenant, locale }))
|
|
39
37
|
edmx = cds.localize(service.model, locale, edmx, extBundle)
|
|
@@ -23,7 +23,6 @@ const getError = require('../../../../common/error')
|
|
|
23
23
|
const { getSapMessages } = require('../../../../common/error/frontend')
|
|
24
24
|
const { getPageSize, commonGenericPaging } = require('../../../../common/generic/paging')
|
|
25
25
|
const { handler: commonGenericSorting } = require('../../../../common/generic/sorting')
|
|
26
|
-
const { transformRedirectProperties } = require('../../../../common/utils/stream')
|
|
27
26
|
const { getTransition } = require('../../../../common/utils/resolveView')
|
|
28
27
|
const { Readable } = require('stream')
|
|
29
28
|
|
|
@@ -481,7 +480,6 @@ const _readAndTransform = (tx, req, odataReq) => {
|
|
|
481
480
|
}
|
|
482
481
|
|
|
483
482
|
const _postProcess = (odataReq, req, odataRes, service, result) => {
|
|
484
|
-
transformRedirectProperties(req, service, result.value)
|
|
485
483
|
const functionReturnType = getActionOrFunctionReturnType(
|
|
486
484
|
odataReq.getUriInfo().getPathSegments(),
|
|
487
485
|
service.model.definitions
|
|
@@ -125,6 +125,21 @@ const _hasEtag = target => {
|
|
|
125
125
|
return target._etag
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
const _getStructValue = (prop, result, segments) => {
|
|
129
|
+
const path = [prop]
|
|
130
|
+
for (let i = segments.length - 2; i >= 0; i--) {
|
|
131
|
+
const segment = segments[i]
|
|
132
|
+
if (segment.getKind() === 'COMPLEX.PROPERTY') {
|
|
133
|
+
path.unshift(segment.getProperty().getName())
|
|
134
|
+
} else break
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let res = result
|
|
138
|
+
path.forEach(p => (res = res[p]))
|
|
139
|
+
|
|
140
|
+
return { value: res }
|
|
141
|
+
}
|
|
142
|
+
|
|
128
143
|
/**
|
|
129
144
|
* The handler that will be registered with odata-v4.
|
|
130
145
|
*
|
|
@@ -201,7 +216,9 @@ const update = service => {
|
|
|
201
216
|
if (err) next(err)
|
|
202
217
|
else if (primitive && result) {
|
|
203
218
|
const prop = odataReq.getUriInfo().getLastSegment().getProperty().getName()
|
|
204
|
-
const res =
|
|
219
|
+
const res = cds.env.effective.odata.structs
|
|
220
|
+
? _getStructValue(prop, result, odataReq.getUriInfo().getPathSegments())
|
|
221
|
+
: { value: result[prop] }
|
|
205
222
|
for (const k of Object.keys(result).filter(k => k.match(/^\*/))) res[k] = result[k]
|
|
206
223
|
next(null, res)
|
|
207
224
|
} else next(null, toODataResult(result, req))
|
|
@@ -9,7 +9,7 @@ const readToCQN = require('./readToCQN')
|
|
|
9
9
|
const updateToCQN = require('./updateToCQN')
|
|
10
10
|
const createToCQN = require('./createToCQN')
|
|
11
11
|
const deleteToCQN = require('./deleteToCQN')
|
|
12
|
-
const parseToCqn = require('../../../../../odata/parseToCqn')
|
|
12
|
+
const parseToCqn = require('../../../../../odata/parse/parseToCqn')
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* This method transforms an odata request into a CQN object.
|
|
@@ -187,18 +187,22 @@ const _parsePrimitiveValue = (edmRef, value) => {
|
|
|
187
187
|
const getSegmentKeyValue = segmentParam => {
|
|
188
188
|
const edmRef = segmentParam.getEdmRef()
|
|
189
189
|
const keyName = edmRef.getName()
|
|
190
|
+
let val
|
|
190
191
|
if (segmentParam.getAliasValue()) {
|
|
191
192
|
const value = segmentParam.getAliasValue()
|
|
192
193
|
// must be JSON or a string according to
|
|
193
194
|
// https://docs.oasis-open.org/odata/odata/v4.01/os/part2-url-conventions/odata-v4.01-os-part2-url-conventions.html#sec_ComplexandCollectionLiterals
|
|
194
195
|
try {
|
|
195
|
-
|
|
196
|
+
val = value === 'undefined' ? undefined : JSON.parse(value)
|
|
197
|
+
return { keyName, val }
|
|
196
198
|
} catch (e) {
|
|
197
199
|
// plain string
|
|
198
200
|
}
|
|
199
|
-
|
|
201
|
+
val = _parsePrimitiveValue(edmRef, value) //> REVISIT: undefined handling needed here as well?
|
|
202
|
+
return { keyName, val }
|
|
200
203
|
}
|
|
201
|
-
|
|
204
|
+
val = segmentParam.getText() === undefined ? undefined : _parsePrimitiveValue(edmRef, segmentParam.getText())
|
|
205
|
+
return { keyName, val }
|
|
202
206
|
}
|
|
203
207
|
|
|
204
208
|
module.exports = {
|
|
@@ -17,6 +17,7 @@ const FullQualifiedName = require('../FullQualifiedName')
|
|
|
17
17
|
const UriResource = require('./UriResource')
|
|
18
18
|
const TransientStructuredType = require('../edm/TransientStructuredType')
|
|
19
19
|
const FeatureSupport = require('../FeatureSupport')
|
|
20
|
+
const cds = require('../../../../../../cds')
|
|
20
21
|
|
|
21
22
|
const TOKEN = "(?:[-!#$%&'*+.^_`|~A-Za-z0-9]+)"
|
|
22
23
|
const FORMAT_REGEXP = new RegExp(
|
|
@@ -176,7 +177,7 @@ class UriParser {
|
|
|
176
177
|
*/
|
|
177
178
|
parseQueryOptions (queryOptions, uriInfo) {
|
|
178
179
|
// EXPERIMENTAL FEATURE FLAGS!
|
|
179
|
-
const { okra_skip_query_options, odata_new_parser } =
|
|
180
|
+
const { okra_skip_query_options, odata_new_parser } = cds.env.features
|
|
180
181
|
if (okra_skip_query_options && odata_new_parser) return
|
|
181
182
|
|
|
182
183
|
const lastSegment = uriInfo.getLastSegment()
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
|
+
const cds = require('../../../../../cds')
|
|
3
|
+
const TRACE = cds.debug('trace')
|
|
4
|
+
TRACE?.time('require okra'.padEnd(22))
|
|
2
5
|
|
|
3
6
|
const commons = require('../odata-commons')
|
|
4
7
|
|
|
@@ -30,3 +33,5 @@ module.exports = {
|
|
|
30
33
|
Components: require('./core/ComponentManager').Components, // eslint-disable-line global-require
|
|
31
34
|
BatchExitHandler: require('./batch/BatchExitHandler') // eslint-disable-line global-require
|
|
32
35
|
}
|
|
36
|
+
|
|
37
|
+
TRACE?.timeEnd('require okra'.padEnd(22))
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const cds = require('../../../../cds')
|
|
2
|
+
|
|
1
3
|
const {
|
|
2
4
|
Components: { DATA_DELETE_HANDLER, DATA_READ_HANDLER, DATA_CREATE_HANDLER, DATA_UPDATE_HANDLER }
|
|
3
5
|
} = require('../okra/odata-server')
|
|
@@ -158,54 +160,84 @@ const _addForeignKeys = (service, odataReq, data) => {
|
|
|
158
160
|
}
|
|
159
161
|
}
|
|
160
162
|
|
|
161
|
-
const _getFunctionParameters = (lastSegment, keyValues, service, target) => {
|
|
163
|
+
const _getFunctionParameters = (lastSegment, keyValues, service, target, odataReq) => {
|
|
164
|
+
const { cds_assert: CDS_ASSERT } = cds.env.features
|
|
165
|
+
|
|
162
166
|
const functionParameters = lastSegment.getFunctionParameters()
|
|
163
167
|
const paramValues = _getParamData(functionParameters)
|
|
164
168
|
|
|
165
169
|
// Working assumption for the case of name collisions: take the entity's key
|
|
166
|
-
for (const key in keyValues)
|
|
167
|
-
|
|
168
|
-
|
|
170
|
+
for (const key in keyValues) paramValues[key] = keyValues[key]
|
|
171
|
+
|
|
172
|
+
let assertTarget
|
|
173
|
+
|
|
169
174
|
const errors = []
|
|
170
175
|
if (lastSegment.getKind() === 'BOUND.FUNCTION') {
|
|
171
176
|
const targetFunction = target && target.actions && target.actions[lastSegment.getFunction().getName()]
|
|
172
177
|
if (!targetFunction.params) return {}
|
|
173
|
-
convertStructured(service, targetFunction, paramValues, { errors })
|
|
178
|
+
if (!CDS_ASSERT) convertStructured(service, targetFunction, paramValues, { errors })
|
|
179
|
+
else assertTarget = targetFunction
|
|
174
180
|
} else if (lastSegment.getKind() === 'FUNCTION.IMPORT') {
|
|
175
181
|
const { namespace, name } = lastSegment.getFunctionImport().getFullQualifiedName()
|
|
176
182
|
const targetFunction = service.model && service.model.definitions[`${namespace}.${name}`]
|
|
177
183
|
if (!targetFunction.params) return {}
|
|
178
|
-
convertStructured(service, targetFunction, paramValues, { errors })
|
|
184
|
+
if (!CDS_ASSERT) convertStructured(service, targetFunction, paramValues, { errors })
|
|
185
|
+
else assertTarget = targetFunction
|
|
179
186
|
}
|
|
180
187
|
|
|
181
188
|
if (errors.length > 1) throw Object.assign(new Error(MULTIPLE_ERRORS), { details: errors })
|
|
182
189
|
if (errors.length === 1) throw errors[0]
|
|
183
190
|
|
|
191
|
+
if (CDS_ASSERT) {
|
|
192
|
+
const assertOptions = { filter: true, http: { req: odataReq.getIncomingRequest() } }
|
|
193
|
+
const errs = cds.assert(paramValues, assertTarget, assertOptions)
|
|
194
|
+
if (errs) {
|
|
195
|
+
if (errs.length === 1) throw errs[0]
|
|
196
|
+
throw Object.assign(new Error('MULTIPLE_ERRORS'), { statusCode: 400, details: errs })
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
184
200
|
return paramValues
|
|
185
201
|
}
|
|
186
202
|
|
|
187
|
-
const _getCopiedData = (odataReq,
|
|
203
|
+
const _getCopiedData = (odataReq, segments, service, target) => {
|
|
204
|
+
const { cds_assert: CDS_ASSERT } = cds.env.features
|
|
205
|
+
|
|
206
|
+
const lastSegment = segments[segments.length - 1]
|
|
207
|
+
|
|
188
208
|
let data = odataReq.getBody() || {}
|
|
209
|
+
let assertTarget = target
|
|
189
210
|
|
|
190
211
|
if (lastSegment.getKind() === 'PRIMITIVE.PROPERTY') {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const { namespace, name } = lastSegment.getActionImport().getFullQualifiedName()
|
|
202
|
-
const targetAction = service.model && service.model.definitions[`${namespace}.${name}`]
|
|
203
|
-
if (!targetAction || !targetAction.params) return {}
|
|
204
|
-
convertStructured(service, targetAction, data)
|
|
212
|
+
data = { [lastSegment.getProperty().getName()]: data }
|
|
213
|
+
if (cds.env.effective.odata.structs) {
|
|
214
|
+
for (let i = segments.length - 2; i >= 0; i--) {
|
|
215
|
+
const segment = segments[i]
|
|
216
|
+
if (segment.getKind() === 'COMPLEX.PROPERTY') {
|
|
217
|
+
const name = segment.getProperty().getName()
|
|
218
|
+
data = { [name]: data }
|
|
219
|
+
} else break
|
|
220
|
+
}
|
|
221
|
+
}
|
|
205
222
|
} else {
|
|
206
|
-
|
|
223
|
+
data = deepCopy(data)
|
|
224
|
+
if (lastSegment.getKind() === 'BOUND.ACTION') {
|
|
225
|
+
const targetAction = target.actions && target.actions[lastSegment.getAction().getName()]
|
|
226
|
+
if (!targetAction || !targetAction.params) return { data: {} }
|
|
227
|
+
if (!CDS_ASSERT) convertStructured(service, targetAction, data)
|
|
228
|
+
else assertTarget = targetAction
|
|
229
|
+
} else if (lastSegment.getKind() === 'ACTION.IMPORT') {
|
|
230
|
+
const { namespace, name } = lastSegment.getActionImport().getFullQualifiedName()
|
|
231
|
+
const targetAction = service.model && service.model.definitions[`${namespace}.${name}`]
|
|
232
|
+
if (!targetAction || !targetAction.params) return { data: {} }
|
|
233
|
+
if (!CDS_ASSERT) convertStructured(service, targetAction, data)
|
|
234
|
+
else assertTarget = targetAction
|
|
235
|
+
} else {
|
|
236
|
+
if (!CDS_ASSERT) convertStructured(service, target, data)
|
|
237
|
+
}
|
|
207
238
|
}
|
|
208
|
-
|
|
239
|
+
|
|
240
|
+
return { data, assertTarget }
|
|
209
241
|
}
|
|
210
242
|
|
|
211
243
|
/**
|
|
@@ -231,7 +263,7 @@ const getData = (component, odataReq, service, target) => {
|
|
|
231
263
|
const keyValues = _getParamData(keyPredicates)
|
|
232
264
|
|
|
233
265
|
if (component === DATA_READ_HANDLER && _isFunctionInvocation(odataReq)) {
|
|
234
|
-
return _getFunctionParameters(lastSegment, keyValues, service, target)
|
|
266
|
+
return _getFunctionParameters(lastSegment, keyValues, service, target, odataReq)
|
|
235
267
|
}
|
|
236
268
|
|
|
237
269
|
if (component === DATA_DELETE_HANDLER || component === DATA_READ_HANDLER) {
|
|
@@ -242,7 +274,7 @@ const getData = (component, odataReq, service, target) => {
|
|
|
242
274
|
}
|
|
243
275
|
|
|
244
276
|
// copy so that original payload is preserved
|
|
245
|
-
const data = _getCopiedData(odataReq,
|
|
277
|
+
const { data, assertTarget } = _getCopiedData(odataReq, segments, service, target)
|
|
246
278
|
|
|
247
279
|
// Only to be done for post via navigation
|
|
248
280
|
if (
|
|
@@ -263,6 +295,20 @@ const getData = (component, odataReq, service, target) => {
|
|
|
263
295
|
Array.isArray(data) ? Object.assign(data[0], keyValues) : Object.assign(data, keyValues)
|
|
264
296
|
}
|
|
265
297
|
|
|
298
|
+
// we need the (foreign) keys from the URL before asserting -> we can't assert in _getCopiedData()
|
|
299
|
+
if (cds.env.features.cds_assert && assertTarget) {
|
|
300
|
+
const assertOptions = {
|
|
301
|
+
filter: true,
|
|
302
|
+
http: { req: odataReq.getIncomingRequest() },
|
|
303
|
+
mandatories: component === 'CREATE' || undefined
|
|
304
|
+
}
|
|
305
|
+
const errs = cds.assert(data, assertTarget, assertOptions)
|
|
306
|
+
if (errs) {
|
|
307
|
+
if (errs.length === 1) throw errs[0]
|
|
308
|
+
throw Object.assign(new Error('MULTIPLE_ERRORS'), { statusCode: 400, details: errs })
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
266
312
|
return data
|
|
267
313
|
}
|
|
268
314
|
|
|
@@ -255,11 +255,15 @@ const _isSingleEntity = options => {
|
|
|
255
255
|
return isServiceEntity || isTargetComposition
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
+
const _isStructuredProperty = ({ returnType }) => {
|
|
259
|
+
return returnType.elements && returnType.kind === 'element'
|
|
260
|
+
}
|
|
261
|
+
|
|
258
262
|
const _getContextUrl = options => {
|
|
259
263
|
if (!options.returnType) return ''
|
|
260
264
|
const contextUrlPrefix = _getContextUrlPrefix(options)
|
|
261
265
|
const returnTypeUrl = _getReturnTypeUrl(options)
|
|
262
|
-
const columnsStringified = _listColumns(options)
|
|
266
|
+
const columnsStringified = options.propertyName || _isStructuredProperty(options) ? '' : _listColumns(options)
|
|
263
267
|
const $entity = _isSingleEntity(options) ? '/$entity' : ''
|
|
264
268
|
return `${contextUrlPrefix}$metadata#${returnTypeUrl}${columnsStringified}${$entity}`
|
|
265
269
|
}
|
|
@@ -299,6 +303,7 @@ const _partialCopyColumns = query => {
|
|
|
299
303
|
return []
|
|
300
304
|
}
|
|
301
305
|
|
|
306
|
+
// eslint-disable-next-line complexity
|
|
302
307
|
const _getPathInfo = (query, model) => {
|
|
303
308
|
const queryFrom =
|
|
304
309
|
(query.SELECT && resolveFromSelect(query)) ||
|
|
@@ -321,7 +326,10 @@ const _getPathInfo = (query, model) => {
|
|
|
321
326
|
returnType = target
|
|
322
327
|
isCollection = Array.isArray(query) || (query.SELECT && !query.SELECT.one)
|
|
323
328
|
propertyName = query._propertyAccess && query.SELECT.columns[0].ref[query.SELECT.columns[0].ref.length - 1]
|
|
324
|
-
if (propertyName
|
|
329
|
+
if (propertyName && path.slice(-1)[0] !== propertyName) {
|
|
330
|
+
path.push(propertyName)
|
|
331
|
+
returnType = query.SELECT.columns[0].ref.reduce((r, c) => r.elements[c], target)
|
|
332
|
+
}
|
|
325
333
|
}
|
|
326
334
|
const isStream = propertyName && target.elements[propertyName]?.['@Core.MediaType']
|
|
327
335
|
return {
|
|
@@ -14,7 +14,12 @@ const setLocationHeader = (req, { model }) => {
|
|
|
14
14
|
const { odataRes } = req._
|
|
15
15
|
const cqn = getSimpleSelectCQN(req.target, req.data)
|
|
16
16
|
const { path: location } = cds.odata.urlify(cqn, { kind: 'odata', model, method: 'GET' })
|
|
17
|
-
|
|
17
|
+
// REVISIT: needs reworking for new adapter, especially re $batch
|
|
18
|
+
if (odataRes) {
|
|
19
|
+
odataRes.setHeader('Location', location.replace(/\?.*$/, ''))
|
|
20
|
+
} else if (req.http?.res) {
|
|
21
|
+
req.http.res.set('location', location.replace(/\?.*$/, ''))
|
|
22
|
+
}
|
|
18
23
|
}
|
|
19
24
|
|
|
20
25
|
const _isNoAccessError = e => Number(e.code) === 403 || Number(e.code) === 401
|