1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474
|
//---------------------------------------------------------------------
// <copyright file="Sql8ExpressionRewriter.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Data.Common;
using System.Data.Common.CommandTrees;
using System.Data.Common.CommandTrees.Internal;
using System.Data.Metadata.Edm;
using System.Data.Common.CommandTrees.ExpressionBuilder;
namespace System.Data.SqlClient.SqlGen
{
/// <summary>
/// Rewrites an expression tree to make it suitable for translation to SQL appropriate for SQL Server 2000
/// In particular, it replaces expressions that are not directly supported on SQL Server 2000
/// with alternative translations. The following expressions are translated:
/// <list type="bullet">
/// <item><see cref="DbExceptExpression"/></item>
/// <item><see cref="DbIntersectExpression"/></item>
/// <item><see cref="DbSkipExpression"/></item>
/// </list>
///
/// The other expressions are copied unmodified.
/// The new expression belongs to a new query command tree.
/// </summary>
internal class Sql8ExpressionRewriter : DbExpressionRebinder
{
#region Entry Point
/// <summary>
/// The only entry point.
/// Rewrites the given tree by replacing expressions that are not directly supported on SQL Server 2000
/// with alterntive translations.
/// </summary>
/// <param name="originalTree">The tree to rewrite</param>
/// <returns>The new tree</returns>
internal static DbQueryCommandTree Rewrite(DbQueryCommandTree originalTree)
{
Debug.Assert(originalTree != null, "OriginalTree is null");
Sql8ExpressionRewriter rewriter = new Sql8ExpressionRewriter(originalTree.MetadataWorkspace);
DbExpression newQuery = rewriter.VisitExpression(originalTree.Query);
return DbQueryCommandTree.FromValidExpression(originalTree.MetadataWorkspace, originalTree.DataSpace, newQuery);
}
#endregion
#region Constructor
/// <summary>
/// Private Constructor.
/// </summary>
/// <param name="metadata"></param>
private Sql8ExpressionRewriter(MetadataWorkspace metadata)
:base(metadata)
{
}
#endregion
#region DbExpressionVisitor<DbExpression> Members
/// <summary>
/// <see cref="TransformIntersectOrExcept(DbExpression left, DbExpression right, DbExpressionKind expressionKind, IList<DbPropertyExpression> sortExpressionsOverLeft, string sortExpressionsBindingVariableName)"/>
/// </summary>
/// <param name="e"></param>
/// <returns></returns>
public override DbExpression Visit(DbExceptExpression e)
{
return TransformIntersectOrExcept(VisitExpression(e.Left), VisitExpression(e.Right), DbExpressionKind.Except);
}
/// <summary>
/// <see cref="TransformIntersectOrExcept(DbExpression left, DbExpression right, DbExpressionKind expressionKind, IList<DbPropertyExpression> sortExpressionsOverLeft, string sortExpressionsBindingVariableName)"/>
/// </summary>
/// <param name="e"></param>
/// <returns></returns>
public override DbExpression Visit(DbIntersectExpression e)
{
return TransformIntersectOrExcept(VisitExpression(e.Left), VisitExpression(e.Right), DbExpressionKind.Intersect);
}
/// <summary>
/// Logicaly, <see cref="DbSkipExpression"/> translates to:
/// SELECT Y.x1, Y.x2, ..., Y.xn
/// FROM (
/// SELECT X.x1, X.x2, ..., X.xn,
/// FROM input AS X
/// EXCEPT
/// SELECT TOP(count) Z.x1, Z.x2, ..., Z.xn
/// FROM input AS Z
/// ORDER BY sk1, sk2, ...
/// ) AS Y
/// ORDER BY sk1, sk2, ...
///
/// Here, input refers to the input of the <see cref="DbSkipExpression"/>, and count to the count property of the <see cref="DbSkipExpression"/>.
/// The implementation of EXCEPT is non-duplicate eliminating, and does equality comparison only over the
/// equality comparable columns of the input.
///
/// This corresponds to the following expression tree:
///
/// SORT
/// |
/// NON-DISTINCT EXCEPT (specially translated, <see cref="TransformIntersectOrExcept(DbExpression left, DbExpression right, DbExpressionKind expressionKind, IList<DbPropertyExpression> sortExpressionsOverLeft, string sortExpressionsBindingVariableName)"/>
/// |
/// | - Left: clone of input
/// | - Right:
/// |
/// Limit
/// |
/// | - Limit: Count
/// | - Input
/// |
/// Sort
/// |
/// input
///
/// </summary>
/// <param name="e"></param>
/// <returns></returns>
public override DbExpression Visit(DbSkipExpression e)
{
//Build the right input of the except
DbExpression rightInput = VisitExpressionBinding(e.Input).Sort(VisitSortOrder(e.SortOrder)).Limit(VisitExpression(e.Count));
//Build the left input for the except
DbExpression leftInput = VisitExpression(e.Input.Expression); //Another copy of the input
IList<DbSortClause> sortOrder = VisitSortOrder(e.SortOrder); //Another copy of the sort order
// Create a list of the sort expressions to be used for translating except
IList<DbPropertyExpression> sortExpressions = new List<DbPropertyExpression>(e.SortOrder.Count);
foreach (DbSortClause sortClause in sortOrder)
{
//We only care about property expressions, not about constants
if (sortClause.Expression.ExpressionKind == DbExpressionKind.Property)
{
sortExpressions.Add((DbPropertyExpression)sortClause.Expression);
}
}
DbExpression exceptExpression = TransformIntersectOrExcept(leftInput, rightInput, DbExpressionKind.Skip, sortExpressions, e.Input.VariableName);
DbExpression result = exceptExpression.BindAs(e.Input.VariableName).Sort(sortOrder);
return result;
}
#endregion
#region DbExpressionVisitor<DbExpression> Member Helpers
/// <summary>
/// This method is invoked when tranforming <see cref="DbIntersectExpression"/> and <see cref="DbExceptExpression"/> by doing comparison over all input columns.
/// <see cref="TransformIntersectOrExcept(DbExpression left, DbExpression right, DbExpressionKind expressionKind, IList<DbPropertyExpression> sortExpressionsOverLeft, string sortExpressionsBindingVariableName)"/>
/// </summary>
/// <param name="left"></param>
/// <param name="right"></param>
/// <param name="expressionKind"></param>
/// <returns></returns>
private DbExpression TransformIntersectOrExcept(DbExpression left, DbExpression right, DbExpressionKind expressionKind)
{
return TransformIntersectOrExcept( left, right, expressionKind, null, null);
}
/// <summary>
/// This method is used for translating <see cref="DbIntersectExpression"/> and <see cref="DbExceptExpression"/>,
/// and for translating the "Except" part of <see cref="DbSkipExpression"/>.
/// into the follwoing expression:
///
/// A INTERSECT B, A EXCEPT B
///
/// (DISTINCT)
/// |
/// FILTER
/// |
/// | - Input: A
/// | - Predicate:(NOT)
/// |
/// ANY
/// |
/// | - Input: B
/// | - Predicate: (B.b1 = A.a1 or (B.b1 is null and A.a1 is null))
/// AND (B.b2 = A.a2 or (B.b2 is null and A.a2 is null))
/// AND ...
/// AND (B.bn = A.an or (B.bn is null and A.an is null)))
///
/// Here, A corresponds to right and B to left.
/// (NOT) is present when transforming Except
/// for the purpose of translating <see cref="DbExceptExpression"/> or <see cref="DbSkipExpression"/>.
/// (DISTINCT) is present when transforming for the purpose of translating
/// <see cref="DbExceptExpression"/> or <see cref="DbIntersectExpression"/>.
///
/// For <see cref="DbSkipExpression"/>, the input to ANY is caped with project which projects out only
/// the columns represented in the sortExpressionsOverLeft list and only these are used in the predicate.
/// This is because we want to support skip over input with non-equal comarable columns and we have no way to recognize these.
/// </summary>
/// <param name="left"></param>
/// <param name="right"></param>
/// <param name="expressionKind"></param>
/// <param name="sortExpressionsOverLeft">note that this list gets destroyed by this method</param>
/// <param name="sortExpressionsBindingVariableName"></param>
/// <returns></returns>
private DbExpression TransformIntersectOrExcept(DbExpression left, DbExpression right, DbExpressionKind expressionKind, IList<DbPropertyExpression> sortExpressionsOverLeft, string sortExpressionsBindingVariableName)
{
bool negate = (expressionKind == DbExpressionKind.Except) || (expressionKind == DbExpressionKind.Skip);
bool distinct = (expressionKind == DbExpressionKind.Except) || (expressionKind == DbExpressionKind.Intersect);
DbExpressionBinding leftInputBinding = left.Bind();
DbExpressionBinding rightInputBinding = right.Bind();
IList<DbPropertyExpression> leftFlattenedProperties = new List<DbPropertyExpression>();
IList<DbPropertyExpression> rightFlattenedProperties = new List<DbPropertyExpression>();
FlattenProperties(leftInputBinding.Variable, leftFlattenedProperties);
FlattenProperties(rightInputBinding.Variable, rightFlattenedProperties);
//For Skip, we need to ignore any columns that are not in the original sort list. We can recognize these by comparing the left flattened properties and
// the properties in the list sortExpressionsOverLeft
// If any such columns exist, we need to add an additional project, to keep the rest of the columns from being projected, as if any among these
// are non equal comparable, SQL Server 2000 throws.
if (expressionKind == DbExpressionKind.Skip)
{
if (RemoveNonSortProperties(leftFlattenedProperties, rightFlattenedProperties, sortExpressionsOverLeft, leftInputBinding.VariableName, sortExpressionsBindingVariableName))
{
rightInputBinding = CapWithProject(rightInputBinding, rightFlattenedProperties);
}
}
Debug.Assert(leftFlattenedProperties.Count == rightFlattenedProperties.Count, "The left and the right input to INTERSECT or EXCEPT have a different number of properties");
Debug.Assert(leftFlattenedProperties.Count != 0, "The inputs to INTERSECT or EXCEPT have no properties");
//Build the predicate for the quantifier:
// (B.b1 = A.a1 or (B.b1 is null and A.a1 is null))
// AND (B.b2 = A.a2 or (B.b2 is null and A.a2 is null))
// AND ...
// AND (B.bn = A.an or (B.bn is null and A.an is null)))
DbExpression existsPredicate = null;
for (int i = 0; i < leftFlattenedProperties.Count; i++)
{
//A.ai == B.bi
DbExpression equalsExpression = leftFlattenedProperties[i].Equal(rightFlattenedProperties[i]);
//A.ai is null AND B.bi is null
DbExpression leftIsNullExpression = leftFlattenedProperties[i].IsNull();
DbExpression rightIsNullExpression = rightFlattenedProperties[i].IsNull();
DbExpression bothNullExpression = leftIsNullExpression.And(rightIsNullExpression);
DbExpression orExpression = equalsExpression.Or(bothNullExpression);
if (i == 0)
{
existsPredicate = orExpression;
}
else
{
existsPredicate = existsPredicate.And(orExpression);
}
}
//Build the quantifier
DbExpression quantifierExpression = rightInputBinding.Any(existsPredicate);
DbExpression filterPredicate;
//Negate if needed
if (negate)
{
filterPredicate = quantifierExpression.Not();
}
else
{
filterPredicate = quantifierExpression;
}
//Build the filter
DbExpression result = leftInputBinding.Filter(filterPredicate);
//Apply distinct in needed
if (distinct)
{
result = result.Distinct();
}
return result;
}
/// <summary>
/// Adds the flattened properties on the input to the flattenedProperties list.
/// </summary>
/// <param name="input"></param>
/// <param name="flattenedProperties"></param>
private void FlattenProperties(DbExpression input, IList<DbPropertyExpression> flattenedProperties)
{
IList<EdmProperty> properties = TypeHelpers.GetProperties(input.ResultType);
Debug.Assert(properties.Count != 0, "No nested properties when FlattenProperties called?");
for (int i = 0; i < properties.Count; i++)
{
DbExpression propertyInput = input;
DbPropertyExpression propertyExpression = propertyInput.Property(properties[i]);
if (TypeSemantics.IsPrimitiveType(properties[i].TypeUsage))
{
flattenedProperties.Add(propertyExpression);
}
else
{
Debug.Assert(TypeSemantics.IsEntityType(properties[i].TypeUsage) || TypeSemantics.IsRowType(properties[i].TypeUsage),
"The input to FlattenProperties is not of EntityType or RowType?");
FlattenProperties(propertyExpression, flattenedProperties);
}
}
}
/// <summary>
/// Helper method for <see cref="TransformIntersectOrExcept(DbExpression left, DbExpression right, DbExpressionKind expressionKind, IList<DbPropertyExpression> sortExpressionsOverLeft, string sortExpressionsBindingVariableName)"/>
/// Removes all pairs of property expressions from list1 and list2, for which the property expression in list1
/// does not have a 'matching' property expression in list2.
/// The lists list1 and list2 are known to not create duplicate, and the purpose of the sortList is just for this method.
/// Thus, to optimize the match process, we remove the seen property expressions from the sort list in <see cref="HasMatchInList"/>
/// when iterating both list simultaneously.
/// </summary>
/// <param name="list1"></param>
/// <param name="list2"></param>
/// <param name="sortList"></param>
/// <param name="list1BindingVariableName"></param>
/// <param name="sortExpressionsBindingVariableName"></param>
/// <returns></returns>
private static bool RemoveNonSortProperties(IList<DbPropertyExpression> list1, IList<DbPropertyExpression> list2, IList<DbPropertyExpression> sortList, string list1BindingVariableName, string sortExpressionsBindingVariableName)
{
bool result = false;
for (int i = list1.Count - 1; i >= 0; i--)
{
if (!HasMatchInList(list1[i], sortList, list1BindingVariableName, sortExpressionsBindingVariableName))
{
list1.RemoveAt(i);
list2.RemoveAt(i);
result = true;
}
}
return result;
}
/// <summary>
/// Helper method for <see cref="RemoveNonSortProperties"/>
/// Checks whether expr has a 'match' in the given list of property expressions.
/// If it does, the matching expression is removed form the list, to speed up future matching.
/// </summary>
/// <param name="expr"></param>
/// <param name="sortList"></param>
/// <param name="exprBindingVariableName"></param>
/// <param name="sortExpressionsBindingVariableName"></param>
/// <returns></returns>
private static bool HasMatchInList(DbPropertyExpression expr, IList<DbPropertyExpression> list, string exprBindingVariableName, string listExpressionsBindingVariableName)
{
for (int i=0; i<list.Count; i++)
{
if (AreMatching(expr, list[i], exprBindingVariableName, listExpressionsBindingVariableName))
{
// This method is used for matching element of two list without duplicates,
// thus if match is found, remove it from the list, to speed up future matching.
list.RemoveAt(i);
return true;
}
}
return false;
}
/// <summary>
/// Determines whether two expressions match.
/// They match if they are of the shape
/// expr1 -> DbPropertyExpression(... (DbPropertyExpression(DbVariableReferenceExpression(expr1BindingVariableName), nameX), ..., name1)
/// expr1 -> DbPropertyExpression(... (DbPropertyExpression(DbVariableReferenceExpression(expr2BindingVariableName), nameX), ..., name1),
///
/// i.e. if they only differ in the name of the binding.
/// </summary>
/// <param name="expr1"></param>
/// <param name="expr2"></param>
/// <param name="expr1BindingVariableName"></param>
/// <param name="expr2BindingVariableName"></param>
/// <returns></returns>
private static bool AreMatching(DbPropertyExpression expr1, DbPropertyExpression expr2, string expr1BindingVariableName, string expr2BindingVariableName)
{
if (expr1.Property.Name != expr2.Property.Name)
{
return false;
}
if (expr1.Instance.ExpressionKind != expr2.Instance.ExpressionKind)
{
return false;
}
if (expr1.Instance.ExpressionKind == DbExpressionKind.Property)
{
return AreMatching((DbPropertyExpression)expr1.Instance, (DbPropertyExpression)expr2.Instance, expr1BindingVariableName, expr2BindingVariableName);
}
DbVariableReferenceExpression instance1 = (DbVariableReferenceExpression)expr1.Instance;
DbVariableReferenceExpression instance2 = (DbVariableReferenceExpression)expr2.Instance;
return (String.Equals(instance1.VariableName, expr1BindingVariableName, StringComparison.Ordinal)
&& String.Equals(instance2.VariableName, expr2BindingVariableName, StringComparison.Ordinal));
}
/// <summary>
/// Helper method for <see cref="TransformIntersectOrExcept(DbExpression left, DbExpression right, DbExpressionKind expressionKind, IList<DbPropertyExpression> sortExpressionsOverLeft, string sortExpressionsBindingVariableName)"/>
/// Creates a <see cref="DbProjectExpression"/> over the given inputBinding that projects out the given flattenedProperties.
/// and updates the flattenedProperties to be over the newly created project.
/// </summary>
/// <param name="inputBinding"></param>
/// <param name="flattenedProperties"></param>
/// <returns>An <see cref="DbExpressionBinding"/> over the newly created <see cref="DbProjectExpression"/></returns>
private DbExpressionBinding CapWithProject(DbExpressionBinding inputBinding, IList<DbPropertyExpression> flattenedProperties)
{
List<KeyValuePair<string, DbExpression>> projectColumns = new List<KeyValuePair<string, DbExpression>>(flattenedProperties.Count);
//List of all the columnNames used in the projection.
Dictionary<string, int> columnNames = new Dictionary<string, int>(flattenedProperties.Count);
foreach (DbPropertyExpression pe in flattenedProperties)
{
//There may be conflicting property names, thus we may need to rename.
string name = pe.Property.Name;
int i;
if (columnNames.TryGetValue(name, out i))
{
string newName;
do
{
++i;
newName = name + i.ToString(System.Globalization.CultureInfo.InvariantCulture);
} while (columnNames.ContainsKey(newName));
columnNames[name] = i;
name = newName;
}
// Add this column name to list of known names so that there are no subsequent
// collisions
columnNames[name] = 0;
projectColumns.Add(new KeyValuePair<string, DbExpression>(name, pe));
}
//Build the project
DbExpression rowExpr = DbExpressionBuilder.NewRow(projectColumns);
DbProjectExpression projectExpression = inputBinding.Project(rowExpr);
//Create the new inputBinding
DbExpressionBinding resultBinding = projectExpression.Bind();
//Create the list of flattenedProperties over the new project
flattenedProperties.Clear();
RowType rowExprType = (RowType)rowExpr.ResultType.EdmType;
foreach (KeyValuePair<string, DbExpression> column in projectColumns)
{
EdmProperty prop = rowExprType.Properties[column.Key];
flattenedProperties.Add(resultBinding.Variable.Property(prop));
}
return resultBinding;
}
#endregion
}
}
|