1 //--------------------------------------------------------------------- 2 // <copyright file="Propagator.Evaluator.cs" company="Microsoft"> 3 // Copyright (c) Microsoft Corporation. All rights reserved. 4 // </copyright> 5 // 6 // @owner Microsoft 7 // @backupOwner Microsoft 8 //--------------------------------------------------------------------- 9 10 namespace System.Data.Mapping.Update.Internal 11 { 12 using System.Collections.Generic; 13 using System.Data.Common.CommandTrees; 14 using System.Data.Common.Utils; 15 using System.Data.Entity; 16 using System.Data.Metadata.Edm; 17 using System.Diagnostics; 18 using System.Globalization; 19 20 internal partial class Propagator 21 { 22 /// <summary> 23 /// Helper class supporting the evaluation of highly constrained expressions of the following 24 /// form: 25 /// 26 /// P := P AND P | P OR P | NOT P | V is of type | V eq V | V 27 /// V := P 28 /// V := Property(V) | Constant | CASE WHEN P THEN V ... ELSE V | Row | new Instance | Null 29 /// 30 /// The evaluator supports SQL style ternary logic for unknown results (bool? is used, where 31 /// null --> unknown, true --> TRUE and false --> FALSE 32 /// </summary> 33 /// <remarks> 34 /// Assumptions: 35 /// 36 /// - The node and the row passed in must be type compatible. 37 /// 38 /// Any var refs in the node must have the same type as the input row. This is a natural 39 /// requirement given the usage of this method in the propagator, since each propagator handler 40 /// produces rows of the correct type for its parent. Keep in mind that every var ref in a CQT is 41 /// bound specifically to the direct child. 42 /// 43 /// - Equality comparisons are CLR culture invariant. Practically, this introduces the following 44 /// differences from SQL comparisons: 45 /// 46 /// - String comparisons are not collation sensitive 47 /// - The constants we compare come from a fixed repertoire of scalar types implementing IComparable 48 /// 49 /// 50 /// For the purposes of update mapping view evaluation, these assumptions are safe because we 51 /// only support mapping of non-null constants to fields (these constants are non-null discriminators) 52 /// and key comparisons (where the key values are replicated across a reference). 53 /// </remarks> 54 private class Evaluator : UpdateExpressionVisitor<PropagatorResult> 55 { 56 #region Constructors 57 /// <summary> 58 /// Constructs an evaluator for evaluating expressions for the given row. 59 /// </summary> 60 /// <param name="row">Row to match</param> 61 /// <param name="parent">Propagator context</param> Evaluator(PropagatorResult row, Propagator parent)62 private Evaluator(PropagatorResult row, Propagator parent) 63 { 64 EntityUtil.CheckArgumentNull(row, "row"); 65 EntityUtil.CheckArgumentNull(parent, "parent"); 66 67 m_row = row; 68 m_parent = parent; 69 } 70 #endregion 71 72 #region Fields 73 private PropagatorResult m_row; 74 private Propagator m_parent; 75 private static readonly string s_visitorName = typeof(Evaluator).FullName; 76 #endregion 77 78 #region Properties 79 override protected string VisitorName 80 { 81 get { return s_visitorName; } 82 } 83 #endregion 84 85 #region Methods 86 /// <summary> 87 /// Utility method filtering out a set of rows given a predicate. 88 /// </summary> 89 /// <param name="predicate">Match criteria.</param> 90 /// <param name="rows">Input rows.</param> 91 /// <param name="parent">Propagator context</param> 92 /// <returns>Input rows matching criteria.</returns> Filter(DbExpression predicate, IEnumerable<PropagatorResult> rows, Propagator parent)93 internal static IEnumerable<PropagatorResult> Filter(DbExpression predicate, IEnumerable<PropagatorResult> rows, Propagator parent) 94 { 95 foreach (PropagatorResult row in rows) 96 { 97 if (EvaluatePredicate(predicate, row, parent)) 98 { 99 yield return row; 100 } 101 } 102 } 103 104 /// <summary> 105 /// Utility method determining whether a row matches a predicate. 106 /// </summary> 107 /// <remarks> 108 /// See Walker class for an explanation of this coding pattern. 109 /// </remarks> 110 /// <param name="predicate">Match criteria.</param> 111 /// <param name="row">Input row.</param> 112 /// <param name="parent">Propagator context</param> 113 /// <returns><c>true</c> if the row matches the criteria; <c>false</c> otherwise</returns> EvaluatePredicate(DbExpression predicate, PropagatorResult row, Propagator parent)114 internal static bool EvaluatePredicate(DbExpression predicate, PropagatorResult row, Propagator parent) 115 { 116 Evaluator evaluator = new Evaluator(row, parent); 117 PropagatorResult expressionResult = predicate.Accept(evaluator); 118 119 bool? result = ConvertResultToBool(expressionResult); 120 121 // unknown --> false at base of predicate 122 return result ?? false; 123 } 124 125 /// <summary> 126 /// Evaluates scalar node. 127 /// </summary> 128 /// <param name="node">Sub-query returning a scalar value.</param> 129 /// <param name="row">Row to evaluate.</param> 130 /// <param name="parent">Propagator context.</param> 131 /// <returns>Scalar result.</returns> Evaluate(DbExpression node, PropagatorResult row, Propagator parent)132 static internal PropagatorResult Evaluate(DbExpression node, PropagatorResult row, Propagator parent) 133 { 134 DbExpressionVisitor<PropagatorResult> evaluator = new Evaluator(row, parent); 135 return node.Accept(evaluator); 136 } 137 138 /// <summary> 139 /// Given an expression, converts to a (nullable) bool. Only boolean constant and null are 140 /// supported. 141 /// </summary> 142 /// <param name="result">Result to convert</param> 143 /// <returns>true if true constant; false if false constant; null is null constant</returns> ConvertResultToBool(PropagatorResult result)144 private static bool? ConvertResultToBool(PropagatorResult result) 145 { 146 Debug.Assert(null != result && result.IsSimple, "Must be a simple Boolean result"); 147 148 if (result.IsNull) 149 { 150 return null; 151 } 152 else 153 { 154 // rely on cast exception to identify invalid cases (CQT validation should already take care of this) 155 return (bool)result.GetSimpleValue(); 156 } 157 } 158 159 /// <summary> 160 /// Converts a (nullable) bool to an expression. 161 /// </summary> 162 /// <param name="booleanValue">Result</param> 163 /// <param name="inputs">Inputs contributing to the result</param> 164 /// <returns>DbExpression</returns> 165 private static PropagatorResult ConvertBoolToResult(bool? booleanValue, params PropagatorResult[] inputs) 166 { 167 object result; 168 if (booleanValue.HasValue) 169 { 170 result = booleanValue.Value; ; 171 } 172 else 173 { 174 result = null; 175 } 176 PropagatorFlags flags = PropagateUnknownAndPreserveFlags(null, inputs); 177 return PropagatorResult.CreateSimpleValue(flags, result); 178 } 179 180 #region DbExpressionVisitor implementation 181 /// <summary> 182 /// Determines whether the argument being evaluated has a given type (declared in the IsOfOnly predicate). 183 /// </summary> 184 /// <param name="predicate">IsOfOnly predicate.</param> 185 /// <returns>True if the row being evaluated is of the requested type; false otherwise.</returns> Visit(DbIsOfExpression predicate)186 public override PropagatorResult Visit(DbIsOfExpression predicate) 187 { 188 EntityUtil.CheckArgumentNull(predicate, "predicate"); 189 190 if (DbExpressionKind.IsOfOnly != predicate.ExpressionKind) 191 { 192 throw ConstructNotSupportedException(predicate); 193 } 194 195 PropagatorResult childResult = Visit(predicate.Argument); 196 bool result; 197 if (childResult.IsNull) 198 { 199 // Null value expressions are typed, but the semantics of null are slightly different. 200 result = false; 201 } 202 else 203 { 204 result = childResult.StructuralType.EdmEquals(predicate.OfType.EdmType); 205 } 206 207 return ConvertBoolToResult(result, childResult); 208 } 209 210 /// <summary> 211 /// Determines whether the row being evaluated has the given type (declared in the IsOf predicate). 212 /// </summary> 213 /// <param name="predicate">Equals predicate.</param> 214 /// <returns>True if the values being compared are equivalent; false otherwise.</returns> Visit(DbComparisonExpression predicate)215 public override PropagatorResult Visit(DbComparisonExpression predicate) 216 { 217 EntityUtil.CheckArgumentNull(predicate, "predicate"); 218 219 if (DbExpressionKind.Equals == predicate.ExpressionKind) 220 { 221 // Retrieve the left and right hand sides of the equality predicate. 222 PropagatorResult leftResult = Visit(predicate.Left); 223 PropagatorResult rightResult = Visit(predicate.Right); 224 225 bool? result; 226 227 if (leftResult.IsNull || rightResult.IsNull) 228 { 229 result = null; // unknown 230 } 231 else 232 { 233 object left = leftResult.GetSimpleValue(); 234 object right = rightResult.GetSimpleValue(); 235 236 // Perform a comparison between the sides of the equality predicate using invariant culture. 237 // See assumptions outlined in the documentation for this class for additional information. 238 result = ByValueEqualityComparer.Default.Equals(left, right); 239 } 240 241 return ConvertBoolToResult(result, leftResult, rightResult); 242 } 243 else 244 { 245 throw ConstructNotSupportedException(predicate); 246 } 247 } 248 249 /// <summary> 250 /// Evaluates an 'and' expression given results of evalating its children. 251 /// </summary> 252 /// <param name="predicate">And predicate</param> 253 /// <returns>True if both child predicates are satisfied; false otherwise.</returns> Visit(DbAndExpression predicate)254 public override PropagatorResult Visit(DbAndExpression predicate) 255 { 256 EntityUtil.CheckArgumentNull(predicate, "predicate"); 257 258 PropagatorResult left = Visit(predicate.Left); 259 PropagatorResult right = Visit(predicate.Right); 260 bool? leftResult = ConvertResultToBool(left); 261 bool? rightResult = ConvertResultToBool(right); 262 bool? result; 263 264 // Optimization: if either argument is false, preserved and known, return a 265 // result that is false, preserved and known. 266 if ((leftResult.HasValue && !leftResult.Value && PreservedAndKnown(left)) || 267 (rightResult.HasValue && !rightResult.Value && PreservedAndKnown(right))) 268 { 269 return CreatePerservedAndKnownResult(false); 270 } 271 272 result = EntityUtil.ThreeValuedAnd(leftResult, rightResult); 273 274 return ConvertBoolToResult(result, left, right); 275 } 276 277 /// <summary> 278 /// Evaluates an 'or' expression given results of evaluating its children. 279 /// </summary> 280 /// <param name="predicate">'Or' predicate</param> 281 /// <returns>True if either child predicate is satisfied; false otherwise.</returns> Visit(DbOrExpression predicate)282 public override PropagatorResult Visit(DbOrExpression predicate) 283 { 284 EntityUtil.CheckArgumentNull(predicate, "predicate"); 285 286 PropagatorResult left = Visit(predicate.Left); 287 PropagatorResult right = Visit(predicate.Right); 288 bool? leftResult = ConvertResultToBool(left); 289 bool? rightResult = ConvertResultToBool(right); 290 bool? result; 291 292 // Optimization: if either argument is true, preserved and known, return a 293 // result that is true, preserved and known. 294 if ((leftResult.HasValue && leftResult.Value && PreservedAndKnown(left)) || 295 (rightResult.HasValue && rightResult.Value && PreservedAndKnown(right))) 296 { 297 return CreatePerservedAndKnownResult(true); 298 } 299 300 result = EntityUtil.ThreeValuedOr(leftResult, rightResult); 301 302 return ConvertBoolToResult(result, left, right); 303 } 304 CreatePerservedAndKnownResult(object value)305 private static PropagatorResult CreatePerservedAndKnownResult(object value) 306 { 307 // Known is the default (no explicit flag required) 308 return PropagatorResult.CreateSimpleValue(PropagatorFlags.Preserve, value); 309 } 310 PreservedAndKnown(PropagatorResult result)311 private static bool PreservedAndKnown(PropagatorResult result) 312 { 313 // Check that the preserve flag is set, and the unknown flag is not set 314 return PropagatorFlags.Preserve == (result.PropagatorFlags & (PropagatorFlags.Preserve | PropagatorFlags.Unknown)); 315 } 316 317 /// <summary> 318 /// Evalutes a 'not' expression given results 319 /// </summary> 320 /// <param name="predicate">'Not' predicate</param> 321 /// <returns>True of the argument to the 'not' predicate evaluator to false; false otherwise</returns> Visit(DbNotExpression predicate)322 public override PropagatorResult Visit(DbNotExpression predicate) 323 { 324 EntityUtil.CheckArgumentNull(predicate, "predicate"); 325 326 PropagatorResult child = Visit(predicate.Argument); 327 bool? childResult = ConvertResultToBool(child); 328 329 bool? result = EntityUtil.ThreeValuedNot(childResult); 330 331 return ConvertBoolToResult(result, child); 332 } 333 334 /// <summary> 335 /// Returns the result of evaluating a case expression. 336 /// </summary> 337 /// <param name="node">Case expression node.</param> 338 /// <returns>Result of evaluating case expression over the input row for this visitor.</returns> Visit(DbCaseExpression node)339 public override PropagatorResult Visit(DbCaseExpression node) 340 { 341 Debug.Assert(null != node, "node is not visited when null"); 342 343 int match = -1; 344 int statementOrdinal = 0; 345 346 List<PropagatorResult> inputs = new List<PropagatorResult>(); 347 348 foreach (DbExpression when in node.When) 349 { 350 PropagatorResult whenResult = Visit(when); 351 inputs.Add(whenResult); 352 353 bool matches = ConvertResultToBool(whenResult) ?? false; // ternary logic resolution 354 355 if (matches) 356 { 357 match = statementOrdinal; 358 break; 359 } 360 361 statementOrdinal++; 362 } 363 364 PropagatorResult matchResult; 365 if (-1 == match) { matchResult = Visit(node.Else); } else { matchResult = Visit(node.Then[match]); } 366 inputs.Add(matchResult); 367 368 // Clone the result to avoid modifying expressions that may be used elsewhere 369 // (design invariant: only set markup for expressions you create) 370 PropagatorFlags resultFlags = PropagateUnknownAndPreserveFlags(matchResult, inputs); 371 PropagatorResult result = matchResult.ReplicateResultWithNewFlags(resultFlags); 372 373 return result; 374 } 375 376 /// <summary> 377 /// Evaluates a var ref. In practice, this corresponds to the input row for the visitor (the row is 378 /// a member of the referenced input for a projection or filter). 379 /// We assert that types are consistent here. 380 /// </summary> 381 /// <param name="node">Var ref expression node</param> 382 /// <returns>Input row for the visitor.</returns> Visit(DbVariableReferenceExpression node)383 public override PropagatorResult Visit(DbVariableReferenceExpression node) 384 { 385 Debug.Assert(null != node, "node is not visited when null"); 386 387 return m_row; 388 } 389 390 /// <summary> 391 /// Evaluates a property expression given the result of evaluating the property's instance. 392 /// </summary> 393 /// <param name="node">Property expression node.</param> 394 /// <returns>DbExpression resulting from the evaluation of property.</returns> Visit(DbPropertyExpression node)395 public override PropagatorResult Visit(DbPropertyExpression node) 396 { 397 Debug.Assert(null != node, "node is not visited when null"); 398 399 // Retrieve the result of evaluating the instance for the property. 400 PropagatorResult instance = Visit(node.Instance); 401 PropagatorResult result; 402 403 if (instance.IsNull) 404 { 405 result = PropagatorResult.CreateSimpleValue(instance.PropagatorFlags, null); 406 } 407 else 408 { 409 // find member 410 result = instance.GetMemberValue(node.Property); 411 } 412 413 // We do not markup the result since the property value already contains the necessary context 414 // (determined at record extraction time) 415 return result; 416 } 417 418 /// <summary> 419 /// Evaluates a constant expression (trivial: the result is the constant expression) 420 /// </summary> 421 /// <param name="node">Constant expression node.</param> 422 /// <returns>Constant expression</returns> Visit(DbConstantExpression node)423 public override PropagatorResult Visit(DbConstantExpression node) 424 { 425 Debug.Assert(null != node, "node is not visited when null"); 426 427 // Flag the expression as 'preserve', since constants (by definition) cannot vary 428 PropagatorResult result = PropagatorResult.CreateSimpleValue(PropagatorFlags.Preserve, node.Value); 429 430 return result; 431 } 432 433 /// <summary> 434 /// Evaluates a ref key expression based on the result of evaluating the argument to the ref. 435 /// </summary> 436 /// <param name="node">Ref key expression node.</param> 437 /// <returns>The structural key of the ref as a new instance (record).</returns> Visit(DbRefKeyExpression node)438 public override PropagatorResult Visit(DbRefKeyExpression node) 439 { 440 Debug.Assert(null != node, "node is not visited when null"); 441 442 // Retrieve the result of evaluating the child argument. 443 PropagatorResult argument = Visit(node.Argument); 444 445 // Return the argument directly (propagator results treat refs as standard structures) 446 return argument; 447 } 448 449 /// <summary> 450 /// Evaluates a null expression (trivial: the result is the null expression) 451 /// </summary> 452 /// <param name="node">Null expression node.</param> 453 /// <returns>Null expression</returns> Visit(DbNullExpression node)454 public override PropagatorResult Visit(DbNullExpression node) 455 { 456 Debug.Assert(null != node, "node is not visited when null"); 457 458 // Flag the expression as 'preserve', since nulls (by definition) cannot vary 459 PropagatorResult result = PropagatorResult.CreateSimpleValue(PropagatorFlags.Preserve, null); 460 461 return result; 462 } 463 464 /// <summary> 465 /// Evaluates treat expression given a result for the argument to the treat. 466 /// </summary> 467 /// <param name="node">Treat expression</param> 468 /// <returns>Null if the argument is of the given type, the argument otherwise</returns> Visit(DbTreatExpression node)469 public override PropagatorResult Visit(DbTreatExpression node) 470 { 471 Debug.Assert(null != node, "node is not visited when null"); 472 473 PropagatorResult childResult = Visit(node.Argument); 474 TypeUsage nodeType = node.ResultType; 475 476 if (MetadataHelper.IsSuperTypeOf(nodeType.EdmType, childResult.StructuralType)) 477 { 478 // Doing an up cast is not required because all property/ordinal 479 // accesses are unaffected for more derived types (derived members 480 // are appended) 481 return childResult; 482 } 483 484 // "Treat" where the result does not implement the given type results in a null 485 // result 486 PropagatorResult result = PropagatorResult.CreateSimpleValue(childResult.PropagatorFlags, null); 487 return result; 488 } 489 490 /// <summary> 491 /// Casts argument to expression. 492 /// </summary> 493 /// <param name="node">Cast expression node</param> 494 /// <returns>Result of casting argument</returns> Visit(DbCastExpression node)495 public override PropagatorResult Visit(DbCastExpression node) 496 { 497 Debug.Assert(null != node, "node is not visited when null"); 498 499 PropagatorResult childResult = Visit(node.Argument); 500 TypeUsage nodeType = node.ResultType; 501 502 if (!childResult.IsSimple || BuiltInTypeKind.PrimitiveType != nodeType.EdmType.BuiltInTypeKind) 503 { 504 throw EntityUtil.NotSupported(Strings.Update_UnsupportedCastArgument(nodeType.EdmType.Name)); 505 } 506 507 object resultValue; 508 509 if (childResult.IsNull) 510 { 511 resultValue = null; 512 } 513 else 514 { 515 try 516 { 517 resultValue = Cast(childResult.GetSimpleValue(), ((PrimitiveType)nodeType.EdmType).ClrEquivalentType); 518 } 519 catch 520 { 521 Debug.Fail("view generator failed to validate cast in update mapping view"); 522 throw; 523 } 524 } 525 526 PropagatorResult result = childResult.ReplicateResultWithNewValue(resultValue); 527 return result; 528 } 529 530 /// <summary> 531 /// Casts an object instance to the specified model type. 532 /// </summary> 533 /// <param name="value">Value to cast</param> 534 /// <param name="clrPrimitiveType">clr type to which the value is casted to</param> 535 /// <returns>Cast value</returns> Cast(object value, Type clrPrimitiveType)536 private static object Cast(object value, Type clrPrimitiveType) 537 { 538 IFormatProvider formatProvider = CultureInfo.InvariantCulture; 539 540 if (null == value || value == DBNull.Value || value.GetType() == clrPrimitiveType) 541 { 542 return value; 543 } 544 else 545 { 546 //Convert is not handling DateTime to DateTimeOffset conversion 547 if ( (value is DateTime) && (clrPrimitiveType == typeof(DateTimeOffset))) 548 { 549 return new DateTimeOffset(((DateTime)value).Ticks, TimeSpan.Zero); 550 } 551 else 552 { 553 return Convert.ChangeType(value, clrPrimitiveType, formatProvider); 554 } 555 } 556 } 557 558 /// <summary> 559 /// Evaluate a null expression. 560 /// </summary> 561 /// <param name="node">Is null expression</param> 562 /// <returns>A boolean expression describing the result of evaluating the Is Null predicate</returns> Visit(DbIsNullExpression node)563 public override PropagatorResult Visit(DbIsNullExpression node) 564 { 565 Debug.Assert(null != node, "node is not visited when null"); 566 567 PropagatorResult argumentResult = Visit(node.Argument); 568 bool result = argumentResult.IsNull; 569 570 return ConvertBoolToResult(result, argumentResult); 571 } 572 #endregion 573 /// <summary> 574 /// Supports propagation of preserve and unknown values when evaluating expressions. If any input 575 /// to an expression is marked as unknown, the same is true of the result of evaluating 576 /// that expression. If all inputs to an expression are marked 'preserve', then the result is also 577 /// marked preserve. 578 /// </summary> 579 /// <param name="result">Result to markup</param> 580 /// <param name="inputs">Expressions contributing to the result</param> 581 /// <returns>Marked up result.</returns> PropagateUnknownAndPreserveFlags(PropagatorResult result, IEnumerable<PropagatorResult> inputs)582 private static PropagatorFlags PropagateUnknownAndPreserveFlags(PropagatorResult result, IEnumerable<PropagatorResult> inputs) 583 { 584 bool unknown = false; 585 bool preserve = true; 586 bool noInputs = true; 587 588 // aggregate all flags on the inputs 589 foreach (PropagatorResult input in inputs) 590 { 591 noInputs = false; 592 PropagatorFlags inputFlags = input.PropagatorFlags; 593 if (PropagatorFlags.NoFlags != (PropagatorFlags.Unknown & inputFlags)) 594 { 595 unknown = true; 596 } 597 if (PropagatorFlags.NoFlags == (PropagatorFlags.Preserve & inputFlags)) 598 { 599 preserve = false; 600 } 601 } 602 if (noInputs) { preserve = false; } 603 604 if (null != result) 605 { 606 // Merge with existing flags 607 PropagatorFlags flags = result.PropagatorFlags; 608 if (unknown) 609 { 610 flags |= PropagatorFlags.Unknown; 611 } 612 if (!preserve) 613 { 614 flags &= ~PropagatorFlags.Preserve; 615 } 616 617 return flags; 618 } 619 else 620 { 621 // if there is no input result, create new markup from scratch 622 PropagatorFlags flags = PropagatorFlags.NoFlags; 623 if (unknown) 624 { 625 flags |= PropagatorFlags.Unknown; 626 } 627 if (preserve) 628 { 629 flags |= PropagatorFlags.Preserve; 630 } 631 return flags; 632 } 633 } 634 #endregion 635 } 636 } 637 } 638