public override INodeSchema GetSchema(IDictionary <string, DataSource> dataSources, IDictionary <string, Type> parameterTypes) { var schema = base.GetSchema(dataSources, parameterTypes); schema.ContainsColumn(LeftAttribute.GetColumnName(), out var left); schema.ContainsColumn(RightAttribute.GetColumnName(), out var right); if (JoinType == QualifiedJoinType.Inner || JoinType == QualifiedJoinType.LeftOuter) { ((NodeSchema)schema).SortOrder.Add(left); ((NodeSchema)schema).SortOrder.Add(right); } else if (JoinType == QualifiedJoinType.RightOuter) { ((NodeSchema)schema).SortOrder.Add(right); ((NodeSchema)schema).SortOrder.Add(left); } return(schema); }
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 } }
protected override IEnumerable <Entity> ExecuteInternal(IDictionary <string, DataSource> dataSources, IQueryExecutionOptions options, IDictionary <string, Type> parameterTypes, IDictionary <string, object> parameterValues) { _hashTable = new Dictionary <object, List <OuterRecord> >(); var mergedSchema = GetSchema(dataSources, parameterTypes, true); var additionalJoinCriteria = AdditionalJoinCriteria?.Compile(mergedSchema, parameterTypes); // Build the hash table var leftSchema = LeftSource.GetSchema(dataSources, parameterTypes); leftSchema.ContainsColumn(LeftAttribute.GetColumnName(), out var leftCol); foreach (var entity in LeftSource.Execute(dataSources, options, parameterTypes, parameterValues)) { var key = entity[leftCol]; if (!_hashTable.TryGetValue(key, out var list)) { list = new List <OuterRecord>(); _hashTable[key] = list; } list.Add(new OuterRecord { Entity = entity }); } // Probe the hash table using the right source var rightSchema = RightSource.GetSchema(dataSources, parameterTypes); rightSchema.ContainsColumn(RightAttribute.GetColumnName(), out var rightCol); foreach (var entity in RightSource.Execute(dataSources, options, parameterTypes, parameterValues)) { var key = entity[rightCol]; var matched = false; if (_hashTable.TryGetValue(key, out var list)) { foreach (var left in list) { if (SemiJoin && left.Used) { continue; } var merged = Merge(left.Entity, leftSchema, entity, rightSchema); if (additionalJoinCriteria == null || additionalJoinCriteria(merged, parameterValues, options)) { yield return(merged); left.Used = true; matched = true; } } } if (!matched && (JoinType == QualifiedJoinType.RightOuter || JoinType == QualifiedJoinType.FullOuter)) { yield return(Merge(null, leftSchema, entity, rightSchema)); } } if (JoinType == QualifiedJoinType.LeftOuter || JoinType == QualifiedJoinType.FullOuter) { foreach (var unmatched in _hashTable.SelectMany(kvp => kvp.Value).Where(e => !e.Used)) { yield return(Merge(unmatched.Entity, leftSchema, null, rightSchema)); } } }