@sap/cds 5.7.5 → 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 +72 -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/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 -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/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 +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 +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 +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/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 +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 +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/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 +8 -6
- 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 +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/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 +19 -16
- 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/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 +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 +33 -20
- package/libx/_runtime/remote/utils/data.js +52 -11
- 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/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
|
@@ -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) => {
|
|
@@ -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
|
}
|
|
@@ -152,9 +152,15 @@ const _newColumns = (columns = [], transition, service, withAlias = false) => {
|
|
|
152
152
|
const newColumns = []
|
|
153
153
|
|
|
154
154
|
columns.forEach(column => {
|
|
155
|
+
let newColumn
|
|
156
|
+
if (column.func) {
|
|
157
|
+
newColumn = { ...column }
|
|
158
|
+
newColumn.args = _newColumns(column.args, transition, service, withAlias)
|
|
159
|
+
newColumns.push(newColumn)
|
|
160
|
+
return newColumns
|
|
161
|
+
}
|
|
155
162
|
const mapped = column.ref && transition.mapping.get(column.ref[0])
|
|
156
163
|
|
|
157
|
-
let newColumn
|
|
158
164
|
if (mapped && mapped.ref) {
|
|
159
165
|
newColumn = { ...column }
|
|
160
166
|
|
|
@@ -238,7 +244,7 @@ const _newEntries = (entries = [], transition, service) =>
|
|
|
238
244
|
const _newWhere = (where = [], transition, tableName, alias, isSubselect = false) => {
|
|
239
245
|
const newWhere = where.map(whereElement => {
|
|
240
246
|
const newWhereElement = { ...whereElement }
|
|
241
|
-
if (!whereElement.ref && !whereElement.SELECT) return whereElement
|
|
247
|
+
if (!whereElement.ref && !whereElement.SELECT && !whereElement.func) return whereElement
|
|
242
248
|
if (whereElement.SELECT && whereElement.SELECT.where) {
|
|
243
249
|
newWhereElement.SELECT.where = _newWhere(whereElement.SELECT.where, transition, tableName, alias, true)
|
|
244
250
|
return newWhereElement
|
|
@@ -246,6 +252,9 @@ const _newWhere = (where = [], transition, tableName, alias, isSubselect = false
|
|
|
246
252
|
if (newWhereElement.ref) {
|
|
247
253
|
_newWhereRef(newWhereElement, transition, alias, tableName, isSubselect)
|
|
248
254
|
return newWhereElement
|
|
255
|
+
} else if (newWhereElement.func) {
|
|
256
|
+
newWhereElement.args = _newWhere(newWhereElement.args, transition, tableName, alias)
|
|
257
|
+
return newWhereElement
|
|
249
258
|
} else {
|
|
250
259
|
return whereElement
|
|
251
260
|
}
|
|
@@ -337,8 +346,7 @@ const _newSelect = (query, transitions, service) => {
|
|
|
337
346
|
}
|
|
338
347
|
if (!newSelect.columns && targetTransition.mapping.size) newSelect.columns = _initialColumns(targetTransition)
|
|
339
348
|
if (newSelect.columns) {
|
|
340
|
-
|
|
341
|
-
rewriteAsterisks({ SELECT: query.SELECT }, service.model, isDB)
|
|
349
|
+
rewriteAsterisks({ SELECT: query.SELECT }, service.model, { _4db: service instanceof cds.DatabaseService })
|
|
342
350
|
newSelect.columns = _newColumns(newSelect.columns, targetTransition, service, service.kind !== 'app-service')
|
|
343
351
|
}
|
|
344
352
|
if (newSelect.having) newSelect.having = _newColumns(newSelect.having, targetTransition)
|
|
@@ -30,10 +30,11 @@ const _cqlDraftColumns = target => {
|
|
|
30
30
|
]
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
const _expandColumn = (column, target,
|
|
33
|
+
const _expandColumn = (column, target, _4db) => {
|
|
34
34
|
if (!(column.ref && column.expand)) return
|
|
35
35
|
const nextTarget = getNavigationIfStruct(target, column.ref)
|
|
36
|
-
if (nextTarget && nextTarget._target && nextTarget._target.elements)
|
|
36
|
+
if (nextTarget && nextTarget._target && nextTarget._target.elements)
|
|
37
|
+
_rewriteAsterisks(column, nextTarget._target, _4db)
|
|
37
38
|
return column
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -46,32 +47,33 @@ const rewriteExpandAsterisk = (columns, target) => {
|
|
|
46
47
|
const { expand } = columns.splice(expandAllColIdx, 1)[0]
|
|
47
48
|
for (const elName in target.elements) {
|
|
48
49
|
if (target.elements[elName]._target && !columns.find(col => col.expand && col.ref && col.ref[0] === elName)) {
|
|
50
|
+
if (elName === 'SiblingEntity') continue
|
|
49
51
|
columns.push({ ref: [elName], expand: [...expand] })
|
|
50
52
|
}
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
const _rewriteAsterisk = (columns, target,
|
|
57
|
+
const _rewriteAsterisk = (columns, target, _4db, isRoot) => {
|
|
56
58
|
const asteriskColumnIndex = columns.findIndex(col => isAsteriskColumn(col))
|
|
57
59
|
if (asteriskColumnIndex > -1) {
|
|
58
60
|
columns.splice(
|
|
59
61
|
asteriskColumnIndex,
|
|
60
62
|
1,
|
|
61
|
-
...getColumns(target, {
|
|
63
|
+
...getColumns(target, { _4db })
|
|
62
64
|
.map(c => ({ ref: [c.name] }))
|
|
63
65
|
.filter(c => !columns.find(_isDuplicate(c)) && (isRoot || c.ref[0] !== 'DraftAdministrativeData_DraftUUID'))
|
|
64
66
|
)
|
|
65
67
|
}
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
const _rewriteAsterisks = (cqn, target,
|
|
70
|
+
const _rewriteAsterisks = (cqn, target, _4db, isRoot) => {
|
|
69
71
|
if (cqn.expand === '*') cqn.expand = ['*']
|
|
70
72
|
const columns = cqn.expand || cqn.columns
|
|
71
|
-
_rewriteAsterisk(columns, target,
|
|
73
|
+
_rewriteAsterisk(columns, target, _4db, isRoot)
|
|
72
74
|
rewriteExpandAsterisk(columns, target)
|
|
73
75
|
for (const column of columns) {
|
|
74
|
-
_expandColumn(column, target,
|
|
76
|
+
_expandColumn(column, target, _4db)
|
|
75
77
|
}
|
|
76
78
|
return columns
|
|
77
79
|
}
|
|
@@ -83,9 +85,17 @@ const _targetOfQueryIfNotDraft = (query, model) => {
|
|
|
83
85
|
return target
|
|
84
86
|
}
|
|
85
87
|
|
|
86
|
-
const rewriteAsterisks = (query, model,
|
|
88
|
+
const rewriteAsterisks = (query, model, options) => {
|
|
89
|
+
/*
|
|
90
|
+
* REVISIT:
|
|
91
|
+
* - _4db: called on db level
|
|
92
|
+
* - _4fiori: cqn2cqn4sql called in a fiori handler
|
|
93
|
+
* this is extremely obfuscated!
|
|
94
|
+
*/
|
|
95
|
+
const { _4db, _4fiori } = options
|
|
96
|
+
|
|
87
97
|
if (!query.SELECT.columns || !query.SELECT.columns.length) {
|
|
88
|
-
if (
|
|
98
|
+
if (_4db || _4fiori) {
|
|
89
99
|
if (
|
|
90
100
|
query.SELECT.from.SET &&
|
|
91
101
|
query.SELECT.from.SET.args[0] &&
|
|
@@ -101,7 +111,7 @@ const rewriteAsterisks = (query, model, db = false, isDraft = false, onlyKeys =
|
|
|
101
111
|
for (const arg of query.SELECT.from.args) {
|
|
102
112
|
const _targetName = arg.ref[0].id || arg.ref[0]
|
|
103
113
|
const _target = model.definitions[ensureNoDraftsSuffix(_targetName)]
|
|
104
|
-
const columns = getColumns(_target, {
|
|
114
|
+
const columns = getColumns(_target, { _4db })
|
|
105
115
|
.filter(
|
|
106
116
|
c =>
|
|
107
117
|
!query.SELECT.columns.some(
|
|
@@ -116,16 +126,20 @@ const rewriteAsterisks = (query, model, db = false, isDraft = false, onlyKeys =
|
|
|
116
126
|
} else {
|
|
117
127
|
const target = _targetOfQueryIfNotDraft(query, model)
|
|
118
128
|
if (!target) return
|
|
119
|
-
|
|
120
|
-
|
|
129
|
+
|
|
130
|
+
query.SELECT.columns = getColumns(target, { _4db }).map(col => ({ ref: [col.name] }))
|
|
131
|
+
if (_4db && target._isDraftEnabled) query.SELECT.columns.push(..._cqlDraftColumns(target))
|
|
121
132
|
}
|
|
122
133
|
}
|
|
134
|
+
|
|
123
135
|
return
|
|
124
136
|
}
|
|
137
|
+
|
|
125
138
|
const target = _targetOfQueryIfNotDraft(query, model)
|
|
126
139
|
if (!target) return
|
|
140
|
+
|
|
127
141
|
// REVISIT: Also support JOINs/SETs here
|
|
128
|
-
query.SELECT.columns = _rewriteAsterisks(query.SELECT, target,
|
|
142
|
+
query.SELECT.columns = _rewriteAsterisks(query.SELECT, target, _4db, true)
|
|
129
143
|
}
|
|
130
144
|
|
|
131
145
|
module.exports = {
|
|
@@ -18,8 +18,8 @@ const _search2cqn4sql = (query, model, options = {}) => {
|
|
|
18
18
|
|
|
19
19
|
// Call custom (optimized search to cqn for sql implementation) that tries
|
|
20
20
|
// to optimize the search behavior for a specific database service.
|
|
21
|
-
//
|
|
22
|
-
if (typeof search2cqn4sql === 'function' && !query.SELECT.
|
|
21
|
+
// REVISIT: $search query option combined with $count is not currently optimized
|
|
22
|
+
if (typeof search2cqn4sql === 'function' && !query.SELECT.count) {
|
|
23
23
|
const search2cqnOptions = { columns, locale: options.locale }
|
|
24
24
|
return search2cqn4sql(query, entity, search2cqnOptions)
|
|
25
25
|
}
|
|
@@ -30,9 +30,14 @@ const _search2cqn4sql = (query, model, options = {}) => {
|
|
|
30
30
|
query._aggregated || /* if new parser */ query.SELECT.groupBy ? query.having(expression) : query.where(expression)
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
const search2cqn4sql = (query, model, options) => {
|
|
34
|
+
if (query.SELECT.from.SET) {
|
|
35
|
+
return query.SELECT.from.SET.args.forEach(arg => _search2cqn4sql(arg, model, options))
|
|
36
|
+
}
|
|
37
|
+
|
|
37
38
|
return _search2cqn4sql(query, model, options)
|
|
38
39
|
}
|
|
40
|
+
|
|
41
|
+
// convert $search system query option to WHERE/HAVING clause using
|
|
42
|
+
// the operator LIKE or CONTAINS
|
|
43
|
+
module.exports = search2cqn4sql
|
|
@@ -1,11 +1,28 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
const cds = require('../../../../libx/_runtime/cds')
|
|
2
|
+
|
|
3
|
+
const getAppMetadata = () => {
|
|
4
|
+
const appMetadata = cds.env.app
|
|
5
|
+
|
|
6
|
+
if (appMetadata) {
|
|
7
|
+
return {
|
|
8
|
+
appID: appMetadata.id,
|
|
9
|
+
appName: appMetadata.name,
|
|
10
|
+
appURL: appMetadata.url
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// fallback: if the app metadata is undefined, then extract the metadata from the underlying environment (CF/Kyma/...)
|
|
15
|
+
const vcapApplication = process.env.VCAP_APPLICATION && JSON.parse(process.env.VCAP_APPLICATION)
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
appID: vcapApplication && vcapApplication.application_id,
|
|
19
|
+
appName: vcapApplication && vcapApplication.application_name,
|
|
20
|
+
appURL:
|
|
21
|
+
vcapApplication &&
|
|
22
|
+
vcapApplication.application_uris &&
|
|
23
|
+
vcapApplication.application_uris[0] &&
|
|
24
|
+
`https://${vcapApplication.application_uris[0].replace(/^https?:\/\//, '')}`
|
|
25
|
+
}
|
|
11
26
|
}
|
|
27
|
+
|
|
28
|
+
module.exports = getAppMetadata()
|