in objectModel/CSharp/Microsoft.CommonDataModel.ObjectModel/Cdm/CdmAttributeContext.cs [663:872]
internal bool PruneToScope(HashSet<CdmAttributeContext> scopeSet)
{
// run over the whole tree and make a set of the nodes that should be saved for sure. This is anything NOT under a generated set
// (so base entity chains, entity attributes entity definitions)
// for testing, don't delete this
//Func<CdmObject, long> CountNodes = null;
//CountNodes = (subItem) =>
//{
// if (!(subItem is CdmAttributeContext))
// {
// return 1;
// }
// CdmAttributeContext ac = subItem as CdmAttributeContext;
// if (ac.Contents == null || ac.Contents.Count == 0)
// {
// return 1;
// }
// // look at all children
// long total = 0;
// foreach (var subSub in ac.Contents)
// {
// total += CountNodes(subSub);
// }
// return 1 + total;
//};
//System.Diagnostics.Debug.WriteLine($"Pre Prune {CountNodes(this)}");
// so ... the change from the old behavior is to depend on the lineage pointers to save the attribute defs
// in the 'structure' part of the tree that might matter. keep all of the other structure info and keep some
// special nodes (like the ones that have removed attributes) that won't get found from lineage trace but that are
// needed to understand what took place in resolution
HashSet<CdmAttributeContext> nodesToSave = new HashSet<CdmAttributeContext>();
// helper that save the passed node and anything up the parent chain
Func<CdmAttributeContext, bool> SaveParentNodes = null;
SaveParentNodes = (currNode) =>
{
if (nodesToSave.Contains(currNode))
{
return true;
}
nodesToSave.Add(currNode);
// get the parent
if (currNode.Parent?.ExplicitReference != null)
{
return SaveParentNodes(currNode.Parent.ExplicitReference as CdmAttributeContext);
}
return true;
};
// helper that saves the current node (and parents) plus anything in the lineage (with their parents)
Func<CdmAttributeContext, bool> SaveLineageNodes = null;
SaveLineageNodes = (currNode) =>
{
if (!SaveParentNodes(currNode))
{
return false;
}
if (currNode.Lineage != null && currNode.Lineage.Count > 0)
{
foreach (var lin in currNode.Lineage)
{
if (lin.ExplicitReference != null)
{
if (!SaveLineageNodes(lin.ExplicitReference as CdmAttributeContext))
{
return false;
}
}
}
}
return true;
};
Func<CdmObject, bool, bool, bool, bool> SaveStructureNodes = null;
SaveStructureNodes = (subItem, inGenerated, inProjection, inRemove) =>
{
if (!(subItem is CdmAttributeContext))
{
return true;
}
CdmAttributeContext ac = subItem as CdmAttributeContext;
if (ac.Type == CdmAttributeContextType.GeneratedSet)
{
inGenerated = true; // special mode where we hate everything except the removed att notes
}
if (inGenerated && ac.Type == CdmAttributeContextType.OperationExcludeAttributes)
{
inRemove = true; // triggers us to know what to do in the next code block.
}
bool removedAttribute = false;
if (ac.Type == CdmAttributeContextType.AttributeDefinition)
{
// empty attribute nodes are descriptions of source attributes that may or may not be needed. lineage will sort it out.
// the exception is for attribute descriptions under a remove attributes operation. they are gone from the resolved att set, so
// no history would remain
if (inRemove)
{
removedAttribute = true;
}
else if (ac.Contents == null || ac.Contents.Count == 0)
{
return true;
}
}
// this attribute was removed by a projection operation, but we want to keep the node to indicate what the operation did
if (ac.Type == CdmAttributeContextType.AttributeExcluded)
{
removedAttribute = true;
}
if (!inGenerated || removedAttribute)
{
// mark this as something worth saving, sometimes
// these get discovered at the leaf of a tree that we want to mostly ignore, so can cause a
// discontinuity in the 'save' chains, so fix that
SaveLineageNodes(ac);
}
if (ac.Type == CdmAttributeContextType.Projection)
{
inProjection = true; // track this so we can do the next thing ...
}
if (ac.Type == CdmAttributeContextType.Entity && inProjection)
{
// this is far enough, the entity that is somewhere under a projection chain
// things under this might get saved through lineage, but down to this point will get in for sure
return true;
}
if (ac.Contents == null || ac.Contents.Count == 0)
{
return true;
}
// look at all children
foreach (var subSub in ac.Contents)
{
if (!SaveStructureNodes(subSub, inGenerated, inProjection, inRemove))
{
return false;
}
}
return true;
};
if (!SaveStructureNodes(this, false, false, false))
{
return false;
}
// next, look at the attCtx for every resolved attribute. follow the lineage chain and mark all of those nodes as ones to save
// also mark any parents of those as savers
// so, do that ^^^ for every primary context found earlier
foreach (var primCtx in scopeSet)
{
if (!SaveLineageNodes(primCtx))
{
return false;
}
}
// now the cleanup, we have a set of the nodes that should be saved
// run over the tree and re-build the contents collection with only the things to save
Func<CdmObject, bool> CleanSubGroup = null;
CleanSubGroup = (subItem) =>
{
if (subItem.ObjectType == CdmObjectType.AttributeRef)
{
return true; // not empty
}
CdmAttributeContext ac = subItem as CdmAttributeContext;
if (!nodesToSave.Contains(ac))
{
return false; // don't even look at content, this all goes away
}
if (ac.Contents != null && ac.Contents.Count > 0)
{
// need to clean up the content array without triggering the code that fixes in document or paths
var newContent = new List<CdmObject>();
foreach (var sub in ac.Contents)
{
// true means keep this as a child
if (CleanSubGroup(sub))
{
newContent.Add(sub);
}
}
// clear the old content and replace
ac.Contents.Clear();
ac.Contents.AddRange(newContent);
}
return true;
};
CleanSubGroup(this);
//System.Diagnostics.Debug.WriteLine($"Post Prune {CountNodes(this)}");
return true;
}