Skip to content

Commit 5b9b458

Browse files
fix: Support capturing the Object ID and the value when there is a nested duplicated association name. (#58)
1. fix the issue that no value is returned for 'parent.parent.x' when building hierachical entities with CAP compositions. 2. when the app creates both parent and child nodes, the parent node does not exist in the data table, so the corresponding data needs to be obtained from the draft table. 3. when updating a parent node and deleting a child node, the corresponding node will disappear from the draft table. Therefore, it is necessary to get the data from the data table. This PR includes fixes for all three of these situations. --------- Co-authored-by: I560824 <[email protected]>
1 parent a9d5671 commit 5b9b458

22 files changed

+773
-41
lines changed

lib/change-log.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ const _getEntityIDs = function (txParams) {
9797
* ...
9898
* }
9999
*/
100-
const _formatAssociationContext = async function (changes) {
100+
const _formatAssociationContext = async function (changes, reqData) {
101101
for (const change of changes) {
102102
const a = cds.model.definitions[change.serviceEntity].elements[change.attribute]
103103
if (a?.type !== "cds.Association") continue
@@ -111,10 +111,10 @@ const _formatAssociationContext = async function (changes) {
111111
SELECT.one.from(a.target).where({ [ID]: change.valueChangedTo })
112112
])
113113

114-
const fromObjId = await getObjectId(a.target, semkeys, { curObjFromDbQuery: from || undefined }) // Note: ... || undefined is important for subsequent object destructuring with defaults
114+
const fromObjId = await getObjectId(reqData, a.target, semkeys, { curObjFromDbQuery: from || undefined }) // Note: ... || undefined is important for subsequent object destructuring with defaults
115115
if (fromObjId) change.valueChangedFrom = fromObjId
116116

117-
const toObjId = await getObjectId(a.target, semkeys, { curObjFromDbQuery: to || undefined }) // Note: ... || undefined is important for subsequent object destructuring with defaults
117+
const toObjId = await getObjectId(reqData, a.target, semkeys, { curObjFromDbQuery: to || undefined }) // Note: ... || undefined is important for subsequent object destructuring with defaults
118118
if (toObjId) change.valueChangedTo = toObjId
119119

120120
const isVLvA = a["@Common.ValueList.viaAssociation"]
@@ -219,7 +219,7 @@ const _getObjectIdByPath = async function (
219219
const entityUUID = getUUIDFromPathVal(nodePathVal)
220220
const obj = await getCurObjFromDbQuery(entityName, entityUUID)
221221
const curObj = { curObjFromReqData, curObjFromDbQuery: obj }
222-
return getObjectId(entityName, objIdElementNames, curObj)
222+
return getObjectId(reqData, entityName, objIdElementNames, curObj)
223223
}
224224

225225
const _formatObjectID = async function (changes, reqData) {
@@ -267,7 +267,7 @@ const _isCompositionContextPath = function (aPath) {
267267

268268
const _formatChangeLog = async function (changes, req) {
269269
await _formatObjectID(changes, req.data)
270-
await _formatAssociationContext(changes)
270+
await _formatAssociationContext(changes, req.data)
271271
await _formatCompositionContext(changes, req.data)
272272
}
273273

@@ -349,7 +349,7 @@ async function track_changes (req) {
349349
let isComposition = _isCompositionContextPath(req.context.path)
350350
let entityKey = diff.ID
351351

352-
if (req.event === "DELETE") {
352+
if (cds.transaction(req).context.event === "DELETE") {
353353
if (isDraftEnabled || !isComposition) {
354354
return await DELETE.from(`sap.changelog.ChangeLog`).where({ entityKey })
355355
}

lib/entity-helper.js

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ const getCurObjFromReqData = function (reqData, nodePathVal, pathVal) {
7070
}
7171

7272

73-
async function getObjectId (entityName, fields, curObj) {
73+
async function getObjectId (reqData, entityName, fields, curObj) {
7474
let all = [], { curObjFromReqData: req_data={}, curObjFromDbQuery: db_data={} } = curObj
7575
let entity = cds.model.definitions[entityName]
7676
if (!fields?.length) fields = entity["@changelog"]?.map?.(k => k['='] || k) || []
@@ -81,13 +81,30 @@ async function getObjectId (entityName, fields, curObj) {
8181
while (path.length > 1) {
8282
let assoc = current.elements[path[0]]; if (!assoc?.isAssociation) break
8383
let foreignKey = assoc.keys?.[0]?.$generatedFieldName
84-
let IDval = req_data[foreignKey] || _db_data[foreignKey]
84+
let IDval =
85+
req_data[foreignKey] && current.name === entityName
86+
? req_data[foreignKey]
87+
: _db_data[foreignKey]
8588
if (IDval) try {
8689
// REVISIT: This always reads all elements -> should read required ones only!
8790
let ID = assoc.keys?.[0]?.ref[0] || 'ID'
88-
// When parent/child nodes are created simultaneously, data is taken from draft table
89-
_db_data = await SELECT.one.from(assoc._target).where({[ID]: IDval}) ||
90-
await SELECT.one.from(`${assoc._target}.drafts`).where({[ID]: IDval}) || {}
91+
const isComposition = hasComposition(assoc._target, current)
92+
// Peer association and composition are distinguished by the value of isComposition.
93+
if (isComposition) {
94+
// This function can recursively retrieve the desired information from reqData without having to read it from db.
95+
_db_data = _getCompositionObjFromReq(reqData, IDval)
96+
// When multiple layers of child nodes are deleted at the same time, the deep layer of child nodes will lose the information of the upper nodes, so data needs to be extracted from the db.
97+
if (!_db_data || JSON.stringify(_db_data) === '{}') {
98+
_db_data =
99+
(await SELECT.one
100+
.from(assoc._target)
101+
.where({ [ID]: IDval })) || {}
102+
}
103+
} else {
104+
_db_data =
105+
(await SELECT.one.from(assoc._target).where({ [ID]: IDval })) ||
106+
{}
107+
}
91108
} catch (e) {
92109
LOG.error("Failed to generate object Id for an association entity.", e)
93110
throw new Error("Failed to generate object Id for an association entity.", e)
@@ -134,6 +151,39 @@ const getValueEntityType = function (entityName, fields) {
134151
return types.join(', ')
135152
}
136153

154+
const hasComposition = function (parentEntity, subEntity) {
155+
if (!parentEntity.compositions) {
156+
return false
157+
}
158+
159+
const compositions = Object.values(parentEntity.compositions);
160+
161+
for (const composition of compositions) {
162+
if (composition.target === subEntity.name) {
163+
return true;
164+
}
165+
}
166+
167+
return false
168+
}
169+
170+
const _getCompositionObjFromReq = function (obj, targetID) {
171+
if (obj.ID === targetID) {
172+
return obj;
173+
}
174+
175+
for (const key in obj) {
176+
if (typeof obj[key] === "object" && obj[key] !== null) {
177+
const result = _getCompositionObjFromReq(obj[key], targetID);
178+
if (result) {
179+
return result;
180+
}
181+
}
182+
}
183+
184+
return null;
185+
};
186+
137187
module.exports = {
138188
getCurObjFromReqData,
139189
getCurObjFromDbQuery,

tests/bookshop/db/_i18n/i18n.properties

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,20 @@ serviceAuthors.name=Author Name
5050
#XTIT
5151
bookStoreRegistry.objectTitle=Book Store Registry
5252
bookStoreRegistry.code=Code
53-
bookStoreRegistry.validOn=Valid On
53+
bookStoreRegistry.validOn=Valid On
54+
55+
## RootEntity
56+
#XTIT
57+
RootEntity.objectTitle= Root Entity
58+
59+
## Level1Entity
60+
#XTIT
61+
Level1Entity.objectTitle=Level1 Entity
62+
63+
## Level2Entity
64+
#XTIT
65+
Level2Entity.objectTitle=Level2 Entity
66+
67+
## Level3Entity
68+
#XTIT
69+
Level3Entity.objectTitle=Level3 Entity
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
ID;name;info_ID
2+
bc21e0d9-a313-4f52-8336-c1be5f88c346;Mission1;bc21e0d9-a313-4f52-8336-c3da5f66d537
3+
bc21e0d9-a313-4f52-8336-c1be5f55d137;Mission2;bc21e0d9-a313-4f52-8336-d4ad6c55d563
4+
bc21e0d9-a313-4f52-8336-c1be5f44f435;Mission3;bc21e0d9-a313-4f52-8336-b5fa4d22a123
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
ID;name
2+
bc21e0d9-a313-4f52-8336-a4eb6d55c137;Super Mario1
3+
bc21e0d9-a313-4f52-8336-a2dcec6d33f541;Super Mario2
4+
bc21e0d9-a313-4f52-8336-d3da5a66c153;Super Mario3
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
ID;name;info_ID
2+
bc21e0d9-a313-4f52-8336-c3da5f66d537;Track1;bc21e0d9-a313-4f52-8336-a4eb6d55c137
3+
c21e0d9-a313-4f52-8336-d4ad6c55d563;Track2;bc21e0d9-a313-4f52-8336-a2dcec6d33f541
4+
bc21e0d9-a313-4f52-8336-b5fa4d22a123;Track3;bc21e0d9-a313-4f52-8336-d3da5a66c153
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
ID;title;parent_ID
2+
9d703c23-54a8-4eff-81c1-cdce6b8376b1;Level1 Wuthering Heights;64625905-c234-4d0d-9bc1-283ee8940812
3+
676059d4-8851-47f1-b558-3bdc461bf7d5;Level1 Jane Eyre;5ab2a87b-3a56-4d97-a697-7af72334b123
4+
42bc7997-f6ce-4ae9-8a64-ee5e02ef1087;Level1 The Raven;5ab2a87b-3a56-4d97-a697-7af72334b213
5+
9297e4ea-396e-47a4-8815-cd4622dea8b1;Level1 Eleonora;8aaed432-8336-4b0d-be7e-3ef1ce7f14dc
6+
574c8add-0ee3-4175-ab62-ca09a92c723c;Level1 Catweazle;8aaed432-8336-4b0d-be7e-3ef1ce7f14dc
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
ID;title;parent_ID
2+
9a61178f-bfb3-4c17-8d17-c6b4a63e0802;Level1Object title1;0a41a187-a2ff-4df6-bd12-fae8996e7e28
3+
ae0d8b10-84cf-4777-a489-a198d1717c75;Level1Object title2;6ac4afbf-deda-45ae-88e6-2883157cd576
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ID;title;parent_ID
2+
dd1fdd7d-da2a-4600-940b-0baf2946c4ff;Level2 The title;9d703c23-54a8-4eff-81c1-cdce6b8376b1
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
ID;title;parent_ID
2+
a40a9fd8-573d-4f41-1111-fa8ea0d8b1bc;Level2Object title1;9a61178f-bfb3-4c17-8d17-c6b4a63e0802
3+
55bb60e4-ed86-46e6-9378-346153eba8d4;Level2Object title2;ae0d8b10-84cf-4777-a489-a198d1717c75

0 commit comments

Comments
 (0)