|
//---------------------------------------------------------------------
// <copyright file="ExpressionKeyGen.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner Microsoft
// @backupOwner venkatja
//---------------------------------------------------------------------
namespace System.Data.Common.CommandTrees.Internal
{
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Data.Common.CommandTrees;
using System.Data.Common.Utils;
using System.Data.Metadata.Edm;
using System.Data.Spatial;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
/// <summary>
/// Generates a key for a command tree.
/// </summary>
internal sealed class ExpressionKeyGen : DbExpressionVisitor
{
internal static bool TryGenerateKey(DbExpression tree, out string key)
{
var keyGen = new ExpressionKeyGen();
try
{
tree.Accept(keyGen);
key = keyGen._key.ToString();
return true;
}
catch (NotSupportedException)
{
key = null;
return false;
}
}
private ExpressionKeyGen() { }
#region Fields
private readonly StringBuilder _key = new StringBuilder();
private static string[] _exprKindNames = InitializeExprKindNames();
private static string[] InitializeExprKindNames()
{
#if DEBUG
var values = Enum.GetValues(typeof(DbExpressionKind)).Cast<int>().ToArray();
for (int i = 0; i < values.Length; ++i)
{
// If there are gaps, then we need to change the algorithm for building _exprKindNames.
Debug.Assert(i == values[i], "Are there any gaps in DbExpressionKind members?");
}
#endif
var names = Enum.GetNames(typeof(DbExpressionKind));
// Arithmetic
names[(int)DbExpressionKind.Divide] = "/";
names[(int)DbExpressionKind.Modulo] = "%";
names[(int)DbExpressionKind.Multiply] = "*";
names[(int)DbExpressionKind.Plus] = "+";
names[(int)DbExpressionKind.Minus] = "-";
names[(int)DbExpressionKind.UnaryMinus] = "-";
// Comparison
names[(int)DbExpressionKind.Equals] = "=";
names[(int)DbExpressionKind.LessThan] = "<";
names[(int)DbExpressionKind.LessThanOrEquals] = "<=";
names[(int)DbExpressionKind.GreaterThan] = ">";
names[(int)DbExpressionKind.GreaterThanOrEquals] = ">=";
names[(int)DbExpressionKind.NotEquals] = "<>";
names[(int)DbExpressionKind.Property] = ".";
// Relops
names[(int)DbExpressionKind.InnerJoin] = "IJ";
names[(int)DbExpressionKind.FullOuterJoin] = "FOJ";
names[(int)DbExpressionKind.LeftOuterJoin] = "LOJ";
names[(int)DbExpressionKind.CrossApply] = "CA";
names[(int)DbExpressionKind.OuterApply] = "OA";
return names;
}
#endregion
private void VisitVariableName(string varName)
{
#if DEBUG
// There are generally four sources of var names:
// 1. generated by default alias generator (DbExpressionBuilder.AliasGenerator): "Var_123"
// 2. generated by ExpressionConverted.AliasGenerator (ELinq compiler): "LQ123"
// 3. generated by SemanticResolver.GenerateInternalName (eSQL compiler): "_##hint123"
// 4. inferred from user-defined artefacts, such as local names introduced inside a linq query
// Out of these four sources, ##2, 3 and 4 provide stable names in the sense that the same conversion by ExpressionConverted
// will produce the same variable names for the same linq query. It is assumed that unless there is a code defect,
// ELinq queries will contain variables from the stable sources only, so this check is debug only.
var _notSupportedVarNames = new Regex("^" + ExpressionBuilder.DbExpressionBuilder.AliasGenerator.Prefix + "[0-9]+");
Debug.Assert(_notSupportedVarNames.Match(varName).Success == false, "ExpressionKeyGen does not support variables generated using default expression builder alias generator.");
#endif
_key.Append('\'');
_key.Append(varName.Replace("'", "''"));
_key.Append('\'');
}
private void VisitBinding(DbExpressionBinding binding)
{
_key.Append("BV");
VisitVariableName(binding.VariableName);
_key.Append("=(");
binding.Expression.Accept(this);
_key.Append(')');
}
private void VisitGroupBinding(DbGroupExpressionBinding groupBinding)
{
_key.Append("GBVV");
VisitVariableName(groupBinding.VariableName);
_key.Append(",");
VisitVariableName(groupBinding.GroupVariableName);
_key.Append("=(");
groupBinding.Expression.Accept(this);
_key.Append(')');
}
private void VisitFunction(EdmFunction func, IList<DbExpression> args)
{
_key.Append("FUNC<");
_key.Append(func.Identity);
_key.Append(">:ARGS(");
foreach (var a in args)
{
_key.Append('(');
a.Accept(this);
_key.Append(')');
}
_key.Append(')');
}
private void VisitExprKind(DbExpressionKind kind)
{
_key.Append('[');
_key.Append(_exprKindNames[(int)kind]);
_key.Append(']');
}
private void VisitUnary(DbUnaryExpression expr)
{
VisitExprKind(expr.ExpressionKind);
_key.Append('(');
expr.Argument.Accept(this);
_key.Append(')');
}
private void VisitBinary(DbBinaryExpression expr)
{
VisitExprKind(expr.ExpressionKind);
_key.Append('(');
expr.Left.Accept(this);
_key.Append(',');
expr.Right.Accept(this);
_key.Append(')');
}
private void VisitCastOrTreat(DbUnaryExpression e)
{
VisitExprKind(e.ExpressionKind);
_key.Append('(');
e.Argument.Accept(this);
_key.Append(":");
_key.Append(e.ResultType.Identity);
_key.Append(')');
}
#region DbExpressionVisitor Members
public override void Visit(DbExpression e)
{
throw EntityUtil.NotSupported(System.Data.Entity.Strings.Cqt_General_UnsupportedExpression(e.GetType().FullName));
}
public override void Visit(DbConstantExpression e)
{
Debug.Assert(TypeSemantics.IsScalarType(e.ResultType), "Non-scalar type constant expressions are not supported.");
var primitive = TypeHelpers.GetPrimitiveTypeUsageForScalar(e.ResultType);
switch (((PrimitiveType)primitive.EdmType).PrimitiveTypeKind)
{
case PrimitiveTypeKind.Binary:
var byteArray = e.Value as byte[];
if (byteArray != null)
{
_key.Append("'");
foreach (byte b in byteArray)
{
_key.AppendFormat("{0:X2}", b);
}
_key.Append("'");
}
else
{
throw new NotSupportedException();
}
break;
case PrimitiveTypeKind.String:
var @string = e.Value as string;
if (@string != null)
{
_key.Append("'");
_key.Append(@string.Replace("'", "''"));
_key.Append("'");
}
else
{
throw new NotSupportedException();
}
break;
case PrimitiveTypeKind.Boolean:
case PrimitiveTypeKind.Byte:
case PrimitiveTypeKind.Decimal:
case PrimitiveTypeKind.Double:
case PrimitiveTypeKind.Guid:
case PrimitiveTypeKind.Single:
case PrimitiveTypeKind.SByte:
case PrimitiveTypeKind.Int16:
case PrimitiveTypeKind.Int32:
case PrimitiveTypeKind.Int64:
case PrimitiveTypeKind.Time:
_key.AppendFormat(CultureInfo.InvariantCulture, "{0}", e.Value);
break;
case PrimitiveTypeKind.DateTime:
_key.Append(((DateTime)e.Value).ToString("o", CultureInfo.InvariantCulture));
break;
case PrimitiveTypeKind.DateTimeOffset:
_key.Append(((DateTimeOffset)e.Value).ToString("o", CultureInfo.InvariantCulture));
break;
case PrimitiveTypeKind.Geometry:
case PrimitiveTypeKind.GeometryPoint:
case PrimitiveTypeKind.GeometryLineString:
case PrimitiveTypeKind.GeometryPolygon:
case PrimitiveTypeKind.GeometryMultiPoint:
case PrimitiveTypeKind.GeometryMultiLineString:
case PrimitiveTypeKind.GeometryMultiPolygon:
case PrimitiveTypeKind.GeometryCollection:
var geometry = e.Value as DbGeometry;
if (geometry != null)
{
_key.Append(geometry.AsText());
}
else
{
throw new NotSupportedException();
}
break;
case PrimitiveTypeKind.Geography:
case PrimitiveTypeKind.GeographyPoint:
case PrimitiveTypeKind.GeographyLineString:
case PrimitiveTypeKind.GeographyPolygon:
case PrimitiveTypeKind.GeographyMultiPoint:
case PrimitiveTypeKind.GeographyMultiLineString:
case PrimitiveTypeKind.GeographyMultiPolygon:
case PrimitiveTypeKind.GeographyCollection:
var geography = e.Value as DbGeography;
if (geography != null)
{
_key.Append(geography.AsText());
}
else
{
throw new NotSupportedException();
}
break;
default:
throw new NotSupportedException();
}
_key.Append(":");
_key.Append(e.ResultType.Identity);
}
public override void Visit(DbNullExpression e)
{
_key.Append("NULL:");
_key.Append(e.ResultType.Identity);
}
public override void Visit(DbVariableReferenceExpression e)
{
_key.Append("Var(");
VisitVariableName(e.VariableName);
_key.Append(")");
}
public override void Visit(DbParameterReferenceExpression e)
{
_key.Append("@");
_key.Append(e.ParameterName);
_key.Append(":");
_key.Append(e.ResultType.Identity);
}
public override void Visit(DbFunctionExpression e)
{
VisitFunction(e.Function, e.Arguments);
}
public override void Visit(DbLambdaExpression expression)
{
_key.Append("Lambda(");
foreach (var v in expression.Lambda.Variables)
{
_key.Append("(V");
VisitVariableName(v.VariableName);
_key.Append(":");
_key.Append(v.ResultType.Identity);
_key.Append(')');
}
_key.Append("=");
foreach (var a in expression.Arguments)
{
_key.Append('(');
a.Accept(this);
_key.Append(')');
}
_key.Append(")Body(");
expression.Lambda.Body.Accept(this);
_key.Append(")");
}
public override void Visit(DbPropertyExpression e)
{
e.Instance.Accept(this);
VisitExprKind(e.ExpressionKind);
_key.Append(e.Property.Name);
}
public override void Visit(DbComparisonExpression e)
{
VisitBinary(e);
}
public override void Visit(DbLikeExpression e)
{
VisitExprKind(e.ExpressionKind);
_key.Append('(');
e.Argument.Accept(this);
_key.Append(")(");
e.Pattern.Accept(this);
_key.Append(")(");
if (e.Escape != null)
{
e.Escape.Accept(this);
}
e.Argument.Accept(this);
_key.Append(')');
}
public override void Visit(DbLimitExpression e)
{
VisitExprKind(e.ExpressionKind);
if (e.WithTies)
{
_key.Append("WithTies");
}
_key.Append('(');
e.Argument.Accept(this);
_key.Append(")(");
e.Limit.Accept(this);
_key.Append(')');
}
public override void Visit(DbIsNullExpression e)
{
VisitUnary(e);
}
public override void Visit(DbArithmeticExpression e)
{
VisitExprKind(e.ExpressionKind);
foreach (var a in e.Arguments)
{
_key.Append('(');
a.Accept(this);
_key.Append(')');
}
}
public override void Visit(DbAndExpression e)
{
VisitBinary(e);
}
public override void Visit(DbOrExpression e)
{
VisitBinary(e);
}
public override void Visit(DbNotExpression e)
{
VisitUnary(e);
}
public override void Visit(DbDistinctExpression e)
{
VisitUnary(e);
}
public override void Visit(DbElementExpression e)
{
VisitUnary(e);
}
public override void Visit(DbIsEmptyExpression e)
{
VisitUnary(e);
}
public override void Visit(DbUnionAllExpression e)
{
VisitBinary(e);
}
public override void Visit(DbIntersectExpression e)
{
VisitBinary(e);
}
public override void Visit(DbExceptExpression e)
{
VisitBinary(e);
}
public override void Visit(DbTreatExpression e)
{
VisitCastOrTreat(e);
}
public override void Visit(DbCastExpression e)
{
VisitCastOrTreat(e);
}
public override void Visit(DbIsOfExpression e)
{
VisitExprKind(e.ExpressionKind);
_key.Append('(');
e.Argument.Accept(this);
_key.Append(":");
_key.Append(e.OfType.EdmType.Identity);
_key.Append(')');
}
public override void Visit(DbOfTypeExpression e)
{
VisitExprKind(e.ExpressionKind);
_key.Append('(');
e.Argument.Accept(this);
_key.Append(":");
_key.Append(e.OfType.EdmType.Identity);
_key.Append(')');
}
public override void Visit(DbCaseExpression e)
{
VisitExprKind(e.ExpressionKind);
_key.Append('(');
for (int idx = 0; idx < e.When.Count; idx++)
{
_key.Append("WHEN:(");
e.When[idx].Accept(this);
_key.Append(")THEN:(");
e.Then[idx].Accept(this);
}
_key.Append("ELSE:(");
e.Else.Accept(this);
_key.Append("))");
}
public override void Visit(DbNewInstanceExpression e)
{
VisitExprKind(e.ExpressionKind);
_key.Append(':');
_key.Append(e.ResultType.EdmType.Identity);
_key.Append('(');
foreach (var a in e.Arguments)
{
_key.Append('(');
a.Accept(this);
_key.Append(')');
}
if (e.HasRelatedEntityReferences)
{
foreach (DbRelatedEntityRef relatedRef in e.RelatedEntityReferences)
{
_key.Append("RE(A(");
_key.Append(relatedRef.SourceEnd.DeclaringType.Identity);
_key.Append(")(");
_key.Append(relatedRef.SourceEnd.Name);
_key.Append("->");
_key.Append(relatedRef.TargetEnd.Name);
_key.Append(")(");
relatedRef.TargetEntityReference.Accept(this);
_key.Append("))");
}
}
_key.Append(')');
}
public override void Visit(DbRefExpression e)
{
//
VisitExprKind(e.ExpressionKind);
_key.Append("(ESET(");
_key.Append(e.EntitySet.EntityContainer.Name);
_key.Append('.');
_key.Append(e.EntitySet.Name);
_key.Append(")T(");
_key.Append(TypeHelpers.GetEdmType<RefType>(e.ResultType).ElementType.FullName);
_key.Append(")(");
e.Argument.Accept(this);
_key.Append(')');
}
public override void Visit(DbRelationshipNavigationExpression e)
{
VisitExprKind(e.ExpressionKind);
_key.Append('(');
e.NavigationSource.Accept(this);
_key.Append(")A(");
_key.Append(e.NavigateFrom.DeclaringType.Identity);
_key.Append(")(");
_key.Append(e.NavigateFrom.Name);
_key.Append("->");
_key.Append(e.NavigateTo.Name);
_key.Append("))");
}
public override void Visit(DbDerefExpression e)
{
VisitUnary(e);
}
public override void Visit(DbRefKeyExpression e)
{
VisitUnary(e);
}
public override void Visit(DbEntityRefExpression e)
{
VisitUnary(e);
}
public override void Visit(DbScanExpression e)
{
VisitExprKind(e.ExpressionKind);
_key.Append('(');
_key.Append(e.Target.EntityContainer.Name);
_key.Append('.');
_key.Append(e.Target.Name);
_key.Append(':');
_key.Append(e.ResultType.EdmType.Identity);
_key.Append(')');
}
public override void Visit(DbFilterExpression e)
{
VisitExprKind(e.ExpressionKind);
_key.Append('(');
VisitBinding(e.Input);
_key.Append('(');
e.Predicate.Accept(this);
_key.Append("))");
}
public override void Visit(DbProjectExpression e)
{
VisitExprKind(e.ExpressionKind);
_key.Append('(');
VisitBinding(e.Input);
_key.Append('(');
e.Projection.Accept(this);
_key.Append("))");
}
public override void Visit(DbCrossJoinExpression e)
{
VisitExprKind(e.ExpressionKind);
_key.Append('(');
foreach (var i in e.Inputs)
{
VisitBinding(i);
}
_key.Append(')');
}
public override void Visit(DbJoinExpression e)
{
VisitExprKind(e.ExpressionKind);
_key.Append('(');
VisitBinding(e.Left);
VisitBinding(e.Right);
_key.Append('(');
e.JoinCondition.Accept(this);
_key.Append("))");
}
public override void Visit(DbApplyExpression e)
{
VisitExprKind(e.ExpressionKind);
_key.Append('(');
VisitBinding(e.Input);
VisitBinding(e.Apply);
_key.Append(')');
}
public override void Visit(DbGroupByExpression e)
{
VisitExprKind(e.ExpressionKind);
_key.Append('(');
VisitGroupBinding(e.Input);
foreach (var k in e.Keys)
{
_key.Append("K(");
k.Accept(this);
_key.Append(')');
}
foreach (var a in e.Aggregates)
{
var ga = a as DbGroupAggregate;
if (ga != null)
{
_key.Append("GA(");
Debug.Assert(ga.Arguments.Count == 1, "Group aggregate must have one argument.");
ga.Arguments[0].Accept(this);
_key.Append(')');
}
else
{
_key.Append("A:");
var fa = (DbFunctionAggregate)a;
if (fa.Distinct)
{
_key.Append("D:");
}
VisitFunction(fa.Function, fa.Arguments);
}
}
_key.Append(')');
}
private void VisitSortOrder(IList<DbSortClause> sortOrder)
{
_key.Append("SO(");
foreach (var clause in sortOrder)
{
_key.Append(clause.Ascending ? "ASC(" : "DESC(");
clause.Expression.Accept(this);
_key.Append(')');
if (!String.IsNullOrEmpty(clause.Collation))
{
_key.Append(":(");
_key.Append(clause.Collation);
_key.Append(')');
}
}
_key.Append(')');
}
public override void Visit(DbSkipExpression e)
{
VisitExprKind(e.ExpressionKind);
_key.Append('(');
VisitBinding(e.Input);
VisitSortOrder(e.SortOrder);
_key.Append('(');
e.Count.Accept(this);
_key.Append("))");
}
public override void Visit(DbSortExpression e)
{
VisitExprKind(e.ExpressionKind);
_key.Append('(');
VisitBinding(e.Input);
VisitSortOrder(e.SortOrder);
_key.Append(')');
}
public override void Visit(DbQuantifierExpression e)
{
VisitExprKind(e.ExpressionKind);
_key.Append('(');
VisitBinding(e.Input);
_key.Append('(');
e.Predicate.Accept(this);
_key.Append("))");
}
#endregion
}
}
|