protected override IEnumerable <Entity> ExecuteInternal(IDictionary <string, DataSource> dataSources, IQueryExecutionOptions options, IDictionary <string, Type> parameterTypes, IDictionary <string, object> parameterValues) { // https://sqlserverfast.com/epr/merge-join/ // Implemented inner, left outer, right outer and full outer variants // Not implemented semi joins // TODO: Handle many-to-many joins // TODO: Handle union & concatenate // Left & Right: GetNext, mark as unmatched var leftSchema = LeftSource.GetSchema(dataSources, parameterTypes); var rightSchema = RightSource.GetSchema(dataSources, parameterTypes); var left = LeftSource.Execute(dataSources, options, parameterTypes, parameterValues).GetEnumerator(); var right = RightSource.Execute(dataSources, options, parameterTypes, parameterValues).GetEnumerator(); var mergedSchema = GetSchema(dataSources, parameterTypes, true); var additionalJoinCriteria = AdditionalJoinCriteria?.Compile(mergedSchema, parameterTypes); var hasLeft = left.MoveNext(); var hasRight = right.MoveNext(); var leftMatched = false; var rightMatched = false; var lt = new BooleanComparisonExpression { FirstExpression = LeftAttribute, ComparisonType = BooleanComparisonType.LessThan, SecondExpression = RightAttribute }.Compile(mergedSchema, parameterTypes); var eq = new BooleanComparisonExpression { FirstExpression = LeftAttribute, ComparisonType = BooleanComparisonType.Equals, SecondExpression = RightAttribute }.Compile(mergedSchema, parameterTypes); var gt = new BooleanComparisonExpression { FirstExpression = LeftAttribute, ComparisonType = BooleanComparisonType.GreaterThan, SecondExpression = RightAttribute }.Compile(mergedSchema, parameterTypes); while (!Done(hasLeft, hasRight)) { // Compare key values var merged = Merge(hasLeft ? left.Current : null, leftSchema, hasRight ? right.Current : null, rightSchema); var isLt = lt(merged, parameterValues, options); var isEq = eq(merged, parameterValues, options); var isGt = gt(merged, parameterValues, options); if (isLt || (hasLeft && !hasRight)) { if (!leftMatched && (JoinType == QualifiedJoinType.LeftOuter || JoinType == QualifiedJoinType.FullOuter)) { yield return(Merge(left.Current, leftSchema, null, rightSchema)); } hasLeft = left.MoveNext(); leftMatched = false; } else if (isEq) { if ((!leftMatched || !SemiJoin) && (additionalJoinCriteria == null || additionalJoinCriteria(merged, parameterValues, options) == true)) { yield return(merged); } leftMatched = true; hasRight = right.MoveNext(); rightMatched = false; } else if (hasRight) { if (!rightMatched && (JoinType == QualifiedJoinType.RightOuter || JoinType == QualifiedJoinType.FullOuter)) { yield return(Merge(null, leftSchema, right.Current, rightSchema)); } hasRight = right.MoveNext(); rightMatched = false; } } }
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)); } } }