@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 ctUtils = require('./utils')
|
|
|
5
5
|
|
|
6
6
|
const { ensureNoDraftsSuffix } = require('../utils/draft')
|
|
7
7
|
const { getEntityNameFromDeleteCQN } = require('../utils/cqn')
|
|
8
|
+
const { cqn2cqn4sql } = require('../utils/cqn2cqn4sql')
|
|
9
|
+
const { getComp2oneParents } = require('../utils/csn')
|
|
10
|
+
const getColumns = require('../../db/utils/columns')
|
|
8
11
|
|
|
9
12
|
/*
|
|
10
13
|
* own utils
|
|
@@ -13,19 +16,21 @@ const { getEntityNameFromDeleteCQN } = require('../utils/cqn')
|
|
|
13
16
|
// Poor man's alias algorithm
|
|
14
17
|
// REVISIT: Extract and adapt the alias functionality from `expandCQNToJoin.js`: _adaptWhereOrderBy
|
|
15
18
|
const _recursivelyAliasRefs = (something, newAlias, oldAlias, subselect = false) => {
|
|
16
|
-
if (Array.isArray(something))
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
if (Array.isArray(something)) return something.map(s => _recursivelyAliasRefs(s, newAlias, oldAlias, subselect))
|
|
20
|
+
if (typeof something === 'object') {
|
|
21
|
+
if (something.ref) {
|
|
22
|
+
something = { ref: [...something.ref] }
|
|
20
23
|
if (oldAlias && something.ref[0] === oldAlias) something.ref[0] = newAlias
|
|
21
24
|
else if (!subselect) something.ref.unshift(newAlias)
|
|
22
25
|
} else {
|
|
26
|
+
something = Object.assign({}, something)
|
|
23
27
|
for (const key in something) {
|
|
24
|
-
if (key === 'from') continue // Workaround: Deep delete to be rewritten
|
|
25
|
-
_recursivelyAliasRefs(something[key], newAlias, oldAlias, subselect || key === 'SELECT')
|
|
28
|
+
if (key === 'from' || key === 'val') continue // Workaround: Deep delete to be rewritten
|
|
29
|
+
something[key] = _recursivelyAliasRefs(something[key], newAlias, oldAlias, subselect || key === 'SELECT')
|
|
26
30
|
}
|
|
27
31
|
}
|
|
28
32
|
}
|
|
33
|
+
return something
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
function _getSubWhereAndEntities(element, parentWhere, draft, level = 0, compositionTree = {}) {
|
|
@@ -88,7 +93,7 @@ function _getSubWhereAndEntities(element, parentWhere, draft, level = 0, composi
|
|
|
88
93
|
SELECT: {
|
|
89
94
|
columns: [{ val: 1, as: '_exists' }],
|
|
90
95
|
from: { ref: [entity2.entityName], as: entity2.alias },
|
|
91
|
-
where: parentWhere ? ['(', ...parentWhere, ')', 'and', '(', ...subWhere, ')'] : subWhere
|
|
96
|
+
where: parentWhere && parentWhere.length ? ['(', ...parentWhere, ')', 'and', '(', ...subWhere, ')'] : subWhere
|
|
92
97
|
}
|
|
93
98
|
})
|
|
94
99
|
|
|
@@ -130,18 +135,18 @@ function _getStaticWhere(allBackLinks, entity1) {
|
|
|
130
135
|
}, [])
|
|
131
136
|
}
|
|
132
137
|
|
|
133
|
-
const _is2oneComposition = (element,
|
|
134
|
-
const csnElement = element.target && definitions[element.target].elements[element.name]
|
|
138
|
+
const _is2oneComposition = (element, model) => {
|
|
139
|
+
const csnElement = element.target && model.definitions[element.target].elements[element.name]
|
|
135
140
|
return csnElement && csnElement.is2one && csnElement._isCompositionEffective
|
|
136
141
|
}
|
|
137
142
|
|
|
138
|
-
const _addToCQNs = (cqns, subCQN, element,
|
|
143
|
+
const _addToCQNs = (cqns, subCQN, element, model, level) => {
|
|
139
144
|
cqns[level] = cqns[level] || []
|
|
140
145
|
// Since `>2.5.2` compiler generates constraints for compositions of one like for annotations
|
|
141
146
|
// Thus only single 2one case (`$self`-managed composition) has DELETE CASCADE
|
|
142
147
|
// Here it's ignored to simplify i.e. handle all "2ones" in a same manner
|
|
143
148
|
// REVISIT: why is _db_foreign_key_constraints necessary?
|
|
144
|
-
if (!cds.env.features._db_foreign_key_constraints || _is2oneComposition(element,
|
|
149
|
+
if (!cds.env.features._db_foreign_key_constraints || _is2oneComposition(element, model)) {
|
|
145
150
|
cqns[level].push(subCQN)
|
|
146
151
|
}
|
|
147
152
|
}
|
|
@@ -150,15 +155,7 @@ const _addToCQNs = (cqns, subCQN, element, definitions, level) => {
|
|
|
150
155
|
const DEEP_DELETE_MAX_RECURSION_DEPTH =
|
|
151
156
|
(cds.env.features.recursion_depth && Number(cds.env.features.recursion_depth)) || 2
|
|
152
157
|
|
|
153
|
-
const _addSubCascadeDeleteCQN = (
|
|
154
|
-
definitions,
|
|
155
|
-
compositionTree,
|
|
156
|
-
parentWhere,
|
|
157
|
-
level,
|
|
158
|
-
cqns,
|
|
159
|
-
draft,
|
|
160
|
-
elementMap = new Map()
|
|
161
|
-
) => {
|
|
158
|
+
const _addSubCascadeDeleteCQN = (model, compositionTree, parentWhere, level, cqns, draft, elementMap = new Map()) => {
|
|
162
159
|
for (const element of compositionTree.compositionElements) {
|
|
163
160
|
if (element.skipPersistence) continue
|
|
164
161
|
|
|
@@ -176,13 +173,13 @@ const _addSubCascadeDeleteCQN = (
|
|
|
176
173
|
if (where.length) {
|
|
177
174
|
const subCQN = { DELETE: { from: { ref: [entity1.entityName], as: entity1.alias }, where: where } }
|
|
178
175
|
|
|
179
|
-
_addToCQNs(cqns, subCQN, element,
|
|
176
|
+
_addToCQNs(cqns, subCQN, element, model, level)
|
|
180
177
|
|
|
181
178
|
// Make a copy and do not share the same map among brother compositions
|
|
182
179
|
// as we're only interested in deep recursions, not wide recursions.
|
|
183
180
|
const newElementMap = new Map(elementMap)
|
|
184
181
|
newElementMap.set(fqn, (seen && seen + 1) || 1)
|
|
185
|
-
_addSubCascadeDeleteCQN(
|
|
182
|
+
_addSubCascadeDeleteCQN(model, element, subCQN.DELETE.where, level + 1, cqns, draft, newElementMap)
|
|
186
183
|
}
|
|
187
184
|
}
|
|
188
185
|
|
|
@@ -193,7 +190,7 @@ const _addSubCascadeDeleteCQN = (
|
|
|
193
190
|
* exports
|
|
194
191
|
*/
|
|
195
192
|
|
|
196
|
-
const hasDeepDelete = (
|
|
193
|
+
const hasDeepDelete = (model, cqn) => {
|
|
197
194
|
const from = getEntityNameFromDeleteCQN(cqn)
|
|
198
195
|
if (!from) return false
|
|
199
196
|
|
|
@@ -201,66 +198,118 @@ const hasDeepDelete = (definitions, cqn) => {
|
|
|
201
198
|
// Hence, we do not need a deep delete in that case.
|
|
202
199
|
if (cqn._suppressDeepDelete) return false
|
|
203
200
|
|
|
204
|
-
const entity = definitions
|
|
201
|
+
const entity = model.definitions[ensureNoDraftsSuffix(from)]
|
|
205
202
|
|
|
206
203
|
if (entity) return !!Object.keys(entity.elements || {}).find(k => entity.elements[k]._isCompositionEffective)
|
|
207
204
|
|
|
208
205
|
return false
|
|
209
206
|
}
|
|
210
207
|
|
|
211
|
-
const
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
208
|
+
const resolveNavigationTarget = (cqn, ref, model) => {
|
|
209
|
+
const lastTransition = cqn.UPDATE._transitions && cqn.UPDATE._transitions[cqn.UPDATE._transitions.length - 1]
|
|
210
|
+
const target = lastTransition
|
|
211
|
+
? lastTransition.target
|
|
212
|
+
: model.definitions[(cqn.UPDATE.entity.ref && cqn.UPDATE.entity.ref[0]) || cqn.UPDATE.entity]
|
|
213
|
+
let elementName = ref[ref.length - 1].id || ref[ref.length - 1]
|
|
214
|
+
const resolved = lastTransition && lastTransition.mapping && lastTransition.mapping.get(elementName)
|
|
215
|
+
if (resolved) elementName = resolved.ref[0]
|
|
216
|
+
return { target, elementName }
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// eslint-disable-next-line complexity
|
|
220
|
+
const getSetNullParentForeignKeyCQNs = async (model, req, dbQuery) => {
|
|
221
|
+
const cqns = []
|
|
222
|
+
const query = dbQuery || req.query
|
|
223
|
+
const origQuery = (req._tx instanceof cds.DatabaseService && req._ && req._.query) || req.query
|
|
224
|
+
if (!dbQuery && origQuery && origQuery.DELETE && origQuery.DELETE.from.ref && origQuery.DELETE.from.ref.length > 1) {
|
|
225
|
+
// delete via 2one navigation => parent is known => no need to SELECT
|
|
226
|
+
const ref = origQuery.DELETE.from.ref
|
|
227
|
+
const cqn = cqn2cqn4sql(UPDATE.entity({ ref: ref.slice(0, ref.length - 1) }), model)
|
|
228
|
+
const { target: parent, elementName } = resolveNavigationTarget(cqn, ref, model)
|
|
229
|
+
const element = parent.elements[elementName]
|
|
230
|
+
if (element && element._isCompositionEffective && element.is2one) {
|
|
231
|
+
const onCond = element && parent._relations[element.name].join('$$whatever', '$$parent')
|
|
232
|
+
const data = onCond.reduce((res, e) => {
|
|
233
|
+
if (!e.ref || e.ref[0] !== '$$parent') return res
|
|
234
|
+
const fk = e.ref.slice(1).join('_')
|
|
235
|
+
if (!parent.keys[fk]) res[fk] = null
|
|
236
|
+
return res
|
|
218
237
|
}, {})
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
238
|
+
cqn.data(data)
|
|
239
|
+
cqn.__4delete = true
|
|
240
|
+
if (Object.keys(data).length) cqns.push(cqn)
|
|
241
|
+
}
|
|
242
|
+
} else if (query.DELETE.where && query.DELETE.where.length) {
|
|
243
|
+
// direct or internal request => parent is uknown => need to SELECT
|
|
244
|
+
const entityName = getEntityNameFromDeleteCQN(query)
|
|
245
|
+
const target = model.definitions[entityName]
|
|
246
|
+
const comp2oneParents = getComp2oneParents(target, model)
|
|
247
|
+
for (const element of comp2oneParents) {
|
|
248
|
+
const parent = element.parent
|
|
249
|
+
const onCond = parent._relations[element.name].join('$$child', '$$parent')
|
|
250
|
+
const data = onCond.reduce((res, e) => {
|
|
251
|
+
if (!e.ref || e.ref[0] !== '$$parent') return res
|
|
252
|
+
const fk = e.ref.slice(1).join('_')
|
|
253
|
+
if (!parent.keys[fk]) res[fk] = null
|
|
254
|
+
return res
|
|
255
|
+
}, {})
|
|
256
|
+
const columns = getColumns(parent, { _4db: true, onlyKeys: true }).map(c => ({ ref: ['$$parent', c.name] }))
|
|
257
|
+
const as = query.DELETE.from.as || (query.DELETE.from.ref && query.DELETE.from.ref[0]) || query.DELETE.from
|
|
258
|
+
const selectCQN = SELECT.from(`${parent.name} as $$parent`)
|
|
259
|
+
.join(`${element.target} as $$child`)
|
|
260
|
+
.on(onCond)
|
|
261
|
+
.columns(columns)
|
|
262
|
+
.where(_recursivelyAliasRefs(query.DELETE.where, '$$child', as))
|
|
263
|
+
const results = await cds.tx(req).run(selectCQN)
|
|
264
|
+
if (results.length && Object.keys(data).length) {
|
|
265
|
+
const cqn = UPDATE.entity(parent).data(data)
|
|
266
|
+
for (const result of results) {
|
|
267
|
+
const where = []
|
|
268
|
+
for (const col of columns) {
|
|
269
|
+
if (where.length) where.push('and')
|
|
270
|
+
const ref = col.ref.slice(1)
|
|
271
|
+
where.push({ ref }, '=', { val: result[ref[0]] })
|
|
227
272
|
}
|
|
228
|
-
|
|
273
|
+
cqn.UPDATE.where = where
|
|
274
|
+
cqn.__4delete = true
|
|
275
|
+
cqns.push(cqn)
|
|
276
|
+
}
|
|
229
277
|
}
|
|
230
278
|
}
|
|
231
279
|
}
|
|
232
|
-
return
|
|
280
|
+
return cqns
|
|
233
281
|
}
|
|
234
282
|
|
|
235
|
-
const getDeepDeleteCQNs = (
|
|
236
|
-
const
|
|
237
|
-
|
|
283
|
+
const getDeepDeleteCQNs = async (model, req, dbQuery) => {
|
|
284
|
+
const query = dbQuery || req.query
|
|
285
|
+
const from = getEntityNameFromDeleteCQN(query)
|
|
286
|
+
if (!from) return [[query]]
|
|
238
287
|
|
|
239
288
|
const entityName = ensureNoDraftsSuffix(from)
|
|
240
289
|
// REVISIT: baaad check!
|
|
241
290
|
const draft = entityName !== from
|
|
242
291
|
const compositionTree = getCompositionTree({
|
|
243
|
-
definitions,
|
|
292
|
+
definitions: model.definitions,
|
|
244
293
|
rootEntityName: entityName,
|
|
245
294
|
checkRoot: false,
|
|
246
295
|
resolveViews: !draft,
|
|
247
296
|
service: cds.db
|
|
248
297
|
})
|
|
249
|
-
const parentWhere =
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const setNullUpdates =
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
return [[cqn], ...subCascadeDeletes, ...setNullUpdates].reverse()
|
|
298
|
+
const parentWhere = _recursivelyAliasRefs(
|
|
299
|
+
query.DELETE.where,
|
|
300
|
+
'ALIAS0',
|
|
301
|
+
query.DELETE.from.as || (query.DELETE.from.ref && query.DELETE.from.ref[0]) || query.DELETE.from
|
|
302
|
+
)
|
|
303
|
+
const setNullUpdates =
|
|
304
|
+
(model.definitions[entityName].own('__oneCompositionParents') &&
|
|
305
|
+
(await getSetNullParentForeignKeyCQNs(model, req, dbQuery))) ||
|
|
306
|
+
[]
|
|
307
|
+
const subCascadeDeletes = _addSubCascadeDeleteCQN(model, compositionTree, parentWhere, 0, [], draft)
|
|
308
|
+
return [[query], ...subCascadeDeletes, setNullUpdates].reverse()
|
|
261
309
|
}
|
|
262
310
|
|
|
263
311
|
module.exports = {
|
|
264
312
|
hasDeepDelete,
|
|
265
|
-
getDeepDeleteCQNs
|
|
313
|
+
getDeepDeleteCQNs,
|
|
314
|
+
getSetNullParentForeignKeyCQNs
|
|
266
315
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const { getCompositionTree, getCompositionRoot } = require('./tree')
|
|
2
|
-
const { hasDeepInsert, getDeepInsertCQNs
|
|
2
|
+
const { hasDeepInsert, getDeepInsertCQNs } = require('./insert')
|
|
3
3
|
const { hasDeepUpdate, getDeepUpdateCQNs } = require('./update')
|
|
4
|
-
const { hasDeepDelete, getDeepDeleteCQNs } = require('./delete')
|
|
4
|
+
const { hasDeepDelete, getDeepDeleteCQNs, getSetNullParentForeignKeyCQNs } = require('./delete')
|
|
5
5
|
const { selectDeepUpdateData } = require('./data')
|
|
6
6
|
|
|
7
7
|
module.exports = {
|
|
@@ -11,13 +11,13 @@ module.exports = {
|
|
|
11
11
|
// insert
|
|
12
12
|
hasDeepInsert,
|
|
13
13
|
getDeepInsertCQNs,
|
|
14
|
-
cleanEmptyCompositionsOfMany,
|
|
15
14
|
// update
|
|
16
15
|
hasDeepUpdate,
|
|
17
16
|
getDeepUpdateCQNs,
|
|
18
17
|
// delete
|
|
19
18
|
hasDeepDelete,
|
|
20
19
|
getDeepDeleteCQNs,
|
|
20
|
+
getSetNullParentForeignKeyCQNs,
|
|
21
21
|
// data
|
|
22
22
|
selectDeepUpdateData
|
|
23
23
|
}
|
|
@@ -10,18 +10,18 @@ const { deepCopyArray } = require('../utils/copy')
|
|
|
10
10
|
* own utils
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
function
|
|
13
|
+
function _hasCompOrAssoc(entity, k) {
|
|
14
14
|
// TODO once REST also uses same logic as odata structured check if we can omit 'entity.elements[k] &&'
|
|
15
|
-
return entity.elements[k] && (entity.elements[k].is2one ||
|
|
15
|
+
return entity.elements[k] && (entity.elements[k].is2one || entity.elements[k].is2many)
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const _addSubDeepInsertCQN = (
|
|
18
|
+
const _addSubDeepInsertCQN = (model, compositionTree, data, cqns, draft) => {
|
|
19
19
|
compositionTree.compositionElements.forEach(element => {
|
|
20
20
|
if (element.skipPersistence) {
|
|
21
21
|
return
|
|
22
22
|
}
|
|
23
23
|
// element source must be changed in comp tree
|
|
24
|
-
const subEntity = definitions[element.source]
|
|
24
|
+
const subEntity = model.definitions[element.source]
|
|
25
25
|
const into = ctUtils.addDraftSuffix(draft, subEntity.name)
|
|
26
26
|
const insertCQN = { INSERT: { into: into, entries: [] } }
|
|
27
27
|
const subData = data.reduce((result, entry) => {
|
|
@@ -43,7 +43,7 @@ const _addSubDeepInsertCQN = (definitions, compositionTree, data, cqns, draft) =
|
|
|
43
43
|
cqns.push(insertCQN)
|
|
44
44
|
}
|
|
45
45
|
if (subData.length > 0) {
|
|
46
|
-
_addSubDeepInsertCQN(
|
|
46
|
+
_addSubDeepInsertCQN(model, element, subData, cqns, draft)
|
|
47
47
|
}
|
|
48
48
|
})
|
|
49
49
|
return cqns
|
|
@@ -53,32 +53,20 @@ const _addSubDeepInsertCQN = (definitions, compositionTree, data, cqns, draft) =
|
|
|
53
53
|
* exports
|
|
54
54
|
*/
|
|
55
55
|
|
|
56
|
-
const _entityFromINSERT = (
|
|
56
|
+
const _entityFromINSERT = (model, INSERT) => {
|
|
57
57
|
if (INSERT && INSERT.into) {
|
|
58
58
|
const entityName = ensureNoDraftsSuffix(INSERT.into.name || INSERT.into)
|
|
59
|
-
return definitions
|
|
59
|
+
return model.definitions[entityName]
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
const
|
|
64
|
-
const entity = _entityFromINSERT(definitions, cqn.INSERT)
|
|
65
|
-
if (!entity) return
|
|
66
|
-
for (const entry of cqn.INSERT.entries || []) {
|
|
67
|
-
for (const elName in entry || {}) {
|
|
68
|
-
const el = entity.elements[elName]
|
|
69
|
-
if (!el) continue
|
|
70
|
-
if (el.is2many && !entry[elName].length) delete entry[elName]
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const hasDeepInsert = (definitions, cqn) => {
|
|
63
|
+
const hasDeepInsert = (model, cqn) => {
|
|
76
64
|
if (cqn.INSERT.entries) {
|
|
77
|
-
const entity = _entityFromINSERT(
|
|
65
|
+
const entity = _entityFromINSERT(model, cqn.INSERT)
|
|
78
66
|
if (entity) {
|
|
79
67
|
return !!cqn.INSERT.entries.find(entry => {
|
|
80
68
|
return !!Object.keys(entry || {}).find(k => {
|
|
81
|
-
return
|
|
69
|
+
return _hasCompOrAssoc(entity, k)
|
|
82
70
|
})
|
|
83
71
|
})
|
|
84
72
|
}
|
|
@@ -86,14 +74,14 @@ const hasDeepInsert = (definitions, cqn) => {
|
|
|
86
74
|
return false
|
|
87
75
|
}
|
|
88
76
|
|
|
89
|
-
const getDeepInsertCQNs = (
|
|
77
|
+
const getDeepInsertCQNs = (model, cqn) => {
|
|
90
78
|
const into = cqn.INSERT.into.name || cqn.INSERT.into
|
|
91
79
|
const entityName = ensureNoDraftsSuffix(into)
|
|
92
80
|
const draft = entityName !== into
|
|
93
81
|
const dataEntries = cqn.INSERT.entries ? deepCopyArray(cqn.INSERT.entries) : []
|
|
94
|
-
const entity = definitions
|
|
82
|
+
const entity = model.definitions[entityName]
|
|
95
83
|
const compositionTree = getCompositionTree({
|
|
96
|
-
definitions,
|
|
84
|
+
definitions: model.definitions,
|
|
97
85
|
rootEntityName: entityName,
|
|
98
86
|
checkRoot: false,
|
|
99
87
|
resolveViews: !draft,
|
|
@@ -107,11 +95,10 @@ const getDeepInsertCQNs = (definitions, cqn) => {
|
|
|
107
95
|
flattenedCqn.INSERT.entries.push(ctUtils.cleanDeepData(entity, Object.assign({}, dataEntry)))
|
|
108
96
|
)
|
|
109
97
|
|
|
110
|
-
return [flattenedCqn, ..._addSubDeepInsertCQN(
|
|
98
|
+
return [flattenedCqn, ..._addSubDeepInsertCQN(model, compositionTree, dataEntries, [], draft)]
|
|
111
99
|
}
|
|
112
100
|
|
|
113
101
|
module.exports = {
|
|
114
|
-
cleanEmptyCompositionsOfMany,
|
|
115
102
|
hasDeepInsert,
|
|
116
103
|
getDeepInsertCQNs
|
|
117
104
|
}
|
|
@@ -38,7 +38,7 @@ const _foreignKeysToLinks = (element, inverse) =>
|
|
|
38
38
|
const _resolvedElement = (element, service) => {
|
|
39
39
|
if (!element.target) return element
|
|
40
40
|
// skip forbidden view check if association to view with foreign key in target
|
|
41
|
-
const skipForbiddenViewCheck = element._isAssociationStrict &&
|
|
41
|
+
const skipForbiddenViewCheck = element._isAssociationStrict && !element['@odata.contained']
|
|
42
42
|
const { target, mapping } = getTransition(element._target, service, skipForbiddenViewCheck)
|
|
43
43
|
const newElement = { target: target.name, _target: target }
|
|
44
44
|
Object.setPrototypeOf(newElement, element)
|
|
@@ -37,6 +37,7 @@ const _dataByKey = (entity, data) => {
|
|
|
37
37
|
function _addSubDeepUpdateCQNForDelete({ entity, data, selectData, deleteCQN }) {
|
|
38
38
|
const dataByKey = _dataByKey(entity, data)
|
|
39
39
|
for (const selectEntry of selectData) {
|
|
40
|
+
if (!selectEntry) continue
|
|
40
41
|
const dataEntry = dataByKey.get(_serializedKey(entity, selectEntry))
|
|
41
42
|
if (!dataEntry) {
|
|
42
43
|
if (deleteCQN.DELETE.where.length > 0) {
|
|
@@ -67,7 +68,7 @@ function _fillLinkFromStructuredData(entity, entry) {
|
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
const _diffData = (newData, oldData, entity, newEntry, oldEntry,
|
|
71
|
+
const _diffData = (newData, oldData, entity, newEntry, oldEntry, model) => {
|
|
71
72
|
const result = {}
|
|
72
73
|
|
|
73
74
|
const keysSet = new Set(Object.keys(newData).concat(Object.keys(oldData)))
|
|
@@ -96,7 +97,7 @@ const _diffData = (newData, oldData, entity, newEntry, oldEntry, definitions) =>
|
|
|
96
97
|
nav.isComposition &&
|
|
97
98
|
nav.is2one &&
|
|
98
99
|
newEntry[nav.name] !== undefined &&
|
|
99
|
-
!definitions[nav.target]._hasPersistenceSkip
|
|
100
|
+
!model.definitions[nav.target]._hasPersistenceSkip
|
|
100
101
|
) {
|
|
101
102
|
result[key] = null
|
|
102
103
|
}
|
|
@@ -106,15 +107,7 @@ const _diffData = (newData, oldData, entity, newEntry, oldEntry, definitions) =>
|
|
|
106
107
|
return result
|
|
107
108
|
}
|
|
108
109
|
|
|
109
|
-
function _addSubDeepUpdateCQNForUpdateInsert({
|
|
110
|
-
entity,
|
|
111
|
-
entityName,
|
|
112
|
-
data,
|
|
113
|
-
selectData,
|
|
114
|
-
updateCQNs,
|
|
115
|
-
insertCQN,
|
|
116
|
-
definitions
|
|
117
|
-
}) {
|
|
110
|
+
function _addSubDeepUpdateCQNForUpdateInsert({ entity, entityName, data, selectData, updateCQNs, insertCQN, model }) {
|
|
118
111
|
const selectDataByKey = _dataByKey(entity, selectData)
|
|
119
112
|
const deepUpdateData = []
|
|
120
113
|
for (const entry of data) {
|
|
@@ -127,7 +120,7 @@ function _addSubDeepUpdateCQNForUpdateInsert({
|
|
|
127
120
|
deepUpdateData.push(entry)
|
|
128
121
|
const newData = ctUtils.cleanDeepData(entity, entry)
|
|
129
122
|
const oldData = ctUtils.cleanDeepData(entity, selectEntry)
|
|
130
|
-
const diff = _diffData(newData, oldData, entity, entry, selectEntry,
|
|
123
|
+
const diff = _diffData(newData, oldData, entity, entry, selectEntry, model)
|
|
131
124
|
// empty updates will be removed later
|
|
132
125
|
updateCQNs.push({ UPDATE: { entity: entityName, data: diff, where: ctUtils.whereKey(key) } })
|
|
133
126
|
} else {
|
|
@@ -138,7 +131,7 @@ function _addSubDeepUpdateCQNForUpdateInsert({
|
|
|
138
131
|
return deepUpdateData
|
|
139
132
|
}
|
|
140
133
|
|
|
141
|
-
function _addSubDeepUpdateCQNCollect(
|
|
134
|
+
async function _addSubDeepUpdateCQNCollect(model, cqns, updateCQNs, insertCQN, deleteCQN, req) {
|
|
142
135
|
if (updateCQNs.length > 0) {
|
|
143
136
|
cqns[0] = cqns[0] || []
|
|
144
137
|
cqns[0].push(...updateCQNs)
|
|
@@ -146,7 +139,7 @@ function _addSubDeepUpdateCQNCollect(definitions, cqns, updateCQNs, insertCQN, d
|
|
|
146
139
|
|
|
147
140
|
if (insertCQN.INSERT.entries.length > 0) {
|
|
148
141
|
cqns[0] = cqns[0] || []
|
|
149
|
-
const deepInsertCQNs = getDeepInsertCQNs(
|
|
142
|
+
const deepInsertCQNs = getDeepInsertCQNs(model, insertCQN)
|
|
150
143
|
deepInsertCQNs.forEach(insertCQN => {
|
|
151
144
|
const intoCQN = cqns[0].find(cqn => {
|
|
152
145
|
return cqn.INSERT && cqn.INSERT.into === insertCQN.INSERT.into
|
|
@@ -161,7 +154,7 @@ function _addSubDeepUpdateCQNCollect(definitions, cqns, updateCQNs, insertCQN, d
|
|
|
161
154
|
|
|
162
155
|
if (deleteCQN.DELETE.where.length > 0) {
|
|
163
156
|
cqns[0] = cqns[0] || []
|
|
164
|
-
const deepDeleteCQNs = getDeepDeleteCQNs(
|
|
157
|
+
const deepDeleteCQNs = await getDeepDeleteCQNs(model, req, deleteCQN)
|
|
165
158
|
deepDeleteCQNs.forEach((deleteCQNs, index) => {
|
|
166
159
|
deleteCQNs.forEach(el => {
|
|
167
160
|
cqns[index] = cqns[index] || []
|
|
@@ -180,7 +173,7 @@ const _addToData = (subData, entity, element, entry) => {
|
|
|
180
173
|
subData.push(...unwrappedSubData)
|
|
181
174
|
}
|
|
182
175
|
|
|
183
|
-
function _addSubDeepUpdateCQNRecursion({
|
|
176
|
+
async function _addSubDeepUpdateCQNRecursion({ model, compositionTree, entity, data, selectData, cqns, draft, req }) {
|
|
184
177
|
const selectDataByKey = _dataByKey(entity, selectData)
|
|
185
178
|
|
|
186
179
|
for (const element of compositionTree.compositionElements) {
|
|
@@ -202,23 +195,24 @@ function _addSubDeepUpdateCQNRecursion({ definitions, compositionTree, entity, d
|
|
|
202
195
|
}
|
|
203
196
|
}
|
|
204
197
|
|
|
205
|
-
_addSubDeepUpdateCQN({
|
|
206
|
-
|
|
198
|
+
await _addSubDeepUpdateCQN({
|
|
199
|
+
model,
|
|
207
200
|
compositionTree: element,
|
|
208
201
|
data: subData,
|
|
209
202
|
selectData: selectSubData,
|
|
210
203
|
cqns,
|
|
211
|
-
draft
|
|
204
|
+
draft,
|
|
205
|
+
req
|
|
212
206
|
})
|
|
213
207
|
}
|
|
214
208
|
|
|
215
209
|
return cqns
|
|
216
210
|
}
|
|
217
211
|
|
|
218
|
-
const _addSubDeepUpdateCQN = ({
|
|
212
|
+
const _addSubDeepUpdateCQN = async ({ model, compositionTree, data, selectData, cqns, draft, req }) => {
|
|
219
213
|
// We handle each level for deepUpdate, the moment we see that there will be an INSERT,
|
|
220
214
|
// it'll be removed from our deepUpdateData (and handled deep separately).
|
|
221
|
-
const entity = definitions
|
|
215
|
+
const entity = model.definitions[compositionTree.source]
|
|
222
216
|
|
|
223
217
|
if (entity._hasPersistenceSkip) return Promise.resolve()
|
|
224
218
|
|
|
@@ -234,21 +228,22 @@ const _addSubDeepUpdateCQN = ({ definitions, compositionTree, data, selectData,
|
|
|
234
228
|
selectData,
|
|
235
229
|
updateCQNs,
|
|
236
230
|
insertCQN,
|
|
237
|
-
|
|
231
|
+
model
|
|
238
232
|
})
|
|
239
233
|
|
|
240
|
-
_addSubDeepUpdateCQNCollect(
|
|
234
|
+
await _addSubDeepUpdateCQNCollect(model, cqns, updateCQNs, insertCQN, deleteCQN, req)
|
|
241
235
|
|
|
242
236
|
if (deepUpdateData.length === 0) return Promise.resolve()
|
|
243
237
|
|
|
244
238
|
return _addSubDeepUpdateCQNRecursion({
|
|
245
|
-
|
|
239
|
+
model,
|
|
246
240
|
compositionTree,
|
|
247
241
|
entity,
|
|
248
242
|
data: deepUpdateData,
|
|
249
243
|
selectData,
|
|
250
244
|
cqns,
|
|
251
|
-
draft
|
|
245
|
+
draft,
|
|
246
|
+
req
|
|
252
247
|
})
|
|
253
248
|
}
|
|
254
249
|
|
|
@@ -256,11 +251,11 @@ const _addSubDeepUpdateCQN = ({ definitions, compositionTree, data, selectData,
|
|
|
256
251
|
* exports
|
|
257
252
|
*/
|
|
258
253
|
|
|
259
|
-
const hasDeepUpdate = (
|
|
254
|
+
const hasDeepUpdate = (model, cqn) => {
|
|
260
255
|
if (cqn && cqn.UPDATE && cqn.UPDATE.entity && (cqn.UPDATE.data || cqn.UPDATE.with)) {
|
|
261
256
|
const updateEntity = cqn.UPDATE.entity
|
|
262
257
|
const entityName = (updateEntity.ref && updateEntity.ref[0]) || updateEntity.name || updateEntity
|
|
263
|
-
const entity = definitions
|
|
258
|
+
const entity = model.definitions[ensureNoDraftsSuffix(entityName)]
|
|
264
259
|
|
|
265
260
|
if (entity) {
|
|
266
261
|
const keys = Object.keys(Object.assign({}, cqn.UPDATE.data || {}, cqn.UPDATE.with || {}))
|
|
@@ -271,7 +266,8 @@ const hasDeepUpdate = (definitions, cqn) => {
|
|
|
271
266
|
return false
|
|
272
267
|
}
|
|
273
268
|
|
|
274
|
-
const getDeepUpdateCQNs = (
|
|
269
|
+
const getDeepUpdateCQNs = async (model, req, selectData) => {
|
|
270
|
+
const { query } = req
|
|
275
271
|
if (!Array.isArray(selectData)) selectData = [selectData]
|
|
276
272
|
|
|
277
273
|
if (selectData.length === 0) return []
|
|
@@ -279,22 +275,31 @@ const getDeepUpdateCQNs = (definitions, cqn, selectData) => {
|
|
|
279
275
|
if (selectData.length > 1) throw getError('Deep update can only be performed on a single instance')
|
|
280
276
|
|
|
281
277
|
const cqns = []
|
|
282
|
-
const from =
|
|
278
|
+
const from =
|
|
279
|
+
(query.UPDATE.entity.ref && query.UPDATE.entity.ref[0]) || query.UPDATE.entity.name || query.UPDATE.entity
|
|
283
280
|
const entityName = ensureNoDraftsSuffix(from)
|
|
284
281
|
const draft = entityName !== from
|
|
285
|
-
const data =
|
|
286
|
-
const withObj =
|
|
287
|
-
const entity = definitions
|
|
282
|
+
const data = query.UPDATE.data ? deepCopyObject(query.UPDATE.data) : {}
|
|
283
|
+
const withObj = query.UPDATE.with ? deepCopyObject(query.UPDATE.with) : {}
|
|
284
|
+
const entity = model.definitions[entityName]
|
|
288
285
|
const entry = Object.assign({}, data, withObj, ctUtils.key(entity, selectData[0]))
|
|
289
286
|
const compositionTree = getCompositionTree({
|
|
290
|
-
definitions,
|
|
287
|
+
definitions: model.definitions,
|
|
291
288
|
rootEntityName: entityName,
|
|
292
289
|
checkRoot: false,
|
|
293
290
|
resolveViews: !draft,
|
|
294
291
|
service: cds.db
|
|
295
292
|
})
|
|
296
293
|
|
|
297
|
-
const subCQNs = _addSubDeepUpdateCQN({
|
|
294
|
+
const subCQNs = await _addSubDeepUpdateCQN({
|
|
295
|
+
model,
|
|
296
|
+
compositionTree,
|
|
297
|
+
data: [entry],
|
|
298
|
+
selectData,
|
|
299
|
+
cqns: [],
|
|
300
|
+
draft,
|
|
301
|
+
req
|
|
302
|
+
})
|
|
298
303
|
subCQNs.forEach((subCQNs, index) => {
|
|
299
304
|
cqns[index] = cqns[index] || []
|
|
300
305
|
cqns[index].push(...subCQNs)
|
|
@@ -30,9 +30,8 @@ const _getFiltered = err => {
|
|
|
30
30
|
Object.keys(err)
|
|
31
31
|
.concat(['message'])
|
|
32
32
|
.forEach(k => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
33
|
+
// REVISIT: do not remove innererror with cds^6
|
|
34
|
+
if (k === 'innererror' && process.env.NODE_ENV === 'production' && !err[SKIP_SANITIZATION]) return
|
|
36
35
|
if (ALLOWED_PROPERTIES.includes(k) || k.startsWith('@')) {
|
|
37
36
|
error[k] = err[k]
|
|
38
37
|
} else if (k === 'numericSeverity') {
|
|
@@ -90,6 +89,20 @@ const _anonymousUser = req => {
|
|
|
90
89
|
return Object.defineProperty(new cds.User(), '_req', { enumerable: false, value: req })
|
|
91
90
|
}
|
|
92
91
|
|
|
92
|
+
// - for one unique value, we use it
|
|
93
|
+
// - if at least one 5xx exists, we use 500
|
|
94
|
+
// - else if at least one 4xx exists, we use 400
|
|
95
|
+
// - else we use 500
|
|
96
|
+
const _statusCodeFromDetails = details => {
|
|
97
|
+
const uniqueStatusCodes = new Set(
|
|
98
|
+
details.map(d => d.status || d.statusCode || d.code).map(c => (!isNaN(c) && Number(c)) || c)
|
|
99
|
+
)
|
|
100
|
+
if (uniqueStatusCodes.size === 1) return uniqueStatusCodes.values().next().value
|
|
101
|
+
if ([...uniqueStatusCodes].some(s => s >= 500)) return 500
|
|
102
|
+
if ([...uniqueStatusCodes].some(s => s >= 400)) return 400
|
|
103
|
+
return 500
|
|
104
|
+
}
|
|
105
|
+
|
|
93
106
|
const normalizeError = (err, req) => {
|
|
94
107
|
const user = (req && req.user) || _anonymousUser(req)
|
|
95
108
|
const locale = user.locale
|
|
@@ -98,8 +111,9 @@ const normalizeError = (err, req) => {
|
|
|
98
111
|
|
|
99
112
|
// derive status code from err status OR root code OR matching detail codes
|
|
100
113
|
let statusCode = err.status || err.statusCode || (_isAllowedError(error.code) && error.code)
|
|
101
|
-
if (!statusCode && error.details
|
|
102
|
-
|
|
114
|
+
if (!statusCode && error.details) {
|
|
115
|
+
const detailsCode = _statusCodeFromDetails(error.details)
|
|
116
|
+
if (_isAllowedError(detailsCode)) statusCode = detailsCode
|
|
103
117
|
}
|
|
104
118
|
|
|
105
119
|
// make sure it's a number
|