5abadcbd1ca112f03fac1b18f11d2cdc7c6a4a82
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / Mapping / ViewGeneration / DiscriminatorMap.cs
1 //---------------------------------------------------------------------
2 // <copyright file="DiscriminatorMap.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner Microsoft
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
9
10 using System.Data.Common.CommandTrees;
11 using System.Data.Common.CommandTrees.Internal;
12 using System.Collections.Generic;
13 using System.Data.Metadata.Edm;
14 using System.Linq;
15 using System.Globalization;
16 using System.Diagnostics;
17 using System.Data.Common.Utils;
18
19 namespace System.Data.Mapping.ViewGeneration
20 {
21     /// <summary>
22     /// Describes top-level query mapping view projection of the form:
23     /// 
24     /// SELECT VALUE CASE 
25     ///     WHEN Discriminator = DiscriminatorValue1 THEN EntityType1(...)
26     ///     WHEN Discriminator = DiscriminatorValue2 THEN EntityType2(...)
27     ///     ...
28     ///     
29     /// Supports optimizing queries to leverage user supplied discriminator values
30     /// in TPH mappings rather than introducing our own. This avoids the need
31     /// to introduce a CASE statement in the store.
32     /// </summary>
33     internal class DiscriminatorMap
34     {
35         /// <summary>
36         /// Expression retrieving discriminator value from projection input.
37         /// </summary>
38         internal readonly DbPropertyExpression Discriminator;
39         /// <summary>
40         /// Map from discriminator values to implied entity type.
41         /// </summary>
42         internal readonly System.Collections.ObjectModel.ReadOnlyCollection<KeyValuePair<object, EntityType>> TypeMap;
43         /// <summary>
44         /// Map from entity property to expression generating value for that property. Note that
45         /// the expression must be the same for all types in discriminator map.
46         /// </summary>
47         internal readonly System.Collections.ObjectModel.ReadOnlyCollection<KeyValuePair<EdmProperty, DbExpression>> PropertyMap;
48         /// <summary>
49         /// Map from entity relproperty to expression generating value for that property. Note that
50         /// the expression must be the same for all types in discriminator map.
51         /// </summary>
52         internal readonly System.Collections.ObjectModel.ReadOnlyCollection<KeyValuePair<Query.InternalTrees.RelProperty, DbExpression>> RelPropertyMap;
53
54         /// <summary>
55         /// EntitySet to which the map applies.
56         /// </summary>
57         internal readonly EntitySet EntitySet;
58
59         private DiscriminatorMap(DbPropertyExpression discriminator,
60             List<KeyValuePair<object, EntityType>> typeMap,
61             Dictionary<EdmProperty, DbExpression> propertyMap,
62             Dictionary<Query.InternalTrees.RelProperty, DbExpression> relPropertyMap,
63             EntitySet entitySet)
64         {
65             this.Discriminator = discriminator;
66             this.TypeMap = typeMap.AsReadOnly();
67             this.PropertyMap = propertyMap.ToList().AsReadOnly();
68             this.RelPropertyMap = relPropertyMap.ToList().AsReadOnly();
69             this.EntitySet = entitySet;
70         }
71
72         /// <summary>
73         /// Determines whether the given query view matches the discriminator map pattern.
74         /// </summary>
75         internal static bool TryCreateDiscriminatorMap(EntitySet entitySet, DbExpression queryView, out DiscriminatorMap discriminatorMap)
76         {
77             discriminatorMap = null;
78
79             if (queryView.ExpressionKind != DbExpressionKind.Project) { return false; }
80             var project = (DbProjectExpression)queryView;
81
82             if (project.Projection.ExpressionKind != DbExpressionKind.Case) { return false; }
83             var caseExpression = (DbCaseExpression)project.Projection;
84             if (project.Projection.ResultType.EdmType.BuiltInTypeKind != BuiltInTypeKind.EntityType) { return false; }
85
86             // determine value domain by walking filter
87             if (project.Input.Expression.ExpressionKind != DbExpressionKind.Filter) { return false; }
88             var filterExpression = (DbFilterExpression)project.Input.Expression;
89
90             HashSet<object> discriminatorDomain = new HashSet<object>();
91             if (!ViewSimplifier.TryMatchDiscriminatorPredicate(filterExpression, (equalsExp, discriminatorValue) => discriminatorDomain.Add(discriminatorValue)))
92             {
93                 return false;
94             }
95                         
96             var typeMap = new List<KeyValuePair<object, EntityType>>();
97             var propertyMap = new Dictionary<EdmProperty, DbExpression>();
98             var relPropertyMap = new Dictionary<Query.InternalTrees.RelProperty, DbExpression>();
99             var typeToRelPropertyMap = new Dictionary<EntityType, List<Query.InternalTrees.RelProperty>>();
100             DbPropertyExpression discriminator = null;
101
102             EdmProperty discriminatorProperty = null;
103             for (int i = 0; i < caseExpression.When.Count; i++)
104             {
105                 var when = caseExpression.When[i];
106                 var then = caseExpression.Then[i];
107
108                 var projectionVariableName = project.Input.VariableName;
109
110                 DbPropertyExpression currentDiscriminator;
111                 object discriminatorValue;
112                 if (!ViewSimplifier.TryMatchPropertyEqualsValue(when, projectionVariableName, out currentDiscriminator, out discriminatorValue)) { return false; }
113
114                 // must be the same discriminator in every case
115                 if (null == discriminatorProperty) { discriminatorProperty = (EdmProperty)currentDiscriminator.Property; }
116                 else if (discriminatorProperty != currentDiscriminator.Property) { return false; }
117                 discriminator = currentDiscriminator;
118
119                 // right hand side must be entity type constructor
120                 EntityType currentType;
121                 if (!TryMatchEntityTypeConstructor(then, propertyMap, relPropertyMap, typeToRelPropertyMap, out currentType)) { return false; }
122
123                 // remember type + discriminator value
124                 typeMap.Add(new KeyValuePair<object, EntityType>(discriminatorValue, currentType));
125
126                 // remove discriminator value from domain
127                 discriminatorDomain.Remove(discriminatorValue);
128             }
129
130             // make sure only one member of discriminator domain remains...
131             if (1 != discriminatorDomain.Count) { return false; }
132
133             // check default case
134             EntityType elseType;
135             if (null == caseExpression.Else ||
136                 !TryMatchEntityTypeConstructor(caseExpression.Else, propertyMap, relPropertyMap, typeToRelPropertyMap, out elseType)) { return false; }
137             typeMap.Add(new KeyValuePair<object, EntityType>(discriminatorDomain.Single(), elseType));
138
139             // Account for cases where some type in the hierarchy specifies a rel-property, but another
140             // type in the hierarchy does not
141             if (!CheckForMissingRelProperties(relPropertyMap, typeToRelPropertyMap))
142             {
143                 return false;
144             }
145
146             // since the store may right-pad strings, ensure discriminator values are unique in their trimmed
147             // form
148             var discriminatorValues = typeMap.Select(map => map.Key);
149             int uniqueValueCount = discriminatorValues.Distinct(TrailingSpaceComparer.Instance).Count();
150             int valueCount = typeMap.Count;
151             if (uniqueValueCount != valueCount) { return false; }
152
153             discriminatorMap = new DiscriminatorMap(discriminator, typeMap, propertyMap, relPropertyMap, entitySet);
154             return true;
155         }
156
157         private static bool CheckForMissingRelProperties(
158             Dictionary<Query.InternalTrees.RelProperty, DbExpression> relPropertyMap,
159             Dictionary<EntityType, List<Query.InternalTrees.RelProperty>> typeToRelPropertyMap)
160         {
161             // Easily the lousiest implementation of this search.
162             // Check to see that for each relProperty that we see in the relPropertyMap
163             // (presumably because some type constructor specified it), every type for
164             // which that rel-property is specified *must* also have specified it.
165             // We don't need to check for equivalence here - because that's already been
166             // checked
167             foreach (Query.InternalTrees.RelProperty relProperty in relPropertyMap.Keys)
168             {
169                 foreach (KeyValuePair<EntityType, List<Query.InternalTrees.RelProperty>> kv in typeToRelPropertyMap)
170                 {
171                     if (kv.Key.IsSubtypeOf(relProperty.FromEnd.TypeUsage.EdmType))
172                     {
173                         if (!kv.Value.Contains(relProperty))
174                         {
175                             return false;
176                         }
177                     }
178                 }
179             }
180             return true;
181         }
182
183         private static bool TryMatchEntityTypeConstructor(DbExpression then,
184             Dictionary<EdmProperty, DbExpression> propertyMap,
185             Dictionary<Query.InternalTrees.RelProperty, DbExpression> relPropertyMap,
186             Dictionary<EntityType, List<Query.InternalTrees.RelProperty>> typeToRelPropertyMap,
187             out EntityType entityType)
188         {
189             if (then.ExpressionKind != DbExpressionKind.NewInstance)
190             {
191                 entityType = null;
192                 return false;
193             }
194             var constructor = (DbNewInstanceExpression)then;
195             entityType = (EntityType)constructor.ResultType.EdmType;
196
197             // process arguments to constructor (must be aligned across all case statements)
198             Debug.Assert(entityType.Properties.Count == constructor.Arguments.Count, "invalid new instance");
199             for (int j = 0; j < entityType.Properties.Count; j++)
200             {
201                 var property = entityType.Properties[j];
202                 var assignment = constructor.Arguments[j];
203                 DbExpression existingAssignment;
204                 if (propertyMap.TryGetValue(property, out existingAssignment))
205                 {
206                     if (!ExpressionsCompatible(assignment, existingAssignment)) { return false; }
207                 }
208                 else
209                 {
210                     propertyMap.Add(property, assignment);
211                 }
212             }
213
214             // Now handle the rel properties
215             if (constructor.HasRelatedEntityReferences)
216             {
217                 List<Query.InternalTrees.RelProperty> relPropertyList;
218                 if (!typeToRelPropertyMap.TryGetValue(entityType, out relPropertyList))
219                 {
220                     relPropertyList = new List<System.Data.Query.InternalTrees.RelProperty>();
221                     typeToRelPropertyMap[entityType] = relPropertyList;
222                 }
223                 foreach (DbRelatedEntityRef relatedRef in constructor.RelatedEntityReferences)
224                 {
225                     Query.InternalTrees.RelProperty relProperty = new System.Data.Query.InternalTrees.RelProperty((RelationshipType)relatedRef.TargetEnd.DeclaringType,
226                         relatedRef.SourceEnd, relatedRef.TargetEnd);
227                     DbExpression assignment = relatedRef.TargetEntityReference;
228                     DbExpression existingAssignment;
229                     if (relPropertyMap.TryGetValue(relProperty, out existingAssignment))
230                     {
231                         if (!ExpressionsCompatible(assignment, existingAssignment)) { return false; }
232                     }
233                     else
234                     {
235                         relPropertyMap.Add(relProperty, assignment);
236                     }
237                     relPropertyList.Add(relProperty);
238                 }
239             }
240             return true;
241         }
242
243         /// <summary>
244         /// Utility method determining whether two expressions appearing within the same scope
245         /// are equivalent. May return false negatives, but no false positives. In other words,
246         /// 
247         ///     x != y --> !ExpressionsCompatible(x, y)
248         ///     
249         /// but does not guarantee
250         /// 
251         ///     x == y --> ExpressionsCompatible(x, y)
252         /// </summary>
253         private static bool ExpressionsCompatible(DbExpression x, DbExpression y)
254         {
255             if (x.ExpressionKind != y.ExpressionKind) { return false; }
256             switch (x.ExpressionKind)
257             {
258                 case DbExpressionKind.Property:
259                     {
260                         var prop1 = (DbPropertyExpression)x;
261                         var prop2 = (DbPropertyExpression)y;
262                         return prop1.Property == prop2.Property &&
263                             ExpressionsCompatible(prop1.Instance, prop2.Instance);
264                     }
265                 case DbExpressionKind.VariableReference:
266                     return ((DbVariableReferenceExpression)x).VariableName ==
267                         ((DbVariableReferenceExpression)y).VariableName;
268                 case DbExpressionKind.NewInstance:
269                     {
270                         var newX = (DbNewInstanceExpression)x;
271                         var newY = (DbNewInstanceExpression)y;
272                         if (!newX.ResultType.EdmType.EdmEquals(newY.ResultType.EdmType)) { return false; }
273                         for (int i = 0; i < newX.Arguments.Count; i++)
274                         {
275                             if (!ExpressionsCompatible(newX.Arguments[i], newY.Arguments[i]))
276                             {
277                                 return false;
278                             }
279                         }
280                         return true;
281                     }
282                 case DbExpressionKind.Ref:
283                     {
284                         DbRefExpression refX = (DbRefExpression)x;
285                         DbRefExpression refY = (DbRefExpression)y;
286                         return (refX.EntitySet.EdmEquals(refY.EntitySet) &&
287                             ExpressionsCompatible(refX.Argument, refY.Argument));
288                     }
289                 default:
290                     // here come the false negatives...
291                     return false;
292             }
293         }
294     }
295 }