protected override IEnumerable <Entity> ExecuteInternal(IDictionary <string, DataSource> dataSources, IQueryExecutionOptions options, IDictionary <string, Type> parameterTypes, IDictionary <string, object> parameterValues) { var leftSchema = LeftSource.GetSchema(dataSources, parameterTypes); var innerParameterTypes = GetInnerParameterTypes(leftSchema, parameterTypes); if (OuterReferences != null) { if (parameterTypes == null) { innerParameterTypes = new Dictionary <string, Type>(); } else { innerParameterTypes = new Dictionary <string, Type>(parameterTypes); } foreach (var kvp in OuterReferences) { innerParameterTypes[kvp.Value] = leftSchema.Schema[kvp.Key]; } } var rightSchema = RightSource.GetSchema(dataSources, innerParameterTypes); var mergedSchema = GetSchema(dataSources, parameterTypes, true); var joinCondition = JoinCondition?.Compile(mergedSchema, parameterTypes); foreach (var left in LeftSource.Execute(dataSources, options, parameterTypes, parameterValues)) { var innerParameters = parameterValues; if (OuterReferences != null) { if (parameterValues == null) { innerParameters = new Dictionary <string, object>(); } else { innerParameters = new Dictionary <string, object>(parameterValues); } foreach (var kvp in OuterReferences) { left.Attributes.TryGetValue(kvp.Key, out var outerValue); innerParameters[kvp.Value] = outerValue; } } var hasRight = false; foreach (var right in RightSource.Execute(dataSources, options, innerParameterTypes, innerParameters)) { var merged = Merge(left, leftSchema, right, rightSchema); if (joinCondition == null || joinCondition(merged, parameterValues, options)) { yield return(merged); } hasRight = true; if (SemiJoin) { break; } } if (!hasRight && JoinType == QualifiedJoinType.LeftOuter) { yield return(Merge(left, leftSchema, null, rightSchema)); } } }
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)); } } }