@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,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
|
|
@@ -13,54 +13,10 @@ const { getPathFromRef, getEntityFromPath } = require('../../common/utils/path')
|
|
|
13
13
|
const { addToWhere } = require('../../common/utils/cqn')
|
|
14
14
|
const { removeIsActiveEntityRecursively } = require('../../fiori/utils/where')
|
|
15
15
|
const { addRefToWhereIfNecessary } = require('../../../odata/afterburner')
|
|
16
|
+
const { addAliasToExpression, PARENT_ALIAS, FOREIGN_ALIAS } = require('../../db/utils/generateAliases')
|
|
16
17
|
|
|
17
|
-
const PARENT_ALIAS = '_parent_'
|
|
18
|
-
const PARENT_ALIAS_REGEX = new RegExp('^' + PARENT_ALIAS + '\\d*$')
|
|
19
|
-
const FOREIGN_ALIAS = '_foreign_'
|
|
20
18
|
const OPERATIONS = ['=', '>', '<', '!=', '<>', '>=', '<=', 'like', 'between', 'in', 'not in']
|
|
21
19
|
|
|
22
|
-
// special case in lambda functions
|
|
23
|
-
const _addParentAlias = (where, alias) => {
|
|
24
|
-
where.forEach(e => {
|
|
25
|
-
if (e.ref && e.ref[0].match(PARENT_ALIAS_REGEX)) {
|
|
26
|
-
e.ref[0] = alias
|
|
27
|
-
}
|
|
28
|
-
})
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const _addAliasToElement = (e, alias) => {
|
|
32
|
-
if (e.ref) {
|
|
33
|
-
return { ref: [alias, ...e.ref] }
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (e.list) {
|
|
37
|
-
return { list: e.list.map(arg => _addAliasToElement(arg, alias)) }
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (e.func) {
|
|
41
|
-
const args = e.args.map(arg => _addAliasToElement(arg, alias))
|
|
42
|
-
return { ...e, args }
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (e.SELECT && e.SELECT.where) {
|
|
46
|
-
_addParentAlias(e.SELECT.where, alias)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (e.xpr) {
|
|
50
|
-
return { xpr: e.xpr.map(e1 => _addAliasToElement(e1, alias)) }
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return e
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const _addAliasToExpression = (expression, alias) => {
|
|
57
|
-
if (!alias) {
|
|
58
|
-
return expression
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return expression.map(e => _addAliasToElement(e, alias))
|
|
62
|
-
}
|
|
63
|
-
|
|
64
20
|
const _elementFromRef = (name, entity) => {
|
|
65
21
|
if (!entity) return
|
|
66
22
|
|
|
@@ -112,7 +68,7 @@ const convertPathExpressionToWhere = (fromClause, model, options) => {
|
|
|
112
68
|
const currentSelect = SELECT.from(`${currentEntityName} as ${tableAlias}`)
|
|
113
69
|
|
|
114
70
|
if (fromClause.ref[i].where) {
|
|
115
|
-
currentSelect.where(
|
|
71
|
+
currentSelect.where(addAliasToExpression(fromClause.ref[i].where, tableAlias))
|
|
116
72
|
}
|
|
117
73
|
|
|
118
74
|
// REVISIT: Only args in last segment are handled, intermediate ones are ignored
|
|
@@ -326,35 +282,15 @@ const _createWindowCQN = (SELECT, model) => {
|
|
|
326
282
|
delete SELECT.groupBy
|
|
327
283
|
}
|
|
328
284
|
|
|
329
|
-
const
|
|
330
|
-
|
|
285
|
+
const _unshiftRefsWithNavigation = nav => el => {
|
|
286
|
+
if (el.ref) return { ref: [...nav, ...el.ref] }
|
|
287
|
+
if (el.xpr) return { xpr: el.xpr.map(_unshiftRefsWithNavigation(nav)) }
|
|
288
|
+
return el
|
|
331
289
|
}
|
|
332
290
|
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// eslint-disable-next-line complexity
|
|
339
|
-
const _getWhereExistsSubSelect = (cqn, where, index, model, options) => {
|
|
340
|
-
const _unshiftRefsWithNavigation = nav => el => {
|
|
341
|
-
if (el.ref) return { ref: [...nav, ...el.ref] }
|
|
342
|
-
if (el.xpr) return { xpr: el.xpr.map(_unshiftRefsWithNavigation(nav)) }
|
|
343
|
-
return el
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
if (!options.lambdaIteration) options.lambdaIteration = 1
|
|
347
|
-
const outerAlias =
|
|
348
|
-
(cqn.SELECT.from.ref && cqn.SELECT.from.as) ||
|
|
349
|
-
(cqn.SELECT.from.args && cqn.SELECT.from.args[0].ref && cqn.SELECT.from.args[0].as) ||
|
|
350
|
-
PARENT_ALIAS + options.lambdaIteration
|
|
351
|
-
const innerAlias = FOREIGN_ALIAS + options.lambdaIteration
|
|
352
|
-
cqn.SELECT.from.as = outerAlias
|
|
353
|
-
const ref = cqn.SELECT.from.ref || (cqn.SELECT.from.args && cqn.SELECT.from.args[0].ref)
|
|
354
|
-
const queryTarget = getEntityFromPath(getPathFromRef(ref), model)
|
|
355
|
-
|
|
356
|
-
let nav = where[index].ref.map(el => (el.id ? el.id : el))
|
|
357
|
-
const last = where[index].ref.slice(-1)[0]
|
|
291
|
+
const _getWhereExistsSubSelect = (queryTarget, outerAlias, innerAlias, ref, model, options) => {
|
|
292
|
+
let nav = ref.map(el => (el.id ? el.id : el))
|
|
293
|
+
const last = ref.slice(-1)[0]
|
|
358
294
|
const navName = queryTarget.elements[nav[0]] ? nav[0] : nav[nav.length - 1]
|
|
359
295
|
const navElement = queryTarget.elements[navName]
|
|
360
296
|
|
|
@@ -381,17 +317,24 @@ const _getWhereExistsSubSelect = (cqn, where, index, model, options) => {
|
|
|
381
317
|
: last.where
|
|
382
318
|
: undefined
|
|
383
319
|
|
|
320
|
+
if (!innerAlias && !outerAlias) {
|
|
321
|
+
innerAlias = navElement.target
|
|
322
|
+
outerAlias = navElement.parent.name
|
|
323
|
+
}
|
|
324
|
+
|
|
384
325
|
const subSelect = SELECT.from({ ref: [navElement.target], as: innerAlias })
|
|
385
326
|
if (condition) {
|
|
386
327
|
if (subSelect.SELECT.from.as) {
|
|
387
328
|
for (let i = 0; i < condition.length; i++) {
|
|
329
|
+
if (!condition[i].ref) continue
|
|
388
330
|
if (
|
|
389
|
-
condition[i].ref &&
|
|
390
331
|
condition[i].ref.length > 1 &&
|
|
391
332
|
condition[i].ref.every(r => typeof r === 'string') &&
|
|
392
333
|
condition[i].ref[0] === navName
|
|
393
334
|
) {
|
|
394
335
|
condition[i].ref[0] = subSelect.SELECT.from.as
|
|
336
|
+
} else if (condition[i].ref.length === 1) {
|
|
337
|
+
condition[i].ref.unshift(subSelect.SELECT.from.as)
|
|
395
338
|
}
|
|
396
339
|
}
|
|
397
340
|
}
|
|
@@ -446,42 +389,9 @@ const _ensureExpandedNestedWhereExists = ({ ref }, aliases) => {
|
|
|
446
389
|
return { ref: [acc] }
|
|
447
390
|
}
|
|
448
391
|
|
|
449
|
-
const _convertWhereExists = (where, cqn, model, options) => {
|
|
450
|
-
for (let i = 0; i < where.length; i++) {
|
|
451
|
-
const element = where[i]
|
|
452
|
-
|
|
453
|
-
// ensure where exists are fully expanded
|
|
454
|
-
if (
|
|
455
|
-
(element === 'exists' && where[i + 1].ref && where[i > 1 ? i - 1 : 0] !== 'not') ||
|
|
456
|
-
(element === 'not' && where[i + 1] === 'exists' && where[i + 2].ref)
|
|
457
|
-
) {
|
|
458
|
-
const offset = element === 'not' ? 2 : 1
|
|
459
|
-
const aliases = cqn.SELECT.from.as
|
|
460
|
-
? [cqn.SELECT.from.as]
|
|
461
|
-
: cqn.SELECT.from.join
|
|
462
|
-
? cqn.SELECT.from.args.map(arg => arg.as)
|
|
463
|
-
: []
|
|
464
|
-
where[i + offset] = _ensureExpandedNestedWhereExists(where[i + offset], aliases)
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
if (element.xpr) {
|
|
468
|
-
_convertWhereExists(element.xpr, cqn, model, options) // > recursing into nested {xpr}
|
|
469
|
-
} else if (element === 'exists' && _isAny(where[i + 1])) {
|
|
470
|
-
where[i + 1] = _getWhereExistsSubSelect(cqn, where, i + 1, model, options)
|
|
471
|
-
} else if (element === 'not' && where[i + 1] === 'exists' && _isAll(where[i + 2])) {
|
|
472
|
-
where[i + 2] = _getWhereExistsSubSelect(cqn, where, i + 2, model, options)
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
const convertWhereExists = (cqn, model, options) => {
|
|
478
|
-
if (cqn.SELECT.where) _convertWhereExists(cqn.SELECT.where, cqn, model, options)
|
|
479
|
-
}
|
|
480
|
-
|
|
481
392
|
const _getRefIndex = (where, index) => {
|
|
482
393
|
if (
|
|
483
394
|
where[index - 1].ref &&
|
|
484
|
-
// REVISIT DRAFT HANDLING: cqn2cqn4sql must not be called there
|
|
485
395
|
where[index - 1].ref[where[index - 1].ref.length - 1] !== 'InProcessByUser' &&
|
|
486
396
|
where[index + 1].val !== undefined &&
|
|
487
397
|
where[index + 1].val !== null &&
|
|
@@ -491,7 +401,6 @@ const _getRefIndex = (where, index) => {
|
|
|
491
401
|
}
|
|
492
402
|
if (
|
|
493
403
|
where[index + 1].ref &&
|
|
494
|
-
// REVISIT DRAFT HANDLING: cqn2cqn4sql must not be called there
|
|
495
404
|
where[index + 1].ref[where[index + 1].ref.length - 1] !== 'InProcessByUser' &&
|
|
496
405
|
where[index - 1].val !== undefined &&
|
|
497
406
|
where[index - 1].val !== null &&
|
|
@@ -501,6 +410,77 @@ const _getRefIndex = (where, index) => {
|
|
|
501
410
|
}
|
|
502
411
|
}
|
|
503
412
|
|
|
413
|
+
const _convertWhereExistsColumn = (column, model, options, queryTarget) => {
|
|
414
|
+
const lambdaIteration = options.lambdaIteration
|
|
415
|
+
if (typeof column === 'object') {
|
|
416
|
+
if (column.SELECT) {
|
|
417
|
+
convertWhereExists(column.SELECT, model, options)
|
|
418
|
+
options.lambdaIteration = lambdaIteration
|
|
419
|
+
} else if (column.where) {
|
|
420
|
+
convertWhereExists(column, model, options, queryTarget)
|
|
421
|
+
options.lambdaIteration = lambdaIteration
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// eslint-disable-next-line complexity
|
|
427
|
+
const convertWhereExists = (query, model, options, currentTarget) => {
|
|
428
|
+
const { where, columns, expand } = query
|
|
429
|
+
let innerAlias, outerAlias, queryTarget
|
|
430
|
+
const { ref, as } =
|
|
431
|
+
(query.ref && query) ||
|
|
432
|
+
(query.from &&
|
|
433
|
+
((query.from.ref && query.from) || (query.from.args && query.from.args[0].ref && query.from.args[0]))) ||
|
|
434
|
+
{}
|
|
435
|
+
if (!ref) return
|
|
436
|
+
|
|
437
|
+
if (!options.lambdaIteration) options.lambdaIteration = 1
|
|
438
|
+
const lambdaIteration = options.lambdaIteration
|
|
439
|
+
if (!currentTarget) {
|
|
440
|
+
queryTarget = getEntityFromPath(getPathFromRef(ref), model)
|
|
441
|
+
outerAlias = as || PARENT_ALIAS + lambdaIteration
|
|
442
|
+
innerAlias = FOREIGN_ALIAS + lambdaIteration
|
|
443
|
+
} else {
|
|
444
|
+
const element = currentTarget.elements[ref]
|
|
445
|
+
if (element && element.isAssociation) {
|
|
446
|
+
queryTarget = element._target
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (where) {
|
|
451
|
+
for (let i = 0; i < where.length; i++) {
|
|
452
|
+
const element = where[i]
|
|
453
|
+
|
|
454
|
+
// ensure where exists are fully expanded
|
|
455
|
+
if (element === 'exists' && where[i + 1].ref) {
|
|
456
|
+
const ref = query.from || query
|
|
457
|
+
const aliases = ref.as ? [ref.as] : ref.join ? ref.args.map(arg => arg.as) : []
|
|
458
|
+
where[i + 1] = _ensureExpandedNestedWhereExists(where[i + 1], aliases)
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (element.xpr) {
|
|
462
|
+
convertWhereExists({ ...query, where: element.xpr }, model, options) // > recursing into nested {xpr}
|
|
463
|
+
} else if (element === 'exists' && where[i + 1].ref) {
|
|
464
|
+
if (query.from) {
|
|
465
|
+
query.from.as = outerAlias
|
|
466
|
+
}
|
|
467
|
+
where[i + 1] = _getWhereExistsSubSelect(queryTarget, outerAlias, innerAlias, where[i + 1].ref, model, options)
|
|
468
|
+
i += 1
|
|
469
|
+
} else if (element.SELECT) {
|
|
470
|
+
convertWhereExists(element.SELECT, model, options)
|
|
471
|
+
}
|
|
472
|
+
options.lambdaIteration = lambdaIteration
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (columns) {
|
|
477
|
+
columns.forEach(column => _convertWhereExistsColumn(column, model, options, queryTarget))
|
|
478
|
+
}
|
|
479
|
+
if (expand && expand !== '*') {
|
|
480
|
+
expand.forEach(column => _convertWhereExistsColumn(column, model, options, queryTarget))
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
504
484
|
const _convertNotEqual = (container, partName = 'where') => {
|
|
505
485
|
const where = container[partName]
|
|
506
486
|
|
|
@@ -668,7 +648,7 @@ const _convertPathExpression = (SELECT, model, options = {}) => {
|
|
|
668
648
|
if (alias) {
|
|
669
649
|
SELECT.from.as = alias
|
|
670
650
|
if (SELECT.where && alias !== prevAlias) {
|
|
671
|
-
SELECT.where =
|
|
651
|
+
SELECT.where = addAliasToExpression(SELECT.where, alias)
|
|
672
652
|
}
|
|
673
653
|
}
|
|
674
654
|
if (columns) {
|
|
@@ -691,7 +671,7 @@ const _convertPathExpression = (SELECT, model, options = {}) => {
|
|
|
691
671
|
}
|
|
692
672
|
// TODO: REVISIT: We need to add alias to subselect in .where, .columns, .from, ... etc
|
|
693
673
|
if (where) {
|
|
694
|
-
if (options.
|
|
674
|
+
if (options._4fiori) {
|
|
695
675
|
addToWhere({ SELECT }, where)
|
|
696
676
|
} else {
|
|
697
677
|
addToWhere({ SELECT }, removeIsActiveEntityRecursively(where))
|
|
@@ -703,8 +683,7 @@ const _convertPathExpression = (SELECT, model, options = {}) => {
|
|
|
703
683
|
const _convertSelect = (query, model, _options) => {
|
|
704
684
|
const options = Object.assign(
|
|
705
685
|
{
|
|
706
|
-
|
|
707
|
-
isDraft: _options.draft,
|
|
686
|
+
_4db: _options.service instanceof cds.DatabaseService,
|
|
708
687
|
isStreaming: query._streaming
|
|
709
688
|
},
|
|
710
689
|
_options
|
|
@@ -720,7 +699,7 @@ const _convertSelect = (query, model, _options) => {
|
|
|
720
699
|
if (cds.env.features.odata_new_parser) _flattenCQN(query)
|
|
721
700
|
|
|
722
701
|
// lambda functions
|
|
723
|
-
convertWhereExists(query, model, options)
|
|
702
|
+
convertWhereExists(query.SELECT, model, options)
|
|
724
703
|
|
|
725
704
|
// add 'or is null' in case of '!='
|
|
726
705
|
if (query.SELECT._4odata) {
|
|
@@ -729,7 +708,7 @@ const _convertSelect = (query, model, _options) => {
|
|
|
729
708
|
}
|
|
730
709
|
|
|
731
710
|
_convertPathExpression(query.SELECT, model, options)
|
|
732
|
-
rewriteAsterisks(query, model, options
|
|
711
|
+
rewriteAsterisks(query, model, options)
|
|
733
712
|
if (query.SELECT.where && _isCountNavigation(query.SELECT.where)) {
|
|
734
713
|
_convertCountNavigation(query.SELECT, model)
|
|
735
714
|
}
|
|
@@ -780,12 +759,6 @@ const _convertInsert = (query, model, options) => {
|
|
|
780
759
|
|
|
781
760
|
const resolvedView = resolveView(insert, model, cds.db)
|
|
782
761
|
|
|
783
|
-
if (query.INSERT.into.ref && query.INSERT.into.ref.length > 1) {
|
|
784
|
-
const copyFrom = [...query.INSERT.into.ref]
|
|
785
|
-
copyFrom.pop()
|
|
786
|
-
resolvedView._validationQuery = _convertSelect(SELECT.from({ ref: copyFrom }).columns([1]), model, options)
|
|
787
|
-
}
|
|
788
|
-
|
|
789
762
|
return resolvedView
|
|
790
763
|
}
|
|
791
764
|
|
|
@@ -831,7 +804,7 @@ const _convertDelete = (query, model, options) => {
|
|
|
831
804
|
|
|
832
805
|
if (alias) deleet.DELETE.from = { ref: [target], as: alias }
|
|
833
806
|
if (where) deleet.where(where)
|
|
834
|
-
if (query.DELETE.where) deleet.where(
|
|
807
|
+
if (query.DELETE.where) deleet.where(addAliasToExpression(query.DELETE.where, alias))
|
|
835
808
|
|
|
836
809
|
const targetEntity = model.definitions[target]
|
|
837
810
|
if (!targetEntity) return deleet
|
|
@@ -868,7 +841,7 @@ const _convertUpdate = (query, model, options) => {
|
|
|
868
841
|
|
|
869
842
|
if (alias) update.UPDATE.entity = { ref: [target], as: alias }
|
|
870
843
|
if (where) update.where(where)
|
|
871
|
-
if (query.UPDATE.where) update.where(
|
|
844
|
+
if (query.UPDATE.where) update.where(addAliasToExpression(query.UPDATE.where, alias))
|
|
872
845
|
|
|
873
846
|
const targetEntity = model.definitions[target]
|
|
874
847
|
if (!targetEntity) return update
|
|
@@ -6,17 +6,27 @@ const getEtagElement = entity => {
|
|
|
6
6
|
return Object.values(entity.elements).find(element => element['@odata.etag'])
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
const
|
|
9
|
+
const getComp2oneParents = (entity, model) => {
|
|
10
|
+
if (!entity) return []
|
|
11
|
+
if (entity.own('__comp2oneParents')) return entity.__comp2oneParents
|
|
12
|
+
const invalidationFn = element => !(element.is2one && element._isCompositionEffective)
|
|
13
|
+
const comp2oneParents = _getUps(entity, model, invalidationFn)
|
|
14
|
+
return entity.set('__comp2oneParents', comp2oneParents)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const _getUps = (entity, model, invalidationFn) => {
|
|
18
|
+
if (entity.own('__parents')) return entity.__parents
|
|
10
19
|
const ups = []
|
|
11
20
|
for (const def of Object.values(model.definitions)) {
|
|
12
21
|
if (def.kind !== 'entity' || !def.associations) continue
|
|
13
22
|
for (const element of Object.values(def.associations)) {
|
|
14
23
|
if (element.target !== entity.name || element._isBacklink) continue
|
|
15
24
|
if (element.name === 'SiblingEntity') continue
|
|
25
|
+
if (invalidationFn && invalidationFn(element)) continue
|
|
16
26
|
ups.push(element)
|
|
17
27
|
}
|
|
18
28
|
}
|
|
19
|
-
return ups
|
|
29
|
+
return entity.set('__parents', ups)
|
|
20
30
|
}
|
|
21
31
|
|
|
22
32
|
const _ifDataSubject = (entity, role) => {
|
|
@@ -40,7 +50,7 @@ const _getDataSubjectUp = (role, model, entity, prev, next, result) => {
|
|
|
40
50
|
}
|
|
41
51
|
|
|
42
52
|
const _getDataSubjectDown = (role, entity, prev, next) => {
|
|
43
|
-
const associations = Object.values(entity.associations).filter(e => !e._isBacklink)
|
|
53
|
+
const associations = Object.values(entity.associations || {}).filter(e => !e._isBacklink)
|
|
44
54
|
for (const element of associations) {
|
|
45
55
|
const me = { entity, relative: entity, element }
|
|
46
56
|
if (_ifDataSubject(element._target, role)) {
|
|
@@ -69,8 +79,20 @@ const getDataSubject = (entity, model, role) => {
|
|
|
69
79
|
return entity.set(hash, dataSubject)
|
|
70
80
|
}
|
|
71
81
|
|
|
72
|
-
const
|
|
73
|
-
model.entities(namespace)[name] || model.definitions[`${namespace}.${name}`]
|
|
82
|
+
const _findInModel = (name, model, namespace) => {
|
|
83
|
+
return model.entities(namespace)[name] || model.definitions[`${namespace}.${name}`]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const _resolve = (name, model, namespace) => {
|
|
87
|
+
const resolved = _findInModel(name, model, namespace)
|
|
88
|
+
// the edm name has an additional suffix 'Parameters' in case of views with parameters
|
|
89
|
+
if (!resolved && name.endsWith('Parameters')) {
|
|
90
|
+
const viewWithParam = _findInModel(name.replace(/Parameters$/, ''), model, namespace)
|
|
91
|
+
if (!viewWithParam || !viewWithParam.params) return
|
|
92
|
+
return viewWithParam
|
|
93
|
+
}
|
|
94
|
+
return resolved
|
|
95
|
+
}
|
|
74
96
|
|
|
75
97
|
const _findRootEntity = (model, edmName, namespace) => {
|
|
76
98
|
const parts = edmName.split('_')
|
|
@@ -189,5 +211,6 @@ module.exports = {
|
|
|
189
211
|
getElementDeep,
|
|
190
212
|
isRootEntity,
|
|
191
213
|
getDataSubject,
|
|
192
|
-
alias2ref
|
|
214
|
+
alias2ref,
|
|
215
|
+
getComp2oneParents
|
|
193
216
|
}
|
|
@@ -178,12 +178,32 @@ const _resolveTargetForeignKey = targetKey => {
|
|
|
178
178
|
return { targetName, propagation }
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
const _resolveColumnsFromQuery = query => {
|
|
182
|
+
if (query && query.SET) return _resolveColumnsFromQuery(query.SET.args[0])
|
|
183
|
+
if (query && query.SELECT && query.SELECT.columns) return query.SELECT.columns
|
|
184
|
+
return []
|
|
185
|
+
}
|
|
186
|
+
|
|
181
187
|
const _resolvedKeys = (foreignKeys, targetKeys, fillChild) => {
|
|
182
188
|
const foreignKeyPropagations = []
|
|
183
189
|
|
|
184
190
|
for (let i = 0; i < foreignKeys.length; i++) {
|
|
185
191
|
const fk = foreignKeys[i]
|
|
186
|
-
|
|
192
|
+
let tk
|
|
193
|
+
|
|
194
|
+
/*
|
|
195
|
+
* REVISIT: poor man's look-up of target key with fallback to targetKeys[i]
|
|
196
|
+
* Look at elements, then try to find it in query and resolve recursively until you have the full path.
|
|
197
|
+
* Once you have the full path, you can find it in the target entity.
|
|
198
|
+
* NOTE: There can be projections upon projections and renamings in every projection. -> not yet covered!!!
|
|
199
|
+
*/
|
|
200
|
+
const tkCol = _resolveColumnsFromQuery(targetKeys[i].parent.query).find(
|
|
201
|
+
c => c.ref && `${fk['@odata.foreignKey4']}_${c.ref.join('_')}` === fk.name
|
|
202
|
+
)
|
|
203
|
+
tk = tkCol && targetKeys.find(tk => tk.name === (tkCol.as ? tkCol.as : tkCol.ref.join('_')))
|
|
204
|
+
// with composition of aspects, the lookup fails -> we need this final fallback
|
|
205
|
+
if (!tk) tk = targetKeys[i]
|
|
206
|
+
|
|
187
207
|
if (fk._isStructured) {
|
|
188
208
|
i += _resolve4struct(targetKeys, fk, foreignKeyPropagations, fillChild, false, i)
|
|
189
209
|
} else if (tk._isStructured) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { where2obj } = require('./cqn')
|
|
2
|
+
const { deepCopyArray } = require('./copy')
|
|
2
3
|
|
|
3
4
|
function _getOnCondElements(onCond, onCondElements = []) {
|
|
4
5
|
const andIndex = onCond.indexOf('and')
|
|
@@ -17,7 +18,7 @@ function _getOnCondElements(onCond, onCondElements = []) {
|
|
|
17
18
|
function _modifyWhereWithNavigations(where, newWhere, targetKeyElement, keyName) {
|
|
18
19
|
if (where) {
|
|
19
20
|
// copy where else query will be modified
|
|
20
|
-
const whereCopy =
|
|
21
|
+
const whereCopy = deepCopyArray(where)
|
|
21
22
|
if (newWhere.length > 0) newWhere.push('and')
|
|
22
23
|
newWhere.push(...whereCopy)
|
|
23
24
|
}
|
|
@@ -24,7 +24,7 @@ const getEntityFromPath = (path, model) => {
|
|
|
24
24
|
while (segments.length) {
|
|
25
25
|
const segment = segments.shift()
|
|
26
26
|
current = current.elements[segment.id || segment]
|
|
27
|
-
if (current.target) current = model.definitions[current.target]
|
|
27
|
+
if (current && current.target) current = model.definitions[current.target]
|
|
28
28
|
}
|
|
29
29
|
return current
|
|
30
30
|
}
|