コード例 #1
0
ファイル: MergeJoinNode.cs プロジェクト: MarkMpn/Sql4Cds
        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);
        }
コード例 #2
0
        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
                    }
                }
コード例 #3
0
ファイル: HashJoinNode.cs プロジェクト: MarkMpn/Sql4Cds
        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));
                }
            }
        }