@sap/cds 5.7.2 → 5.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +108 -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 -42
- 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 +18 -8
- 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 +7 -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 +21 -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 +1 -1
- 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 +7 -5
- 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 +2 -1
- package/libx/_runtime/common/composition/insert.js +13 -13
- package/libx/_runtime/common/composition/update.js +39 -34
- package/libx/_runtime/common/error/frontend.js +17 -2
- 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 +3 -0
- package/libx/_runtime/common/utils/cqn.js +2 -6
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +97 -123
- package/libx/_runtime/common/utils/csn.js +14 -3
- 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 +1 -1
- 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 +27 -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 -3
- package/libx/_runtime/db/query/read.js +15 -8
- 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/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 -17
- package/libx/_runtime/fiori/generic/readOverDraft.js +1 -4
- package/libx/_runtime/hana/Service.js +1 -1
- package/libx/_runtime/hana/conversion.js +10 -0
- package/libx/_runtime/hana/execute.js +33 -16
- package/libx/_runtime/hana/localized.js +1 -1
- package/libx/_runtime/hana/search.js +3 -3
- package/libx/_runtime/hana/search2cqn4sql.js +22 -21
- 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/message-queuing-utils/options-messaging.js +1 -0
- package/libx/_runtime/messaging/service.js +16 -7
- package/libx/_runtime/remote/utils/client.js +33 -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/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 +101 -45
- package/libx/odata/index.js +7 -1
- 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,6 +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
|
|
76
|
+
EXPAND_COUNT_UNSUPPORTED="$count" is not supported for expand operation
|
|
77
|
+
ORDERBY_LAMBDA_UNSUPPORTED="$orderby" does not support lambda
|
|
75
78
|
|
|
76
79
|
# rest protocol adapter
|
|
77
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,22 +317,30 @@ 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
|
}
|
|
398
341
|
// skip for where: [{ val: 1 }]
|
|
399
|
-
|
|
342
|
+
// where: [{ func: 'contains', args: [] }] must be evaluated
|
|
343
|
+
if (condition.length > 1 || (condition.length === 1 && !('val' in condition[0]))) subSelect.where(condition)
|
|
400
344
|
}
|
|
401
345
|
|
|
402
346
|
subSelect.where(queryTarget._relations[navName].join(innerAlias, outerAlias))
|
|
@@ -445,42 +389,9 @@ const _ensureExpandedNestedWhereExists = ({ ref }, aliases) => {
|
|
|
445
389
|
return { ref: [acc] }
|
|
446
390
|
}
|
|
447
391
|
|
|
448
|
-
const _convertWhereExists = (where, cqn, model, options) => {
|
|
449
|
-
for (let i = 0; i < where.length; i++) {
|
|
450
|
-
const element = where[i]
|
|
451
|
-
|
|
452
|
-
// ensure where exists are fully expanded
|
|
453
|
-
if (
|
|
454
|
-
(element === 'exists' && where[i + 1].ref && where[i > 1 ? i - 1 : 0] !== 'not') ||
|
|
455
|
-
(element === 'not' && where[i + 1] === 'exists' && where[i + 2].ref)
|
|
456
|
-
) {
|
|
457
|
-
const offset = element === 'not' ? 2 : 1
|
|
458
|
-
const aliases = cqn.SELECT.from.as
|
|
459
|
-
? [cqn.SELECT.from.as]
|
|
460
|
-
: cqn.SELECT.from.join
|
|
461
|
-
? cqn.SELECT.from.args.map(arg => arg.as)
|
|
462
|
-
: []
|
|
463
|
-
where[i + offset] = _ensureExpandedNestedWhereExists(where[i + offset], aliases)
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
if (element.xpr) {
|
|
467
|
-
_convertWhereExists(element.xpr) // > recursing into nested {xpr}
|
|
468
|
-
} else if (element === 'exists' && _isAny(where[i + 1])) {
|
|
469
|
-
where[i + 1] = _getWhereExistsSubSelect(cqn, where, i + 1, model, options)
|
|
470
|
-
} else if (element === 'not' && where[i + 1] === 'exists' && _isAll(where[i + 2])) {
|
|
471
|
-
where[i + 2] = _getWhereExistsSubSelect(cqn, where, i + 2, model, options)
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
const convertWhereExists = (cqn, model, options) => {
|
|
477
|
-
if (cqn.SELECT.where) _convertWhereExists(cqn.SELECT.where, cqn, model, options)
|
|
478
|
-
}
|
|
479
|
-
|
|
480
392
|
const _getRefIndex = (where, index) => {
|
|
481
393
|
if (
|
|
482
394
|
where[index - 1].ref &&
|
|
483
|
-
// REVISIT DRAFT HANDLING: cqn2cqn4sql must not be called there
|
|
484
395
|
where[index - 1].ref[where[index - 1].ref.length - 1] !== 'InProcessByUser' &&
|
|
485
396
|
where[index + 1].val !== undefined &&
|
|
486
397
|
where[index + 1].val !== null &&
|
|
@@ -490,7 +401,6 @@ const _getRefIndex = (where, index) => {
|
|
|
490
401
|
}
|
|
491
402
|
if (
|
|
492
403
|
where[index + 1].ref &&
|
|
493
|
-
// REVISIT DRAFT HANDLING: cqn2cqn4sql must not be called there
|
|
494
404
|
where[index + 1].ref[where[index + 1].ref.length - 1] !== 'InProcessByUser' &&
|
|
495
405
|
where[index - 1].val !== undefined &&
|
|
496
406
|
where[index - 1].val !== null &&
|
|
@@ -500,6 +410,77 @@ const _getRefIndex = (where, index) => {
|
|
|
500
410
|
}
|
|
501
411
|
}
|
|
502
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
|
+
|
|
503
484
|
const _convertNotEqual = (container, partName = 'where') => {
|
|
504
485
|
const where = container[partName]
|
|
505
486
|
|
|
@@ -667,7 +648,7 @@ const _convertPathExpression = (SELECT, model, options = {}) => {
|
|
|
667
648
|
if (alias) {
|
|
668
649
|
SELECT.from.as = alias
|
|
669
650
|
if (SELECT.where && alias !== prevAlias) {
|
|
670
|
-
SELECT.where =
|
|
651
|
+
SELECT.where = addAliasToExpression(SELECT.where, alias)
|
|
671
652
|
}
|
|
672
653
|
}
|
|
673
654
|
if (columns) {
|
|
@@ -690,7 +671,7 @@ const _convertPathExpression = (SELECT, model, options = {}) => {
|
|
|
690
671
|
}
|
|
691
672
|
// TODO: REVISIT: We need to add alias to subselect in .where, .columns, .from, ... etc
|
|
692
673
|
if (where) {
|
|
693
|
-
if (options.
|
|
674
|
+
if (options._4fiori) {
|
|
694
675
|
addToWhere({ SELECT }, where)
|
|
695
676
|
} else {
|
|
696
677
|
addToWhere({ SELECT }, removeIsActiveEntityRecursively(where))
|
|
@@ -702,8 +683,7 @@ const _convertPathExpression = (SELECT, model, options = {}) => {
|
|
|
702
683
|
const _convertSelect = (query, model, _options) => {
|
|
703
684
|
const options = Object.assign(
|
|
704
685
|
{
|
|
705
|
-
|
|
706
|
-
isDraft: _options.draft,
|
|
686
|
+
_4db: _options.service instanceof cds.DatabaseService,
|
|
707
687
|
isStreaming: query._streaming
|
|
708
688
|
},
|
|
709
689
|
_options
|
|
@@ -719,7 +699,7 @@ const _convertSelect = (query, model, _options) => {
|
|
|
719
699
|
if (cds.env.features.odata_new_parser) _flattenCQN(query)
|
|
720
700
|
|
|
721
701
|
// lambda functions
|
|
722
|
-
convertWhereExists(query, model, options)
|
|
702
|
+
convertWhereExists(query.SELECT, model, options)
|
|
723
703
|
|
|
724
704
|
// add 'or is null' in case of '!='
|
|
725
705
|
if (query.SELECT._4odata) {
|
|
@@ -728,7 +708,7 @@ const _convertSelect = (query, model, _options) => {
|
|
|
728
708
|
}
|
|
729
709
|
|
|
730
710
|
_convertPathExpression(query.SELECT, model, options)
|
|
731
|
-
rewriteAsterisks(query, model, options
|
|
711
|
+
rewriteAsterisks(query, model, options)
|
|
732
712
|
if (query.SELECT.where && _isCountNavigation(query.SELECT.where)) {
|
|
733
713
|
_convertCountNavigation(query.SELECT, model)
|
|
734
714
|
}
|
|
@@ -779,12 +759,6 @@ const _convertInsert = (query, model, options) => {
|
|
|
779
759
|
|
|
780
760
|
const resolvedView = resolveView(insert, model, cds.db)
|
|
781
761
|
|
|
782
|
-
if (query.INSERT.into.ref && query.INSERT.into.ref.length > 1) {
|
|
783
|
-
const copyFrom = [...query.INSERT.into.ref]
|
|
784
|
-
copyFrom.pop()
|
|
785
|
-
resolvedView._validationQuery = _convertSelect(SELECT.from({ ref: copyFrom }).columns([1]), model, options)
|
|
786
|
-
}
|
|
787
|
-
|
|
788
762
|
return resolvedView
|
|
789
763
|
}
|
|
790
764
|
|
|
@@ -830,7 +804,7 @@ const _convertDelete = (query, model, options) => {
|
|
|
830
804
|
|
|
831
805
|
if (alias) deleet.DELETE.from = { ref: [target], as: alias }
|
|
832
806
|
if (where) deleet.where(where)
|
|
833
|
-
if (query.DELETE.where) deleet.where(
|
|
807
|
+
if (query.DELETE.where) deleet.where(addAliasToExpression(query.DELETE.where, alias))
|
|
834
808
|
|
|
835
809
|
const targetEntity = model.definitions[target]
|
|
836
810
|
if (!targetEntity) return deleet
|
|
@@ -867,7 +841,7 @@ const _convertUpdate = (query, model, options) => {
|
|
|
867
841
|
|
|
868
842
|
if (alias) update.UPDATE.entity = { ref: [target], as: alias }
|
|
869
843
|
if (where) update.where(where)
|
|
870
|
-
if (query.UPDATE.where) update.where(
|
|
844
|
+
if (query.UPDATE.where) update.where(addAliasToExpression(query.UPDATE.where, alias))
|
|
871
845
|
|
|
872
846
|
const targetEntity = model.definitions[target]
|
|
873
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) => {
|
|
@@ -189,5 +199,6 @@ module.exports = {
|
|
|
189
199
|
getElementDeep,
|
|
190
200
|
isRootEntity,
|
|
191
201
|
getDataSubject,
|
|
192
|
-
alias2ref
|
|
202
|
+
alias2ref,
|
|
203
|
+
getComp2oneParents
|
|
193
204
|
}
|
|
@@ -183,7 +183,24 @@ const _resolvedKeys = (foreignKeys, targetKeys, fillChild) => {
|
|
|
183
183
|
|
|
184
184
|
for (let i = 0; i < foreignKeys.length; i++) {
|
|
185
185
|
const fk = foreignKeys[i]
|
|
186
|
-
|
|
186
|
+
let tk
|
|
187
|
+
|
|
188
|
+
/*
|
|
189
|
+
* REVISIT: poor man's look-up of target key with fallback to targetKeys[i]
|
|
190
|
+
* Look at elements, then try to find it in query and resolve recursively until you have the full path.
|
|
191
|
+
* Once you have the full path, you can find it in the target entity.
|
|
192
|
+
* NOTE: There can be projections upon projections and renamings in every projection. -> not yet covered!!!
|
|
193
|
+
*/
|
|
194
|
+
const tkCol =
|
|
195
|
+
targetKeys[i].parent.query &&
|
|
196
|
+
targetKeys[i].parent.query.SELECT.columns &&
|
|
197
|
+
targetKeys[i].parent.query.SELECT.columns.find(
|
|
198
|
+
c => c.ref && `${fk['@odata.foreignKey4']}_${c.ref.join('_')}` === fk.name
|
|
199
|
+
)
|
|
200
|
+
tk = tkCol && targetKeys.find(tk => tk.name === (tkCol.as ? tkCol.as : tkCol.ref.join('_')))
|
|
201
|
+
// with composition of aspects, the lookup fails -> we need this final fallback
|
|
202
|
+
if (!tk) tk = targetKeys[i]
|
|
203
|
+
|
|
187
204
|
if (fk._isStructured) {
|
|
188
205
|
i += _resolve4struct(targetKeys, fk, foreignKeyPropagations, fillChild, false, i)
|
|
189
206
|
} 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
|
}
|