@sap/cds 7.8.2 → 7.9.0
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 +37 -0
- package/_i18n/i18n_ar.properties +3 -0
- package/_i18n/i18n_cs.properties +3 -0
- package/_i18n/i18n_da.properties +3 -0
- package/_i18n/i18n_es_MX.properties +3 -0
- package/_i18n/i18n_fi.properties +3 -0
- package/_i18n/i18n_hu.properties +6 -0
- package/_i18n/i18n_ko.properties +3 -0
- package/_i18n/i18n_ms.properties +3 -0
- package/_i18n/i18n_nl.properties +3 -0
- package/_i18n/i18n_no.properties +3 -0
- package/_i18n/i18n_ro.properties +3 -0
- package/_i18n/i18n_sv.properties +3 -0
- package/_i18n/i18n_th.properties +3 -0
- package/_i18n/i18n_tr.properties +6 -0
- package/_i18n/i18n_zh_TW.properties +3 -0
- package/bin/serve.js +5 -5
- package/lib/auth/basic-auth.js +1 -1
- package/lib/compile/cdsc.js +33 -6
- package/lib/compile/etc/_localized.js +14 -7
- package/lib/compile/for/lean_drafts.js +9 -0
- package/lib/compile/to/edm-files.js +116 -0
- package/lib/compile/to/edm.js +8 -1
- package/lib/compile/to/hdbtabledata.js +3 -3
- package/lib/compile/to/sql.js +4 -2
- package/lib/compile/to/yaml.js +22 -21
- package/lib/dbs/cds-deploy.js +5 -6
- package/lib/env/cds-env.js +7 -0
- package/lib/env/cds-requires.js +20 -1
- package/lib/env/defaults.js +21 -5
- package/lib/env/schemas/cds-package.js +1 -1
- package/lib/env/schemas/cds-rc.js +85 -4
- package/lib/index.js +1 -1
- package/lib/linked/entities.js +10 -0
- package/lib/linked/models.js +1 -1
- package/lib/plugins.js +1 -1
- package/lib/ql/INSERT.js +17 -3
- package/lib/ql/Query.js +4 -0
- package/lib/ql/infer.js +1 -1
- package/lib/req/request.js +1 -1
- package/lib/srv/cds-serve.js +1 -0
- package/lib/srv/middlewares/cds-context.js +1 -1
- package/lib/srv/protocols/odata-v4.js +5 -6
- package/lib/srv/srv-models.js +9 -2
- package/lib/utils/cds-test.js +2 -0
- package/lib/utils/cds-utils.js +9 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +3 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +22 -10
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +4 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +4 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +4 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +38 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +32 -21
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +0 -10
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +3 -1
- package/libx/_runtime/cds-services/services/utils/compareJson.js +2 -274
- package/libx/_runtime/{cds-services/services → common}/Service.js +39 -29
- package/libx/_runtime/common/generic/auth/autoexpose.js +41 -0
- package/libx/_runtime/common/generic/auth/index.js +2 -0
- package/libx/_runtime/common/generic/auth/readOnly.js +0 -11
- package/libx/_runtime/common/generic/auth/restrict.js +6 -5
- package/libx/_runtime/common/generic/auth/utils.js +1 -1
- package/libx/_runtime/common/generic/crud.js +5 -8
- package/libx/_runtime/common/generic/etag.js +8 -6
- package/libx/_runtime/common/generic/sorting.js +2 -2
- package/libx/_runtime/common/i18n/messages.properties +1 -0
- package/libx/_runtime/{cds-services/services → common}/utils/columns.js +4 -4
- package/libx/_runtime/common/utils/compareJson.js +274 -0
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +1 -1
- package/libx/_runtime/{cds-services/services → common}/utils/differ.js +8 -8
- package/libx/_runtime/common/utils/ensureIEEE754.js +29 -0
- package/libx/_runtime/common/utils/{postProcessing.js → postProcess.js} +1 -3
- package/libx/_runtime/common/utils/resolveView.js +0 -16
- package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -1
- package/libx/_runtime/common/utils/search2cqn4sql.js +1 -1
- package/libx/_runtime/common/utils/streamProp.js +9 -2
- package/libx/_runtime/common/utils/ucsn.js +1 -1
- package/libx/_runtime/db/expand/expandCQNToJoin.js +5 -3
- package/libx/_runtime/db/generic/rewrite.js +7 -13
- package/libx/_runtime/fiori/generic/activate.js +1 -1
- package/libx/_runtime/fiori/generic/edit.js +1 -1
- package/libx/_runtime/fiori/generic/prepare.js +1 -1
- package/libx/_runtime/fiori/lean-draft.js +151 -46
- package/libx/_runtime/fiori/utils/handler.js +1 -1
- package/libx/_runtime/hana/execute.js +6 -2
- package/libx/_runtime/hana/search2cqn4sql.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +1 -2
- package/libx/_runtime/messaging/event-broker.js +212 -0
- package/libx/_runtime/remote/Service.js +9 -32
- package/libx/_runtime/remote/utils/client.js +13 -21
- package/libx/_runtime/sqlite/convertAssocToOneManaged.js +7 -1
- package/libx/_runtime/sqlite/execute.js +8 -3
- package/libx/_runtime/ucl/Service.js +259 -0
- package/libx/common/assert/index.js +5 -11
- package/libx/common/assert/validation.js +6 -1
- package/libx/odata/index.js +47 -25
- package/libx/odata/middleware/batch.js +8 -7
- package/libx/odata/middleware/create.js +42 -16
- package/libx/odata/middleware/delete.js +18 -11
- package/libx/odata/middleware/metadata.js +15 -14
- package/libx/odata/middleware/operation.js +30 -40
- package/libx/odata/middleware/parse.js +2 -3
- package/libx/odata/middleware/read.js +59 -52
- package/libx/odata/middleware/service-document.js +7 -7
- package/libx/odata/middleware/stream.js +26 -24
- package/libx/odata/middleware/update.js +53 -92
- package/libx/odata/parse/afterburner.js +45 -47
- package/libx/odata/parse/grammar.peggy +3 -3
- package/libx/odata/parse/multipartToJson.js +10 -22
- package/libx/odata/parse/parser.js +1 -1
- package/libx/odata/utils/etag.js +13 -0
- package/libx/odata/utils/handler.js +120 -0
- package/libx/odata/utils/index.js +15 -2
- package/libx/odata/utils/metaInfo.js +410 -0
- package/libx/odata/utils/path.js +5 -2
- package/libx/odata/utils/readAfterWrite.js +23 -0
- package/libx/odata/utils/result.js +4 -5
- package/libx/rest/RestAdapter.js +4 -13
- package/libx/rest/middleware/parse.js +40 -7
- package/package.json +1 -1
- package/server.js +1 -0
- package/libx/_runtime/cds-services/util/dataProcessUtils.js +0 -93
- package/libx/_runtime/common/utils/thenable.js +0 -51
- package/libx/_runtime/rest/service.js +0 -2
- package/libx/odata/parse/parseToCqn.js +0 -39
- package/libx/rest/middleware/input.js +0 -54
- package/libx/rest/middleware/payload.js +0 -13
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
const cds = require('
|
|
1
|
+
const cds = require('../../cds')
|
|
2
2
|
const { SELECT } = cds.ql
|
|
3
3
|
|
|
4
4
|
const { compareJson } = require('./compareJson')
|
|
5
|
-
const { selectDeepUpdateData } = require('
|
|
6
|
-
const { ensureDraftsSuffix } = require('
|
|
5
|
+
const { selectDeepUpdateData } = require('../composition')
|
|
6
|
+
const { ensureDraftsSuffix } = require('../../fiori/utils/handler')
|
|
7
7
|
|
|
8
|
-
const { DRAFT_COLUMNS_MAP } = require('
|
|
9
|
-
const { cqn2cqn4sql, convertPathExpressionToWhere } = require('
|
|
10
|
-
const { revertData } = require('
|
|
11
|
-
const { removeIsActiveEntityRecursively } = require('
|
|
12
|
-
const { enrichDataWithKeysFromWhere } = require('
|
|
8
|
+
const { DRAFT_COLUMNS_MAP } = require('../constants/draft')
|
|
9
|
+
const { cqn2cqn4sql, convertPathExpressionToWhere } = require('./cqn2cqn4sql')
|
|
10
|
+
const { revertData } = require('./resolveView')
|
|
11
|
+
const { removeIsActiveEntityRecursively } = require('../../fiori/utils/where')
|
|
12
|
+
const { enrichDataWithKeysFromWhere } = require('./keys')
|
|
13
13
|
|
|
14
14
|
module.exports = class Differ {
|
|
15
15
|
constructor(srv) {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const getTemplate = require('./template')
|
|
2
|
+
const templateProcessor = require('./templateProcessor')
|
|
3
|
+
|
|
4
|
+
const _pick = element => {
|
|
5
|
+
return element._type in { 'cds.Decimal': 1, 'cds.Integer64': 1 }
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const _processorFn = () => elementInfo => {
|
|
9
|
+
const { row, key } = elementInfo
|
|
10
|
+
if (typeof row?.[key] !== 'number') return
|
|
11
|
+
row[key] = `${row[key]}`
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const ensureIEEE754 = (target, service, result) => {
|
|
15
|
+
const { model } = service
|
|
16
|
+
if (!target || !result || !model || !model.definitions[target.name]) return
|
|
17
|
+
|
|
18
|
+
const template = getTemplate('ensureIEEE754', service, target, { pick: _pick })
|
|
19
|
+
if (template.elements.size === 0) return
|
|
20
|
+
|
|
21
|
+
if (typeof result === 'object' && result != null) {
|
|
22
|
+
const processFn = _processorFn()
|
|
23
|
+
for (const row of Array.isArray(result) ? result : [result]) {
|
|
24
|
+
templateProcessor({ processFn, row, template })
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = ensureIEEE754
|
|
@@ -714,21 +714,6 @@ const resolveView = (query, model, service) => {
|
|
|
714
714
|
return newQuery
|
|
715
715
|
}
|
|
716
716
|
|
|
717
|
-
/**
|
|
718
|
-
* Restores the link of req.data and req.query in case req.query was overwritten.
|
|
719
|
-
* Only applicable for UPDATEs and INSERTs.
|
|
720
|
-
*
|
|
721
|
-
* @param {*} req
|
|
722
|
-
*/
|
|
723
|
-
const restoreLink = req => {
|
|
724
|
-
if (req.query.INSERT?.entries) {
|
|
725
|
-
if (Array.isArray(req.query.INSERT.entries)) req.data = req.query.INSERT.entries[0]
|
|
726
|
-
else req.data = req.query.INSERT.entries
|
|
727
|
-
} else if (req.query.UPDATE?.data) {
|
|
728
|
-
req.data = req.query.UPDATE.data
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
|
|
732
717
|
/**
|
|
733
718
|
* Retrieves the actual query target by evaluating the created transitions.
|
|
734
719
|
* @param {*} q - the resolved query
|
|
@@ -753,6 +738,5 @@ module.exports = {
|
|
|
753
738
|
getDBTable,
|
|
754
739
|
resolveView,
|
|
755
740
|
getTransition,
|
|
756
|
-
restoreLink,
|
|
757
741
|
revertData
|
|
758
742
|
}
|
|
@@ -42,7 +42,7 @@ const _resolveTarget = (ref, target) => {
|
|
|
42
42
|
const element = target.elements[_ref]
|
|
43
43
|
if (element) return element._target
|
|
44
44
|
|
|
45
|
-
throw cds.error(`Navigation property
|
|
45
|
+
throw cds.error(`Navigation property "${_ref}" is not defined in ${target.name}`, { code: 400 })
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
const rewriteExpandAsterisk = (columns, target) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { computeColumnsToBeSearched } = require('
|
|
1
|
+
const { computeColumnsToBeSearched } = require('./columns')
|
|
2
2
|
const searchToLike = require('./searchToLike')
|
|
3
3
|
const { getEntityNameFromCQN } = require('./entityFromCqn')
|
|
4
4
|
|
|
@@ -3,7 +3,9 @@ const { ensureNoDraftsSuffix, ensureUnlocalized } = require('../../fiori/utils/h
|
|
|
3
3
|
const { isDuplicate } = require('./rewriteAsterisks')
|
|
4
4
|
|
|
5
5
|
const _addColumn = (name, type, columns, url) => {
|
|
6
|
+
// we do not want to return these additional odata properties with new adapter
|
|
6
7
|
if (cds.env.features.odata_new_adapter) return
|
|
8
|
+
|
|
7
9
|
if (typeof type === 'object') {
|
|
8
10
|
let mType = type['='].replaceAll(/\./g, '_')
|
|
9
11
|
const ref = {
|
|
@@ -27,6 +29,7 @@ const _addColumn = (name, type, columns, url) => {
|
|
|
27
29
|
|
|
28
30
|
const _addColumns = (target, columns) => {
|
|
29
31
|
if (cds.env.features.odata_new_adapter) return
|
|
32
|
+
|
|
30
33
|
for (const k in target.elements) {
|
|
31
34
|
const el = target.elements[k]
|
|
32
35
|
if (el['@Core.MediaType']) {
|
|
@@ -35,6 +38,7 @@ const _addColumns = (target, columns) => {
|
|
|
35
38
|
}
|
|
36
39
|
}
|
|
37
40
|
|
|
41
|
+
// eslint-disable-next-line complexity
|
|
38
42
|
const handleStreamProperties = (target, columns, model) => {
|
|
39
43
|
if (!target || !model || !columns) return
|
|
40
44
|
|
|
@@ -44,11 +48,14 @@ const handleStreamProperties = (target, columns, model) => {
|
|
|
44
48
|
const name = col.ref && col.ref[col.ref.length - 1]
|
|
45
49
|
const element = name && target.elements[name]
|
|
46
50
|
const type = element && element.type
|
|
47
|
-
const mediaType = element
|
|
51
|
+
const mediaType = element?.['@Core.MediaType']
|
|
52
|
+
|
|
53
|
+
// new adapter handles urls correctly and just returns them as they are, okra handles them wrongly due to wrong EDM generation by compiler
|
|
54
|
+
const ignoreMediaType = cds.env.features.odata_new_adapter && mediaType && element['@Core.IsURL']
|
|
48
55
|
|
|
49
56
|
if (col === '*') {
|
|
50
57
|
_addColumns(target, columns)
|
|
51
|
-
} else if (col.ref && (type === 'cds.LargeBinary' || mediaType)) {
|
|
58
|
+
} else if (col.ref && (type === 'cds.LargeBinary' || (mediaType && !ignoreMediaType))) {
|
|
52
59
|
if (mediaType) {
|
|
53
60
|
_addColumn(name, mediaType, columns, element['@Core.IsURL'])
|
|
54
61
|
columns.splice(index, 1)
|
|
@@ -75,7 +75,7 @@ const _cleanup = (row, definition, cleanupNull, cleanupStruct, errors, prefix =
|
|
|
75
75
|
_cleanup(row[key], definition, cleanupNull, cleanupStruct, errors, [...prefix, key])
|
|
76
76
|
} else {
|
|
77
77
|
if (errors) {
|
|
78
|
-
errors.push(getError(400,
|
|
78
|
+
errors.push(getError(400, `Property "${key}" does not exist in ${definition.name}`))
|
|
79
79
|
}
|
|
80
80
|
delete row[key]
|
|
81
81
|
}
|
|
@@ -1024,7 +1024,7 @@ class JoinCQNFromExpanded {
|
|
|
1024
1024
|
// Add table alias or name to handle cases, where joined tables have same column names
|
|
1025
1025
|
if (this._isElement(column.ref, entity)) {
|
|
1026
1026
|
const alias = tableAlias || ensureNoDraftsSuffix(entity.name)
|
|
1027
|
-
aliasedElement.ref = alias ? [alias, column.ref
|
|
1027
|
+
aliasedElement.ref = alias ? [alias, ...column.ref] : [...column.ref]
|
|
1028
1028
|
}
|
|
1029
1029
|
|
|
1030
1030
|
if (skipMapping) return aliasedElement
|
|
@@ -1050,8 +1050,10 @@ class JoinCQNFromExpanded {
|
|
|
1050
1050
|
}
|
|
1051
1051
|
|
|
1052
1052
|
_isElement(ref, entity) {
|
|
1053
|
-
if (!ref
|
|
1054
|
-
|
|
1053
|
+
if (!ref) return false
|
|
1054
|
+
if (ref.length > 1) {
|
|
1055
|
+
return entity.elements[ref[0]]?.isAssociation
|
|
1056
|
+
}
|
|
1055
1057
|
// Normal element
|
|
1056
1058
|
if (entity.elements[ref[0]]) return true
|
|
1057
1059
|
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
const cds = require('../../cds')
|
|
2
2
|
const { cqn2cqn4sql } = require('../../common/utils/cqn2cqn4sql')
|
|
3
3
|
const { generateAliases } = require('../utils/generateAliases')
|
|
4
|
-
const { restoreLink } = require('../../common/utils/resolveView')
|
|
5
4
|
|
|
6
|
-
const
|
|
5
|
+
const _restoreLink = req => {
|
|
7
6
|
if (req.query.INSERT?.entries) {
|
|
8
|
-
|
|
9
|
-
return req.data === req.query.INSERT.entries
|
|
7
|
+
return (req.data = Array.isArray(req.query.INSERT.entries) ? req.query.INSERT.entries[0] : req.query.INSERT.entries)
|
|
10
8
|
}
|
|
11
|
-
|
|
12
9
|
if (req.query.UPDATE?.data) {
|
|
13
|
-
return req.data
|
|
10
|
+
return (req.data = req.query.UPDATE.data)
|
|
14
11
|
}
|
|
15
12
|
}
|
|
16
13
|
|
|
@@ -26,19 +23,16 @@ function handler(req) {
|
|
|
26
23
|
return
|
|
27
24
|
}
|
|
28
25
|
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
// for restore link to req.data
|
|
32
|
-
const linked = _isLinked(req)
|
|
26
|
+
const _streaming = cds.env.features.stream_compat && req.query._streaming
|
|
33
27
|
|
|
34
28
|
// convert to sql cqn
|
|
35
29
|
req.query = cqn2cqn4sql(req.query, this.model, { service: this })
|
|
36
30
|
|
|
37
|
-
// REVISIT: should not be necessary
|
|
38
31
|
// restore link to req.data
|
|
39
|
-
|
|
32
|
+
_restoreLink(req)
|
|
33
|
+
|
|
34
|
+
if (_streaming) req.query._streaming = _streaming
|
|
40
35
|
|
|
41
|
-
if (cds.env.features.stream_compat && streaming) req.query._streaming = streaming
|
|
42
36
|
generateAliases(req.query)
|
|
43
37
|
}
|
|
44
38
|
|
|
@@ -10,7 +10,7 @@ const {
|
|
|
10
10
|
} = require('../utils/handler')
|
|
11
11
|
const { readAndDeleteKeywords, isActiveEntityRequested, getKeyData } = require('../utils/where')
|
|
12
12
|
const { isDraftRootEntity } = require('../../fiori/utils/csn')
|
|
13
|
-
const { getColumns } = require('../../
|
|
13
|
+
const { getColumns } = require('../../common/utils/columns')
|
|
14
14
|
const { DRAFT_COLUMNS_MAP } = require('../../common/constants/draft')
|
|
15
15
|
|
|
16
16
|
const _getRootCQN = (context, requestActiveData) => {
|
|
@@ -3,7 +3,7 @@ const getLockInfo = require('../utils/lockInfo')
|
|
|
3
3
|
const { INSERT, SELECT, DELETE } = cds.ql
|
|
4
4
|
|
|
5
5
|
const { getCompositionTree } = require('../../common/composition')
|
|
6
|
-
const { getColumns } = require('../../
|
|
6
|
+
const { getColumns } = require('../../common/utils/columns')
|
|
7
7
|
const { draftIsLocked, ensureDraftsSuffix, ensureNoDraftsSuffix, getSubCQNs } = require('../utils/handler')
|
|
8
8
|
const { isActiveEntityRequested } = require('../utils/where')
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ const { SELECT } = cds.ql
|
|
|
3
3
|
|
|
4
4
|
const { isActiveEntityRequested } = require('../utils/where')
|
|
5
5
|
const { ensureDraftsSuffix, ensureNoDraftsSuffix } = require('../utils/handler')
|
|
6
|
-
const { getColumns } = require('../../
|
|
6
|
+
const { getColumns } = require('../../common/utils/columns')
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Generic Handler for PreparationAction requests.
|
|
@@ -5,6 +5,9 @@ const { getKeyData } = require('./utils/where')
|
|
|
5
5
|
|
|
6
6
|
const { getPageSize, commonGenericPaging } = require('../common/generic/paging')
|
|
7
7
|
const { handler: commonGenericSorting } = require('../common/generic/sorting')
|
|
8
|
+
const { addEtagColumns } = require('../common/utils/etag')
|
|
9
|
+
|
|
10
|
+
const { calculateLocationHeader } = require('../../odata/utils')
|
|
8
11
|
|
|
9
12
|
const LOG = cds.log('fiori|drafts')
|
|
10
13
|
const original = Symbol('original')
|
|
@@ -73,10 +76,10 @@ const LOCK_TIMEOUT = {
|
|
|
73
76
|
}
|
|
74
77
|
|
|
75
78
|
const reject_bypassed_draft = req => {
|
|
76
|
-
const
|
|
79
|
+
const message =
|
|
77
80
|
!cds.profiles?.includes('production') &&
|
|
78
81
|
'`cds.env.fiori.bypass_draft` must be enabled or the entity must be annotated with `@odata.draft.bypass` to support the directly modification of active instances.'
|
|
79
|
-
return req.reject(501,
|
|
82
|
+
return req.reject({ code: 501, statusCode: 501, message })
|
|
80
83
|
}
|
|
81
84
|
|
|
82
85
|
const DRAFT_ELEMENTS = new Set([
|
|
@@ -322,8 +325,10 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
322
325
|
return result
|
|
323
326
|
}
|
|
324
327
|
|
|
328
|
+
if (req.event === 'draftEdit') req.event = 'EDIT'
|
|
329
|
+
|
|
325
330
|
if (req.event === 'NEW' || req.event === 'CANCEL' || req.event === 'draftPrepare') {
|
|
326
|
-
if (req.event === 'draftPrepare' && draftParams.IsActiveEntity) req.reject(400)
|
|
331
|
+
if (req.event === 'draftPrepare' && draftParams.IsActiveEntity) req.reject({ code: 400, statusCode: 400 })
|
|
327
332
|
if (req.event === 'NEW' && req.data?.IsActiveEntity === true) {
|
|
328
333
|
if (!cds.env.fiori.bypass_draft && !req.target['@odata.draft.bypass']) return reject_bypassed_draft(req)
|
|
329
334
|
const containsDraftRoot =
|
|
@@ -331,7 +336,7 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
331
336
|
'@Common.DraftRoot.ActivationAction'
|
|
332
337
|
]
|
|
333
338
|
|
|
334
|
-
if (!containsDraftRoot) req.reject(403, 'DRAFT_MODIFICATION_ONLY_VIA_ROOT')
|
|
339
|
+
if (!containsDraftRoot) req.reject({ code: 403, statusCode: 403, message: 'DRAFT_MODIFICATION_ONLY_VIA_ROOT' })
|
|
335
340
|
|
|
336
341
|
const isDirectAccess = typeof req.query.INSERT.into === 'string' || req.query.INSERT.into.ref?.length === 1
|
|
337
342
|
const data = Object.assign({}, req.data) // IsActiveEntity is not enumerable
|
|
@@ -355,7 +360,7 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
355
360
|
rootHasDraft = await SELECT.one([1]).from({ ref: draftsRootRef }).where(keyData)
|
|
356
361
|
}
|
|
357
362
|
|
|
358
|
-
if (rootHasDraft) req.reject(409, 'DRAFT_ALREADY_EXISTS')
|
|
363
|
+
if (rootHasDraft) req.reject({ code: 409, statusCode: 409, message: 'DRAFT_ALREADY_EXISTS' })
|
|
359
364
|
|
|
360
365
|
const cqn = INSERT.into(query.INSERT.into).entries(data)
|
|
361
366
|
await run(cqn, { event: 'CREATE' })
|
|
@@ -393,8 +398,8 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
393
398
|
const draft = await draftQuery
|
|
394
399
|
const inProcessByUser = draft?.DraftAdministrativeData?.InProcessByUser
|
|
395
400
|
if (inProcessByUser && inProcessByUser !== cds.context.user.id)
|
|
396
|
-
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER', [inProcessByUser])
|
|
397
|
-
if (draft) req.reject(403, 'DRAFT_ACTIVE_DELETE_FORBIDDEN_DRAFT_EXISTS')
|
|
401
|
+
req.reject({ code: 403, statusCode: 403, message: 'DRAFT_LOCKED_BY_ANOTHER_USER', args: [inProcessByUser] })
|
|
402
|
+
if (draft) req.reject({ code: 403, statusCode: 403, message: 'DRAFT_ACTIVE_DELETE_FORBIDDEN_DRAFT_EXISTS' })
|
|
398
403
|
await run(query)
|
|
399
404
|
return req.data
|
|
400
405
|
}
|
|
@@ -405,11 +410,15 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
405
410
|
// It would be great if we'd have a SELECT ** to deeply expand the entity (along compositions), that should
|
|
406
411
|
// be implemented in expand implementation.
|
|
407
412
|
if (req.query.SELECT.from.ref.length > 1 || draftParams.IsActiveEntity !== false) {
|
|
408
|
-
req.reject(
|
|
413
|
+
req.reject({
|
|
414
|
+
code: 400,
|
|
415
|
+
statusCode: 400,
|
|
416
|
+
message: 'Action "draftActivate" can only be called on the root draft entity'
|
|
417
|
+
})
|
|
409
418
|
}
|
|
410
419
|
|
|
411
420
|
if (req.target._etag && !req.headers['if-match'] && !req.headers['if-none-match']) {
|
|
412
|
-
req.reject(428)
|
|
421
|
+
req.reject({ code: 428, statusCode: 428 })
|
|
413
422
|
}
|
|
414
423
|
|
|
415
424
|
const targetDraft = req.target.drafts
|
|
@@ -428,9 +437,15 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
428
437
|
{ ref: ['DraftAdministrativeData'], expand: [{ ref: ['InProcessByUser'] }] }
|
|
429
438
|
])
|
|
430
439
|
)
|
|
431
|
-
if (!res)
|
|
440
|
+
if (!res)
|
|
441
|
+
req.reject(_etagValidationType ? { code: 412, statusCode: 412 } : { code: 'DRAFT_NOT_EXISTING', statusCode: 404 })
|
|
432
442
|
if (res.DraftAdministrativeData?.InProcessByUser !== cds.context.user.id) {
|
|
433
|
-
req.reject(
|
|
443
|
+
req.reject({
|
|
444
|
+
code: 403,
|
|
445
|
+
statusCode: 403,
|
|
446
|
+
message: 'DRAFT_LOCKED_BY_ANOTHER_USER',
|
|
447
|
+
args: [res.DraftAdministrativeData?.InProcessByUser]
|
|
448
|
+
})
|
|
434
449
|
}
|
|
435
450
|
const DraftAdministrativeData_DraftUUID = res.DraftAdministrativeData_DraftUUID
|
|
436
451
|
delete res.DraftAdministrativeData_DraftUUID
|
|
@@ -469,12 +484,21 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
469
484
|
// REVISIT: needs reworking for new adapter, especially re $batch
|
|
470
485
|
if (req._?.odataRes) {
|
|
471
486
|
req._?.odataRes?.setStatusCode(201, { overwrite: true })
|
|
472
|
-
} else if (req.
|
|
473
|
-
req.
|
|
487
|
+
} else if (req.res) {
|
|
488
|
+
req.res.status(201)
|
|
474
489
|
}
|
|
475
490
|
}
|
|
476
491
|
|
|
477
|
-
|
|
492
|
+
if (cds.env.features.odata_new_adapter) {
|
|
493
|
+
const read_result = await _readAfterDraftAction.bind(this)({ req, payload: res, action: 'draftActivate' })
|
|
494
|
+
req.res.set(
|
|
495
|
+
'location',
|
|
496
|
+
'../' + calculateLocationHeader(req.target, this, read_result || { ...res, IsActiveEntity: true })
|
|
497
|
+
)
|
|
498
|
+
return read_result
|
|
499
|
+
} else {
|
|
500
|
+
return Object.assign(result, { IsActiveEntity: true })
|
|
501
|
+
}
|
|
478
502
|
}
|
|
479
503
|
|
|
480
504
|
if (req.target.actions?.[req.event] && draftParams.IsActiveEntity === false) {
|
|
@@ -484,27 +508,35 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
484
508
|
rootQuery.SELECT.one = true
|
|
485
509
|
rootQuery.SELECT.from = { ref: [query.SELECT.from.ref[0]] }
|
|
486
510
|
const root = await cds.run(rootQuery)
|
|
487
|
-
if (!root) req.reject(404)
|
|
488
|
-
if (root.DraftAdministrativeData?.InProcessByUser !== cds.context.user.id)
|
|
511
|
+
if (!root) req.reject({ code: 404, statusCode: 404 })
|
|
512
|
+
if (root.DraftAdministrativeData?.InProcessByUser !== cds.context.user.id) {
|
|
513
|
+
req.reject({ code: 403, statusCode: 403 })
|
|
514
|
+
}
|
|
489
515
|
const _req = _newReq(req, query, draftParams, { event: req.event })
|
|
490
516
|
const result = await handle(_req)
|
|
491
517
|
return result
|
|
492
518
|
}
|
|
493
519
|
|
|
494
520
|
if (req.event === 'PATCH') {
|
|
495
|
-
if (!('IsActiveEntity' in draftParams)) req.reject(501)
|
|
521
|
+
if (!('IsActiveEntity' in draftParams)) req.reject({ code: 501, statusCode: 501 })
|
|
496
522
|
|
|
497
523
|
if (draftParams.IsActiveEntity === false) {
|
|
498
524
|
LOG.debug('patch draft')
|
|
499
|
-
if (req.target?.name.endsWith('DraftAdministrativeData')) req.reject(405)
|
|
525
|
+
if (req.target?.name.endsWith('DraftAdministrativeData')) req.reject({ code: 405, statusCode: 405 })
|
|
500
526
|
const draftsRef = _redirectRefToDrafts(query.UPDATE.entity.ref, this.model)
|
|
501
527
|
const res = await SELECT.one.from({ ref: draftsRef }).columns('DraftAdministrativeData_DraftUUID', {
|
|
502
528
|
ref: ['DraftAdministrativeData'],
|
|
503
529
|
expand: [{ ref: ['InProcessByUser'] }]
|
|
504
530
|
})
|
|
505
|
-
if (!res) req.reject(404)
|
|
506
|
-
if (res.DraftAdministrativeData?.InProcessByUser !== cds.context.user.id)
|
|
507
|
-
req.reject(
|
|
531
|
+
if (!res) req.reject({ code: 404, statusCode: 404 })
|
|
532
|
+
if (res.DraftAdministrativeData?.InProcessByUser !== cds.context.user.id) {
|
|
533
|
+
req.reject({
|
|
534
|
+
code: 403,
|
|
535
|
+
statusCode: 403,
|
|
536
|
+
message: 'DRAFT_LOCKED_BY_ANOTHER_USER',
|
|
537
|
+
args: [res.DraftAdministrativeData?.InProcessByUser]
|
|
538
|
+
})
|
|
539
|
+
}
|
|
508
540
|
await UPDATE('DRAFT.DraftAdministrativeData')
|
|
509
541
|
.data({
|
|
510
542
|
InProcessByUser: req.user.id,
|
|
@@ -527,12 +559,12 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
527
559
|
const entityRef = query.UPDATE.entity.ref
|
|
528
560
|
|
|
529
561
|
if (!this.model.definitions[entityRef[0].id]['@Common.DraftRoot.ActivationAction']) {
|
|
530
|
-
req.reject(403, 'DRAFT_MODIFICATION_ONLY_VIA_ROOT')
|
|
562
|
+
req.reject({ code: 403, statusCode: 403, message: 'DRAFT_MODIFICATION_ONLY_VIA_ROOT' })
|
|
531
563
|
}
|
|
532
564
|
|
|
533
565
|
const draftsRef = _redirectRefToDrafts(entityRef, this.model)
|
|
534
566
|
const hasDraft = !!(await SELECT.one([1]).from({ ref: [draftsRef[0]] }))
|
|
535
|
-
if (hasDraft) req.reject(409, 'DRAFT_ALREADY_EXISTS')
|
|
567
|
+
if (hasDraft) req.reject({ code: 409, statusCode: 409, message: 'DRAFT_ALREADY_EXISTS' })
|
|
536
568
|
|
|
537
569
|
await run(query)
|
|
538
570
|
return req.data
|
|
@@ -1018,7 +1050,13 @@ function _cleansed(query, model) {
|
|
|
1018
1050
|
const target = query._target
|
|
1019
1051
|
const q = cds.ql.clone(query)
|
|
1020
1052
|
|
|
1021
|
-
|
|
1053
|
+
if (q.SELECT?.from.SELECT) q.SELECT.from = _cleanseQuery(q.SELECT?.from, draftParams, model)
|
|
1054
|
+
const ref =
|
|
1055
|
+
q.SELECT?.from.SELECT?.from.ref ||
|
|
1056
|
+
q.SELECT?.from.ref ||
|
|
1057
|
+
q.UPDATE?.entity.ref ||
|
|
1058
|
+
q.INSERT?.into.ref ||
|
|
1059
|
+
q.DELETE?.from.ref
|
|
1022
1060
|
const cqn = q.SELECT || q.UPDATE || q.INSERT || q.DELETE
|
|
1023
1061
|
|
|
1024
1062
|
if (ref) {
|
|
@@ -1028,7 +1066,7 @@ function _cleansed(query, model) {
|
|
|
1028
1066
|
if (!entity?.drafts) return r
|
|
1029
1067
|
return r.where ? { ...r, where: _cleanseWhere(r.where, draftParams, DRAFT_ELEMENTS) } : r
|
|
1030
1068
|
})
|
|
1031
|
-
if (q.SELECT) q.SELECT.from = { ...q.SELECT.from, ref: cleansedRef }
|
|
1069
|
+
if (q.SELECT) q.SELECT.from = q.SELECT.from.SELECT ? q.SELECT.from : { ...q.SELECT.from, ref: cleansedRef }
|
|
1032
1070
|
else if (q.DELETE) q.DELETE.from = { ...q.DELETE.from, ref: cleansedRef }
|
|
1033
1071
|
else if (q.UPDATE) q.UPDATE.entity = { ...q.UPDATE.entity, ref: cleansedRef }
|
|
1034
1072
|
else if (q.INSERT) q.INSERT.into = { ...q.INSERT.into, ref: cleansedRef }
|
|
@@ -1175,11 +1213,11 @@ function expandStarStar(target, recursion = new Map()) {
|
|
|
1175
1213
|
async function onNew(req) {
|
|
1176
1214
|
LOG.debug('new draft')
|
|
1177
1215
|
if (req.target.actives['@Capabilities.InsertRestrictions.Insertable'] === false || req.target.actives['@readonly'])
|
|
1178
|
-
req.reject(405)
|
|
1216
|
+
req.reject({ code: 405, statusCode: 405 })
|
|
1179
1217
|
const isDirectAccess = typeof req.query.INSERT.into === 'string' || req.query.INSERT.into.ref?.length === 1
|
|
1180
1218
|
// Only allowed for pseudo draft roots (entities with this action)
|
|
1181
1219
|
if (isDirectAccess && !req.target.actives['@Common.DraftRoot.ActivationAction'])
|
|
1182
|
-
req.reject(403, 'DRAFT_MODIFICATION_ONLY_VIA_ROOT')
|
|
1220
|
+
req.reject({ code: 403, statusCode: 403, message: 'DRAFT_MODIFICATION_ONLY_VIA_ROOT' })
|
|
1183
1221
|
|
|
1184
1222
|
_cleanUpOldDrafts(this, req.tenant)
|
|
1185
1223
|
|
|
@@ -1192,9 +1230,14 @@ async function onNew(req) {
|
|
|
1192
1230
|
{ ref: ['DraftAdministrativeData'], expand: [{ ref: ['InProcessByUser'] }] }
|
|
1193
1231
|
])
|
|
1194
1232
|
.where(req.query.INSERT.into.ref[0].where)
|
|
1195
|
-
if (!rootData) req.reject(404)
|
|
1233
|
+
if (!rootData) req.reject({ code: 404, statusCode: 404 })
|
|
1196
1234
|
if (rootData.DraftAdministrativeData?.InProcessByUser !== req.user.id)
|
|
1197
|
-
req.reject(
|
|
1235
|
+
req.reject({
|
|
1236
|
+
code: 403,
|
|
1237
|
+
statusCode: 403,
|
|
1238
|
+
message: 'DRAFT_LOCKED_BY_ANOTHER_USER',
|
|
1239
|
+
args: [rootData.DraftAdministrativeData.InProcessByUser]
|
|
1240
|
+
})
|
|
1198
1241
|
DraftUUID = rootData.DraftAdministrativeData_DraftUUID
|
|
1199
1242
|
}
|
|
1200
1243
|
const timestamp = cds.context.timestamp.toISOString() // REVISIT: toISOString should be done on db layer
|
|
@@ -1251,7 +1294,11 @@ async function onEdit(req) {
|
|
|
1251
1294
|
// use symbol for _draftParams
|
|
1252
1295
|
const draftParams = req.query[DRAFT_PARAMS]
|
|
1253
1296
|
if (req.query.SELECT.from.ref.length > 1 || draftParams.IsActiveEntity !== true) {
|
|
1254
|
-
req.reject(
|
|
1297
|
+
req.reject({
|
|
1298
|
+
code: 400,
|
|
1299
|
+
statusCode: 400,
|
|
1300
|
+
message: 'Action "draftEdit" can only be called on the root active entity'
|
|
1301
|
+
})
|
|
1255
1302
|
}
|
|
1256
1303
|
|
|
1257
1304
|
if (
|
|
@@ -1259,10 +1306,10 @@ async function onEdit(req) {
|
|
|
1259
1306
|
req.target['@insertonly'] ||
|
|
1260
1307
|
req.target['@readonly']
|
|
1261
1308
|
) {
|
|
1262
|
-
req.reject(405)
|
|
1309
|
+
req.reject({ code: 405, statusCode: 405 })
|
|
1263
1310
|
}
|
|
1264
1311
|
|
|
1265
|
-
if (draftParams.IsActiveEntity !== true) req.reject(400)
|
|
1312
|
+
if (draftParams.IsActiveEntity !== true) req.reject({ code: 400, statusCode: 400 })
|
|
1266
1313
|
|
|
1267
1314
|
_cleanUpOldDrafts(this, req.tenant)
|
|
1268
1315
|
|
|
@@ -1337,8 +1384,8 @@ async function onEdit(req) {
|
|
|
1337
1384
|
await this.run(activeLockCQN)
|
|
1338
1385
|
} catch (e) {
|
|
1339
1386
|
const draft = await this.run(existingDraft)
|
|
1340
|
-
if (draft) req.reject(409, 'DRAFT_ALREADY_EXISTS')
|
|
1341
|
-
req.reject(409, 'ENTITY_LOCKED')
|
|
1387
|
+
if (draft) req.reject({ code: 409, statusCode: 409, message: 'DRAFT_ALREADY_EXISTS' })
|
|
1388
|
+
req.reject({ code: 409, statusCode: 409, message: 'ENTITY_LOCKED' })
|
|
1342
1389
|
}
|
|
1343
1390
|
|
|
1344
1391
|
const cqns = [
|
|
@@ -1366,12 +1413,12 @@ async function onEdit(req) {
|
|
|
1366
1413
|
])
|
|
1367
1414
|
}
|
|
1368
1415
|
|
|
1369
|
-
if (!res) req.reject(404)
|
|
1416
|
+
if (!res) req.reject({ code: 404, statusCode: 404 })
|
|
1370
1417
|
const preserveChanges = req.data?.PreserveChanges
|
|
1371
1418
|
const inProcessByUser = draft?.DraftAdministrativeData?.InProcessByUser
|
|
1372
1419
|
|
|
1373
1420
|
if (draft) {
|
|
1374
|
-
if (inProcessByUser || preserveChanges) req.reject(409, 'DRAFT_ALREADY_EXISTS')
|
|
1421
|
+
if (inProcessByUser || preserveChanges) req.reject({ code: 409, statusCode: 409, message: 'DRAFT_ALREADY_EXISTS' })
|
|
1375
1422
|
const keys = {}
|
|
1376
1423
|
for (const key in req.target.drafts.keys) keys[key] = res[key]
|
|
1377
1424
|
await _promiseAll([
|
|
@@ -1405,11 +1452,20 @@ async function onEdit(req) {
|
|
|
1405
1452
|
// REVISIT: needs reworking for new adapter, especially re $batch
|
|
1406
1453
|
if (req._?.odataRes) {
|
|
1407
1454
|
req._?.odataRes?.setStatusCode(201, { overwrite: true })
|
|
1408
|
-
} else if (req.
|
|
1409
|
-
req.
|
|
1455
|
+
} else if (req.res) {
|
|
1456
|
+
req.res.status(201)
|
|
1410
1457
|
}
|
|
1411
1458
|
|
|
1412
|
-
|
|
1459
|
+
if (cds.env.features.odata_new_adapter) {
|
|
1460
|
+
const read_result = await _readAfterDraftAction.bind(this)({ req, payload: res, action: 'draftEdit' })
|
|
1461
|
+
req.res.set(
|
|
1462
|
+
'location',
|
|
1463
|
+
'../' + calculateLocationHeader(req.target, this, read_result || { ...res, IsActiveEntity: false })
|
|
1464
|
+
)
|
|
1465
|
+
return read_result
|
|
1466
|
+
} else {
|
|
1467
|
+
return { ...res, IsActiveEntity: false } // REVISIT: Flatten?
|
|
1468
|
+
}
|
|
1413
1469
|
}
|
|
1414
1470
|
|
|
1415
1471
|
async function onCancel(req) {
|
|
@@ -1430,7 +1486,7 @@ async function onCancel(req) {
|
|
|
1430
1486
|
if (draft) {
|
|
1431
1487
|
const processByUser = draft.DraftAdministrativeData?.InProcessByUser
|
|
1432
1488
|
if (processByUser && processByUser !== cds.context.user.id)
|
|
1433
|
-
req.reject(403, 'DRAFT_LOCKED_BY_ANOTHER_USER', [processByUser])
|
|
1489
|
+
req.reject({ code: 403, statusCode: 403, message: 'DRAFT_LOCKED_BY_ANOTHER_USER', args: [processByUser] })
|
|
1434
1490
|
}
|
|
1435
1491
|
const queries = !draft ? [] : [this.run(DELETE.from({ ref: req.query.DELETE.from.ref }), req.data)]
|
|
1436
1492
|
if (draft && req.target['@Common.DraftRoot.ActivationAction'])
|
|
@@ -1457,23 +1513,72 @@ async function onPrepare(req) {
|
|
|
1457
1513
|
LOG.debug('prepare draft')
|
|
1458
1514
|
const draftParams = req.query[DRAFT_PARAMS]
|
|
1459
1515
|
if (req.query.SELECT.from.ref.length > 1 || draftParams.IsActiveEntity !== false) {
|
|
1460
|
-
req.reject(
|
|
1516
|
+
req.reject({
|
|
1517
|
+
code: 400,
|
|
1518
|
+
statusCode: 400,
|
|
1519
|
+
message: 'Action "draftPrepare" can only be called on the root draft entity'
|
|
1520
|
+
})
|
|
1461
1521
|
}
|
|
1462
1522
|
const where = req.query.SELECT.from.ref[0].where
|
|
1463
1523
|
|
|
1464
1524
|
const draftQuery = SELECT.one
|
|
1465
1525
|
.from(req.target, d => {
|
|
1466
|
-
d.DraftAdministrativeData(a => a.InProcessByUser)
|
|
1526
|
+
d`.*`, d.DraftAdministrativeData(a => a.InProcessByUser)
|
|
1467
1527
|
})
|
|
1468
|
-
.columns(entity_keys(req.target))
|
|
1469
1528
|
.where(where)
|
|
1470
1529
|
draftQuery[DRAFT_PARAMS] = draftParams
|
|
1471
1530
|
const data = await draftQuery
|
|
1472
|
-
if (!data) req.reject(404)
|
|
1531
|
+
if (!data) req.reject({ code: 404, statusCode: 404 })
|
|
1473
1532
|
if (data.DraftAdministrativeData?.InProcessByUser !== req.user.id)
|
|
1474
|
-
req.reject(
|
|
1533
|
+
req.reject({
|
|
1534
|
+
code: 403,
|
|
1535
|
+
statusCode: 403,
|
|
1536
|
+
message: 'DRAFT_LOCKED_BY_ANOTHER_USER',
|
|
1537
|
+
args: [data.DraftAdministrativeData?.InProcessByUser]
|
|
1538
|
+
})
|
|
1475
1539
|
delete data.DraftAdministrativeData
|
|
1476
|
-
|
|
1540
|
+
// result must not include DraftAdministrativeData_DraftUUID for plain v4 usage, however required for odata-v2
|
|
1541
|
+
if (data && req.headers?.['x-cds-odata-version'] !== 'v2') {
|
|
1542
|
+
delete data.DraftAdministrativeData_DraftUUID
|
|
1543
|
+
}
|
|
1544
|
+
return { ...data, IsActiveEntity: false, HasDraftEntity: false, HasActiveEntity: data.HasActiveEntity || false }
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
const _readAfterDraftAction = async function ({ req, payload, action }) {
|
|
1548
|
+
const entity = action === 'draftActivate' ? req.target : req.target.drafts
|
|
1549
|
+
|
|
1550
|
+
// read after write with query options
|
|
1551
|
+
const keys = {}
|
|
1552
|
+
entity.keys.forEach(key => {
|
|
1553
|
+
if (key.name === 'IsActiveEntity' || key.isAssociation || key.virtual) return
|
|
1554
|
+
keys[key.name] = payload[key.name]
|
|
1555
|
+
})
|
|
1556
|
+
const read = SELECT.one.from(entity, keys)
|
|
1557
|
+
if (req.req.query.$select || req.req.query.$expand) {
|
|
1558
|
+
const queryOptions = []
|
|
1559
|
+
if (req.req.query.$select) queryOptions.push(`$select=${req.req.query.$select}`)
|
|
1560
|
+
if (req.req.query.$expand) queryOptions.push(`$expand=${req.req.query.$expand}`)
|
|
1561
|
+
read.columns(cds.odata.parse(`/X?${queryOptions.join('&')}`).SELECT.columns)
|
|
1562
|
+
// ensure keys are always selected
|
|
1563
|
+
Object.keys(keys).forEach(key => {
|
|
1564
|
+
if (!read.SELECT.columns.some(c => c.ref?.[0] === key)) read.SELECT.columns.push({ ref: [key] })
|
|
1565
|
+
})
|
|
1566
|
+
// also ensure selection of etag columns
|
|
1567
|
+
addEtagColumns(read.SELECT.columns, entity)
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
try {
|
|
1571
|
+
const read_result = await this.run(read)
|
|
1572
|
+
// result must not include DraftAdministrativeData_DraftUUID for plain v4 usage, however required for odata-v2
|
|
1573
|
+
if (read_result && req.headers?.['x-cds-odata-version'] !== 'v2') {
|
|
1574
|
+
delete read_result.DraftAdministrativeData_DraftUUID
|
|
1575
|
+
}
|
|
1576
|
+
return read_result
|
|
1577
|
+
} catch (err) {
|
|
1578
|
+
if (!(Number(err.code) in { 401: 1, 403: 1, 404: 1, 405: 1 })) throw err
|
|
1579
|
+
// it's important to return null if one of the above accepted errors occurs
|
|
1580
|
+
return null
|
|
1581
|
+
}
|
|
1477
1582
|
}
|
|
1478
1583
|
|
|
1479
1584
|
module.exports = {
|