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/Comparers/DatabaseComparer.cs

323 lines
11 KiB

using Connected.Expressions.Collections;
using Connected.Expressions.Translation;
using Connected.Expressions.Translation.Resolvers;
using System.Collections.ObjectModel;
using System.Linq.Expressions;
namespace Connected.Expressions.Comparers;
internal sealed class DatabaseComparer : ExpressionComparer
{
protected DatabaseComparer(ScopedDictionary<ParameterExpression, ParameterExpression>? parameterScope, Func<object?, object?, bool>? comparer,
ScopedDictionary<Alias, Alias>? aliasScope)
: base(parameterScope, comparer)
{
AliasScope = aliasScope;
}
private ScopedDictionary<Alias, Alias>? AliasScope { get; set; }
public new static bool AreEqual(Expression? a, Expression? b)
{
return AreEqual(null, null, a, b, null);
}
public new static bool AreEqual(Expression? a, Expression? b, Func<object?, object?, bool>? fnCompare)
{
return AreEqual(null, null, a, b, fnCompare);
}
public static bool AreEqual(ScopedDictionary<ParameterExpression, ParameterExpression>? parameterScope, ScopedDictionary<Alias, Alias>? aliasScope, Expression? a, Expression? b)
{
return new DatabaseComparer(parameterScope, null, aliasScope).Compare(a, b);
}
public static bool AreEqual(ScopedDictionary<ParameterExpression, ParameterExpression>? parameterScope, ScopedDictionary<Alias, Alias>? aliasScope, Expression? a, Expression? b, Func<object?, object?, bool>? fnCompare)
{
return new DatabaseComparer(parameterScope, fnCompare, aliasScope).Compare(a, b);
}
protected override bool Compare(Expression? a, Expression? b)
{
if (a == b)
return true;
if (a is null || b is null)
return false;
if (a.NodeType != b.NodeType)
return false;
if (a.Type != b.Type)
return false;
return (DatabaseExpressionType)a.NodeType switch
{
DatabaseExpressionType.Table => CompareTable((TableExpression)a, (TableExpression)b),
DatabaseExpressionType.Column => CompareColumn((ColumnExpression)a, (ColumnExpression)b),
DatabaseExpressionType.Select => CompareSelect((SelectExpression)a, (SelectExpression)b),
DatabaseExpressionType.Join => CompareJoin((JoinExpression)a, (JoinExpression)b),
DatabaseExpressionType.Aggregate => CompareAggregate((AggregateExpression)a, (AggregateExpression)b),
DatabaseExpressionType.Scalar or DatabaseExpressionType.Exists or DatabaseExpressionType.In => CompareSubquery((SubqueryExpression)a, (SubqueryExpression)b),
DatabaseExpressionType.AggregateSubquery => CompareAggregateSubquery((AggregateSubqueryExpression)a, (AggregateSubqueryExpression)b),
DatabaseExpressionType.IsNull => CompareIsNull((IsNullExpression)a, (IsNullExpression)b),
DatabaseExpressionType.Between => CompareBetween((BetweenExpression)a, (BetweenExpression)b),
DatabaseExpressionType.RowCount => CompareRowNumber((RowNumberExpression)a, (RowNumberExpression)b),
DatabaseExpressionType.Projection => CompareProjection((ProjectionExpression)a, (ProjectionExpression)b),
DatabaseExpressionType.NamedValue => CompareNamedValue((NamedValueExpression)a, (NamedValueExpression)b),
DatabaseExpressionType.Batch => CompareBatch((BatchExpression)a, (BatchExpression)b),
DatabaseExpressionType.Function => CompareFunction((FunctionExpression)a, (FunctionExpression)b),
DatabaseExpressionType.Entity => CompareEntity((EntityExpression)a, (EntityExpression)b),
DatabaseExpressionType.If => CompareIf((IfCommandExpression)a, (IfCommandExpression)b),
DatabaseExpressionType.Block => CompareBlock((BlockExpression)a, (BlockExpression)b),
_ => base.Compare(a, b),
};
}
private static bool CompareTable(TableExpression a, TableExpression b)
{
return a.Name == b.Name;
}
private bool CompareColumn(ColumnExpression a, ColumnExpression b)
{
return CompareAlias(a.Alias, b.Alias) && a.Name == b.Name;
}
private bool CompareAlias(Alias a, Alias b)
{
if (AliasScope is not null)
{
if (AliasScope.TryGetValue(a, out Alias? mapped))
return mapped == b;
}
return a == b;
}
private bool CompareSelect(SelectExpression a, SelectExpression b)
{
var save = AliasScope;
try
{
if (!Compare(a.From, b.From))
return false;
AliasScope = new ScopedDictionary<Alias, Alias>(save);
MapAliases(a.From, b.From);
return Compare(a.Where, b.Where)
&& CompareOrderList(a.OrderBy, b.OrderBy)
&& CompareExpressionList(a.GroupBy, b.GroupBy)
&& Compare(a.Skip, b.Skip)
&& Compare(a.Take, b.Take)
&& a.IsDistinct == b.IsDistinct
&& a.IsReverse == b.IsReverse
&& CompareColumnDeclarations(a.Columns, b.Columns);
}
finally
{
AliasScope = save;
}
}
private void MapAliases(Expression a, Expression b)
{
if (AliasScope is null)
throw new NullReferenceException(nameof(AliasScope));
var prodA = DeclaredAliasesResolver.Resolve(a).ToArray();
var prodB = DeclaredAliasesResolver.Resolve(b).ToArray();
for (int i = 0, n = prodA.Length; i < n; i++)
AliasScope.Add(prodA[i], prodB[i]);
}
private bool CompareOrderList(ReadOnlyCollection<OrderExpression>? a, ReadOnlyCollection<OrderExpression>? b)
{
if (a == b)
return true;
if (a is null || b is null)
return false;
if (a.Count != b.Count)
return false;
for (var i = 0; i < a.Count; i++)
{
var left = a[i];
var right = b[i];
if (left.OrderType != right.OrderType || !Compare(left.Expression, right.Expression))
return false;
}
return true;
}
private bool CompareColumnDeclarations(ReadOnlyCollection<ColumnDeclaration>? a, ReadOnlyCollection<ColumnDeclaration>? b)
{
if (a == b)
return true;
if (a is null || b is null)
return false;
if (a.Count != b.Count)
return false;
for (var i = 0; i < a.Count; i++)
{
if (!CompareColumnDeclaration(a[i], b[i]))
return false;
}
return true;
}
private bool CompareColumnDeclaration(ColumnDeclaration a, ColumnDeclaration b)
{
return string.Equals(a.Name, b.Name, StringComparison.OrdinalIgnoreCase) && Compare(a.Expression, b.Expression);
}
private bool CompareJoin(JoinExpression a, JoinExpression b)
{
if (a.Join != b.Join || !Compare(a.Left, b.Left))
return false;
if (a.Join == JoinType.CrossApply || a.Join == JoinType.OuterApply)
{
var save = AliasScope;
try
{
AliasScope = new ScopedDictionary<Alias, Alias>(AliasScope);
MapAliases(a.Left, b.Left);
return Compare(a.Right, b.Right) && Compare(a.Condition, b.Condition);
}
finally
{
AliasScope = save;
}
}
else
return Compare(a.Right, b.Right) && Compare(a.Condition, b.Condition);
}
private bool CompareAggregate(AggregateExpression a, AggregateExpression b)
{
return string.Equals(a.AggregateName, b.AggregateName, StringComparison.OrdinalIgnoreCase) && Compare(a.Argument, b.Argument);
}
private bool CompareIsNull(IsNullExpression a, IsNullExpression b)
{
return Compare(a.Expression, b.Expression);
}
private bool CompareBetween(BetweenExpression a, BetweenExpression b)
{
return Compare(a.Expression, b.Expression) && Compare(a.Lower, b.Lower) && Compare(a.Upper, b.Upper);
}
private bool CompareRowNumber(RowNumberExpression a, RowNumberExpression b)
{
return CompareOrderList(a.OrderBy, b.OrderBy);
}
private bool CompareNamedValue(NamedValueExpression a, NamedValueExpression b)
{
return string.Equals(a.Name, b.Name, StringComparison.OrdinalIgnoreCase) && Compare(a.Value, b.Value);
}
private bool CompareSubquery(SubqueryExpression a, SubqueryExpression b)
{
if (a.NodeType != b.NodeType)
return false;
return (DatabaseExpressionType)a.NodeType switch
{
DatabaseExpressionType.Scalar => CompareScalar((ScalarExpression)a, (ScalarExpression)b),
DatabaseExpressionType.Exists => CompareExists((ExistsExpression)a, (ExistsExpression)b),
DatabaseExpressionType.In => CompareIn((InExpression)a, (InExpression)b),
_ => false,
};
}
private bool CompareScalar(ScalarExpression a, ScalarExpression b)
{
return Compare(a.Select, b.Select);
}
private bool CompareExists(ExistsExpression a, ExistsExpression b)
{
return Compare(a.Select, b.Select);
}
private bool CompareIn(InExpression a, InExpression b)
{
return Compare(a.Expression, b.Expression) && Compare(a.Select, b.Select) && CompareExpressionList(a.Values, b.Values);
}
private bool CompareAggregateSubquery(AggregateSubqueryExpression a, AggregateSubqueryExpression b)
{
return Compare(a.AggregateAsSubquery, b.AggregateAsSubquery) && Compare(a.AggregateInGroupSelect, b.AggregateInGroupSelect) && a.GroupByAlias == b.GroupByAlias;
}
private bool CompareProjection(ProjectionExpression a, ProjectionExpression b)
{
if (!Compare(a.Select, b.Select))
return false;
var save = AliasScope;
try
{
AliasScope = new ScopedDictionary<Alias, Alias>(AliasScope);
AliasScope.Add(a.Select.Alias, b.Select.Alias);
return Compare(a.Projector, b.Projector)
&& Compare(a.Aggregator, b.Aggregator)
&& a.IsSingleton == b.IsSingleton;
}
finally
{
AliasScope = save;
}
}
private bool CompareBatch(BatchExpression x, BatchExpression y)
{
return Compare(x.Input, y.Input) && Compare(x.Operation, y.Operation) && Compare(x.BatchSize, y.BatchSize) && Compare(x.Stream, y.Stream);
}
private bool CompareIf(IfCommandExpression x, IfCommandExpression y)
{
return Compare(x.Check, y.Check) && Compare(x.IfTrue, y.IfTrue) && Compare(x.IfFalse, y.IfFalse);
}
private bool CompareBlock(BlockExpression x, BlockExpression y)
{
if (x.Commands.Count != y.Commands.Count)
return false;
for (var i = 0; i < x.Commands.Count; i++)
{
if (!Compare(x.Commands[i], y.Commands[i]))
return false;
}
return true;
}
private bool CompareFunction(FunctionExpression x, FunctionExpression y)
{
return string.Equals(x.Name, y.Name, StringComparison.OrdinalIgnoreCase) && CompareExpressionList(x.Arguments, y.Arguments);
}
private bool CompareEntity(EntityExpression x, EntityExpression y)
{
return x.EntityType == y.EntityType && Compare(x.Expression, y.Expression);
}
}