You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Connected.Framework/Connected.Expressions/Translation/Optimization/SubqueryMerger.cs

135 lines
4.5 KiB

2 years ago
using System.Linq.Expressions;
using Connected.Expressions.Expressions;
using Connected.Expressions.Visitors;
namespace Connected.Expressions.Translation;
internal sealed class SubqueryMerger : DatabaseVisitor
{
private SubqueryMerger()
{
}
internal static Expression Merge(Expression expression)
{
if (new SubqueryMerger().Visit(expression) is not Expression subqueryExpression)
throw new NullReferenceException(nameof(subqueryExpression));
return subqueryExpression;
}
private bool IsTopLevel { get; set; } = true;
protected override Expression VisitSelect(SelectExpression expression)
{
var wasTopLevel = IsTopLevel;
IsTopLevel = false;
expression = (SelectExpression)base.VisitSelect(expression);
while (CanMergeWithFrom(expression, wasTopLevel))
{
if (GetLeftMostSelect(expression.From) is not SelectExpression fromSelectExpression)
throw new NullReferenceException(nameof(fromSelectExpression));
expression = Subqueries.Remove(expression, fromSelectExpression);
var where = expression.Where;
if (fromSelectExpression.Where is not null)
{
if (where is not null)
where = fromSelectExpression.Where.And(where);
else
where = fromSelectExpression.Where;
}
var orderBy = expression.OrderBy is not null && expression.OrderBy.Count > 0 ? expression.OrderBy : fromSelectExpression.OrderBy;
var groupBy = expression.GroupBy is not null && expression.GroupBy.Count > 0 ? expression.GroupBy : fromSelectExpression.GroupBy;
var skip = expression.Skip is not null ? expression.Skip : fromSelectExpression.Skip;
var take = expression.Take is not null ? expression.Take : fromSelectExpression.Take;
var isDistinct = expression.IsDistinct | fromSelectExpression.IsDistinct;
if (where != expression.Where || orderBy != expression.OrderBy || groupBy != expression.GroupBy || isDistinct != expression.IsDistinct || skip != expression.Skip || take != expression.Take)
expression = new SelectExpression(expression.Alias, expression.Columns, expression.From, where, orderBy, groupBy, isDistinct, skip, take, expression.IsReverse);
}
return expression;
}
private static SelectExpression? GetLeftMostSelect(Expression source)
{
var select = source as SelectExpression;
if (select is not null)
return select;
if (source is JoinExpression join)
return GetLeftMostSelect(join.Left);
return null;
}
private static bool IsColumnProjection(SelectExpression select)
{
for (var i = 0; i < select.Columns.Count; i++)
{
var cd = select.Columns[i];
if (cd.Expression.NodeType != (ExpressionType)DatabaseExpressionType.Column && cd.Expression.NodeType != ExpressionType.Constant)
return false;
}
return true;
}
private static bool CanMergeWithFrom(SelectExpression select, bool isTopLevel)
{
var fromSelect = GetLeftMostSelect(select.From);
if (fromSelect is null)
return false;
if (!IsColumnProjection(fromSelect))
return false;
var selHasNameMapProjection = RedundantSubqueries.IsNameMapProjection(select);
var selHasOrderBy = select.OrderBy is not null && select.OrderBy.Count > 0;
var selHasGroupBy = select.GroupBy is not null && select.GroupBy.Count > 0;
var selHasAggregates = AggregateChecker.HasAggregates(select);
var selHasJoin = select.From is JoinExpression;
var frmHasOrderBy = fromSelect.OrderBy is not null && fromSelect.OrderBy.Count > 0;
var frmHasGroupBy = fromSelect.GroupBy is not null && fromSelect.GroupBy.Count > 0;
var frmHasAggregates = AggregateChecker.HasAggregates(fromSelect);
if (selHasOrderBy && frmHasOrderBy)
return false;
if (selHasGroupBy && frmHasGroupBy)
return false;
if (select.IsReverse || fromSelect.IsReverse)
return false;
if (frmHasOrderBy && (selHasGroupBy || selHasAggregates || select.IsDistinct))
return false;
if (frmHasGroupBy)
return false;
if (fromSelect.Take is not null && (select.Take is not null || select.Skip is not null || select.IsDistinct || selHasAggregates || selHasGroupBy || selHasJoin))
return false;
if (fromSelect.Skip is not null && (select.Skip is not null || select.IsDistinct || selHasAggregates || selHasGroupBy || selHasJoin))
return false;
if (fromSelect.IsDistinct && (select.Take is not null || select.Skip is not null || !selHasNameMapProjection || selHasGroupBy || selHasAggregates || (selHasOrderBy && !isTopLevel) || selHasJoin))
return false;
if (frmHasAggregates && (select.Take is not null || select.Skip is not null || select.IsDistinct || selHasAggregates || selHasGroupBy || selHasJoin))
return false;
return true;
}
}