public override IDataExecutionPlanNode FoldQuery(IDictionary <string, DataSource> dataSources, IQueryExecutionOptions options, IDictionary <string, Type> parameterTypes, IList <OptimizerHint> hints) { var leftSchema = LeftSource.GetSchema(dataSources, parameterTypes); LeftSource = LeftSource.FoldQuery(dataSources, options, parameterTypes, hints); LeftSource.Parent = this; var innerParameterTypes = GetInnerParameterTypes(leftSchema, parameterTypes); RightSource = RightSource.FoldQuery(dataSources, options, innerParameterTypes, hints); RightSource.Parent = this; return(this); }
public override IDataExecutionPlanNode FoldQuery(IDictionary <string, DataSource> dataSources, IQueryExecutionOptions options, IDictionary <string, Type> parameterTypes, IList <OptimizerHint> hints) { LeftSource = LeftSource.FoldQuery(dataSources, options, parameterTypes, hints); LeftSource.Parent = this; RightSource = RightSource.FoldQuery(dataSources, options, parameterTypes, hints); RightSource.Parent = this; if (SemiJoin) { return(this); } var leftSchema = LeftSource.GetSchema(dataSources, parameterTypes); var rightSchema = RightSource.GetSchema(dataSources, parameterTypes); if (LeftSource is FetchXmlScan leftFetch && RightSource is FetchXmlScan rightFetch) { // Can't join data from different sources if (!leftFetch.DataSource.Equals(rightFetch.DataSource, StringComparison.OrdinalIgnoreCase)) { return(this); } // If one source is distinct and the other isn't, joining the two won't produce the expected results if (leftFetch.FetchXml.distinct ^ rightFetch.FetchXml.distinct) { return(this); } // Check that the alias is valid for FetchXML if (!FetchXmlScan.IsValidAlias(rightFetch.Alias)) { return(this); } var leftEntity = leftFetch.Entity; var rightEntity = rightFetch.Entity; // Check that the join is on columns that are available in the FetchXML var leftAttribute = LeftAttribute.GetColumnName(); if (!leftSchema.ContainsColumn(leftAttribute, out leftAttribute)) { return(this); } var rightAttribute = RightAttribute.GetColumnName(); if (!rightSchema.ContainsColumn(rightAttribute, out rightAttribute)) { return(this); } var leftAttributeParts = leftAttribute.Split('.'); var rightAttributeParts = rightAttribute.Split('.'); if (leftAttributeParts.Length != 2) { return(this); } if (rightAttributeParts.Length != 2) { return(this); } // If the entities are from different virtual entity data providers it's probably not going to work if (!dataSources.TryGetValue(leftFetch.DataSource, out var dataSource)) { throw new NotSupportedQueryFragmentException("Missing datasource " + leftFetch.DataSource); } if (dataSource.Metadata[leftFetch.Entity.name].DataProviderId != dataSource.Metadata[rightFetch.Entity.name].DataProviderId) { return(this); } // Check we're not going to have too many link entities var leftLinkCount = leftFetch.Entity.GetLinkEntities().Count(); var rightLinkCount = rightFetch.Entity.GetLinkEntities().Count() + 1; if (leftLinkCount + rightLinkCount > 10) { return(this); } // If we're doing a right outer join, switch everything round to do a left outer join // Also switch join order for inner joins to use N:1 relationships instead of 1:N to avoid problems with paging if (JoinType == QualifiedJoinType.RightOuter || JoinType == QualifiedJoinType.Inner && !rightAttributeParts[0].Equals(rightFetch.Alias, StringComparison.OrdinalIgnoreCase) || JoinType == QualifiedJoinType.Inner && leftAttribute == leftSchema.PrimaryKey && rightAttribute != rightSchema.PrimaryKey) { Swap(ref leftFetch, ref rightFetch); Swap(ref leftEntity, ref rightEntity); Swap(ref leftAttribute, ref rightAttribute); Swap(ref leftAttributeParts, ref rightAttributeParts); Swap(ref leftSchema, ref rightSchema); } // Must be joining to the root entity of the right source, i.e. not a child link-entity if (!rightAttributeParts[0].Equals(rightFetch.Alias, StringComparison.OrdinalIgnoreCase)) { return(this); } // If there are any additional join criteria, either they must be able to be translated to FetchXml criteria // in the new link entity or we must be using an inner join so we can use a post-filter node var additionalCriteria = AdditionalJoinCriteria; var additionalLinkEntities = new Dictionary <object, List <FetchLinkEntityType> >(); if (TranslateFetchXMLCriteria(dataSource.Metadata, options, additionalCriteria, rightSchema, rightFetch.Alias, rightEntity.name, rightFetch.Alias, rightEntity.Items, out var filter, additionalLinkEntities)) { rightEntity.AddItem(filter); foreach (var kvp in additionalLinkEntities) { if (kvp.Key is FetchEntityType e) { foreach (var le in kvp.Value) { rightEntity.AddItem(le); } } else { foreach (var le in kvp.Value) { ((FetchLinkEntityType)kvp.Key).AddItem(le); } } } additionalCriteria = null; } if (additionalCriteria != null && JoinType != QualifiedJoinType.Inner) { return(this); } var rightLinkEntity = new FetchLinkEntityType { alias = rightFetch.Alias, name = rightEntity.name, linktype = JoinType == QualifiedJoinType.Inner ? "inner" : "outer", from = rightAttributeParts[1].ToLowerInvariant(), to = leftAttributeParts[1].ToLowerInvariant(), Items = rightEntity.Items }; // Find where the two FetchXml documents should be merged together and return the merged version if (leftAttributeParts[0].Equals(leftFetch.Alias)) { if (leftEntity.Items == null) { leftEntity.Items = new object[] { rightLinkEntity } } ; else { leftEntity.Items = leftEntity.Items.Concat(new object[] { rightLinkEntity }).ToArray(); } } else { var leftLinkEntity = leftFetch.Entity.FindLinkEntity(leftAttributeParts[0]); if (leftLinkEntity == null) { return(this); } if (leftLinkEntity.Items == null) { leftLinkEntity.Items = new object[] { rightLinkEntity } } ; else { leftLinkEntity.Items = leftLinkEntity.Items.Concat(new object[] { rightLinkEntity }).ToArray(); } } if (additionalCriteria != null) { return new FilterNode { Filter = additionalCriteria, Source = leftFetch } }