Пример #1
0
        internal static void FoldFetchXmlColumns(IDataExecutionPlanNode source, List <SelectColumn> columnSet, IDictionary <string, DataSource> dataSources, IDictionary <string, Type> parameterTypes)
        {
            if (source is FetchXmlScan fetchXml)
            {
                if (!dataSources.TryGetValue(fetchXml.DataSource, out var dataSource))
                {
                    throw new NotSupportedQueryFragmentException("Missing datasource " + fetchXml.DataSource);
                }

                // Check if there are any aliases we can apply to the source FetchXml
                var schema = fetchXml.GetSchema(dataSources, parameterTypes);
                var processedSourceColumns = new HashSet <string>(StringComparer.OrdinalIgnoreCase);
                var hasStar    = columnSet.Any(col => col.AllColumns && col.SourceColumn == null);
                var aliasStars = new HashSet <string>(columnSet.Where(col => col.AllColumns && col.SourceColumn != null).Select(col => col.SourceColumn.Replace(".*", "")).Distinct(StringComparer.OrdinalIgnoreCase), StringComparer.OrdinalIgnoreCase);

                foreach (var col in columnSet)
                {
                    if (col.AllColumns)
                    {
                        if (col.SourceColumn == null)
                        {
                            // Add an all-attributes to the main entity and all link-entities
                            fetchXml.Entity.AddItem(new allattributes());

                            foreach (var link in fetchXml.Entity.GetLinkEntities())
                            {
                                if (link.SemiJoin)
                                {
                                    continue;
                                }

                                link.AddItem(new allattributes());
                            }
                        }
                        else if (!hasStar)
                        {
                            // Only add an all-attributes to the appropriate entity/link-entity
                            if (col.SourceColumn.Replace(".*", "").Equals(fetchXml.Alias, StringComparison.OrdinalIgnoreCase))
                            {
                                fetchXml.Entity.AddItem(new allattributes());
                            }
                            else
                            {
                                var link = fetchXml.Entity.FindLinkEntity(col.SourceColumn.Replace(".*", ""));
                                link.AddItem(new allattributes());
                            }
                        }
                    }
                    else if (!hasStar)
                    {
                        // Only fold individual columns down to the FetchXML if there is no corresponding all-attributes
                        var parts = col.SourceColumn.Split('.');

                        if (parts.Length == 1 || !aliasStars.Contains(parts[0]))
                        {
                            var sourceCol = col.SourceColumn;
                            schema.ContainsColumn(sourceCol, out sourceCol);
                            var attr = fetchXml.AddAttribute(sourceCol, null, dataSource.Metadata, out var added, out var linkEntity);

                            // Check if we can fold the alias down to the FetchXML too. Don't do this if the name isn't valid for FetchXML
                            if (sourceCol != col.SourceColumn)
                            {
                                parts = col.SourceColumn.Split('.');
                            }

                            if (!col.OutputColumn.Equals(parts.Last(), StringComparison.OrdinalIgnoreCase) && FetchXmlScan.IsValidAlias(col.OutputColumn))
                            {
                                if (added || (!processedSourceColumns.Contains(sourceCol) && !fetchXml.IsAliasReferenced(attr.alias)))
                                {
                                    // Don't fold the alias if there's also a sort on the same attribute, as it breaks paging
                                    // https://markcarrington.dev/2019/12/10/inside-fetchxml-pt-4-order/#sorting_&_aliases
                                    var items = linkEntity?.Items ?? fetchXml.Entity.Items;

                                    if (items == null || !items.OfType <FetchOrderType>().Any(order => order.attribute == attr.name) || !fetchXml.AllPages)
                                    {
                                        attr.alias = col.OutputColumn;
                                    }
                                }

                                col.SourceColumn = sourceCol.Split('.')[0] + "." + (attr.alias ?? attr.name);
                            }

                            processedSourceColumns.Add(sourceCol);
                        }
                    }
                }
            }
        }
Пример #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
                    }
                }