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