@sap/cds 5.7.3 → 5.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +111 -0
- package/app/fiori/routes.js +1 -1
- package/bin/deploy/to-hana/cfUtil.js +251 -138
- package/bin/deploy/to-hana/gitUtil.js +55 -0
- package/bin/deploy/to-hana/hana.js +92 -93
- package/bin/deploy/to-hana/hdiDeployUtil.js +42 -27
- package/bin/deploy/to-hana/index.js +14 -13
- package/bin/mtx/in-cds.js +1 -0
- package/bin/serve.js +1 -1
- package/bin/version.js +1 -0
- package/lib/compile/cdsc.js +0 -6
- package/lib/compile/minify.js +1 -1
- package/lib/compile/resolve.js +1 -1
- package/lib/compile/to/srvinfo.js +1 -1
- package/lib/core/classes.js +21 -1
- package/lib/env/index.js +3 -2
- package/lib/env/requires.js +4 -0
- package/lib/i18n/localize.js +5 -8
- package/lib/index.js +1 -0
- package/lib/log/errors.js +1 -1
- package/lib/ql/SELECT.js +2 -2
- package/lib/req/cds-context.js +1 -1
- package/lib/req/context.js +1 -1
- package/lib/serve/Transaction.js +9 -5
- package/lib/serve/index.js +13 -21
- package/lib/utils/tests.js +90 -66
- package/libx/_runtime/audit/generic/personal/modification.js +0 -8
- package/libx/_runtime/auth/index.js +7 -6
- package/libx/_runtime/auth/strategies/dwc.js +43 -0
- package/libx/_runtime/auth/utils.js +24 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +11 -38
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +12 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +7 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +24 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +43 -38
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +11 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +13 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/boundToCQN.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/deleteToCQN.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/orderByToCQN.js +9 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +17 -30
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +12 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/AbstractEdmStructuredType.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriHelper.js +7 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriTokenizer.js +5 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +19 -47
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +4 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +7 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/CommandExecutor.js +0 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ContextURLFactory.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ErrorJsonSerializer.js +2 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/TrustedResourceJsonSerializer.js +2 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +6 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +18 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +41 -17
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +1 -17
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +80 -21
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +47 -10
- package/libx/_runtime/cds-services/adapter/rest/Rest.js +22 -1
- package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +8 -3
- package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +3 -0
- package/libx/_runtime/cds-services/services/Service.js +1 -1
- package/libx/_runtime/cds-services/services/utils/columns.js +5 -1
- package/libx/_runtime/cds-services/services/utils/compareJson.js +15 -16
- package/libx/_runtime/cds-services/services/utils/differ.js +2 -8
- package/libx/_runtime/common/aspects/Association.js +16 -0
- package/libx/_runtime/common/composition/data.js +28 -37
- package/libx/_runtime/common/composition/delete.js +107 -58
- package/libx/_runtime/common/composition/index.js +3 -3
- package/libx/_runtime/common/composition/insert.js +14 -27
- package/libx/_runtime/common/composition/update.js +39 -34
- package/libx/_runtime/common/error/frontend.js +19 -5
- package/libx/_runtime/common/generic/auth.js +20 -85
- package/libx/_runtime/common/generic/crud.js +22 -1
- package/libx/_runtime/common/i18n/messages.properties +2 -1
- package/libx/_runtime/common/utils/cqn.js +2 -6
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +95 -122
- package/libx/_runtime/common/utils/csn.js +15 -4
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +18 -1
- package/libx/_runtime/common/utils/keys.js +2 -1
- package/libx/_runtime/common/utils/path.js +1 -1
- package/libx/_runtime/common/utils/resolveView.js +12 -4
- package/libx/_runtime/common/utils/rewriteAsterisks.js +27 -13
- package/libx/_runtime/common/utils/search2cqn4sql.js +11 -6
- package/libx/_runtime/common/utils/structured.js +11 -5
- package/libx/_runtime/common/utils/vcap.js +27 -10
- package/libx/_runtime/db/data-conversion/post-processing.js +42 -35
- package/libx/_runtime/db/expand/expand-v2.js +21 -12
- package/libx/_runtime/db/expand/expandCQNToJoin.js +57 -29
- package/libx/_runtime/db/expand/index.js +3 -0
- package/libx/_runtime/db/generic/create.js +0 -10
- package/libx/_runtime/db/generic/index.js +3 -0
- package/libx/_runtime/db/generic/read.js +2 -24
- package/libx/_runtime/db/generic/rewrite.js +1 -3
- package/libx/_runtime/db/generic/update.js +1 -1
- package/libx/_runtime/db/query/delete.js +10 -4
- package/libx/_runtime/db/query/insert.js +3 -4
- package/libx/_runtime/db/query/read.js +4 -1
- package/libx/_runtime/db/query/update.js +5 -5
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +9 -2
- package/libx/_runtime/db/sql-builder/FunctionBuilder.js +3 -0
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +7 -3
- package/libx/_runtime/db/sql-builder/index.js +3 -0
- package/libx/_runtime/db/utils/columns.js +5 -2
- package/libx/_runtime/db/utils/deep.js +6 -8
- package/libx/_runtime/db/utils/generateAliases.js +56 -6
- package/libx/_runtime/fiori/generic/before.js +73 -49
- package/libx/_runtime/fiori/generic/edit.js +14 -18
- package/libx/_runtime/fiori/generic/patch.js +8 -11
- package/libx/_runtime/fiori/generic/read.js +22 -20
- package/libx/_runtime/fiori/generic/readOverDraft.js +1 -4
- package/libx/_runtime/fiori/utils/handler.js +1 -11
- package/libx/_runtime/hana/Service.js +1 -1
- package/libx/_runtime/hana/conversion.js +12 -1
- package/libx/_runtime/hana/execute.js +31 -16
- package/libx/_runtime/hana/localized.js +1 -1
- package/libx/_runtime/hana/search.js +3 -3
- package/libx/_runtime/hana/search2cqn4sql.js +23 -25
- package/libx/_runtime/hana/searchToContains.js +1 -1
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +4 -2
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +0 -1
- package/libx/_runtime/messaging/enterprise-messaging.js +1 -0
- package/libx/_runtime/messaging/file-based.js +3 -1
- package/libx/_runtime/messaging/service.js +16 -7
- package/libx/_runtime/remote/utils/client.js +37 -20
- package/libx/_runtime/remote/utils/data.js +53 -12
- package/libx/_runtime/sqlite/Service.js +1 -1
- package/libx/_runtime/sqlite/conversion.js +10 -0
- package/libx/_runtime/sqlite/localized.js +1 -1
- package/libx/_runtime/types/api.js +2 -2
- package/libx/gql/resolvers/crud/update.js +8 -5
- package/libx/gql/resolvers/parse/ast/enrich.js +1 -0
- package/libx/odata/afterburner.js +29 -6
- package/libx/odata/cqn2odata.js +9 -0
- package/libx/odata/grammar.pegjs +50 -22
- package/libx/odata/index.js +2 -2
- package/libx/odata/parser.js +1 -1
- package/libx/odata/utils.js +2 -2
- package/libx/rest/RestAdapter.js +29 -1
- package/libx/rest/middleware/auth.js +1 -3
- package/libx/rest/middleware/parse.js +1 -0
- package/package.json +1 -1
- package/server.js +1 -1
- package/bin/deploy/to-hana/logger.js +0 -27
- package/bin/deploy/to-hana/runCommand.js +0 -113
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +0 -37
- package/libx/_runtime/common/utils/auth.js +0 -16
|
@@ -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
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
const cds = require('../../cds')
|
|
6
6
|
const { SELECT } = cds.ql
|
|
7
7
|
|
|
8
|
-
const { getRequiresAsArray } = require('
|
|
8
|
+
const { getRequiresAsArray } = require('../../auth/utils')
|
|
9
9
|
const { cqn2cqn4sql } = require('../utils/cqn2cqn4sql')
|
|
10
10
|
const { isActiveEntityRequested, removeIsActiveEntityRecursively } = require('../../fiori/utils/where')
|
|
11
11
|
const { ensureNoDraftsSuffix } = require('../../fiori/utils/handler')
|
|
@@ -226,88 +226,6 @@ const _getMergedWhere = restricts => {
|
|
|
226
226
|
return xprs
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
-
const _findTableName = (ref, aliases) => {
|
|
230
|
-
const maxLength = Math.max(...aliases.map(alias => alias.length))
|
|
231
|
-
let name = ''
|
|
232
|
-
for (let i = 0; i < ref.length; i++) {
|
|
233
|
-
name += name.length !== 0 ? `.${ref[i]}` : ref[i]
|
|
234
|
-
|
|
235
|
-
if (name >= maxLength) {
|
|
236
|
-
break
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const aliasIndex = aliases.indexOf(name)
|
|
240
|
-
if (aliasIndex !== -1) {
|
|
241
|
-
return { refIndex: i, aliasIndex: aliasIndex, name: name }
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
return { refIndex: -1 }
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const _getTableForColumn = (col, aliases, model) => {
|
|
249
|
-
for (let i = 0; i < aliases.length; i++) {
|
|
250
|
-
const index = aliases.length - i - 1
|
|
251
|
-
const alias = aliases[index]
|
|
252
|
-
if (Object.keys(model.definitions[alias].elements).includes(col)) {
|
|
253
|
-
return { index, table: alias.replace(/\./g, '_') }
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
return { index: -1 }
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const _adaptTableName = (ref, index, name) => {
|
|
261
|
-
const tableName = name.replace(/\./g, '_')
|
|
262
|
-
ref.splice(0, index + 1, tableName)
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const _ensureTableAlias = (ref, aliases, targetFrom, model, hasExpand) => {
|
|
266
|
-
const nameObj = _findTableName(ref, aliases)
|
|
267
|
-
if (nameObj.refIndex === -1) {
|
|
268
|
-
const { index, table } = _getTableForColumn(ref[0], aliases, model)
|
|
269
|
-
if (index !== -1) {
|
|
270
|
-
nameObj.aliasIndex = index
|
|
271
|
-
if (table === targetFrom.name && targetFrom.as) {
|
|
272
|
-
ref.unshift(targetFrom.as)
|
|
273
|
-
} else {
|
|
274
|
-
ref.unshift(table)
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
} else {
|
|
278
|
-
_adaptTableName(ref, nameObj.refIndex, nameObj.name)
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const _enhanceAnnotationSubSelect = (select, model, targetName, targetFrom, hasExpand) => {
|
|
283
|
-
if (select.where) {
|
|
284
|
-
for (const v of select.where) {
|
|
285
|
-
if (v.ref && select.from.ref) {
|
|
286
|
-
_ensureTableAlias(v.ref, [targetName, select.from.ref[0]], targetFrom, model, hasExpand)
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// Add alias symbols to refs if needed and mark ref (for expand) and SELECT.from (for draft)
|
|
293
|
-
const _enhanceAnnotationWhere = (query, where, model) => {
|
|
294
|
-
const cqn2cqn4sqlOptions = { suppressSearch: true }
|
|
295
|
-
query = cqn2cqn4sql(query, model, cqn2cqn4sqlOptions)
|
|
296
|
-
const hasExpand = query.SELECT && query.SELECT.columns && query.SELECT.columns.some(col => col.expand)
|
|
297
|
-
const targetFrom = query.SELECT
|
|
298
|
-
? { name: query.SELECT.from.ref[0].replace(/\./g, '_'), as: query.SELECT.from.as }
|
|
299
|
-
: {}
|
|
300
|
-
for (const w of where) {
|
|
301
|
-
if (w.ref) {
|
|
302
|
-
// REVISIT: can this case be removed permanently?
|
|
303
|
-
// _ensureTableAlias(w.ref, [query._target.name], targetFrom, model, hasExpand)
|
|
304
|
-
} else if (w.SELECT) {
|
|
305
|
-
_enhanceAnnotationSubSelect(w.SELECT, model, query._target.name, targetFrom, hasExpand)
|
|
306
|
-
w.SELECT.__targetFrom = targetFrom
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
229
|
const _getApplicables = (restricts, req) => {
|
|
312
230
|
return restricts.filter(restrict => {
|
|
313
231
|
const event = DRAFT2CRUD[req.event] || req.event
|
|
@@ -419,9 +337,26 @@ const _addRestrictionsToRead = async (req, model, resolvedApplicables) => {
|
|
|
419
337
|
|
|
420
338
|
const restrictionForTarget = _getRestrictionForTarget(resolvedApplicables, req.target)
|
|
421
339
|
if (restrictionForTarget) {
|
|
340
|
+
// adjust free subselects, if necessary
|
|
341
|
+
if (resolvedApplicables.some(ra => ra.where.match(/\s*exists\s*\(\s*select\s*1\s*/i))) {
|
|
342
|
+
for (const ele of restrictionForTarget) {
|
|
343
|
+
if (typeof ele !== 'object' || !ele.SELECT || !ele.SELECT.where) continue
|
|
344
|
+
for (const w of ele.SELECT.where) {
|
|
345
|
+
if (w.ref && w.ref.length > 2) {
|
|
346
|
+
let path = w.ref[0]
|
|
347
|
+
if (!model.definitions[path]) continue
|
|
348
|
+
let i = 1
|
|
349
|
+
for (; i < w.ref.length; i++) {
|
|
350
|
+
if (model.definitions[`${path}.${w.ref[i]}`]) path += `.${w.ref[i]}`
|
|
351
|
+
else break
|
|
352
|
+
}
|
|
353
|
+
w.ref = [path, ...w.ref.slice(i)]
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
// apply restriction
|
|
422
359
|
req.query.where(restrictionForTarget)
|
|
423
|
-
// REVISIT: remove with cds^6
|
|
424
|
-
_enhanceAnnotationWhere(req.query, restrictionForTarget, model)
|
|
425
360
|
}
|
|
426
361
|
}
|
|
427
362
|
|
|
@@ -4,6 +4,7 @@ const { SELECT } = cds.ql
|
|
|
4
4
|
const getTemplate = require('../utils/template')
|
|
5
5
|
const templateProcessor = require('../utils/templateProcessor')
|
|
6
6
|
const replaceManagedData = require('../utils/dollar')
|
|
7
|
+
const { deepCopyArray } = require('../utils/copy')
|
|
7
8
|
|
|
8
9
|
const onlyKeysRemain = require('../utils/onlyKeysRemain')
|
|
9
10
|
|
|
@@ -67,6 +68,7 @@ const _updateReqData = (req, that) => {
|
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
module.exports = cds.service.impl(function () {
|
|
71
|
+
// eslint-disable-next-line complexity
|
|
70
72
|
this.on(['CREATE', 'READ', 'UPDATE', 'DELETE'], '*', async function (req) {
|
|
71
73
|
if (typeof req.query !== 'string' && req.target && req.target._hasPersistenceSkip) {
|
|
72
74
|
req.reject(501, 'PERSISTENCE_SKIP_NO_GENERIC_CRUD', [req.target.name])
|
|
@@ -77,6 +79,19 @@ module.exports = cds.service.impl(function () {
|
|
|
77
79
|
|
|
78
80
|
let result
|
|
79
81
|
|
|
82
|
+
// validate that all elements in path exist on db, if necessary
|
|
83
|
+
// - INSERT has no where clause to do this in one roundtrip
|
|
84
|
+
// - SELECT returns [] -> really empty collection or invalid path?
|
|
85
|
+
let pathExistsQuery
|
|
86
|
+
const { ref } = (req.query.INSERT && req.query.INSERT.into) || (req.query.SELECT && req.query.SELECT.from) || {}
|
|
87
|
+
// REVISIT: why is copy necessary?
|
|
88
|
+
if (ref && ref.length > 1) pathExistsQuery = SELECT(1).from({ ref: deepCopyArray(ref.slice(0, -1)) })
|
|
89
|
+
|
|
90
|
+
if (req.event === 'CREATE' && pathExistsQuery) {
|
|
91
|
+
const res = await pathExistsQuery
|
|
92
|
+
if (res.length === 0) req.reject(404)
|
|
93
|
+
}
|
|
94
|
+
|
|
80
95
|
// no changes, no op (otherwise, @cds.on.update gets new values), but we need to check existence
|
|
81
96
|
if (req.event === 'UPDATE' && onlyKeysRemain(req)) {
|
|
82
97
|
if (await _targetEntityDoesNotExist(req)) req.reject(404)
|
|
@@ -95,7 +110,13 @@ module.exports = cds.service.impl(function () {
|
|
|
95
110
|
result = await cds.tx(req).run(req.query, req.data)
|
|
96
111
|
}
|
|
97
112
|
|
|
98
|
-
if (req.event === 'READ')
|
|
113
|
+
if (req.event === 'READ') {
|
|
114
|
+
if ((result == null || result.length === 0) && pathExistsQuery) {
|
|
115
|
+
const res = await pathExistsQuery
|
|
116
|
+
if (res.length === 0) req.reject(404)
|
|
117
|
+
}
|
|
118
|
+
return result
|
|
119
|
+
}
|
|
99
120
|
|
|
100
121
|
if (req.event === 'DELETE') {
|
|
101
122
|
if (result === 0) req.reject(404)
|
|
@@ -55,6 +55,7 @@ NON_WRITABLE_VIEW={0} on views with join and/or union is not supported
|
|
|
55
55
|
# db
|
|
56
56
|
NO_DATABASE_CONNECTION=No database connection
|
|
57
57
|
ENTITY_ALREADY_EXISTS=Entity already exists
|
|
58
|
+
ENTITY_LOCKED=Entity locked
|
|
58
59
|
UNIQUE_CONSTRAINT_VIOLATION=Unique constraint violation
|
|
59
60
|
FK_CONSTRAINT_VIOLATION=Foreign key constraint violation
|
|
60
61
|
|
|
@@ -72,8 +73,8 @@ ENTITY_IS_NOT_CRUD=Entity "{0}" is not {1}
|
|
|
72
73
|
ENTITY_IS_NOT_CRUD_VIA_NAVIGATION=Entity "{0}" is not {1} via association "{2}"
|
|
73
74
|
ENTITY_IS_AUTOEXPOSED=Entity "{0}" is not explicitely exposed as part of the service
|
|
74
75
|
EXPAND_IS_RESTRICTED=Navigation property "{0}" is not allowed for expand operation
|
|
75
|
-
|
|
76
76
|
EXPAND_COUNT_UNSUPPORTED="$count" is not supported for expand operation
|
|
77
|
+
ORDERBY_LAMBDA_UNSUPPORTED="$orderby" does not support lambda
|
|
77
78
|
|
|
78
79
|
# rest protocol adapter
|
|
79
80
|
INVALID_RESOURCE="{0}" is not a valid resource
|
|
@@ -52,12 +52,8 @@ function targetFromPath(path, model) {
|
|
|
52
52
|
? definitions[current.elements[r.id].target]
|
|
53
53
|
: definitions[r.id] || definitions[r.id.replace(/_drafts$/, '')]
|
|
54
54
|
} else {
|
|
55
|
-
const next = current.elements[r]
|
|
56
|
-
|
|
57
|
-
current = definitions[next.target]
|
|
58
|
-
} else {
|
|
59
|
-
current = next
|
|
60
|
-
}
|
|
55
|
+
const next = current ? current.elements[r] : definitions[r]
|
|
56
|
+
current = next.isAssociation ? definitions[next.target] : next
|
|
61
57
|
}
|
|
62
58
|
}
|
|
63
59
|
return current
|