@sap/cds-compiler
Version:
CDS (Core Data Services) compiler and backends
707 lines (661 loc) • 29.4 kB
JavaScript
;
const { forEachDefinition,
copyAnnotations, forEachMemberRecursively,
transformExpression, transformAnnotationExpression } = require('../../model/csnUtils');
const { isBuiltinType, isMagicVariable } = require('../../base/builtins');
const transformUtils = require('../transformUtils');
const { setProp, forEachGeneric } = require('../../base/model');
const { applyTransformationsOnDictionary,
applyTransformationsOnNonDictionary } = require('../db/applyTransformations.js');
const { handleManagedAssociationsAndCreateForeignKeys } = require('../db/flattening');
const { cloneCsnNonDict } = require('../../model/cloneCsn');
const { forEach } = require('../../utils/objectUtils');
const { assignAnnotation } = require('../../edm/edmUtils');
function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, getFinalTypeInfo, isExternalServiceMember, error, csnUtils, options) {
const allMgdAssocDefs = [];
forEachDefinition(csn, (def, defName) => {
if (def.kind === 'entity' && !isExternalServiceMember(def, defName)) {
['elements', 'params'].forEach(dictName => {
const dict = def[dictName];
if (dict) {
const csnPath = ['definitions', defName, dictName];
const orderedElementList = [];
forEach(dict, (childName, child) => {
const location = [ ...csnPath, childName ];
const rootPrefix = [ defName ];
let resolvedElt = child;
let typeIdx = 0;
if (child.type && !child.elements) {
resolvedElt = getFinalTypeInfo(child.type);
if (resolvedElt.elements)
typeIdx = rootPrefix.length + 1;
}
if (resolvedElt.elements) {
const flattenedSubTree = recurseIntoElement(dictName, child, resolvedElt,
!!child.notNull, location, [ ...rootPrefix, childName ], typeIdx);
flattenedSubTree.forEach(([flatEltName, flatElt]) => {
if (dict[flatEltName] || orderedElementList.some(elt => elt[0] === flatEltName))
error('name-duplicate-element', location,
{ '#': 'flatten-element-exist', name: flatEltName });
propagateToFlatElem(child, flatElt);
rewriteOnCondition(flatElt, flattenedSubTree);
adaptManagedAssociationSpecialFields(flatElt, flatEltName, flattenedSubTree);
orderedElementList.push([flatEltName, flatElt]);
});
}
else {
// TODO: run adaptManagedAssociationSpecialFields here as well
const flatElt = cloneElt(dictName, child, location, [ defName, childName ]);
orderedElementList.push([childName, flatElt]);
}
});
const flatDict = orderedElementList.reduce((elements, [ flatEltName, flatElt ]) => {
if (flatElt.items) {
// rewrite annotation paths inside items.elements
forEachMemberRecursively(flatElt.items, (elt, eltName, _prop, path) => {
const exprAnnos = Object.keys(elt).filter(pn => pn[0] === '@');
flattenAndPrefixExprPaths(elt, exprAnnos, elt.$path, path, 0, true);
if (csnUtils.isManagedAssociation(elt)) {
allMgdAssocDefs.push(elt);
}
}, [ flatEltName ], true, { pathWithoutProp: true } );
} else if (flatElt.targetAspect) {
forEachMemberRecursively(flatElt.targetAspect, (elt, _eltName, _prop, _path) => {
// TODO: check whether that needs to be done for targetAspects as well
// const exprAnnos = Object.keys(elt).filter(pn => pn[0] === '@');
// flattenAndPrefixExprPaths(elt, exprAnnos, elt.$path, path, 0, true);
if (csnUtils.isManagedAssociation(elt)) {
allMgdAssocDefs.push(elt);
}
}, [ flatEltName ], true, { pathWithoutProp: true } );
}
setProp(flatElt, '$pathInStructuredModel', flatElt.$path);
setProp(flatElt, '$path', [ ...csnPath, flatEltName ]);
elements[flatEltName] = flatElt;
return elements;
}, Object.create(null));
setProp(def, `$flat${dictName}`, flatDict);
orderedElementList.reduce(
(mgdAssocs, [ _flatEltName, flatElt ]) => {
if(csnUtils.isManagedAssociation(flatElt))
mgdAssocs.push(flatElt);
return mgdAssocs;
}, allMgdAssocDefs);
}
});
// entity annotations
const flatAnnos = Object.create(null);
const annoNames = copyAnnotations(def, flatAnnos).filter(pn => pn[0] === '@');
flattenAndPrefixExprPaths(flatAnnos, annoNames, [ 'definitions', defName ], [ defName ], 0);
setProp(def, '$flatAnnotations', flatAnnos);
// explicit binding parameter of bound action
if (def.actions) {
const special$self = !csn?.definitions?.$self && '$self';
Object.entries(def.actions).forEach(([actionName, action]) => {
if (action.params) {
const params = Object.entries(action.params);
const firstParam = params[0][1];
const type = firstParam?.items?.type || firstParam?.type;
if (type === special$self) {
const bindingParamName = params[0][0];
const markBindingParam = {
ref: (parent, prop, xpr) => {
if ((xpr[0].id || xpr[0]) === bindingParamName)
setProp(parent, '$bparam', true)
},
};
const refCheck = {
ref: (elemref, prop, xpr, path) => {
const { art, scope } = inspectRef(path);
if (scope !== '$magic' && art) {
const ft = csnUtils.getFinalTypeInfo(art.type);
if (!isBuiltinType(ft?.type) && refCheck.anno !== 'value') {
error('odata-anno-xpr-ref', path, { anno: refCheck.anno, elemref, '#': 'flatten_builtin_type' });
}
}
}
};
const flatAnnos = Object.create(null);
const annoNames = copyAnnotations(action, flatAnnos).filter(pn => pn[0] === '@');
annoNames.forEach((an) => {
refCheck.anno = an;
transformAnnotationExpression(flatAnnos, an,
[ markBindingParam, refCheck, refFlattener ],
[ 'definitions', defName, 'actions', actionName ]);
adaptRefs.forEach(fn => fn(true, 1, (parent) => parent.$bparam));
adaptRefs.length = 0;
});
setProp(action, '$flatAnnotations', flatAnnos);
forEachMemberRecursively(action, (member, memberName, prop, path, _parent) => {
const exprAnnos = Object.keys(member).filter(pn => pn[0] === '@');
exprAnnos.forEach((pn) => {
refCheck.anno = pn;
transformAnnotationExpression(member, pn, [ markBindingParam, refCheck, refFlattener ], path);
adaptRefs.forEach(fn => fn(true, 1, (parent) => parent.$bparam));
adaptRefs.length = 0;
});
},
[ 'definitions', defName, 'actions', actionName ]);
}
}
});
}
}
// loop through types as well in order to collect the managaed associations
// that reside in types definitions
if ((def.kind === 'action' || def.kind === 'function' || def.kind === 'type' || def.kind === 'aspect' || def.kind === 'event')
&& !isExternalServiceMember(def, defName)) {
if (def.kind === 'type' && csnUtils.isManagedAssociation(def))
allMgdAssocDefs.push(def);
else
forEachMemberRecursively(def, (elt, _eltName, _prop, _path) => {
if (csnUtils.isManagedAssociation(elt)) {
allMgdAssocDefs.push(elt);
}
});
}
// loop through actions/functions and action/function parameters as well
if (def.kind === 'entity' && def.actions && !isExternalServiceMember(def, defName)) {
forEachGeneric(def, 'actions', (act) => {
forEachMemberRecursively(act, (elt, _eltName, _prop, _path) => {
if (csnUtils.isManagedAssociation(elt)) {
allMgdAssocDefs.push(elt);
}
});
});
}
});
return allMgdAssocDefs;
function recurseIntoElement(scope, elt, resolvedElt, rootPathIsNotNull, location, rootPrefix = [], typeIdx = 0) {
const eltName = rootPrefix[rootPrefix.length-1];
if (!resolvedElt.elements) {
const flatElt = cloneElt(scope, elt, location, rootPrefix, typeIdx);
if (rootPathIsNotNull)
flatElt.notNull = true;
else
delete flatElt.notNull;
return [[ eltName, flatElt ]];
}
else {
let flattenedSubTree = [];
forEach(resolvedElt.elements, (childName, child) => {
resolvedElt = child;
if (child.type && !child.elements) {
resolvedElt = getFinalTypeInfo(child.type);
if (resolvedElt.elements)
typeIdx = rootPrefix.length + 1;
}
flattenedSubTree = flattenedSubTree.concat(recurseIntoElement(scope, child, resolvedElt,
!!(child.notNull && rootPathIsNotNull),
[... location, 'elements', childName],
[ ...rootPrefix, childName ], typeIdx));
});
// 1) rename, 2) filter duplicates and finalize new elements
const duplicateDict = Object.create(null);
return flattenedSubTree.map(([flatEltName, flatElt]) => {
return [ `${eltName}_${flatEltName}`, flatElt ];
}).filter(([name, flatElt]) =>{
if (duplicateDict[name]) {
error('name-duplicate-element', location,
{ '#': 'flatten-element-gen', name });
return false;
}
else {
propagateToFlatElem(elt, flatElt, rootPrefix, typeIdx);
duplicateDict[name] = flatElt;
return true;
}
})
}
}
function propagateToFlatElem(elt, flatElt, rootPrefix = [], typeIdx = 0) {
// Copy annotations from struct
// (not overwriting, because deep annotations should have precedence).
// This has historic reasons. We don't copy doc-comments because copying annotations
// is questionable to begin with. Only selected annotations should have been copied,
// if at all.
// When flattening structured elements for OData don't propagate the odata.Type annotations
// as these would falsify the flattened elements.
// TODO: directly refer to edmPrimitiveType facets
const excludes = {
'@odata.Type': 1,
'@odata.Scale': 1,
'@odata.Precision': 1,
'@odata.MaxLength': 1,
'@odata.SRID': 1,
'@odata.FixedLength': 1,
'@odata.Collation': 1,
'@odata.Unicode': 1,
};
// TODO: copy only those expression annotations that have no path into unreachable subtree, starting
// of typePathRoot (which could be some completely different path)
const exprAnnoNames = copyAnnotations(elt, flatElt, false, excludes).filter(pn => pn[0] === '@');
flattenAndPrefixExprPaths(flatElt, exprAnnoNames, elt.$path, rootPrefix, typeIdx);
// Copy selected type properties
['key', 'virtual', 'masked', 'viaAll', 'localized'].forEach(p => {
if (elt[p] != null)
flatElt[p] = elt[p];
});
}
// Copy the original element
function cloneElt(scope, elt, location, rootPrefix = [], typeIdx = 0) {
const flatElt = cloneCsnNonDict(elt,
{ ...options,
hiddenPropertiesToClone: [ '$structRef', '$fkExtensions', '$generatedForeignKeys' ]
} );
// needed for @cds.persistence.name
setProp(flatElt, '$defPath', rootPrefix);
setProp(flatElt, '$scope', scope);
retypeCloneWithFinalBaseType(flatElt, location);
if((elt._type || elt).type === 'cds.Map') {
assignAnnotation(flatElt, '@open', elt['@open'] || true);
}
const [ nonAnnoProps, exprAnnoProps ] = Object.keys(flatElt).reduce((acc, pn) => {
if (pn[0] !== '@' && pn !== 'value')
acc[0].push(pn);
else
acc[1].push(pn);
return acc;
}, [[],[]]);
// transform all non annotation properties for that flat element with the generic transformer
// we don't know what's inside the element clone (like anonymous sub elements behind 'items' etc.)
nonAnnoProps.forEach(pn => applyTransformationsOnNonDictionary(flatElt, pn, refFlattener, {}, elt.$path || location))
adaptRefs.forEach(fn => fn());
adaptRefs.length = 0;
// flatten and prefix annotations and 'value' paths
flattenAndPrefixExprPaths(flatElt, exprAnnoProps, elt.$path || location, rootPrefix, typeIdx);
return flatElt;
function retypeCloneWithFinalBaseType(elt, location) {
if (elt.type &&
!isBuiltinType(elt.type) &&
!isODataV4BuiltinFromService(elt.type, location)
&& !isItemsType(elt.type)) {
const resolvedType = csnUtils.getFinalTypeInfo(elt);
delete resolvedType.kind;
if (resolvedType.items)
delete resolvedType.type;
if (elt.items) {
if (resolvedType.items) {
elt.items = resolvedType;
delete elt.items.type;
}
else
elt.items.type = resolvedType;
}
else {
if (resolvedType.items) {
elt.items = resolvedType.items;
delete elt.type;
}
else
elt.type = resolvedType;
}
}
function isItemsType(typeName) {
const typeDef = csn.definitions[typeName];
return !!typeDef?.items;
}
function isODataV4BuiltinFromService( typeName, path ) {
if (options.odataVersion === 'v2' || typeof typeName !== 'string')
return false;
const typeServiceName = csnUtils.getServiceName(typeName);
let finalBaseType = csnUtils.getFinalTypeInfo(typeName).type;
if (!isBuiltinType(finalBaseType)) {
const typeDef = csn.definitions[finalBaseType];
finalBaseType = typeDef?.items?.type || typeDef?.type;
}
// we need the service of the current definition
const currDefServiceName = csnUtils.getServiceName(path[1]);
return typeServiceName === currDefServiceName && isBuiltinType(finalBaseType);
}
}
}
// The path rewriting must be done with the current CSN path of that exact annotation location
// in the element tree and done exactly after the initial copy, (otherwise, csnRefs is not able
// to locate the relative location of the path expression in this element).
// At this time both annotations and values must be rewritten.
//
// Later, the query can/must be rewritten as long as a flat OData CSN is published
// but this then operates on the entity/view which has all struct infos available
function flattenAndPrefixExprPaths(carrier, propNames, csnPath, rootPrefix, typeIdx, refParentIsItems = false) {
const refCheck = {
ref: (elemref, prop, xpr, path) => {
const { links, art, scope } = inspectRef(path);
if (scope !== '$magic' && art) {
// try to find rightmost 'items', terminate if association comes first.
let i = links.length-1;
const getProp = (propName) => links[i].art?.[propName];
let hasItems = false;
for(; i >= 0 && !getProp('target') && !hasItems; i--) {
const art = links[i].art;
hasItems = !!getProp('items') || (art.type && !!csnUtils.getFinalTypeInfo(art.type)?.items)
}
if(!hasItems) {
const ft = csnUtils.getFinalTypeInfo(art.type);
if (!isBuiltinType(ft?.items?.type || ft?.type) && refCheck.anno !== 'value') {
error('odata-anno-xpr-ref', path,
{
anno: refCheck.anno,
elemref,
name: refCheck.eltLocationStr,
'#': 'flatten_builtin'
});
}
}
}
}
}
refCheck.eltLocationStr = (function() {
const [head, ...tail ] = rootPrefix;
if(tail.length)
return `${head}:${tail.join('.')}`;
else
return `${head}`;
})();
const absolutifier = {
ref : (parent, prop, xpr, _path, _p, _ppn, ctx) => {
const head = xpr[0].id || xpr[0];
let isPrefixed = false;
if(!isMagicVariable(head)) {
if (head === '$self' && typeIdx < rootPrefix.length) {
isPrefixed = true;
const [xprHead, ...xprTail] = xpr.slice(1, xpr.length);
if(xprHead) {
if (xprHead.id) {
xprHead.id = rootPrefix.slice(1, typeIdx).concat(xprHead.id).join('_');
parent[prop] = [ xprHead, ...xprTail ];
}
else
parent[prop] = [ rootPrefix.slice(1, typeIdx).concat(xprHead).join('_'), ...xprTail];
}
}
else if (head !== '$self' && !parent.param && rootPrefix.length > 2) {
isPrefixed = true;
const [xprHead, ...xprTail] = xpr;
if (!refParentIsItems) {
if (xprHead.id) {
xprHead.id = rootPrefix.slice(1, -1).concat(xprHead.id).join('_');
parent[prop] = [ xprHead, ...xprTail ];
}
else
parent[prop] = [ rootPrefix.slice(1, -1).concat(xprHead).join('_'), ...xprTail];
}
else
parent[prop] = [ ...rootPrefix.slice(0, rootPrefix.length-1), ...xpr];
}
if(isPrefixed) {
if (carrier.$scope === 'params')
parent.param = true;
else
parent[prop].unshift('$self');
}
}
if(isPrefixed && ctx?.annoExpr?.['=']) {
ctx.annoExpr['='] = true;
}
}
}
refFlattener.$fnArgs = [ refParentIsItems ];
propNames.forEach(pn => {
refCheck.anno = pn;
if(pn[0] === '@') {
transformAnnotationExpression(carrier, pn, [ refCheck, refFlattener ], csnPath);
adaptRefs.forEach(fn => fn(refParentIsItems));
adaptRefs.length = 0;
transformAnnotationExpression(carrier, pn, absolutifier, csnPath);
}
if(pn === 'value') {
transformExpression(carrier, pn, [ refCheck, refFlattener ], csnPath);
adaptRefs.forEach(fn => fn(refParentIsItems));
adaptRefs.length = 0;
transformExpression(carrier, pn, absolutifier, csnPath);
}
});
}
// TODO: This should be part of the generic path rewriting algorithm
// Primitive approach here does not take into account absolute paths via $self etc...
function rewriteOnCondition(flatElt, flattenedSubTree) {
if (flatElt.on) {
// unmanaged relations can't be primary key
delete flatElt.key;
// Make refs resolvable by fixing the first ref step
transformExpression(flatElt, 'on', {
ref: (parent, prop, xpr) => {
const prefix = flatElt.$defPath.slice(1, -1).join('_');
const possibleFlatName = `${prefix}_${xpr[0]}`;
if (flattenedSubTree.find(entry => entry[0] === possibleFlatName))
xpr[0] = possibleFlatName;
}
});
}
}
// update the @odata.foreignKey4 and the $generatedForeignKeys array of
// an association that have been flattened
function adaptManagedAssociationSpecialFields(flatElt, flatEltName, flattenedSubTree) {
if (!(flatElt.target || flatElt.keys) && !flatElt.on )
return;
const structuredAssocName = flatElt.$defPath[flatElt.$defPath.length - 1];
const generatedForeignKeysForAssoc = flattenedSubTree
.filter(se => {
// compare $defPath without the last element
let comeFromSameDef = flatElt.$defPath.slice(0, flatElt.$defPath.length - 1).join('.') === se[1].$defPath.slice(0, se[1].$defPath.length - 1).join('.');
return (comeFromSameDef && (se[1]['@odata.foreignKey4'] && se[1]['@odata.foreignKey4'] === structuredAssocName) && se[0].startsWith(flatEltName))
});
generatedForeignKeysForAssoc.forEach(gfk => gfk[1]['@odata.foreignKey4'] = flatEltName);
// reassign the generated foreign keys for current assoc in order to assign
// correct values for $generatedFieldName later on during flattenManagedAssocsAsKeys();
// eslint-disable-next-line @stylistic/max-statements-per-line
setProp(flatElt, '$generatedForeignKeys', generatedForeignKeysForAssoc.map(gfk => { return { name: gfk[0] }}));
}
}
function flattenAllStructStepsInRefs( csn, refFlattener, adaptRefs, inspectRef, effectiveType,
csnUtils, error, options, iterateOptions = {} ) {
// All anno path flattening is already done, don't do it on locations where we don't want it!
const typeNames = [];
forEachDefinition(csn, (def, defName) => {
if (def.kind === 'entity') {
['query', 'projection'].forEach(dictName => {
applyTransformationsOnNonDictionary(csn.definitions[defName],
dictName, refFlattener, iterateOptions,
[ 'definitions', defName ]);
})
if (csn.definitions[defName].actions)
applyTransformationsOnDictionary(csn.definitions[defName].actions,
refFlattener, iterateOptions,
[ 'definitions', defName, 'actions' ]);
}
if (['type'].includes(def.kind)) {
typeNames.push(defName);
applyTransformationsOnNonDictionary(csn.definitions,
defName, refFlattener, iterateOptions, [ 'definitions' ]);
}
});
adaptRefs.forEach(fn => fn());
adaptRefs.length = 0;
const refCheck = {
ref: (elemref, prop, xpr, path) => {
const { links, art, scope } = inspectRef(path);
if (scope !== '$magic' && art) {
let i = links.length-2;
const getProp = (propName) =>
(links[i].art?.[propName] ||
effectiveType(links[i].art)[propName]);
let target = undefined;
for(; i >= 0 && !getProp('items') && !target; i--) {
target = getProp('target');
}
const ft = csnUtils.getFinalTypeInfo(art.type);
if (target && csn.definitions[target].$flatelements
&& !isBuiltinType(ft?.type) && refCheck.anno !== 'value') {
error('odata-anno-xpr-ref', path,
{ anno: refCheck.anno, elemref, '#': 'flatten_builtin_type' });
}
}
}
}
typeNames.forEach(tn => {
forEachMemberRecursively(csn.definitions[tn], (member, memberName, prop, csnPath) => {
Object.keys(member).filter(pn => pn[0] === '@').forEach(pn => {
refCheck.anno = pn;
transformAnnotationExpression(member, pn, [ refCheck, refFlattener ], csnPath);
adaptRefs.forEach(fn => fn(true, 1));
adaptRefs.length = 0;
});
}, [ 'definitions', tn ]);
})
}
function getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, options, resolved, pathDelimiter) {
const { flattenStructStepsInRef } = transformUtils.getTransformers(csn, options, pathDelimiter);
/**
* For each step of the links, check if there is a type reference.
* If there is, resolve it and store the result in a WeakMap.
*
* @param {Array} [links]
* @todo seems too hacky
* @returns {WeakMap} A WeakMap where a link is the key and the type is the value
*/
function resolveLinkTypes( links = [] ) {
const resolvedLinkTypes = new WeakMap();
links.forEach((link) => {
const { art } = link;
if (art && art.type)
resolvedLinkTypes.set(link, effectiveType(art));
});
return resolvedLinkTypes;
}
const adaptRefs = [];
const transformer = {
ref: (parent, _prop, ref, path, _p, _ppn, ctx) => {
const { links, art, scope } = inspectRef(path);
const resolvedLinkTypes = resolveLinkTypes(links);
setProp(parent, '$path', [ ...path ]);
if (insideKeys(path))
setProp(parent, '_art', art);
const lastRef = ref[ref.length - 1];
const fn = (suspend = false, suspendPos = 0,
refFilter = (_parent) => true) => {
if (refFilter(parent)) {
const scopedPath = [ ...parent.$path ];
// TODO: If foreign key annotations should be assigned via
// full path into target, uncomment this line and
// comment/remove setProp in expansion.js
// setProp(parent, '$structRef', parent.ref);
const flattenParameters = true; // structured parameters are flattened
const [ newRef, refChanged ] = flattenStructStepsInRef(ref,
scopedPath, links, scope, resolvedLinkTypes,
suspend, suspendPos, parent.$bparam,
flattenParameters);
parent.ref = newRef;
resolved.set(parent, { links, art, scope });
// Explicitly set implicit alias for things that are now flattened - but only in columns
// TODO: Can this be done elegantly during expand phase already?
if (parent.$implicitAlias) { // an expanded s -> s.a is marked with this - do not add implicit alias "a" there, we want s_a
if (parent.ref[parent.ref.length - 1] === parent.as) // for a simple s that was expanded - for s.substructure this would not apply
delete parent.as;
delete parent.$implicitAlias;
}
// To handle explicitly written s.a - add implicit alias a, since after flattening it would otherwise be s_a
else if (parent.ref[parent.ref.length - 1] !== lastRef &&
(insideColumns(scopedPath) || insideKeys(scopedPath)) &&
!parent.as) {
parent.as = lastRef;
}
return refChanged;
}
return false;
/**
* Return true if the path points inside columns
*
* @param {CSN.Path} path
* @returns {boolean}
*/
function insideColumns( path ) {
return path.length >= 3
&& (path[path.length - 3] === 'SELECT'
|| path[path.length - 3] === 'projection')
&& path[path.length - 2] === 'columns';
}
};
/**
* Return true if the path points inside keys
*
* @param {CSN.Path} path
* @returns {boolean}
*/
function insideKeys( path ) {
return path.length >= 3
&& path[path.length - 2] === 'keys'
&& typeof path[path.length - 1] === 'number';
}
// adapt queries later
if(ctx?.annoExpr?.['=']) {
const annoExpr = ctx.annoExpr;
adaptRefs.push((...args) => {
if(fn(...args))
annoExpr['='] = true;
});
}
else
adaptRefs.push(fn);
},
}
return { adaptRefs, transformer, inspectRef, effectiveType };
}
// replace managed associations in key refs with their respective foreing keys
function replaceManagedAssocsAsKeys(allFlatManagedAssocDefinitions, csnUtils) {
allFlatManagedAssocDefinitions.forEach( assoc => {
let finished = false;
if (!assoc.keys)
return; // managed to-many assoc
while (!finished) {
const newKeys = [];
finished = processKeys(newKeys);
assoc.keys = newKeys;
}
function processKeys(collector) {
let done = true;
assoc.keys.forEach( key => {
const art = key._art;
if (art && csnUtils.isManagedAssociation(art)) {
done = false;
// key._art is the artifact from the structured model, because of that we need to look
// for the flat representation in the allFlatManagedAssocDefinitions
allFlatManagedAssocDefinitions.find(fa => (fa.$pathInStructuredModel || fa.$path).join() === art.$path.join())
.keys.forEach( keyAssocKey => {
collector.push(cloneAndExtendRef(keyAssocKey, key));
});
}
else if (art && !art.on){
if (!key.$generatedFieldName) {
const flatAssocName = assoc.$path[assoc.$path.length - 1];
// When we have a definition like type "<type_name>: Association to <target_name>",
// we do not generate foreign keys in the definition, therefore no $generatedForeignKeys,
// respectively we do not assign $generatedFieldName
if (assoc.$generatedForeignKeys) {
const generatedForeignKey = assoc.$generatedForeignKeys.find(gfk => gfk.name === `${flatAssocName}_${key.as || key.ref.join('_')}`);
key.$generatedFieldName = generatedForeignKey.name;
}
}
if (key.as && key.as === key.ref[0]) delete key.as;
collector.push(key);
}
})
return done;
}
});
function cloneAndExtendRef(keyAssocKey, key) {
let newKey = { ref: [`${key.ref.join('_')}_${keyAssocKey.as || keyAssocKey.ref.join('_')}`] };
setProp(newKey, '_art', keyAssocKey._art);
if (key.as) {
newKey.as = `${key.as}_${keyAssocKey.as || keyAssocKey.ref.join('_')}`;
}
return newKey;
}
}
module.exports = {
allInOneFlattening,
flattenAllStructStepsInRefs,
handleManagedAssociationsAndCreateForeignKeys,
getStructRefFlatteningTransformer,
replaceManagedAssocsAsKeys
};