Skip to content

Commit

Permalink
Added support for querying entity key metadata
Browse files Browse the repository at this point in the history
Fixes #409
  • Loading branch information
MarkMpn committed Jul 18, 2024
1 parent 4cabe4b commit a1efc46
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 22 deletions.
60 changes: 46 additions & 14 deletions MarkMpn.Sql4Cds.Engine/ExecutionPlan/FilterNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1533,7 +1533,7 @@ private bool FoldFiltersToDataSources(NodeCompilationContext context, IList<Opti
if (source is MetadataQueryNode meta)
{
// If the criteria are ANDed, see if any of the individual conditions can be translated to the metadata query
Filter = ExtractMetadataFilters(foldableContext, Filter, meta, out var entityFilter, out var attributeFilter, out var relationshipFilter);
Filter = ExtractMetadataFilters(foldableContext, Filter, meta, out var entityFilter, out var attributeFilter, out var relationshipFilter, out var keyFilter);

meta.Query.AddFilter(entityFilter);

Expand All @@ -1547,7 +1547,12 @@ private bool FoldFiltersToDataSources(NodeCompilationContext context, IList<Opti

meta.Query.RelationshipQuery.AddFilter(relationshipFilter);

if (entityFilter != null || attributeFilter != null || relationshipFilter != null)
if (keyFilter != null && meta.Query.KeyQuery == null)
meta.Query.KeyQuery = new EntityKeyQueryExpression();

meta.Query.KeyQuery.AddFilter(keyFilter);

if (entityFilter != null || attributeFilter != null || relationshipFilter != null || keyFilter != null)
foldedFilters = true;
}

Expand Down Expand Up @@ -1927,9 +1932,9 @@ private BooleanExpression ExtractFetchXMLFilters(NodeCompilationContext context,
return bin.FirstExpression ?? bin.SecondExpression;
}

protected BooleanExpression ExtractMetadataFilters(NodeCompilationContext context, BooleanExpression criteria, MetadataQueryNode meta, out MetadataFilterExpression entityFilter, out MetadataFilterExpression attributeFilter, out MetadataFilterExpression relationshipFilter)
protected BooleanExpression ExtractMetadataFilters(NodeCompilationContext context, BooleanExpression criteria, MetadataQueryNode meta, out MetadataFilterExpression entityFilter, out MetadataFilterExpression attributeFilter, out MetadataFilterExpression relationshipFilter, out MetadataFilterExpression keyFilter)
{
if (TranslateMetadataCriteria(context, criteria, meta, out entityFilter, out attributeFilter, out relationshipFilter))
if (TranslateMetadataCriteria(context, criteria, meta, out entityFilter, out attributeFilter, out relationshipFilter, out keyFilter))
return null;

if (!(criteria is BooleanBinaryExpression bin))
Expand All @@ -1938,12 +1943,13 @@ protected BooleanExpression ExtractMetadataFilters(NodeCompilationContext contex
if (bin.BinaryExpressionType != BooleanBinaryExpressionType.And)
return criteria;

bin.FirstExpression = ExtractMetadataFilters(context, bin.FirstExpression, meta, out var lhsEntityFilter, out var lhsAttributeFilter, out var lhsRelationshipFilter);
bin.SecondExpression = ExtractMetadataFilters(context, bin.SecondExpression, meta, out var rhsEntityFilter, out var rhsAttributeFilter, out var rhsRelationshipFilter);
bin.FirstExpression = ExtractMetadataFilters(context, bin.FirstExpression, meta, out var lhsEntityFilter, out var lhsAttributeFilter, out var lhsRelationshipFilter, out var lhsKeyFilter);
bin.SecondExpression = ExtractMetadataFilters(context, bin.SecondExpression, meta, out var rhsEntityFilter, out var rhsAttributeFilter, out var rhsRelationshipFilter, out var rhsKeyFilter);

entityFilter = (lhsEntityFilter != null && rhsEntityFilter != null) ? new MetadataFilterExpression { Filters = { lhsEntityFilter, rhsEntityFilter } } : lhsEntityFilter ?? rhsEntityFilter;
attributeFilter = (lhsAttributeFilter != null && rhsAttributeFilter != null) ? new MetadataFilterExpression { Filters = { lhsAttributeFilter, rhsAttributeFilter } } : lhsAttributeFilter ?? rhsAttributeFilter;
relationshipFilter = (lhsRelationshipFilter != null && rhsRelationshipFilter != null) ? new MetadataFilterExpression { Filters = { lhsRelationshipFilter, rhsRelationshipFilter } } : lhsRelationshipFilter ?? rhsRelationshipFilter;
keyFilter = (lhsKeyFilter != null && rhsKeyFilter != null) ? new MetadataFilterExpression { Filters = { lhsKeyFilter, rhsKeyFilter } } : lhsKeyFilter ?? rhsKeyFilter;

if (bin.FirstExpression != null && bin.SecondExpression != null)
return bin;
Expand Down Expand Up @@ -1992,20 +1998,21 @@ private bool TranslateChildFilters(BooleanExpression criteria, INodeSchema schem
return false;
}

protected bool TranslateMetadataCriteria(NodeCompilationContext context, BooleanExpression criteria, MetadataQueryNode meta, out MetadataFilterExpression entityFilter, out MetadataFilterExpression attributeFilter, out MetadataFilterExpression relationshipFilter)
protected bool TranslateMetadataCriteria(NodeCompilationContext context, BooleanExpression criteria, MetadataQueryNode meta, out MetadataFilterExpression entityFilter, out MetadataFilterExpression attributeFilter, out MetadataFilterExpression relationshipFilter, out MetadataFilterExpression keyFilter)
{
entityFilter = null;
attributeFilter = null;
relationshipFilter = null;
keyFilter = null;

var expressionCompilationContext = new ExpressionCompilationContext(context, null, null);
var expressionExecutionContext = new ExpressionExecutionContext(new NodeExecutionContext(context.DataSources, context.Options, context.ParameterTypes, null, null));

if (criteria is BooleanBinaryExpression binary)
{
if (!TranslateMetadataCriteria(context, binary.FirstExpression, meta, out var lhsEntityFilter, out var lhsAttributeFilter, out var lhsRelationshipFilter))
if (!TranslateMetadataCriteria(context, binary.FirstExpression, meta, out var lhsEntityFilter, out var lhsAttributeFilter, out var lhsRelationshipFilter, out var lhsKeyFilter))
return false;
if (!TranslateMetadataCriteria(context, binary.SecondExpression, meta, out var rhsEntityFilter, out var rhsAttributeFilter, out var rhsRelationshipFilter))
if (!TranslateMetadataCriteria(context, binary.SecondExpression, meta, out var rhsEntityFilter, out var rhsAttributeFilter, out var rhsRelationshipFilter, out var rhsKeyFilter))
return false;

if (binary.BinaryExpressionType == BooleanBinaryExpressionType.Or)
Expand All @@ -2029,6 +2036,7 @@ protected bool TranslateMetadataCriteria(NodeCompilationContext context, Boolean
entityFilter = lhsEntityFilter;
attributeFilter = lhsAttributeFilter;
relationshipFilter = lhsRelationshipFilter;
keyFilter = lhsKeyFilter;

if (rhsEntityFilter != null)
{
Expand All @@ -2054,6 +2062,14 @@ protected bool TranslateMetadataCriteria(NodeCompilationContext context, Boolean
relationshipFilter = new MetadataFilterExpression { Filters = { lhsRelationshipFilter, rhsRelationshipFilter }, FilterOperator = binary.BinaryExpressionType == BooleanBinaryExpressionType.And ? LogicalOperator.And : LogicalOperator.Or };
}

if (rhsKeyFilter != null)
{
if (keyFilter == null)
keyFilter = rhsKeyFilter;
else
keyFilter = new MetadataFilterExpression { Filters = { lhsKeyFilter, rhsKeyFilter }, FilterOperator = binary.BinaryExpressionType == BooleanBinaryExpressionType.And ? LogicalOperator.And : LogicalOperator.Or };
}

return true;
}

Expand Down Expand Up @@ -2114,7 +2130,7 @@ protected bool TranslateMetadataCriteria(NodeCompilationContext context, Boolean

var condition = new MetadataConditionExpression(parts[1], op, literal);

return TranslateMetadataCondition(condition, parts[0], meta, out entityFilter, out attributeFilter, out relationshipFilter);
return TranslateMetadataCondition(condition, parts[0], meta, out entityFilter, out attributeFilter, out relationshipFilter, out keyFilter);
}

if (criteria is InPredicate inPred)
Expand All @@ -2138,7 +2154,7 @@ protected bool TranslateMetadataCriteria(NodeCompilationContext context, Boolean

var condition = new MetadataConditionExpression(parts[1], inPred.NotDefined ? MetadataConditionOperator.NotIn : MetadataConditionOperator.In, inPred.Values.ToArray());

return TranslateMetadataCondition(condition, parts[0], meta, out entityFilter, out attributeFilter, out relationshipFilter);
return TranslateMetadataCondition(condition, parts[0], meta, out entityFilter, out attributeFilter, out relationshipFilter, out keyFilter);
}

if (criteria is BooleanIsNullExpression isNull)
Expand All @@ -2159,22 +2175,24 @@ protected bool TranslateMetadataCriteria(NodeCompilationContext context, Boolean

var condition = new MetadataConditionExpression(parts[1], isNull.IsNot ? MetadataConditionOperator.NotEquals : MetadataConditionOperator.Equals, null);

return TranslateMetadataCondition(condition, parts[0], meta, out entityFilter, out attributeFilter, out relationshipFilter);
return TranslateMetadataCondition(condition, parts[0], meta, out entityFilter, out attributeFilter, out relationshipFilter, out keyFilter);
}

return false;
}

private bool TranslateMetadataCondition(MetadataConditionExpression condition, string alias, MetadataQueryNode meta, out MetadataFilterExpression entityFilter, out MetadataFilterExpression attributeFilter, out MetadataFilterExpression relationshipFilter)
private bool TranslateMetadataCondition(MetadataConditionExpression condition, string alias, MetadataQueryNode meta, out MetadataFilterExpression entityFilter, out MetadataFilterExpression attributeFilter, out MetadataFilterExpression relationshipFilter, out MetadataFilterExpression keyFilter)
{
entityFilter = null;
attributeFilter = null;
relationshipFilter = null;
keyFilter = null;

// Translate queries on attribute.EntityLogicalName to entity.LogicalName for better performance
var isEntityFilter = alias.Equals(meta.EntityAlias, StringComparison.OrdinalIgnoreCase);
var isAttributeFilter = alias.Equals(meta.AttributeAlias, StringComparison.OrdinalIgnoreCase);
var isRelationshipFilter = alias.Equals(meta.OneToManyRelationshipAlias, StringComparison.OrdinalIgnoreCase) || alias.Equals(meta.ManyToOneRelationshipAlias, StringComparison.OrdinalIgnoreCase) || alias.Equals(meta.ManyToManyRelationshipAlias, StringComparison.OrdinalIgnoreCase);
var isKeyFilter = alias.Equals(meta.KeyAlias, StringComparison.OrdinalIgnoreCase);

if (isAttributeFilter &&
condition.PropertyName.Equals(nameof(AttributeMetadata.EntityLogicalName), StringComparison.OrdinalIgnoreCase))
Expand All @@ -2200,11 +2218,19 @@ private bool TranslateMetadataCondition(MetadataConditionExpression condition, s
isEntityFilter = true;
}

if (isKeyFilter &&
condition.PropertyName.Equals(nameof(EntityKeyMetadata.EntityLogicalName), StringComparison.OrdinalIgnoreCase))
{
condition.PropertyName = nameof(EntityMetadata.LogicalName);
isKeyFilter = false;
isEntityFilter = true;
}

var filter = new MetadataFilterExpression { Conditions = { condition } };

// Attributes & relationships are polymorphic, but filters can only be applied to the base type. Check the property
// we're filtering on is valid to be folded
var targetType = isEntityFilter ? typeof(EntityMetadata) : isAttributeFilter ? typeof(AttributeMetadata) : typeof(RelationshipMetadataBase);
var targetType = isEntityFilter ? typeof(EntityMetadata) : isAttributeFilter ? typeof(AttributeMetadata) : isRelationshipFilter ? typeof(RelationshipMetadataBase) : typeof(EntityKeyMetadata);
var prop = targetType.GetProperty(condition.PropertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);

if (prop == null)
Expand Down Expand Up @@ -2299,6 +2325,12 @@ private bool TranslateMetadataCondition(MetadataConditionExpression condition, s
return true;
}

if (isKeyFilter)
{
keyFilter = filter;
return true;
}

return false;
}

Expand Down
14 changes: 14 additions & 0 deletions MarkMpn.Sql4Cds.Engine/ExecutionPlan/FoldableJoinNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,20 @@ private bool FoldMetadataJoin(NodeCompilationContext context, IList<OptimizerHin
folded = entityMeta;
return true;
}

if (otherMeta.MetadataSource == MetadataSource.Key)
{
if (!otherKey.Equals($"{otherMeta.KeyAlias}.{nameof(EntityKeyMetadata.EntityLogicalName)}", StringComparison.OrdinalIgnoreCase))
return false;

// Move the key details into the entity source
entityMeta.MetadataSource |= otherMeta.MetadataSource;
entityMeta.KeyAlias = otherMeta.KeyAlias;
entityMeta.Query.KeyQuery = otherMeta.Query.KeyQuery;

folded = entityMeta;
return true;
}
}

return false;
Expand Down
Loading

0 comments on commit a1efc46

Please sign in to comment.