From 56e7c17a207f5c6fca9686b9cdcbc2f1b156b79e Mon Sep 17 00:00:00 2001 From: Wenjun Zheng Date: Fri, 29 Aug 2025 17:49:23 +0800 Subject: [PATCH] fix: enhance change tracking logic to support parent entity checks --- lib/change-log.js | 78 +++++++++++++++++++++++++++++++---------------- 1 file changed, 51 insertions(+), 27 deletions(-) diff --git a/lib/change-log.js b/lib/change-log.js index 61a2429..96eeae0 100644 --- a/lib/change-log.js +++ b/lib/change-log.js @@ -16,7 +16,7 @@ const { } = require("./entity-helper") const { localizeLogFields } = require("./localization") const isRoot = "change-tracking-isRootEntity" - +const hasParent = "change-tracking-parentEntity" function formatDecimal(str, scale) { if (typeof str === "number" && !isNaN(str)) { @@ -439,12 +439,12 @@ async function generatePathAndParams (req, entityKey) { hasComp: true }; - if (hasParentAndForeignKey && parentEntity[isRoot]) { + if (hasParentAndForeignKey && isRootEntity(parentEntity)) { return compContext; } let parentAssoc = await processEntity(targetEntity, targetKey, compContext); - while (parentAssoc && !parentAssoc.entity[isRoot]) { + while (parentAssoc && !isRootEntity(parentAssoc.entity)) { parentAssoc = await processEntity( parentAssoc.entity, parentAssoc.ID, @@ -454,36 +454,59 @@ async function generatePathAndParams (req, entityKey) { return compContext; } -async function processEntity (entity, entityKey, compContext) { +async function processEntity(entity, entityKey, compContext) { + if (!entity || !entityKey || !compContext) return; + const { ID, foreignKey, parentEntity } = getAssociationDetails(entity); - if (foreignKey && parentEntity) { - const parentResult = - (await SELECT.one - .from(entity.name) - .where({ [ID]: entityKey }) - .columns(foreignKey)) || {}; - const hasForeignKey = parentResult[foreignKey]; - if (!hasForeignKey) return; - compContext.path = `${parentEntity.name}/${compContext.path}`; - compContext.params.unshift({ [ID]: parentResult[foreignKey] }); - return { - entity: parentEntity, - [ID]: hasForeignKey ? parentResult[foreignKey] : undefined - }; - } + if (!foreignKey || !parentEntity) return; + + const parentResult = await SELECT.one + .from(entity.name) + .where({ [ID]: entityKey }) + .columns(foreignKey); + + if (!parentResult || typeof parentResult !== 'object') return; + + const hasForeignKey = parentResult[foreignKey]; + if (!hasForeignKey) return; + + compContext.path = `${parentEntity.name}/${compContext.path}`; + compContext.params.unshift({ [ID]: hasForeignKey }); + + return { + entity: parentEntity, + [ID]: hasForeignKey + }; } function getAssociationDetails (entity) { - if (!entity) return {}; - const assocName = entity['change-tracking-parentEntity']?.associationName; - const assoc = entity.elements[assocName]; - const parentEntity = assoc?._target; - const foreignKey = assoc?.keys?.[0]?.$generatedFieldName; - const ID = assoc?.keys?.[0]?.ref[0] || 'ID'; + if (!entity || typeof entity !== 'object') return {}; + + const { name } = entity; + if (!name || typeof name !== 'string') return {}; + + const definition = cds.model.definitions[name]; + if (!definition) return {}; + + const assocName = entity[hasParent]?.associationName ?? definition[hasParent]?.associationName; + if (!assocName) return {}; + + const elements = entity.elements || {}; + const assoc = elements[assocName]; + if (!assoc) return {}; + + const parentEntity = assoc._target; + const foreignKey = assoc.keys?.[0]?.$generatedFieldName; + const ID = assoc.keys?.[0]?.ref?.[0] ?? 'ID'; + return { ID, foreignKey, parentEntity }; } +function isRootEntity (entity) { + return entity[isRoot] || (cds.model.definitions[entity.name]?.[isRoot] || false); +} + function isEmpty(value) { return value === null || value === undefined || value === ""; } @@ -517,8 +540,9 @@ async function trackChangesForDiff(diff, req, that){ let target = req.target let compContext = null; let entityKey = diff.ID + let isTopLevel = isRootEntity(target); const params = convertSubjectToParams(req.subject); - if (req.subject.ref.length === 1 && params.length === 1 && !target[isRoot]) { + if (req.subject.ref.length === 1 && params.length === 1 && !isTopLevel) { compContext = await generatePathAndParams(req, entityKey); } let isComposition = _isCompositionContextPath( @@ -527,7 +551,7 @@ async function trackChangesForDiff(diff, req, that){ ); if ( req.event === "DELETE" && - target[isRoot] && + isTopLevel && !cds.env.requires["change-tracking"]?.preserveDeletes ) { await DELETE.from(`sap.changelog.ChangeLog`).where({ entityKey });