@sap/cds 5.9.1 → 5.9.4
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 +48 -0
- package/lib/compile/etc/_localized.js +3 -2
- package/lib/compile/for/drafts.js +1 -1
- package/lib/connect/bindings.js +1 -1
- package/lib/connect/index.js +2 -3
- package/lib/env/requires.js +1 -1
- package/lib/index.js +2 -1
- package/lib/serve/Service-methods.js +28 -1
- package/lib/serve/adapters.js +6 -6
- package/lib/serve/factory.js +14 -9
- package/lib/serve/index.js +4 -3
- package/libx/_runtime/auth/index.js +16 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +6 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/BatchRequestListBuilder.js +3 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/dispatcherUtils.js +11 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +1 -3
- package/libx/_runtime/cds-services/services/Service.js +1 -1
- package/libx/_runtime/common/aspects/utils.js +8 -2
- package/libx/_runtime/common/composition/data.js +22 -13
- package/libx/_runtime/common/composition/delete.js +14 -12
- package/libx/_runtime/common/generic/auth/expand.js +1 -0
- package/libx/_runtime/common/generic/auth/restrict.js +3 -1
- package/libx/_runtime/{cds-services/services/utils → common/generic/auth}/restrictions.js +8 -1
- package/libx/_runtime/common/generic/input.js +1 -0
- package/libx/_runtime/common/generic/put.js +1 -0
- package/libx/_runtime/common/utils/cqn.js +5 -10
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +37 -63
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +28 -8
- package/libx/_runtime/common/utils/path.js +3 -3
- package/libx/_runtime/common/utils/require.js +2 -1
- package/libx/_runtime/common/utils/resolveView.js +3 -0
- package/libx/_runtime/common/utils/structured.js +6 -1
- package/libx/_runtime/db/Service.js +10 -0
- package/libx/_runtime/db/expand/expand-v2.js +13 -5
- package/libx/_runtime/db/expand/expandCQNToJoin.js +56 -26
- package/libx/_runtime/db/utils/generateAliases.js +9 -0
- package/libx/_runtime/extensibility/uiflex/handler/transformWRITE.js +1 -0
- package/libx/_runtime/fiori/generic/read.js +83 -31
- package/libx/_runtime/fiori/generic/readOverDraft.js +31 -19
- package/libx/_runtime/fiori/utils/handler.js +3 -0
- package/libx/_runtime/fiori/utils/where.js +38 -25
- package/libx/_runtime/hana/execute.js +18 -1
- package/libx/_runtime/hana/search2cqn4sql.js +4 -1
- package/libx/_runtime/sqlite/convertAssocToOneManaged.js +19 -12
- package/package.json +1 -1
|
@@ -19,9 +19,18 @@ const { deleteCondition, readAndDeleteKeywords, removeIsActiveEntityRecursively
|
|
|
19
19
|
const { getColumns } = require('../../cds-services/services/utils/columns')
|
|
20
20
|
const { adaptStreamCQN } = require('../../cds-services/adapter/odata-v4/utils/stream')
|
|
21
21
|
|
|
22
|
+
const _findSubselect = where => {
|
|
23
|
+
return where.find((e, i) => {
|
|
24
|
+
if (e.xpr) {
|
|
25
|
+
return _findSubselect(e.xpr)
|
|
26
|
+
}
|
|
27
|
+
return e.SELECT && where[i - 1] === 'exists'
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
22
31
|
const _findRootSubSelectFor = query => {
|
|
23
32
|
if (query.SELECT.where) {
|
|
24
|
-
const subSelect =
|
|
33
|
+
const subSelect = _findSubselect(query.SELECT.where)
|
|
25
34
|
return subSelect ? _findRootSubSelectFor(subSelect) : query
|
|
26
35
|
}
|
|
27
36
|
return query
|
|
@@ -422,8 +431,8 @@ const _allActive = (req, columns, model) => {
|
|
|
422
431
|
cqn.leftJoin(ensureDraftsSuffix(table.ref[0]) + ' as drafts').on(`${table.as}.${ids[0]} = drafts.${ids[0]}`)
|
|
423
432
|
|
|
424
433
|
for (let i = 1; i < ids.length; i++) {
|
|
425
|
-
//
|
|
426
|
-
cqn.and(
|
|
434
|
+
// this 'and' belongs to the join condition and is not a where and
|
|
435
|
+
cqn.and({ ref: [table.as, ids[i]] }, '=', { ref: ['drafts', ids[i]] })
|
|
427
436
|
}
|
|
428
437
|
}
|
|
429
438
|
|
|
@@ -557,27 +566,38 @@ const _alignAliasForUnion = (table, as, select) => {
|
|
|
557
566
|
return select
|
|
558
567
|
}
|
|
559
568
|
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
}
|
|
570
|
-
if (where[idx + 1] && where[idx + 1] === '=' && isTargetRef(where[idx + 2])) {
|
|
571
|
-
if (links.length) links.push('and')
|
|
572
|
-
links.push(el, '=', where[idx + 2])
|
|
573
|
-
}
|
|
569
|
+
const isTargetRef = (el, targetAlias) => targetAlias && el.ref && el.ref.length > 1 && el.ref[0] === targetAlias
|
|
570
|
+
|
|
571
|
+
const _joinFromWhere = (where, parentAlias, targetAlias) => {
|
|
572
|
+
return where.reduce((links, el, idx, where) => {
|
|
573
|
+
if (el.xpr) {
|
|
574
|
+
const result = _joinFromWhere(el.xpr, parentAlias, targetAlias)
|
|
575
|
+
if (result.length) {
|
|
576
|
+
if (links.length) links.push('and')
|
|
577
|
+
links.push(...result)
|
|
574
578
|
}
|
|
575
579
|
return links
|
|
576
|
-
}
|
|
577
|
-
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (el.ref && el.ref[0] === parentAlias && el.ref[el.ref.length - 1] !== 'IsActiveEntity') {
|
|
583
|
+
if (where[idx - 1] && where[idx - 1] === '=' && isTargetRef(where[idx - 2], targetAlias)) {
|
|
584
|
+
if (links.length) links.push('and')
|
|
585
|
+
links.push(el, '=', where[idx - 2])
|
|
586
|
+
}
|
|
587
|
+
if (where[idx + 1] && where[idx + 1] === '=' && isTargetRef(where[idx + 2], targetAlias)) {
|
|
588
|
+
if (links.length) links.push('and')
|
|
589
|
+
links.push(el, '=', where[idx + 2])
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return links
|
|
593
|
+
}, [])
|
|
578
594
|
}
|
|
579
595
|
|
|
580
|
-
const
|
|
596
|
+
const _findJoinInQuery = (query, parentAlias) => {
|
|
597
|
+
const targetAlias = query.SELECT.from.as
|
|
598
|
+
if (query.SELECT && query.SELECT.where) return _joinFromWhere(query.SELECT.where, parentAlias, targetAlias)
|
|
599
|
+
return []
|
|
600
|
+
}
|
|
581
601
|
|
|
582
602
|
const _isDraftField = element => element.ref && element.ref.length > 1 && element.ref[0] === 'DraftAdministrativeData'
|
|
583
603
|
|
|
@@ -597,6 +617,10 @@ const _isLogicalFunction = (where, index) => {
|
|
|
597
617
|
const _getWhereForActive = where => {
|
|
598
618
|
const activeWhere = []
|
|
599
619
|
for (let i = 0; i < where.length; i++) {
|
|
620
|
+
if (where[i].xpr) {
|
|
621
|
+
activeWhere.push({ xpr: _getWhereForActive(where[i].xpr) })
|
|
622
|
+
continue
|
|
623
|
+
}
|
|
600
624
|
if (_isDraftField(where[i])) {
|
|
601
625
|
activeWhere.push({ val: null })
|
|
602
626
|
} else if (_functionContainsDraftField(where[i])) {
|
|
@@ -721,10 +745,20 @@ const _getSiblingScenario = (req, columns, model, siblingIndex, nav) => {
|
|
|
721
745
|
const draftAdminAlias = _isDraftAdminScenario(req) && req.query.SELECT.from.as
|
|
722
746
|
const params = [...req.params].reverse()
|
|
723
747
|
const _getSiblingQueryFromWhere = (query, queryIndex, parentQuery) => {
|
|
724
|
-
if (query.SELECT && query.SELECT.where) {
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
748
|
+
if (query.SELECT && query.SELECT.where && queryIndex > 0) {
|
|
749
|
+
for (let i = 0; i < query.SELECT.where.length; i++) {
|
|
750
|
+
if (query.SELECT.where[i].xpr && queryIndex > 0) {
|
|
751
|
+
const sibilingQueryFromWhere = _getSiblingQueryFromWhere(
|
|
752
|
+
{ SELECT: { where: query.SELECT.where[i].xpr } },
|
|
753
|
+
queryIndex - 1,
|
|
754
|
+
query
|
|
755
|
+
)
|
|
756
|
+
if (sibilingQueryFromWhere) return sibilingQueryFromWhere
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
if (query.SELECT.where[i] === 'exists' && queryIndex > 0) {
|
|
760
|
+
return _getSiblingQueryFromWhere(query.SELECT.where[i + 1], queryIndex - 1, query)
|
|
761
|
+
}
|
|
728
762
|
}
|
|
729
763
|
}
|
|
730
764
|
const target = { name: query.SELECT.from.ref[0].id || query.SELECT.from.ref[0], as: query.SELECT.from.as }
|
|
@@ -741,9 +775,16 @@ const _getSiblingScenario = (req, columns, model, siblingIndex, nav) => {
|
|
|
741
775
|
return _getSiblingQueryFromWhere(req.query, siblingIndex)
|
|
742
776
|
}
|
|
743
777
|
|
|
744
|
-
const
|
|
745
|
-
|
|
746
|
-
|
|
778
|
+
const _replaceWhereExists = (query, _siblingIndex, siblingCQN) => {
|
|
779
|
+
if (query.SELECT && query.SELECT.where) {
|
|
780
|
+
for (let i = 0; i < query.SELECT.where.length; i++) {
|
|
781
|
+
const whereElement = query.SELECT.where[i]
|
|
782
|
+
if (whereElement.xpr) {
|
|
783
|
+
const res = _replaceWhereExists({ SELECT: { where: whereElement.xpr } }, _siblingIndex, siblingCQN)
|
|
784
|
+
if (res) return res
|
|
785
|
+
continue
|
|
786
|
+
}
|
|
787
|
+
|
|
747
788
|
const indexExists = query.SELECT.where.indexOf('exists')
|
|
748
789
|
if (indexExists > -1) {
|
|
749
790
|
if (_siblingIndex > 0) return _replaceWhereExists(query.SELECT.where[indexExists + 1], _siblingIndex - 1)
|
|
@@ -751,7 +792,10 @@ const _mergeSiblingIntoCQN = (cqn, { cqn: siblingCQN }, siblingIndex) => {
|
|
|
751
792
|
}
|
|
752
793
|
}
|
|
753
794
|
}
|
|
754
|
-
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const _mergeSiblingIntoCQN = (cqn, { cqn: siblingCQN }, siblingIndex) => {
|
|
798
|
+
return _replaceWhereExists(cqn, siblingIndex, siblingCQN)
|
|
755
799
|
}
|
|
756
800
|
|
|
757
801
|
const _getDraftDoc = (req, draftName, draftWhere) => {
|
|
@@ -796,6 +840,11 @@ const _getOrderByEnrichedColumns = (orderBy, columns, entity) => {
|
|
|
796
840
|
|
|
797
841
|
const _replaceDraftAlias = where => {
|
|
798
842
|
where.forEach(element => {
|
|
843
|
+
if (element.xpr) {
|
|
844
|
+
_replaceDraftAlias(element.xpr)
|
|
845
|
+
return
|
|
846
|
+
}
|
|
847
|
+
|
|
799
848
|
if (_isDraftField(element)) {
|
|
800
849
|
element.ref[0] = 'filterAdmin'
|
|
801
850
|
}
|
|
@@ -981,7 +1030,10 @@ const _validatedDraftOfWhichIAmOwner = (req, draftWhere, draftParameters, column
|
|
|
981
1030
|
_isValidDraftOfWhichIAmOwner(draftParameters.isActiveEntity) && _draftOfWhichIAmOwner(req, draftWhere, columns)
|
|
982
1031
|
|
|
983
1032
|
const _draftInSubSelect = (where, req) => {
|
|
984
|
-
return where.some(({ SELECT }) => {
|
|
1033
|
+
return where.some(({ SELECT, xpr }) => {
|
|
1034
|
+
if (xpr) {
|
|
1035
|
+
return _draftInSubSelect(xpr, req)
|
|
1036
|
+
}
|
|
985
1037
|
if (SELECT && SELECT.where) {
|
|
986
1038
|
const isActiveEntity = readAndDeleteKeywords(['IsActiveEntity'], SELECT.where, false)
|
|
987
1039
|
if (isActiveEntity) {
|
|
@@ -1022,7 +1074,7 @@ const _generateCQN = (originalFrom, req, columns, model) => {
|
|
|
1022
1074
|
return _draftAdminTable(req)
|
|
1023
1075
|
}
|
|
1024
1076
|
|
|
1025
|
-
if (!req.query.SELECT.where
|
|
1077
|
+
if (!req.query.SELECT.where) {
|
|
1026
1078
|
return _allActive(req, columns, model)
|
|
1027
1079
|
}
|
|
1028
1080
|
|
|
@@ -1032,7 +1084,7 @@ const _generateCQN = (originalFrom, req, columns, model) => {
|
|
|
1032
1084
|
if (
|
|
1033
1085
|
draftParameters.isActiveEntity &&
|
|
1034
1086
|
_isTrue(draftParameters.isActiveEntity.value.val) &&
|
|
1035
|
-
!draftParameters.siblingIsActive &&
|
|
1087
|
+
!(draftParameters.siblingIsActive && draftParameters.siblingIsActive.value.val === null) &&
|
|
1036
1088
|
!draftParameters.hasDraftEntity
|
|
1037
1089
|
) {
|
|
1038
1090
|
return _allActive(req, columns, model)
|
|
@@ -5,18 +5,14 @@ const { readAndDeleteKeywords } = require('../utils/where')
|
|
|
5
5
|
const { cqn2cqn4sql } = require('../../common/utils/cqn2cqn4sql')
|
|
6
6
|
const { isActiveEntityRequested } = require('../../../_runtime/fiori/utils/where')
|
|
7
7
|
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
cqnDraft.where(whereDraft)
|
|
12
|
-
|
|
13
|
-
if (result && result.value.val === false) {
|
|
14
|
-
const fromRef = cqnDraft.SELECT.from.ref
|
|
15
|
-
cqnDraft.SELECT.from.ref[fromRef.length - 1] = ensureDraftsSuffix(fromRef[fromRef.length - 1])
|
|
16
|
-
}
|
|
8
|
+
const _modifyWhere = (where, context) => {
|
|
9
|
+
for (let i = 0; i < where.length; i++) {
|
|
10
|
+
const element = where[i]
|
|
17
11
|
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
if (element.xpr) {
|
|
13
|
+
_modifyWhere(element.xpr, context)
|
|
14
|
+
continue
|
|
15
|
+
}
|
|
20
16
|
|
|
21
17
|
if (element.SELECT) {
|
|
22
18
|
const subCqnDraft = SELECT.from(
|
|
@@ -27,12 +23,25 @@ const _modifyCQN = (cqnDraft, where, context) => {
|
|
|
27
23
|
[1]
|
|
28
24
|
)
|
|
29
25
|
|
|
30
|
-
|
|
26
|
+
where[i] = subCqnDraft
|
|
31
27
|
_modifyCQN(subCqnDraft, element.SELECT.where, context)
|
|
32
28
|
}
|
|
33
29
|
}
|
|
34
30
|
}
|
|
35
31
|
|
|
32
|
+
const _modifyCQN = (cqnDraft, where, context) => {
|
|
33
|
+
const whereDraft = [...where]
|
|
34
|
+
const result = readAndDeleteKeywords(['IsActiveEntity'], whereDraft)
|
|
35
|
+
cqnDraft.where(whereDraft)
|
|
36
|
+
|
|
37
|
+
if (result && result.value.val === false) {
|
|
38
|
+
const fromRef = cqnDraft.SELECT.from.ref
|
|
39
|
+
cqnDraft.SELECT.from.ref[fromRef.length - 1] = ensureDraftsSuffix(fromRef[fromRef.length - 1])
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
_modifyWhere(cqnDraft.SELECT.where, context)
|
|
43
|
+
}
|
|
44
|
+
|
|
36
45
|
const _hasNavToNonDraftEnclosedAssoc = (pathSegments, definitions, excludeAssoc) => {
|
|
37
46
|
if (pathSegments.length < 2) return false
|
|
38
47
|
const entity = definitions[pathSegments[0]]
|
|
@@ -67,19 +76,22 @@ const _shouldReadOverDraft = (req, definitions) => {
|
|
|
67
76
|
const rootEntityName = typeof firstFromRef === 'string' ? firstFromRef : firstFromRef.id
|
|
68
77
|
const rootEntity = definitions[rootEntityName]
|
|
69
78
|
|
|
70
|
-
// read over the draft only if the root entity is draft-enabled
|
|
71
|
-
|
|
72
|
-
if (!!rootEntity._isDraftEnabled && isActiveEntityRequested(firstFromRef.where)) return false
|
|
79
|
+
// read over the draft only if the root entity is draft-enabled
|
|
80
|
+
if (!rootEntity._isDraftEnabled) return false
|
|
73
81
|
|
|
74
|
-
// read over the draft if the navigation
|
|
75
|
-
//
|
|
76
|
-
|
|
82
|
+
// read over the draft only if the navigation starts from a draft entity, e.g.,
|
|
83
|
+
// /Books(ID=1, IsActiveEntity=false)
|
|
84
|
+
if (isActiveEntityRequested(firstFromRef.where)) return false
|
|
77
85
|
|
|
86
|
+
const pathSegments = fromRef.map(path => (typeof path === 'string' ? path : path.id))
|
|
78
87
|
const excludeAssoc = assoc => {
|
|
79
88
|
if (assoc.name === 'DraftAdministrativeData' || assoc.name === 'SiblingEntity') return true
|
|
80
89
|
return false
|
|
81
90
|
}
|
|
82
91
|
|
|
92
|
+
// Read over the draft only if:
|
|
93
|
+
// - the navigation target is an association and
|
|
94
|
+
// - isn't annotated with the @odata.draft.enclosed annotation
|
|
83
95
|
return _hasNavToNonDraftEnclosedAssoc(pathSegments, definitions, excludeAssoc)
|
|
84
96
|
}
|
|
85
97
|
|
|
@@ -109,7 +121,7 @@ const _readOverDraftHandler = async function (req, next) {
|
|
|
109
121
|
|
|
110
122
|
const hasDraftEntity = hasDraft(definitions, sqlQuery)
|
|
111
123
|
|
|
112
|
-
if (hasDraftEntity && sqlQuery.SELECT.where && sqlQuery.SELECT.where.length
|
|
124
|
+
if (hasDraftEntity && sqlQuery.SELECT.where && sqlQuery.SELECT.where.length) {
|
|
113
125
|
let cqnDraft = SELECT.from({
|
|
114
126
|
ref: [...sqlQuery.SELECT.from.ref],
|
|
115
127
|
as: sqlQuery.SELECT.from.as
|
|
@@ -93,6 +93,9 @@ const getUpdateDraftAdminCQN = ({ user }, draftUUID) => {
|
|
|
93
93
|
const _addAlias = (where, tableName) => {
|
|
94
94
|
// copy where
|
|
95
95
|
return where.map(element => {
|
|
96
|
+
if (element.xpr) {
|
|
97
|
+
return { xpr: _addAlias(element.xpr, tableName) }
|
|
98
|
+
}
|
|
96
99
|
if (element.ref && element.ref.length === 1) {
|
|
97
100
|
// and copy ref
|
|
98
101
|
return { ref: [tableName, element.ref[0]] }
|
|
@@ -57,7 +57,9 @@ const _removeIsActiveEntityCondition = where => {
|
|
|
57
57
|
} else if (where[i] === 'and' && where[i + 1] === '(' && _isActiveEntity(where[i + 2])) {
|
|
58
58
|
i = i + 6
|
|
59
59
|
} else if (where[i].xpr) {
|
|
60
|
-
|
|
60
|
+
if (where[i].xpr.length) {
|
|
61
|
+
newWhere.push({ xpr: _removeIsActiveEntityCondition(where[i].xpr) })
|
|
62
|
+
}
|
|
61
63
|
i++
|
|
62
64
|
} else {
|
|
63
65
|
newWhere.push(where[i])
|
|
@@ -65,7 +67,7 @@ const _removeIsActiveEntityCondition = where => {
|
|
|
65
67
|
}
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
if (newWhere[0] === 'and') {
|
|
70
|
+
if (newWhere[0] === 'and' || newWhere[0] === 'or') {
|
|
69
71
|
newWhere.splice(0, 1)
|
|
70
72
|
} else if (newWhere[0] === '(' && newWhere[1] === 'and') {
|
|
71
73
|
newWhere.splice(0, 2)
|
|
@@ -90,32 +92,35 @@ const deleteCondition = (index, whereCondition, isXpr = false) => {
|
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
const readAndDeleteKeywords = (keywords, whereCondition, toDelete = true) => {
|
|
93
|
-
let index =
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
if (
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
let index = -1
|
|
96
|
+
for (let i = 0; i < whereCondition.length; i++) {
|
|
97
|
+
const entry = whereCondition[i]
|
|
98
|
+
if (entry.xpr) {
|
|
99
|
+
const result = readAndDeleteKeywords(keywords, entry.xpr, toDelete)
|
|
100
|
+
if (result) {
|
|
101
|
+
if (entry.xpr.length === 0) {
|
|
102
|
+
deleteCondition(i, whereCondition, true)
|
|
103
|
+
}
|
|
104
|
+
return result
|
|
105
|
+
}
|
|
106
|
+
} else if (entry.ref) {
|
|
107
|
+
const refLastIndex = entry.ref.length - 1
|
|
108
|
+
|
|
109
|
+
if (keywords.length === 1) {
|
|
110
|
+
if (entry.ref[refLastIndex] === keywords[0]) {
|
|
111
|
+
index = i
|
|
112
|
+
break
|
|
113
|
+
}
|
|
99
114
|
}
|
|
100
|
-
return result
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
index = whereCondition.findIndex(({ ref }) => {
|
|
105
|
-
if (!ref) {
|
|
106
|
-
return false
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const refLastIndex = ref.length - 1
|
|
110
|
-
|
|
111
|
-
if (keywords.length === 1) {
|
|
112
|
-
return ref[refLastIndex] === keywords[0]
|
|
113
|
-
}
|
|
114
115
|
|
|
115
|
-
|
|
116
|
-
|
|
116
|
+
if (keywords.length === 2 && entry.ref.length >= 2) {
|
|
117
|
+
if (entry.ref[refLastIndex - 1] === keywords[0] && entry.ref[refLastIndex] === keywords[1]) {
|
|
118
|
+
index = i
|
|
119
|
+
break
|
|
120
|
+
}
|
|
121
|
+
}
|
|
117
122
|
}
|
|
118
|
-
}
|
|
123
|
+
}
|
|
119
124
|
|
|
120
125
|
if (index === -1) {
|
|
121
126
|
return
|
|
@@ -135,6 +140,10 @@ const readAndDeleteKeywords = (keywords, whereCondition, toDelete = true) => {
|
|
|
135
140
|
|
|
136
141
|
const removeIsActiveEntityRecursively = where => {
|
|
137
142
|
for (const entry of where) {
|
|
143
|
+
if (entry.xpr) {
|
|
144
|
+
entry.xpr = removeIsActiveEntityRecursively(entry.xpr)
|
|
145
|
+
continue
|
|
146
|
+
}
|
|
138
147
|
if (entry.SELECT && entry.SELECT.where && entry.SELECT.from.ref && !entry.SELECT.from.ref[0].endsWith('_drafts')) {
|
|
139
148
|
entry.SELECT.where = removeIsActiveEntityRecursively(entry.SELECT.where)
|
|
140
149
|
|
|
@@ -153,6 +162,10 @@ const isActiveEntityRequested = where => {
|
|
|
153
162
|
let i = 0
|
|
154
163
|
|
|
155
164
|
while (where[i]) {
|
|
165
|
+
if (where[i].xpr) {
|
|
166
|
+
const isRequested = isActiveEntityRequested(where.xpr)
|
|
167
|
+
if (isRequested) return true
|
|
168
|
+
}
|
|
156
169
|
if (
|
|
157
170
|
where[i].ref &&
|
|
158
171
|
where[i].ref[where[i].ref.length - 1] === 'IsActiveEntity' &&
|
|
@@ -280,11 +280,28 @@ function executeInsertCQN(model, dbc, query, user, locale, txTimestamp) {
|
|
|
280
280
|
}
|
|
281
281
|
|
|
282
282
|
return _executeSimpleSQL(dbc, sql, values).then(affectedRows => {
|
|
283
|
+
const entriesOrRows = query.INSERT.entries || query.INSERT.rows
|
|
284
|
+
const affectedRowsCount = Array.isArray(affectedRows)
|
|
285
|
+
? affectedRows.reduce((sum, rows) => sum + rows, 0)
|
|
286
|
+
: affectedRows
|
|
287
|
+
if (entriesOrRows && entriesOrRows.length !== affectedRowsCount) {
|
|
288
|
+
LOG._warn &&
|
|
289
|
+
LOG.warn(
|
|
290
|
+
`INSERT input deviates from affected rows (input: ${entriesOrRows.length}, affectedRows: ${affectedRowsCount})`,
|
|
291
|
+
{
|
|
292
|
+
sql,
|
|
293
|
+
args: values && values.length,
|
|
294
|
+
values,
|
|
295
|
+
query
|
|
296
|
+
}
|
|
297
|
+
)
|
|
298
|
+
throw new Error('Possible data loss by INSERT into HANA db. Please, update a corresponding HANA driver.')
|
|
299
|
+
}
|
|
283
300
|
// InsertResult needs an object per row with its values
|
|
284
301
|
// query.INSERT.values -> one row
|
|
285
302
|
if (query.INSERT.values) return [{ affectedRows: 1, values: [values] }]
|
|
286
303
|
// query.INSERT.entries or .rows -> multiple rows
|
|
287
|
-
if (
|
|
304
|
+
if (entriesOrRows) return values.map(v => ({ affectedRows: 1, values: v }))
|
|
288
305
|
// INSERT into SELECT
|
|
289
306
|
return [{ affectedRows }]
|
|
290
307
|
})
|
|
@@ -37,8 +37,11 @@ const search2cqn4sql = (query, entity, options) => {
|
|
|
37
37
|
if (resolveLocalizedDataAtRuntime) {
|
|
38
38
|
const onCondition = entity._relations[localizedAssociation.name].join(localizedAssociation.target, entity.name)
|
|
39
39
|
|
|
40
|
+
// REVISIT this is dirty but works for now
|
|
40
41
|
// replace $user_locale placeholder with the user locale or the HANA session context
|
|
41
|
-
onCondition[
|
|
42
|
+
if (onCondition[0].xpr) {
|
|
43
|
+
onCondition[0].xpr[onCondition[0].xpr.length - 1] = { val: locale || "SESSION_CONTEXT('LOCALE')" }
|
|
44
|
+
} else onCondition[onCondition.length - 2] = { val: locale || "SESSION_CONTEXT('LOCALE')" }
|
|
42
45
|
|
|
43
46
|
// inner join the target table with the _texts table (the _texts table contains
|
|
44
47
|
// the translated texts)
|
|
@@ -17,29 +17,36 @@ const _getConvertibleEntries = req => {
|
|
|
17
17
|
return [...orders, ...groups, ...filters, ...havings]
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (!req.target || !req.target.elements) return
|
|
20
|
+
const _convert = (refEntries, req) => {
|
|
21
|
+
for (const refEntry of refEntries) {
|
|
22
|
+
if (refEntry.xpr) {
|
|
23
|
+
_convert(refEntry.xpr, req)
|
|
24
|
+
continue
|
|
25
|
+
}
|
|
29
26
|
|
|
30
|
-
for (const refEntry of _getConvertibleEntries(req)) {
|
|
31
27
|
if (!refEntry.ref || refEntry.ref.length < 2) {
|
|
32
28
|
// only check refs in format {ref: ['assoc', 'id']}
|
|
33
29
|
continue
|
|
34
30
|
}
|
|
35
|
-
|
|
36
31
|
const element = req.target.elements[refEntry.ref[0]]
|
|
37
32
|
if (!element || !element.is2one) return
|
|
38
|
-
|
|
39
33
|
_convertRefForAssocToOneManaged(element, refEntry)
|
|
40
34
|
}
|
|
41
35
|
}
|
|
42
36
|
|
|
37
|
+
// REVISIT once sql can handle structured keys properly, this handler should not be required anymore
|
|
38
|
+
const _handler = function (req) {
|
|
39
|
+
// do simple checks upfront and exit early
|
|
40
|
+
if (!(req.query && req.query.SELECT) || typeof req.query === 'string') return
|
|
41
|
+
if (!req.query.SELECT.orderBy && !req.query.SELECT.groupBy && !req.query.SELECT.where && !req.query.SELECT.having) {
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!req.target || !req.target.elements) return
|
|
46
|
+
|
|
47
|
+
_convert(_getConvertibleEntries(req), req)
|
|
48
|
+
}
|
|
49
|
+
|
|
43
50
|
_handler._initial = true
|
|
44
51
|
|
|
45
52
|
module.exports = _handler
|