@sap/cds 5.4.6 → 5.5.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 +208 -2
- package/apis/ql.d.ts +17 -15
- package/app/index.js +1 -1
- package/bin/build/buildTaskEngine.js +26 -42
- package/bin/build/buildTaskFactory.js +6 -10
- package/bin/build/buildTaskHandler.js +2 -4
- package/bin/build/buildTaskProvider.js +3 -1
- package/bin/build/buildTaskProviderFactory.js +9 -15
- package/bin/build/constants.js +15 -3
- package/bin/build/index.js +5 -4
- package/bin/build/mtaUtil.js +8 -11
- package/bin/build/provider/buildTaskHandlerEdmx.js +63 -6
- package/bin/build/provider/buildTaskHandlerInternal.js +2 -34
- package/bin/build/provider/buildTaskProviderInternal.js +16 -42
- package/bin/build/provider/fiori/index.js +13 -24
- package/bin/build/provider/hana/2migration.js +17 -15
- package/bin/build/provider/hana/2tabledata.js +52 -48
- package/bin/build/provider/hana/index.js +27 -25
- package/bin/build/provider/hana/migrationtable.js +91 -67
- package/bin/build/provider/java-cf/index.js +14 -24
- package/bin/build/provider/mtx/index.js +12 -14
- package/bin/build/provider/node-cf/index.js +18 -32
- package/bin/cds.js +5 -5
- package/bin/serve.js +29 -23
- package/bin/version.js +0 -1
- package/lib/compile/etc/_localized.js +4 -9
- package/lib/compile/for/sql.js +5 -2
- package/lib/compile/parse.js +25 -17
- package/lib/compile/to/srvinfo.js +2 -1
- package/lib/connect/bindings.js +2 -1
- package/lib/connect/index.js +48 -49
- package/lib/core/classes.js +1 -1
- package/lib/core/reflect.js +10 -2
- package/lib/deploy.js +26 -23
- package/lib/env/defaults.js +13 -6
- package/lib/env/index.js +73 -78
- package/lib/env/requires.js +38 -19
- package/lib/index.js +9 -10
- package/lib/lazy.js +2 -2
- package/lib/log/index.js +33 -45
- package/lib/log/service/index.js +2 -2
- package/lib/ql/CREATE.js +14 -9
- package/lib/ql/DELETE.js +6 -5
- package/lib/ql/DROP.js +12 -9
- package/lib/ql/INSERT.js +40 -16
- package/lib/ql/Query.js +67 -40
- package/lib/ql/SELECT.js +162 -127
- package/lib/ql/UPDATE.js +74 -42
- package/lib/ql/Whereable.js +77 -87
- package/lib/ql/index.js +36 -24
- package/lib/ql/parse.js +35 -0
- package/lib/req/context.js +44 -8
- package/lib/req/locale.js +7 -7
- package/lib/serve/Service-api.js +21 -14
- package/lib/serve/Service-dispatch.js +28 -12
- package/lib/serve/Transaction.js +22 -10
- package/lib/serve/index.js +16 -11
- package/lib/utils/axios.js +23 -16
- package/lib/utils/data.js +35 -0
- package/lib/utils/tests.js +27 -18
- package/libx/_runtime/audit/generic/personal/access.js +81 -0
- package/libx/_runtime/audit/generic/personal/constants.js +4 -0
- package/libx/_runtime/audit/generic/personal/index.js +50 -0
- package/libx/_runtime/audit/generic/personal/modification.js +138 -0
- package/libx/_runtime/audit/generic/personal/utils.js +186 -0
- package/libx/_runtime/audit/utils/v2.js +10 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +5 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +6 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +5 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +5 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +2 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +4 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +7 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +59 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +11 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +6 -10
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +3 -46
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +2 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/createToCQN.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/deleteToCQN.js +4 -3
- 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/index.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/updateToCQN.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +16 -18
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/EdmEntityType.js +6 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/format/RepresentationKind.js +4 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/core/OdataRequest.js +1 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/SerializerFactory.js +15 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/OperationValidator.js +1 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +8 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +6 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/omitValues.js +12 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +1 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +7 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +14 -18
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +13 -13
- package/libx/_runtime/cds-services/adapter/rest/handlers/create.js +0 -1
- package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +2 -1
- package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +2 -2
- package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +2 -4
- package/libx/_runtime/cds-services/adapter/rest/utils/result.js +4 -2
- package/libx/_runtime/cds-services/services/Service.js +40 -5
- package/libx/_runtime/cds-services/services/utils/columns.js +13 -7
- package/libx/_runtime/cds-services/services/utils/compareJson.js +88 -4
- package/libx/_runtime/cds-services/services/utils/differ.js +24 -6
- package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -2
- package/libx/_runtime/common/composition/data.js +44 -55
- package/libx/_runtime/common/composition/delete.js +97 -71
- package/libx/_runtime/common/composition/index.js +2 -1
- package/libx/_runtime/common/composition/insert.js +34 -11
- package/libx/_runtime/common/composition/tree.js +119 -92
- package/libx/_runtime/common/composition/update.js +4 -1
- package/libx/_runtime/common/composition/utils.js +1 -3
- package/libx/_runtime/common/constants/draft.js +12 -1
- package/libx/_runtime/common/generic/auth.js +6 -22
- package/libx/_runtime/common/generic/crud.js +14 -13
- package/libx/_runtime/common/generic/input.js +23 -26
- package/libx/_runtime/common/generic/put.js +1 -1
- package/libx/_runtime/common/generic/sorting.js +16 -16
- package/libx/_runtime/common/i18n/index.js +1 -1
- package/libx/_runtime/common/i18n/messages.properties +4 -0
- package/libx/_runtime/common/utils/backlinks.js +12 -5
- package/libx/_runtime/common/utils/cqn.js +6 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +102 -101
- package/libx/_runtime/common/utils/csn.js +47 -4
- package/libx/_runtime/common/utils/data.js +0 -37
- package/libx/_runtime/common/utils/enrichWithKeysFromWhere.js +1 -1
- package/libx/_runtime/common/utils/entityFromCqn.js +7 -24
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +39 -7
- package/libx/_runtime/common/utils/generateOnCond.js +11 -12
- package/libx/_runtime/common/utils/onlyKeysRemain.js +10 -0
- package/libx/_runtime/common/utils/path.js +35 -0
- package/libx/_runtime/common/utils/postProcessing.js +86 -0
- package/libx/_runtime/common/utils/quotingStyles.js +37 -26
- package/libx/_runtime/common/utils/resolveView.js +223 -171
- package/libx/_runtime/common/utils/rewriteAsterisk.js +46 -26
- package/libx/_runtime/common/utils/structured.js +6 -12
- package/libx/_runtime/common/utils/template.js +10 -5
- package/libx/_runtime/common/utils/templateDelimiter.js +1 -0
- package/libx/_runtime/common/utils/templateProcessor.js +22 -30
- package/libx/_runtime/common/utils/union.js +31 -0
- package/libx/_runtime/common/utils/unionCqnTemplate.js +184 -0
- package/libx/_runtime/db/Service.js +1 -1
- package/libx/_runtime/db/data-conversion/timestamp.js +2 -9
- package/libx/_runtime/db/expand/expandCQNToJoin.js +204 -297
- package/libx/_runtime/db/expand/index.js +3 -3
- package/libx/_runtime/db/expand/rawToExpanded.js +36 -7
- package/libx/_runtime/db/generic/index.js +1 -1
- package/libx/_runtime/db/generic/input.js +5 -7
- package/libx/_runtime/db/generic/integrity.js +1 -1
- package/libx/_runtime/db/generic/rewrite.js +2 -10
- package/libx/_runtime/db/generic/update.js +13 -5
- package/libx/_runtime/db/generic/virtual.js +22 -58
- package/libx/_runtime/db/query/delete.js +7 -4
- package/libx/_runtime/db/query/insert.js +6 -4
- package/libx/_runtime/db/query/read.js +13 -20
- package/libx/_runtime/db/query/run.js +4 -1
- package/libx/_runtime/db/query/update.js +5 -4
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +35 -2
- package/libx/_runtime/db/sql-builder/FunctionBuilder.js +17 -2
- package/libx/_runtime/db/sql-builder/InsertBuilder.js +6 -5
- package/libx/_runtime/db/sql-builder/ReferenceBuilder.js +10 -0
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +35 -24
- package/libx/_runtime/db/sql-builder/UpdateBuilder.js +14 -4
- package/libx/_runtime/db/sql-builder/arrayed.js +4 -0
- package/libx/_runtime/db/utils/deep.js +8 -0
- package/libx/_runtime/db/utils/generateAliases.js +2 -1
- package/libx/_runtime/fiori/generic/activate.js +19 -15
- package/libx/_runtime/fiori/generic/before.js +3 -11
- package/libx/_runtime/fiori/generic/cancel.js +1 -1
- package/libx/_runtime/fiori/generic/delete.js +3 -1
- package/libx/_runtime/fiori/generic/edit.js +12 -2
- package/libx/_runtime/fiori/generic/new.js +5 -5
- package/libx/_runtime/fiori/generic/patch.js +0 -18
- package/libx/_runtime/fiori/generic/read.js +241 -189
- package/libx/_runtime/fiori/utils/delete.js +36 -7
- package/libx/_runtime/fiori/utils/handler.js +43 -44
- package/libx/_runtime/fiori/utils/where.js +30 -15
- package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +4 -6
- package/libx/_runtime/hana/execute.js +2 -2
- package/libx/_runtime/hana/localized.js +4 -4
- package/libx/_runtime/hana/pool.js +29 -14
- package/libx/_runtime/hana/search2cqn4sql.js +2 -1
- package/libx/_runtime/hana/searchToContains.js +18 -14
- package/libx/_runtime/index.js +0 -5
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +13 -5
- package/libx/_runtime/messaging/common-utils/naming-conventions.js +4 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +31 -19
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +1 -2
- package/libx/_runtime/messaging/enterprise-messaging.js +6 -4
- package/libx/_runtime/messaging/service.js +7 -6
- package/libx/_runtime/odata/cqn2odata.js +110 -43
- package/libx/_runtime/odata/index.js +26 -48
- package/libx/_runtime/odata/odata2cqn.js +1 -6154
- package/libx/_runtime/odata/odata2cqn.pegjs +559 -0
- package/libx/_runtime/odata/readToCqn.js +94 -64
- package/libx/_runtime/remote/Service.js +74 -21
- package/libx/_runtime/remote/cqn2odata/index.js +1 -5
- package/libx/_runtime/remote/utils/client.js +24 -101
- package/libx/_runtime/remote/utils/dataConversion.js +27 -12
- package/libx/_runtime/sqlite/Service.js +3 -5
- package/libx/_runtime/sqlite/execute.js +23 -24
- package/libx/_runtime/sqlite/localized.js +12 -7
- package/libx/_runtime/types/api.js +10 -0
- package/package.json +1 -1
- package/server.js +16 -2
- package/lib/ql/grammar.pegjs +0 -208
- package/lib/ql/parser.js +0 -1
- package/lib/ql/rt/DELETE.js +0 -29
- package/lib/ql/rt/INSERT.js +0 -23
- package/lib/ql/rt/Query.js +0 -84
- package/lib/ql/rt/SELECT.js +0 -174
- package/lib/ql/rt/UPDATE.js +0 -119
- package/lib/ql/rt/_helpers.js +0 -91
- package/lib/ql/rt/index.js +0 -32
- package/libx/_runtime/audit/generic/personal.js +0 -260
- package/libx/_runtime/cds-services/statements/BaseStatement.js +0 -72
- package/libx/_runtime/cds-services/statements/Create.js +0 -57
- package/libx/_runtime/cds-services/statements/Delete.js +0 -33
- package/libx/_runtime/cds-services/statements/Drop.js +0 -42
- package/libx/_runtime/cds-services/statements/Insert.js +0 -201
- package/libx/_runtime/cds-services/statements/Select.js +0 -826
- package/libx/_runtime/cds-services/statements/Update.js +0 -181
- package/libx/_runtime/cds-services/statements/Where.js +0 -726
- package/libx/_runtime/cds-services/statements/index.js +0 -25
- package/libx/_runtime/common/generic/resolve-mock.js +0 -9
|
@@ -26,10 +26,10 @@ const _recursivelyAliasRefs = (something, newAlias, oldAlias, subselect = false)
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
function _getSubWhereAndEntities(
|
|
29
|
+
function _getSubWhereAndEntities(element, parentWhere, draft, level = 0, compositionTree = {}) {
|
|
30
|
+
const allBackLinks = [...element.backLinks, ...element.customBackLinks]
|
|
30
31
|
let entity1, entity2
|
|
31
|
-
const
|
|
32
|
-
const linksForWhere = isBackLink ? allBackLinks : links
|
|
32
|
+
const linksForWhere = allBackLinks.length ? allBackLinks : element.links
|
|
33
33
|
|
|
34
34
|
const subWhere = linksForWhere.reduce((result, backLink) => {
|
|
35
35
|
// exclude static values from subwhere
|
|
@@ -43,29 +43,55 @@ function _getSubWhereAndEntities(allBackLinks, links, draft, element, level) {
|
|
|
43
43
|
entity1 = {
|
|
44
44
|
alias: `ALIAS${level + 1}`,
|
|
45
45
|
entityName: ctUtils.addDraftSuffix(draft, element.source),
|
|
46
|
-
propertyName:
|
|
46
|
+
propertyName: backLink.entityKey
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
const res1 = backLink.entityKey
|
|
50
|
-
? { ref: [entity1.alias, entity1.propertyName] }
|
|
51
|
-
: { val: isBackLink ? backLink.entityVal : backLink.targetVal }
|
|
49
|
+
const res1 = backLink.entityKey ? { ref: [entity1.alias, entity1.propertyName] } : { val: backLink.entityVal }
|
|
52
50
|
|
|
53
51
|
entity2 = {
|
|
54
52
|
alias: `ALIAS${level}`,
|
|
55
53
|
entityName: ctUtils.addDraftSuffix(draft, element.target || element.source),
|
|
56
|
-
propertyName:
|
|
54
|
+
propertyName: backLink.targetKey
|
|
57
55
|
}
|
|
58
56
|
|
|
59
|
-
const res2 = backLink.targetKey
|
|
60
|
-
? { ref: [entity2.alias, entity2.propertyName] }
|
|
61
|
-
: { val: isBackLink ? backLink.targetVal : backLink.entityVal }
|
|
57
|
+
const res2 = backLink.targetKey ? { ref: [entity2.alias, entity2.propertyName] } : { val: backLink.targetVal }
|
|
62
58
|
|
|
63
59
|
result.push(res1, '=', res2)
|
|
64
60
|
return result
|
|
65
61
|
}, [])
|
|
66
62
|
|
|
63
|
+
const where = []
|
|
64
|
+
if (!subWhere.length) return { where, entity1, entity2 }
|
|
65
|
+
|
|
66
|
+
let whereKeys = _getWhereKeys(allBackLinks, entity1)
|
|
67
|
+
const staticWhereValues = _getStaticWhere(allBackLinks, entity1)
|
|
68
|
+
if (whereKeys.length === 0 && element.links.length === 1) {
|
|
69
|
+
// add is null check for each unused backlink
|
|
70
|
+
for (const ce of compositionTree.compositionElements || []) {
|
|
71
|
+
if (ce.source !== element.source) continue
|
|
72
|
+
if (ce.name === element.name) continue
|
|
73
|
+
const wk = _getWhereKeys([...ce.backLinks, ...ce.customBackLinks], entity1, 'null')
|
|
74
|
+
if (whereKeys.length === 0) whereKeys = wk
|
|
75
|
+
else whereKeys.push('and', ...wk)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (whereKeys.length > 0) {
|
|
80
|
+
where.push('(', ...whereKeys, ')', 'and')
|
|
81
|
+
}
|
|
82
|
+
if (staticWhereValues.length > 0) {
|
|
83
|
+
where.push('(', ...staticWhereValues, ')', 'and')
|
|
84
|
+
}
|
|
85
|
+
where.push('exists', {
|
|
86
|
+
SELECT: {
|
|
87
|
+
columns: [{ val: 1, as: '_exists' }],
|
|
88
|
+
from: { ref: [entity2.entityName], as: entity2.alias },
|
|
89
|
+
where: parentWhere ? ['(', ...parentWhere, ')', 'and', '(', ...subWhere, ')'] : subWhere
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
|
|
67
93
|
return {
|
|
68
|
-
|
|
94
|
+
where,
|
|
69
95
|
entity1,
|
|
70
96
|
entity2
|
|
71
97
|
}
|
|
@@ -102,30 +128,25 @@ function _getStaticWhere(allBackLinks, entity1) {
|
|
|
102
128
|
}, [])
|
|
103
129
|
}
|
|
104
130
|
|
|
105
|
-
const
|
|
106
|
-
element.target && definitions[element.target].elements[element.name]
|
|
107
|
-
|
|
108
|
-
const _is2OneManaged = element => {
|
|
109
|
-
return element.is2one && ctUtils.isManaged(element)
|
|
131
|
+
const _is2oneComposition = (element, definitions) => {
|
|
132
|
+
const csnElement = element.target && definitions[element.target].elements[element.name]
|
|
133
|
+
return csnElement && csnElement.is2one && csnElement._isCompositionEffective
|
|
110
134
|
}
|
|
111
135
|
|
|
112
136
|
const _addToCQNs = (cqns, subCQN, element, definitions, level) => {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
// TODO: only managed to-one should be filtered, unmanaged with key in children can be ignored
|
|
119
|
-
if (csnElement && _is2OneManaged(csnElement)) {
|
|
120
|
-
cqns[level] = cqns[level] || []
|
|
121
|
-
cqns[level].push(subCQN)
|
|
122
|
-
}
|
|
123
|
-
} else {
|
|
124
|
-
cqns[level] = cqns[level] || []
|
|
137
|
+
cqns[level] = cqns[level] || []
|
|
138
|
+
// Since `>2.5.2` compiler generates constraints for compositions of one like for annotations
|
|
139
|
+
// Thus only single 2one case (`$self`-managed composition) has DELETE CASCADE
|
|
140
|
+
// Here it's ignored to simplify i.e. handle all "2ones" in a same manner
|
|
141
|
+
if (!cds.env.features._foreign_key_constraints || _is2oneComposition(element, definitions)) {
|
|
125
142
|
cqns[level].push(subCQN)
|
|
126
143
|
}
|
|
127
144
|
}
|
|
128
145
|
|
|
146
|
+
// unofficial config!
|
|
147
|
+
const DEEP_DELETE_MAX_RECURSION_DEPTH =
|
|
148
|
+
(cds.env.features.recursion_depth && Number(cds.env.features.recursion_depth)) || 2
|
|
149
|
+
|
|
129
150
|
const _addSubCascadeDeleteCQN = (
|
|
130
151
|
definitions,
|
|
131
152
|
compositionTree,
|
|
@@ -133,13 +154,14 @@ const _addSubCascadeDeleteCQN = (
|
|
|
133
154
|
level,
|
|
134
155
|
cqns,
|
|
135
156
|
draft,
|
|
136
|
-
|
|
157
|
+
elementMap = new Map()
|
|
137
158
|
) => {
|
|
138
159
|
for (const element of compositionTree.compositionElements) {
|
|
139
160
|
if (element.skipPersistence) continue
|
|
140
161
|
|
|
141
162
|
const fqn = compositionTree.source + ':' + element.name
|
|
142
|
-
|
|
163
|
+
const seen = elementMap.get(fqn)
|
|
164
|
+
if (seen && seen >= DEEP_DELETE_MAX_RECURSION_DEPTH) {
|
|
143
165
|
// recursion -> abort
|
|
144
166
|
continue
|
|
145
167
|
}
|
|
@@ -147,47 +169,17 @@ const _addSubCascadeDeleteCQN = (
|
|
|
147
169
|
// REVISIT: sometimes element.target is undefined which leads to self join
|
|
148
170
|
if (!element.target) element.target = compositionTree.source
|
|
149
171
|
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
let whereKeys = _getWhereKeys(allBackLinks, entity1)
|
|
154
|
-
const staticWhereValues = _getStaticWhere(allBackLinks, entity1)
|
|
155
|
-
if (allBackLinks.length > 0 || element.links.length > 0) {
|
|
156
|
-
const where = []
|
|
157
|
-
|
|
158
|
-
if (whereKeys.length === 0 && element.links.length === 1) {
|
|
159
|
-
// add is null check for each unused backlink
|
|
160
|
-
for (const ce of compositionTree.compositionElements) {
|
|
161
|
-
if (ce.source !== element.source) continue
|
|
162
|
-
if (ce.name === element.name) continue
|
|
163
|
-
const wk = _getWhereKeys([...ce.backLinks, ...ce.customBackLinks], entity1, 'null')
|
|
164
|
-
if (whereKeys.length === 0) whereKeys = wk
|
|
165
|
-
else whereKeys.push('and', ...wk)
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (whereKeys.length > 0) {
|
|
170
|
-
where.push('(', ...whereKeys, ')', 'and')
|
|
171
|
-
}
|
|
172
|
-
if (staticWhereValues.length > 0) {
|
|
173
|
-
where.push('(', ...staticWhereValues, ')', 'and')
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
where.push('exists', {
|
|
177
|
-
SELECT: {
|
|
178
|
-
columns: [{ val: 1, as: '_exists' }],
|
|
179
|
-
from: { ref: [entity2.entityName], as: entity2.alias },
|
|
180
|
-
where: parentWhere ? ['(', ...parentWhere, ')', 'and', '(', ...subWhere, ')'] : subWhere
|
|
181
|
-
}
|
|
182
|
-
})
|
|
183
|
-
|
|
172
|
+
const { entity1, where } = _getSubWhereAndEntities(element, parentWhere, draft, level, compositionTree)
|
|
173
|
+
if (where.length) {
|
|
184
174
|
const subCQN = { DELETE: { from: { ref: [entity1.entityName], as: entity1.alias }, where: where } }
|
|
185
175
|
|
|
186
176
|
_addToCQNs(cqns, subCQN, element, definitions, level)
|
|
187
177
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
178
|
+
// Make a copy and do not share the same map among brother compositions
|
|
179
|
+
// as we're only interested in deep recursions, not wide recursions.
|
|
180
|
+
const newElementMap = new Map(elementMap)
|
|
181
|
+
newElementMap.set(fqn, (seen && seen + 1) || 1)
|
|
182
|
+
_addSubCascadeDeleteCQN(definitions, element, subCQN.DELETE.where, level + 1, cqns, draft, newElementMap)
|
|
191
183
|
}
|
|
192
184
|
}
|
|
193
185
|
|
|
@@ -202,12 +194,41 @@ const hasDeepDelete = (definitions, cqn) => {
|
|
|
202
194
|
const from = getEntityNameFromDeleteCQN(cqn)
|
|
203
195
|
if (!from) return false
|
|
204
196
|
|
|
197
|
+
// hidden flag for DELETEs on draft root, we have a separate mechanism that deletes the rows using the DraftUUID
|
|
198
|
+
// Hence, we do not need a deep delete in that case.
|
|
199
|
+
if (cqn._suppressDeepDelete) return false
|
|
200
|
+
|
|
205
201
|
const entity = definitions && definitions[ensureNoDraftsSuffix(from)]
|
|
202
|
+
|
|
206
203
|
if (entity) return !!Object.keys(entity.elements || {}).find(k => entity.elements[k]._isCompositionEffective)
|
|
207
204
|
|
|
208
205
|
return false
|
|
209
206
|
}
|
|
210
207
|
|
|
208
|
+
const _getSetNullParentForeignKeyCQNs = (definitions, entityName, parentWhere, draft) => {
|
|
209
|
+
const setNullCQNs = []
|
|
210
|
+
for (const { elements } of definitions[entityName].__oneCompositionParents.values()) {
|
|
211
|
+
for (const element of elements.values()) {
|
|
212
|
+
const data = element.links.reduce((d, fk) => {
|
|
213
|
+
d[fk.entityKey] = null
|
|
214
|
+
return d
|
|
215
|
+
}, {})
|
|
216
|
+
const { entity1, where } = _getSubWhereAndEntities(element, parentWhere, draft)
|
|
217
|
+
if (where.length) {
|
|
218
|
+
setNullCQNs.push({
|
|
219
|
+
UPDATE: {
|
|
220
|
+
entity: { ref: [entity1.entityName], as: entity1.alias },
|
|
221
|
+
data,
|
|
222
|
+
where,
|
|
223
|
+
_beforeDelete: true
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return setNullCQNs
|
|
230
|
+
}
|
|
231
|
+
|
|
211
232
|
const getDeepDeleteCQNs = (definitions, cqn) => {
|
|
212
233
|
const from = getEntityNameFromDeleteCQN(cqn)
|
|
213
234
|
if (!from) return [[cqn]]
|
|
@@ -219,15 +240,20 @@ const getDeepDeleteCQNs = (definitions, cqn) => {
|
|
|
219
240
|
definitions,
|
|
220
241
|
rootEntityName: entityName,
|
|
221
242
|
checkRoot: false,
|
|
222
|
-
resolveViews: !draft
|
|
243
|
+
resolveViews: !draft,
|
|
244
|
+
service: cds.db
|
|
223
245
|
})
|
|
224
246
|
const parentWhere = cqn.DELETE.where && JSON.parse(JSON.stringify(cqn.DELETE.where))
|
|
225
247
|
if (parentWhere) {
|
|
226
248
|
const parentAlias = cqn.DELETE.from.as || (cqn.DELETE.from.ref && cqn.DELETE.from.ref[0]) || cqn.DELETE.from // or however we get the table name...
|
|
227
249
|
_recursivelyAliasRefs(parentWhere, 'ALIAS0', parentAlias)
|
|
228
250
|
}
|
|
229
|
-
|
|
230
|
-
|
|
251
|
+
const setNullUpdates = []
|
|
252
|
+
if (cds.env.features._foreign_key_constraints && definitions[entityName].own('__oneCompositionParents')) {
|
|
253
|
+
setNullUpdates.push(..._getSetNullParentForeignKeyCQNs(definitions, entityName, parentWhere, draft))
|
|
254
|
+
}
|
|
255
|
+
const subCascadeDeletes = _addSubCascadeDeleteCQN(definitions, compositionTree, parentWhere, 0, [], draft)
|
|
256
|
+
return [[cqn], ...subCascadeDeletes, ...setNullUpdates].reverse()
|
|
231
257
|
}
|
|
232
258
|
|
|
233
259
|
module.exports = {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const { getCompositionTree, getCompositionRoot } = require('./tree')
|
|
2
|
-
const { hasDeepInsert, getDeepInsertCQNs } = require('./insert')
|
|
2
|
+
const { hasDeepInsert, getDeepInsertCQNs, cleanEmptyCompositionsOfMany } = require('./insert')
|
|
3
3
|
const { hasDeepUpdate, getDeepUpdateCQNs } = require('./update')
|
|
4
4
|
const { hasDeepDelete, getDeepDeleteCQNs } = require('./delete')
|
|
5
5
|
const { selectDeepUpdateData } = require('./data')
|
|
@@ -11,6 +11,7 @@ module.exports = {
|
|
|
11
11
|
// insert
|
|
12
12
|
hasDeepInsert,
|
|
13
13
|
getDeepInsertCQNs,
|
|
14
|
+
cleanEmptyCompositionsOfMany,
|
|
14
15
|
// update
|
|
15
16
|
hasDeepUpdate,
|
|
16
17
|
getDeepUpdateCQNs,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const cds = require('../../cds')
|
|
2
|
+
|
|
1
3
|
const { getCompositionTree } = require('./tree')
|
|
2
4
|
const ctUtils = require('./utils')
|
|
3
5
|
|
|
@@ -25,13 +27,14 @@ const _addSubDeepInsertCQN = (definitions, compositionTree, data, cqns, draft) =
|
|
|
25
27
|
const subData = data.reduce((result, entry) => {
|
|
26
28
|
if (element.name in entry) {
|
|
27
29
|
const elementValue = ctUtils.val(entry[element.name])
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
if (elementValue != null) {
|
|
31
|
+
// remove empty entries
|
|
32
|
+
const subData = ctUtils.array(elementValue).filter(ele => Object.keys(ele).length > 0)
|
|
33
|
+
if (subData.length > 0) {
|
|
34
|
+
// REVISIT: this can make problems
|
|
35
|
+
insertCQN.INSERT.entries.push(...ctUtils.cleanDeepData(subEntity, subData))
|
|
36
|
+
result.push(...subData)
|
|
37
|
+
}
|
|
35
38
|
}
|
|
36
39
|
}
|
|
37
40
|
return result
|
|
@@ -50,10 +53,28 @@ const _addSubDeepInsertCQN = (definitions, compositionTree, data, cqns, draft) =
|
|
|
50
53
|
* exports
|
|
51
54
|
*/
|
|
52
55
|
|
|
56
|
+
const _entityFromINSERT = (definitions, INSERT) => {
|
|
57
|
+
if (INSERT && INSERT.into) {
|
|
58
|
+
const entityName = ensureNoDraftsSuffix(INSERT.into.name || INSERT.into)
|
|
59
|
+
return definitions && definitions[entityName]
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const cleanEmptyCompositionsOfMany = (definitions, cqn) => {
|
|
64
|
+
const entity = _entityFromINSERT(definitions, cqn.INSERT)
|
|
65
|
+
if (!entity) return
|
|
66
|
+
for (const entry of cqn.INSERT.entries || []) {
|
|
67
|
+
for (const elName in entry || {}) {
|
|
68
|
+
const el = entity.elements[elName]
|
|
69
|
+
if (!el) continue
|
|
70
|
+
if (el.is2many && !entry[elName].length) delete entry[elName]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
53
75
|
const hasDeepInsert = (definitions, cqn) => {
|
|
54
|
-
if (cqn
|
|
55
|
-
const
|
|
56
|
-
const entity = definitions && definitions[entityName]
|
|
76
|
+
if (cqn.INSERT.entries) {
|
|
77
|
+
const entity = _entityFromINSERT(definitions, cqn.INSERT)
|
|
57
78
|
if (entity) {
|
|
58
79
|
return !!cqn.INSERT.entries.find(entry => {
|
|
59
80
|
return !!Object.keys(entry || {}).find(k => {
|
|
@@ -75,7 +96,8 @@ const getDeepInsertCQNs = (definitions, cqn) => {
|
|
|
75
96
|
definitions,
|
|
76
97
|
rootEntityName: entityName,
|
|
77
98
|
checkRoot: false,
|
|
78
|
-
resolveViews: !draft
|
|
99
|
+
resolveViews: !draft,
|
|
100
|
+
service: cds.db
|
|
79
101
|
})
|
|
80
102
|
|
|
81
103
|
const flattenedCqn = { INSERT: Object.assign({}, cqn.INSERT) }
|
|
@@ -89,6 +111,7 @@ const getDeepInsertCQNs = (definitions, cqn) => {
|
|
|
89
111
|
}
|
|
90
112
|
|
|
91
113
|
module.exports = {
|
|
114
|
+
cleanEmptyCompositionsOfMany,
|
|
92
115
|
hasDeepInsert,
|
|
93
116
|
getDeepInsertCQNs
|
|
94
117
|
}
|
|
@@ -1,23 +1,45 @@
|
|
|
1
1
|
const cds = require('../../cds')
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
const ctUtils = require('./utils')
|
|
3
|
+
const { isSelfManaged, isBacklink } = require('../utils/backlinks')
|
|
5
4
|
|
|
6
5
|
const { ensureNoDraftsSuffix } = require('../utils/draft')
|
|
7
6
|
const { isRootEntity } = require('../utils/csn')
|
|
8
7
|
const { getTransition, getDBTable } = require('../utils/resolveView')
|
|
9
8
|
|
|
9
|
+
const { foreignKeyPropagations } = require('../utils/foreignKeyPropagations')
|
|
10
|
+
|
|
10
11
|
const getError = require('../../common/error')
|
|
11
12
|
|
|
12
13
|
/*
|
|
13
14
|
* own utils
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
|
-
const
|
|
17
|
+
const _foreignKeysToLinks = (element, inverse) =>
|
|
18
|
+
foreignKeyPropagations(element).map(e => {
|
|
19
|
+
e = inverse
|
|
20
|
+
? {
|
|
21
|
+
childFieldName: e.parentFieldName,
|
|
22
|
+
parentFieldName: e.childFieldName,
|
|
23
|
+
childFieldValue: e.parentFieldValue,
|
|
24
|
+
parentFieldValue: e.childFieldValue,
|
|
25
|
+
prefix: e.prefix
|
|
26
|
+
}
|
|
27
|
+
: e
|
|
28
|
+
const link = {
|
|
29
|
+
entityKey:
|
|
30
|
+
e.prefix && !e.parentFieldName.includes(e.prefix) ? e.prefix + '_' + e.parentFieldName : e.parentFieldName,
|
|
31
|
+
targetKey: e.prefix && !e.childFieldName.includes(e.prefix) ? e.prefix + '_' + e.childFieldName : e.childFieldName
|
|
32
|
+
}
|
|
33
|
+
if (e.parentFieldValue !== undefined) link.entityVal = e.parentFieldValue
|
|
34
|
+
if (e.childFieldValue !== undefined) link.targetVal = e.childFieldValue
|
|
35
|
+
return link
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const _resolvedElement = (element, service) => {
|
|
17
39
|
if (!element.target) return element
|
|
18
40
|
// skip forbidden view check if association to view with foreign key in target
|
|
19
41
|
const skipForbiddenViewCheck = element._isAssociationStrict && element.on && !element['@odata.contained']
|
|
20
|
-
const { target, mapping } = getTransition(element._target,
|
|
42
|
+
const { target, mapping } = getTransition(element._target, service, skipForbiddenViewCheck)
|
|
21
43
|
const newElement = { target: target.name, _target: target }
|
|
22
44
|
Object.setPrototypeOf(newElement, element)
|
|
23
45
|
if (element.on) {
|
|
@@ -35,87 +57,22 @@ const _resolvedElement = element => {
|
|
|
35
57
|
const _navigationExistsInCompositionMap = (element, compositionMap) =>
|
|
36
58
|
compositionMap.has(element.target) && element._isCompositionEffective
|
|
37
59
|
|
|
38
|
-
const _addNavigationToCompositionElements = (element, definitions, compositionTree, compositionMap, isManaged) => {
|
|
39
|
-
const links = element.is2one ? getBackLinks(element) : []
|
|
40
|
-
|
|
41
|
-
const compositionElement = Object.assign({}, compositionMap.get(element.target), { name: element.name, links })
|
|
42
|
-
compositionElement.target = element.parent.name
|
|
43
|
-
const backLinks = element.is2many ? getBackLinks(element) : []
|
|
44
|
-
|
|
45
|
-
if (isManaged) {
|
|
46
|
-
compositionElement.backLinks = backLinks
|
|
47
|
-
} else {
|
|
48
|
-
compositionElement.customBackLinks = backLinks
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
compositionTree.compositionElements.push(compositionElement)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
60
|
const _isUnManaged = element => element.on && !isSelfManaged(element)
|
|
55
61
|
|
|
56
62
|
const _isNonRecursiveNavigation = (element, rootEntityName) =>
|
|
57
63
|
rootEntityName !== element.target && element._isCompositionEffective
|
|
58
64
|
|
|
59
|
-
const _getLinks = element => (element.is2one && !isSelfManaged(element) ? getBackLinks(element) : [])
|
|
60
|
-
|
|
61
65
|
const _skipPersistence = (element, definitions) => definitions[element.target]._hasPersistenceSkip
|
|
62
66
|
|
|
63
|
-
const _createSubElement = (element, definitions
|
|
64
|
-
const
|
|
65
|
-
const backLinks = []
|
|
66
|
-
const subObject = { name: element.name, backLinks, links }
|
|
67
|
-
|
|
67
|
+
const _createSubElement = (element, definitions) => {
|
|
68
|
+
const subObject = { name: element.name, customBackLinks: [], links: [], backLinks: [] }
|
|
68
69
|
if (_skipPersistence(element, definitions)) {
|
|
69
70
|
subObject.skipPersistence = true
|
|
70
71
|
}
|
|
71
|
-
|
|
72
|
-
if (_isUnManaged(element)) {
|
|
73
|
-
subObject.customBackLinks = getBackLinks(element)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
72
|
return subObject
|
|
77
73
|
}
|
|
78
74
|
|
|
79
|
-
|
|
80
|
-
return (
|
|
81
|
-
// REVISIT: are all three checks really required?
|
|
82
|
-
element.target === parent.name && element.isAssociation && element.on
|
|
83
|
-
)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const _checkIfBackLink = (element, definitions) => {
|
|
87
|
-
const target = definitions[element.target]
|
|
88
|
-
for (const elementName in target.elements) {
|
|
89
|
-
const targetElement = target.elements[elementName]
|
|
90
|
-
if (_isAssocComp(targetElement, element.parent)) {
|
|
91
|
-
const onCondElements = getOnCondElements(targetElement.on)
|
|
92
|
-
for (const el of onCondElements) {
|
|
93
|
-
const { entityKey, targetKey } = el
|
|
94
|
-
if (entityKey === `${elementName}.${element.name}` || targetKey === `${elementName}.${element.name}`) {
|
|
95
|
-
return true
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return false
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const _addBackLinksToCompositionTree = (element, definitions, compositionTree) => {
|
|
104
|
-
if (_isUnManaged(element)) {
|
|
105
|
-
if (_checkIfBackLink(element, definitions)) {
|
|
106
|
-
const backLinks = getBackLinks(element).map(backLink => ({
|
|
107
|
-
entityKey: backLink.targetKey,
|
|
108
|
-
targetKey: backLink.entityKey,
|
|
109
|
-
entityVal: backLink.targetVal,
|
|
110
|
-
targetVal: backLink.entityVal
|
|
111
|
-
}))
|
|
112
|
-
compositionTree.customBackLinks.push(...backLinks)
|
|
113
|
-
}
|
|
114
|
-
} else {
|
|
115
|
-
compositionTree.backLinks.push(...getBackLinks(element))
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
75
|
+
// eslint-disable-next-line complexity
|
|
119
76
|
const _getCompositionTreeRec = ({
|
|
120
77
|
rootEntityName,
|
|
121
78
|
definitions,
|
|
@@ -123,7 +80,8 @@ const _getCompositionTreeRec = ({
|
|
|
123
80
|
compositionTree,
|
|
124
81
|
entityName,
|
|
125
82
|
parentEntityName,
|
|
126
|
-
resolveViews
|
|
83
|
+
resolveViews,
|
|
84
|
+
service
|
|
127
85
|
}) => {
|
|
128
86
|
compositionMap.set(parentEntityName, compositionTree)
|
|
129
87
|
compositionTree.source = parentEntityName
|
|
@@ -135,14 +93,52 @@ const _getCompositionTreeRec = ({
|
|
|
135
93
|
compositionTree.customBackLinks = compositionTree.customBackLinks || []
|
|
136
94
|
|
|
137
95
|
const parentEntity = definitions[parentEntityName]
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
if (
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
96
|
+
|
|
97
|
+
for (const elementName in parentEntity.elements) {
|
|
98
|
+
const unresolvedEl = parentEntity.elements[elementName]
|
|
99
|
+
const element = resolveViews ? _resolvedElement(unresolvedEl, service) : unresolvedEl
|
|
100
|
+
if (!element.isAssociation) continue
|
|
101
|
+
if (_navigationExistsInCompositionMap(element, compositionMap)) {
|
|
102
|
+
const compositionElement = Object.assign({}, compositionMap.get(element.target), {
|
|
103
|
+
name: element.name,
|
|
104
|
+
target: parentEntityName,
|
|
105
|
+
links: [],
|
|
106
|
+
backLinks: [],
|
|
107
|
+
customBackLinks: []
|
|
108
|
+
})
|
|
109
|
+
if (!isSelfManaged(element)) {
|
|
110
|
+
const backLinks = _foreignKeysToLinks(element, true) || []
|
|
111
|
+
if (element.is2many) {
|
|
112
|
+
compositionElement.customBackLinks.push(...backLinks)
|
|
113
|
+
} else {
|
|
114
|
+
compositionElement.backLinks.push(...backLinks)
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
const targetEntity = definitions[element.target]
|
|
118
|
+
for (const backLinkName in targetEntity.elements) {
|
|
119
|
+
const _backLink = targetEntity.elements[backLinkName]
|
|
120
|
+
if (!_backLink._isAssociationEffective) continue
|
|
121
|
+
if (isBacklink(_backLink, definitions[compositionElement.target], false, element.name)) {
|
|
122
|
+
const backLinks = _foreignKeysToLinks(_backLink) || []
|
|
123
|
+
if (_isUnManaged(element)) {
|
|
124
|
+
compositionElement.customBackLinks.push(...backLinks)
|
|
125
|
+
} else {
|
|
126
|
+
compositionElement.backLinks.push(...backLinks)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
compositionTree.compositionElements.push(compositionElement)
|
|
132
|
+
} else if (_isNonRecursiveNavigation(element, rootEntityName)) {
|
|
133
|
+
const subObject = _createSubElement(element, definitions)
|
|
134
|
+
if (!isSelfManaged(element)) {
|
|
135
|
+
const backLinks = _foreignKeysToLinks(element, true) || []
|
|
136
|
+
if (element.is2many) {
|
|
137
|
+
subObject.customBackLinks.push(...backLinks)
|
|
138
|
+
} else {
|
|
139
|
+
subObject.backLinks.push(...backLinks)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
146
142
|
compositionTree.compositionElements.push(subObject)
|
|
147
143
|
_getCompositionTreeRec({
|
|
148
144
|
rootEntityName,
|
|
@@ -150,15 +146,20 @@ const _getCompositionTreeRec = ({
|
|
|
150
146
|
compositionMap,
|
|
151
147
|
compositionTree: subObject,
|
|
152
148
|
entityName: parentEntityName,
|
|
153
|
-
parentEntityName:
|
|
149
|
+
parentEntityName: element.target,
|
|
150
|
+
service
|
|
154
151
|
})
|
|
155
152
|
} else if (
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
compositionMap.has(el.target)
|
|
153
|
+
element._isAssociationEffective &&
|
|
154
|
+
isBacklink(element, definitions[compositionTree.target], false, compositionTree.name) &&
|
|
155
|
+
compositionMap.has(element.target)
|
|
160
156
|
) {
|
|
161
|
-
|
|
157
|
+
const backLinks = _foreignKeysToLinks(element) || []
|
|
158
|
+
if (_isUnManaged(element)) {
|
|
159
|
+
compositionTree.customBackLinks.push(...backLinks)
|
|
160
|
+
} else {
|
|
161
|
+
compositionTree.backLinks.push(...backLinks)
|
|
162
|
+
}
|
|
162
163
|
}
|
|
163
164
|
}
|
|
164
165
|
}
|
|
@@ -186,7 +187,7 @@ const _removeLocalizedTextsFromDraftTree = (compositionTree, definitions, checke
|
|
|
186
187
|
}
|
|
187
188
|
}
|
|
188
189
|
|
|
189
|
-
const _getCompositionTree = ({ definitions, rootEntityName, checkRoot = true, resolveViews = false }) => {
|
|
190
|
+
const _getCompositionTree = ({ definitions, rootEntityName, checkRoot = true, resolveViews = false, service }) => {
|
|
190
191
|
const rootName = resolveViews ? _resolvedEntityName(rootEntityName, definitions) : rootEntityName
|
|
191
192
|
|
|
192
193
|
if (checkRoot && !isRootEntity(definitions, rootEntityName)) {
|
|
@@ -200,7 +201,8 @@ const _getCompositionTree = ({ definitions, rootEntityName, checkRoot = true, re
|
|
|
200
201
|
compositionTree,
|
|
201
202
|
entityName: rootName,
|
|
202
203
|
parentEntityName: rootName,
|
|
203
|
-
resolveViews
|
|
204
|
+
resolveViews,
|
|
205
|
+
service
|
|
204
206
|
})
|
|
205
207
|
|
|
206
208
|
if (definitions[rootEntityName]._isDraftEnabled) {
|
|
@@ -210,9 +212,34 @@ const _getCompositionTree = ({ definitions, rootEntityName, checkRoot = true, re
|
|
|
210
212
|
return compositionTree
|
|
211
213
|
}
|
|
212
214
|
|
|
215
|
+
const _cacheCompositionParentsOfOne = ({ definitions }) => {
|
|
216
|
+
for (const parentName in definitions) {
|
|
217
|
+
const parent = definitions[parentName]
|
|
218
|
+
if (!parent.kind === 'entity' || !parent.elements) continue
|
|
219
|
+
for (const elementName in parent.elements) {
|
|
220
|
+
const element = parent.elements[elementName]
|
|
221
|
+
if (element._isCompositionEffective && element.is2one && !isSelfManaged(element)) {
|
|
222
|
+
const targetName = element.target
|
|
223
|
+
const target = definitions[targetName]
|
|
224
|
+
if (!target) continue
|
|
225
|
+
const parentMap = (target.own('__oneCompositionParents') && target.__oneCompositionParents) || new Map()
|
|
226
|
+
const binding = parentMap.get(parentName) || {}
|
|
227
|
+
binding.elements = binding.elements || new Map()
|
|
228
|
+
const el = _createSubElement(element, definitions)
|
|
229
|
+
el.target = targetName
|
|
230
|
+
el.source = parentName
|
|
231
|
+
el.links = _foreignKeysToLinks(element)
|
|
232
|
+
binding.elements.set(element.name, el)
|
|
233
|
+
parentMap.set(parentName, binding)
|
|
234
|
+
target.set('__oneCompositionParents', parentMap)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
213
240
|
const _memoizeGetCompositionTree = fn => {
|
|
214
241
|
const cache = new Map()
|
|
215
|
-
return ({ definitions, rootEntityName, checkRoot = true, resolveViews = false }) => {
|
|
242
|
+
return ({ definitions, rootEntityName, checkRoot = true, resolveViews = false, service }) => {
|
|
216
243
|
const key = [rootEntityName, checkRoot].join('#')
|
|
217
244
|
|
|
218
245
|
// use ApplicationService as cache key for extensibility
|
|
@@ -222,8 +249,8 @@ const _memoizeGetCompositionTree = fn => {
|
|
|
222
249
|
const map = cache.get(cacheKey)
|
|
223
250
|
const cachedResult = map && map.get(key)
|
|
224
251
|
if (cachedResult) return cachedResult
|
|
225
|
-
|
|
226
|
-
const compTree = fn({ definitions, rootEntityName, checkRoot, resolveViews })
|
|
252
|
+
_cacheCompositionParentsOfOne({ definitions })
|
|
253
|
+
const compTree = fn({ definitions, rootEntityName, checkRoot, resolveViews, service })
|
|
227
254
|
|
|
228
255
|
const _map = map || new Map()
|
|
229
256
|
_map.set(key, compTree)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const cds = require('../../cds')
|
|
2
|
+
|
|
1
3
|
const { getCompositionTree } = require('./tree')
|
|
2
4
|
const { getDeepInsertCQNs } = require('./insert')
|
|
3
5
|
const { getDeepDeleteCQNs } = require('./delete')
|
|
@@ -296,7 +298,8 @@ const getDeepUpdateCQNs = (definitions, cqn, selectData) => {
|
|
|
296
298
|
definitions,
|
|
297
299
|
rootEntityName: entityName,
|
|
298
300
|
checkRoot: false,
|
|
299
|
-
resolveViews: !draft
|
|
301
|
+
resolveViews: !draft,
|
|
302
|
+
service: cds.db
|
|
300
303
|
})
|
|
301
304
|
|
|
302
305
|
const subCQNs = _addSubDeepUpdateCQN({ definitions, compositionTree, data: [entry], selectData, cqns: [], draft })
|
|
@@ -9,9 +9,7 @@ const addDraftSuffix = (draft, name) => {
|
|
|
9
9
|
const whereKey = key => {
|
|
10
10
|
const where = []
|
|
11
11
|
Object.keys(key).forEach(keyPart => {
|
|
12
|
-
if (where.length > 0)
|
|
13
|
-
where.push('and')
|
|
14
|
-
}
|
|
12
|
+
if (where.length > 0) where.push('and')
|
|
15
13
|
where.push({ ref: [keyPart] }, '=', { val: key[keyPart] })
|
|
16
14
|
})
|
|
17
15
|
return where
|