@sap/cds 5.7.5 → 5.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +97 -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/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/log/format/kibana.js +3 -3
- 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/ODataRequest.js +1 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +11 -32
- 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/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/ResourcePathParser.js +23 -2
- 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 +2 -5
- 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 +1 -4
- 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 +60 -18
- 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/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/tree.js +1 -1
- 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 +29 -6
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +21 -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 +10 -4
- package/libx/_runtime/common/utils/vcap.js +27 -10
- package/libx/_runtime/db/data-conversion/post-processing.js +20 -13
- package/libx/_runtime/db/expand/expand-v2.js +21 -12
- package/libx/_runtime/db/expand/expandCQNToJoin.js +67 -26
- 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 +16 -14
- 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 +20 -19
- 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/dynatrace.js +11 -5
- package/libx/_runtime/hana/execute.js +132 -19
- 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 +1 -1
- 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 +4 -1
- package/libx/_runtime/remote/utils/client.js +41 -24
- package/libx/_runtime/remote/utils/data.js +54 -12
- package/libx/_runtime/sqlite/Service.js +1 -1
- package/libx/_runtime/sqlite/conversion.js +10 -0
- 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 +49 -21
- 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
|
@@ -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
|
},
|
|
@@ -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(
|
|
@@ -120,26 +120,26 @@ const _subWhere = (result, element) => {
|
|
|
120
120
|
if (links && links.length > 0) {
|
|
121
121
|
where = []
|
|
122
122
|
for (const row of result) {
|
|
123
|
-
if (where.length > 0) {
|
|
124
|
-
where.push('or')
|
|
125
|
-
}
|
|
126
123
|
const whereObj = links.reduce((res, currentLink) => {
|
|
127
|
-
if (Object.prototype.hasOwnProperty.call(row, currentLink.targetKey))
|
|
124
|
+
if (Object.prototype.hasOwnProperty.call(row, currentLink.targetKey) && row[currentLink.targetKey] !== null)
|
|
128
125
|
res[currentLink.entityKey] = row[currentLink.targetKey]
|
|
129
126
|
return res
|
|
130
127
|
}, {})
|
|
131
128
|
const whereCQN = ctUtils.whereKey(whereObj)
|
|
132
|
-
if (whereCQN.length)
|
|
129
|
+
if (whereCQN.length) {
|
|
130
|
+
if (where.length > 0) where.push('or')
|
|
131
|
+
where.push('(', ...whereCQN, ')')
|
|
132
|
+
}
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
return where
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
const _mergeResults = (result, selectData, root,
|
|
138
|
+
const _mergeResults = (result, selectData, root, model, compositionTree, entityName) => {
|
|
139
139
|
if (root) {
|
|
140
140
|
return [...selectData, ...result]
|
|
141
141
|
} else {
|
|
142
|
-
const parent = definitions[compositionTree.target] || definitions[entityName]
|
|
142
|
+
const parent = model.definitions[compositionTree.target] || model.definitions[entityName]
|
|
143
143
|
const assoc = (parent && parent.elements[compositionTree.name]) || {}
|
|
144
144
|
return selectData.map(selectEntry => {
|
|
145
145
|
if (assoc.is2one) {
|
|
@@ -148,8 +148,9 @@ const _mergeResults = (result, selectData, root, definitions, compositionTree, e
|
|
|
148
148
|
selectEntry[compositionTree.name] = selectEntry[compositionTree.name] || []
|
|
149
149
|
}
|
|
150
150
|
const newData = _findWhere(result, _parentKey(compositionTree, selectEntry))
|
|
151
|
-
if (assoc.is2one
|
|
152
|
-
selectEntry[compositionTree.name] = Object.assign(selectEntry[compositionTree.name], newData[0])
|
|
151
|
+
if (assoc.is2one) {
|
|
152
|
+
if (newData[0]) selectEntry[compositionTree.name] = Object.assign(selectEntry[compositionTree.name], newData[0])
|
|
153
|
+
else selectEntry[compositionTree.name] = null
|
|
153
154
|
} else if (assoc.is2many) {
|
|
154
155
|
selectEntry[compositionTree.name].push(...newData)
|
|
155
156
|
}
|
|
@@ -158,14 +159,14 @@ const _mergeResults = (result, selectData, root, definitions, compositionTree, e
|
|
|
158
159
|
}
|
|
159
160
|
}
|
|
160
161
|
|
|
161
|
-
const _columns = (entity, data, compositionTree,
|
|
162
|
+
const _columns = (entity, data, compositionTree, selectAllColumns) => {
|
|
162
163
|
const backLinkKeys = _getLinksOfCompTree(compositionTree)
|
|
163
164
|
const columns = []
|
|
164
165
|
for (const elementName in entity.elements) {
|
|
165
166
|
const element = entity.elements[elementName]
|
|
166
167
|
if (element.virtual || element.isAssociation) continue
|
|
167
168
|
if (
|
|
168
|
-
|
|
169
|
+
selectAllColumns ||
|
|
169
170
|
element.key ||
|
|
170
171
|
backLinkKeys.includes(element.name) ||
|
|
171
172
|
(Array.isArray(data) && data.find(entry => element.name in entry))
|
|
@@ -177,45 +178,42 @@ const _columns = (entity, data, compositionTree, selectAll) => {
|
|
|
177
178
|
}
|
|
178
179
|
|
|
179
180
|
const _select = ({
|
|
180
|
-
|
|
181
|
+
model,
|
|
181
182
|
entityName,
|
|
182
183
|
draft,
|
|
183
184
|
alias,
|
|
184
185
|
compositionTree,
|
|
185
186
|
data,
|
|
186
|
-
|
|
187
|
-
includeAllRootColumns,
|
|
188
|
-
includeAllColumns,
|
|
187
|
+
selectAllColumns,
|
|
189
188
|
where,
|
|
190
189
|
parentKeys,
|
|
191
190
|
orderBy,
|
|
192
191
|
singleton
|
|
193
192
|
}) => {
|
|
194
|
-
const entity = definitions
|
|
193
|
+
const entity = model.definitions[entityName]
|
|
195
194
|
const from = ctUtils.addDraftSuffix(draft, entity.name)
|
|
196
195
|
const selectCQN = SELECT.from(from)
|
|
197
196
|
if (alias) selectCQN.SELECT.from.as = alias
|
|
198
|
-
|
|
199
|
-
selectCQN.SELECT.columns = _columns(entity, data, compositionTree, selectAll)
|
|
197
|
+
selectCQN.SELECT.columns = _columns(entity, data, compositionTree, selectAllColumns)
|
|
200
198
|
if (where) selectCQN.SELECT.where = where
|
|
201
199
|
else if (parentKeys) selectCQN.SELECT.where = _whereKeys(parentKeys)
|
|
202
200
|
if (orderBy) selectCQN.SELECT.orderBy = orderBy
|
|
203
201
|
if (singleton) selectCQN.SELECT.limit = { rows: { val: 1 } }
|
|
204
202
|
// REVISIT: remove once SELECT builder does flattening!
|
|
205
|
-
return cqn2cqn4sql(selectCQN,
|
|
203
|
+
return cqn2cqn4sql(selectCQN, model)
|
|
206
204
|
}
|
|
207
205
|
|
|
208
206
|
const _selectDeepUpdateData = async args => {
|
|
209
|
-
const {
|
|
207
|
+
const { model, compositionTree, entityName, data, root, selectData, tx, selectAllColumns } = args
|
|
210
208
|
const selectCQN = _select(args)
|
|
211
209
|
const result = await tx.run(selectCQN)
|
|
212
210
|
if (!result.length) return Promise.resolve(result)
|
|
213
211
|
|
|
214
|
-
const keys = _keys(definitions[entityName], result)
|
|
212
|
+
const keys = _keys(model.definitions[entityName], result)
|
|
215
213
|
await Promise.all(
|
|
216
214
|
compositionTree.compositionElements.map(element => {
|
|
217
215
|
if (element.skipPersistence) return Promise.resolve()
|
|
218
|
-
if (data !== undefined && !data.find(entry => element.name in entry) && !(
|
|
216
|
+
if (data !== undefined && !data.find(entry => element.name in entry) && !(selectAllColumns && result.length))
|
|
219
217
|
return Promise.resolve()
|
|
220
218
|
const subs = {
|
|
221
219
|
compositionTree: element,
|
|
@@ -235,23 +233,17 @@ const _selectDeepUpdateData = async args => {
|
|
|
235
233
|
})
|
|
236
234
|
)
|
|
237
235
|
|
|
238
|
-
return _mergeResults(result, selectData || [], root,
|
|
236
|
+
return _mergeResults(result, selectData || [], root, model, compositionTree, entityName)
|
|
239
237
|
}
|
|
240
238
|
|
|
241
239
|
/*
|
|
242
240
|
* exports
|
|
243
241
|
*/
|
|
244
242
|
|
|
245
|
-
const selectDeepUpdateData = (
|
|
246
|
-
|
|
247
|
-
cqn,
|
|
248
|
-
req,
|
|
249
|
-
includeAllRootColumns = false,
|
|
250
|
-
includeAllColumns = false,
|
|
251
|
-
service
|
|
252
|
-
) => {
|
|
243
|
+
const selectDeepUpdateData = (service, model, req, selectAllColumns = false) => {
|
|
244
|
+
const query = req.query
|
|
253
245
|
// REVISIT this should be done somewhere before, so it is not done twice for deep updates
|
|
254
|
-
const sqlQuery = cqn2cqn4sql(
|
|
246
|
+
const sqlQuery = cqn2cqn4sql(query, model)
|
|
255
247
|
|
|
256
248
|
if (req && _isSameEntity(sqlQuery, req)) {
|
|
257
249
|
return Promise.resolve(req._.partialPersistentState)
|
|
@@ -263,9 +255,9 @@ const selectDeepUpdateData = (
|
|
|
263
255
|
const entityName = ensureNoDraftsSuffix(from)
|
|
264
256
|
const draft = entityName !== from
|
|
265
257
|
const orderBy = req && req.target && req.target.query && req.target.query.SELECT && req.target.query.SELECT.orderBy
|
|
266
|
-
const data = Object.assign({}, sqlQuery.UPDATE.data || {},
|
|
258
|
+
const data = Object.assign({}, sqlQuery.UPDATE.data || {}, query.UPDATE.with || {})
|
|
267
259
|
const compositionTree = getCompositionTree({
|
|
268
|
-
definitions,
|
|
260
|
+
definitions: model.definitions,
|
|
269
261
|
rootEntityName: entityName, // REVISIT: drafts are resolved too eagerly
|
|
270
262
|
checkRoot: false,
|
|
271
263
|
resolveViews: !draft,
|
|
@@ -274,17 +266,16 @@ const selectDeepUpdateData = (
|
|
|
274
266
|
|
|
275
267
|
return _selectDeepUpdateData({
|
|
276
268
|
tx: cds.tx(req),
|
|
277
|
-
|
|
269
|
+
model,
|
|
278
270
|
compositionTree,
|
|
279
271
|
entityName,
|
|
280
272
|
data: [data],
|
|
281
273
|
where,
|
|
282
274
|
orderBy,
|
|
283
275
|
draft,
|
|
284
|
-
includeAllRootColumns,
|
|
285
276
|
singleton: req && req.target && req.target._isSingleton,
|
|
286
277
|
alias,
|
|
287
|
-
|
|
278
|
+
selectAllColumns,
|
|
288
279
|
root: true,
|
|
289
280
|
service
|
|
290
281
|
})
|