UNPKG

@sap/cds-compiler

Version:

CDS (Core Data Services) compiler and backends

125 lines (108 loc) 4.7 kB
'use strict'; const { applyTransformations, transformAnnotationExpression, implicitAs } = require('../../model/csnUtils'); /** * If a path in an annotation expression can be interpreted as accessing a local foreign key, then * the foreign key reference is replaced with the foreign key itself. * Exception is when the association path step has a filter. * * @param {CSN} csn * @param {Object} csnUtils * @param {Object} iterateOptions */ function replaceForeignKeyRefsInExpressionAnnotations(csn, csnUtils, iterateOptions = {}) { const transformers = { '@': processRef, }; applyTransformations(csn, transformers, [ processRef ], iterateOptions); function processRef(parent, prop, _dict, path) { transformAnnotationExpression(parent, prop, { ref: (parent, _prop, ref, path, _p, _ppn, ctx) => { const { art, links } = parent._art && parent._links ? { art: parent._art, links: parent._links } : csnUtils.inspectRef(path); // if a reference points to a structure(managed assoc or structured element), then we do not process // as we can't guess which specific foreign key is targeted if ( !art || csnUtils.isManagedAssociation(art) || csnUtils.isStructured(art) ) return; const modifiedRef = replaceRefsWithFKs(ref, links, art); // update the ref and string token to true if there was FK replacement if ( modifiedRef.length !== ref.length || !modifiedRef.every((val, index) => val === ref[index]) ) { parent.ref = modifiedRef; if (ctx?.annoExpr?.['=']) ctx.annoExpr['='] = true; } }, }, path); } // Replace references to foreign keys function replaceRefsWithFKs(originalRef, links, expectedFkArt) { let result = [ ...originalRef ]; // stringify the tail of the ref for finding the potential foreign key const refTail = [ originalRef[originalRef.length - 1] ]; for (let i = originalRef.length - 2; i >= 0; i--) { const currentRef = originalRef[i]; const currentLink = links[i].art; // skip processing if the current reference is a filter if (typeof currentRef !== 'string') return result; // check if the current link is a managed association if (csnUtils.isManagedAssociation(currentLink)) { const matchedForeignKey = findMatchingForeignKeyForAssoc(currentLink, currentRef, refTail, expectedFkArt); if (matchedForeignKey) { // update the result and refTailAsStr with the matched foreign key result = [ ...result.slice(0, i), matchedForeignKey.name ]; refTail.unshift(currentRef); } else { return result; // return if no matching foreign key is found } } else { // update refTail for non-association links refTail.unshift(currentRef); } } return result; } // Lookup the foreign key in the association's generated foreign keys function findMatchingForeignKeyForAssoc(assoc, assocName, refTail, expectedFkArt) { const expectedFkName = getExpectedForeignKeyName(assoc, assocName, refTail); const matchedFk = assoc.$generatedForeignKeys?.find(fk => fk.source === expectedFkArt && fk.name === expectedFkName); return matchedFk; } // Generate the expected foreign key name, considering aliases, tuple expansion name changes, etc. function getExpectedForeignKeyName(assoc, assocName, refTail) { const refAliasMapping = assoc.keys.reduce( (acc, key) => { acc[key.ref.join('_')] = key.as || implicitAs(key.ref); return acc; }, {}); // generate the string representation of the reference tail const refTailAsStr = replaceRefsIfAliased(refTail, refAliasMapping) || refTail.join('_'); return `${ assocName }_${ refTailAsStr }`; } // Check if any prefix of refTail matches an alias in refAliasMapping and replace it function replaceRefsIfAliased(refTail, refAliasMapping) { // loop through refTail and try to find a match in the refAliasMapping // no need to look for the longest match as it is not allowed to declare // duplicate key references in one FKs scope let candidate = ''; for (let idx = 0; idx < refTail.length; idx++) { candidate = candidate ? `${ candidate }_${ refTail[idx] }` : refTail[idx]; if (refAliasMapping[candidate]) { refTail.splice(0, idx + 1, refAliasMapping[candidate]); return refTail.join('_'); } } return undefined; } } module.exports = replaceForeignKeyRefsInExpressionAnnotations;