|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|