Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / Query / PlanCompiler / ConstraintManager.cs
1 //---------------------------------------------------------------------
2 // <copyright file="ConstraintManager.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;
11 using System.Collections.Generic;
12 using System.Globalization;
13 using System.Data.Common;
14 using System.Data.Query.InternalTrees;
15 using md=System.Data.Metadata.Edm;
16 //using System.Diagnostics; // Please use PlanCompiler.Assert instead of Debug.Assert in this class...
17
18 // It is fine to use Debug.Assert in cases where you assert an obvious thing that is supposed
19 // to prevent from simple mistakes during development (e.g. method argument validation 
20 // in cases where it was you who created the variables or the variables had already been validated or 
21 // in "else" clauses where due to code changes (e.g. adding a new value to an enum type) the default 
22 // "else" block is chosen why the new condition should be treated separately). This kind of asserts are 
23 // (can be) helpful when developing new code to avoid simple mistakes but have no or little value in 
24 // the shipped product. 
25 // PlanCompiler.Assert *MUST* be used to verify conditions in the trees. These would be assumptions 
26 // about how the tree was built etc. - in these cases we probably want to throw an exception (this is
27 // what PlanCompiler.Assert does when the condition is not met) if either the assumption is not correct 
28 // or the tree was built/rewritten not the way we thought it was.
29 // Use your judgment - if you rather remove an assert than ship it use Debug.Assert otherwise use
30 // PlanCompiler.Assert.
31
32 //
33 // The ConstraintManager module manages foreign key constraints for a query. It reshapes
34 // referential constraints supplied by metadata into a more useful form.
35 //
36 namespace System.Data.Query.PlanCompiler
37 {
38     /// <summary>
39     /// A simple class that represents a pair of extents
40     /// </summary>
41     internal class ExtentPair
42     {
43         #region public surface
44         /// <summary>
45         /// Return the left component of the pair
46         /// </summary>
47         internal md.EntitySetBase Left { get { return m_left; } }
48
49         /// <summary>
50         /// Return the right component of the pair
51         /// </summary>
52         internal md.EntitySetBase Right { get { return m_right; } }
53
54         /// <summary>
55         /// Equals
56         /// </summary>
57         /// <param name="obj"></param>
58         /// <returns></returns>
59         public override bool Equals(object obj)
60         {
61             ExtentPair other = obj as ExtentPair;
62             return (other != null) && other.Left.Equals(this.Left) && other.Right.Equals(this.Right);
63         }
64
65         /// <summary>
66         /// Hashcode
67         /// </summary>
68         /// <returns></returns>
69         public override int GetHashCode()
70         {
71             return (this.Left.GetHashCode() << 4) ^ this.Right.GetHashCode();
72         }
73         #endregion
74
75         #region constructors
76         internal ExtentPair(md.EntitySetBase left, md.EntitySetBase right)
77         {
78             m_left = left;
79             m_right = right;
80         }
81         #endregion
82
83         #region private state
84         private md.EntitySetBase m_left;
85         private md.EntitySetBase m_right;
86         #endregion
87     }
88
89     /// <summary>
90     /// Information about a foreign-key constraint
91     /// </summary>
92     internal class ForeignKeyConstraint
93     {
94         #region public surface
95
96         /// <summary>
97         /// Parent key properties
98         /// </summary>
99         internal List<string> ParentKeys { get { return m_parentKeys; } }
100         /// <summary>
101         /// Child key properties
102         /// </summary>
103         internal List<string> ChildKeys { get { return m_childKeys; } }
104
105         /// <summary>
106         /// Get the parent-child pair
107         /// </summary>
108         internal ExtentPair Pair { get { return m_extentPair; } }
109
110         /// <summary>
111         /// Return the child rowcount
112         /// </summary>
113         internal md.RelationshipMultiplicity ChildMultiplicity { get { return m_constraint.ToRole.RelationshipMultiplicity; } }
114
115         /// <summary>
116         /// Get the corresponding parent (key) property, for a specific child (foreign key) property
117         /// </summary>
118         /// <param name="childPropertyName">child (foreign key) property name</param>
119         /// <param name="parentPropertyName">corresponding parent property name</param>
120         /// <returns>true, if the parent property was found</returns>
121         internal bool GetParentProperty(string childPropertyName, out string parentPropertyName)
122         {
123             BuildKeyMap();
124             return m_keyMap.TryGetValue(childPropertyName, out parentPropertyName);
125         }
126         #endregion
127
128         #region constructors
129         internal ForeignKeyConstraint(md.RelationshipType relType, md.RelationshipSet relationshipSet, md.ReferentialConstraint constraint)
130         {
131             md.AssociationSet assocSet = relationshipSet as md.AssociationSet;
132             md.AssociationEndMember fromEnd = constraint.FromRole as md.AssociationEndMember;
133             md.AssociationEndMember toEnd = constraint.ToRole as md.AssociationEndMember;
134
135             // Currently only Associations are supported
136             if (null == assocSet || null == fromEnd || null == toEnd)
137             {
138                 throw EntityUtil.NotSupported();
139             }
140
141             m_constraint = constraint;
142             md.EntitySet parent = System.Data.Common.Utils.MetadataHelper.GetEntitySetAtEnd(assocSet, fromEnd);// relationshipSet.GetRelationshipEndExtent(constraint.FromRole);
143             md.EntitySet child = System.Data.Common.Utils.MetadataHelper.GetEntitySetAtEnd(assocSet, toEnd);// relationshipSet.GetRelationshipEndExtent(constraint.ToRole);
144             m_extentPair = new ExtentPair(parent, child);
145             m_childKeys = new List<string>();
146             foreach (md.EdmProperty prop in constraint.ToProperties)
147             {
148                 m_childKeys.Add(prop.Name);
149             }
150
151             m_parentKeys = new List<string>();
152             foreach (md.EdmProperty prop in constraint.FromProperties)
153             {
154                 m_parentKeys.Add(prop.Name);
155             }
156
157             PlanCompiler.Assert((md.RelationshipMultiplicity.ZeroOrOne == fromEnd.RelationshipMultiplicity || md.RelationshipMultiplicity.One == fromEnd.RelationshipMultiplicity), "from-end of relationship constraint cannot have multiplicity greater than 1");
158         }
159         #endregion
160
161         #region private state
162         private ExtentPair m_extentPair;
163         private List<string> m_parentKeys;
164         private List<string> m_childKeys;
165         private md.ReferentialConstraint m_constraint;
166         private Dictionary<string, string> m_keyMap;
167         #endregion
168
169         #region private methods
170
171         /// <summary>
172         /// Build up an equivalence map of primary keys and foreign keys (ie) for each
173         /// foreign key column, identify the corresponding primary key property
174         /// </summary>
175         private void BuildKeyMap()
176         {
177             if (m_keyMap != null)
178             {
179                 return;
180             }
181
182             m_keyMap = new Dictionary<string, string>();
183             IEnumerator<md.EdmProperty> parentProps = m_constraint.FromProperties.GetEnumerator();
184             IEnumerator<md.EdmProperty> childProps = m_constraint.ToProperties.GetEnumerator();
185             while (true)
186             {
187                 bool parentOver = !parentProps.MoveNext();
188                 bool childOver = !childProps.MoveNext();
189                 PlanCompiler.Assert(parentOver == childOver, "key count mismatch");
190                 if (parentOver)
191                 {
192                     break;
193                 }
194                 m_keyMap[childProps.Current.Name] = parentProps.Current.Name;
195             }
196         }
197         #endregion
198     }
199
200     /// <summary>
201     /// Keeps track of all foreign key relationships
202     /// </summary>
203     internal class ConstraintManager
204     {
205         #region public methods
206         /// <summary>
207         /// Is there a parent child relationship between table1 and table2 ?
208         /// </summary>
209         /// <param name="table1">parent table ?</param>
210         /// <param name="table2">child table ?</param>
211         /// <param name="constraints">list of constraints ?</param>
212         /// <returns>true if there is at least one constraint</returns>
213         internal bool IsParentChildRelationship(md.EntitySetBase table1, md.EntitySetBase table2,
214             out List<ForeignKeyConstraint> constraints)
215         {
216             LoadRelationships(table1.EntityContainer);
217             LoadRelationships(table2.EntityContainer);
218
219             ExtentPair extentPair = new ExtentPair(table1, table2);
220             return m_parentChildRelationships.TryGetValue(extentPair, out constraints);
221         }
222
223         /// <summary>
224         /// Load all relationships in this entity container
225         /// </summary>
226         /// <param name="entityContainer"></param>
227         internal void LoadRelationships(md.EntityContainer entityContainer)
228         {
229             // Check to see if I've already loaded information for this entity container
230             if (m_entityContainerMap.ContainsKey(entityContainer))
231             {
232                 return;
233             }
234
235             // Load all relationships from this entitycontainer
236             foreach (md.EntitySetBase e in entityContainer.BaseEntitySets)
237             {
238                 md.RelationshipSet relationshipSet = e as md.RelationshipSet;
239                 if (relationshipSet == null)
240                 {
241                     continue;
242                 }
243
244                 // Relationship sets can only contain relationships
245                 md.RelationshipType relationshipType = (md.RelationshipType)relationshipSet.ElementType;
246                 md.AssociationType assocType = relationshipType as md.AssociationType;
247
248                 //
249                 // Handle only binary Association relationships for now
250                 //
251                 if (null == assocType || !IsBinary(relationshipType))
252                 {
253                     continue;
254                 }
255
256                 foreach (md.ReferentialConstraint constraint in assocType.ReferentialConstraints)
257                 {
258                     List<ForeignKeyConstraint> fkConstraintList;
259                     ForeignKeyConstraint fkConstraint = new ForeignKeyConstraint(relationshipType, relationshipSet, constraint);
260                     if (!m_parentChildRelationships.TryGetValue(fkConstraint.Pair, out fkConstraintList))
261                     {
262                         fkConstraintList = new List<ForeignKeyConstraint>();
263                         m_parentChildRelationships[fkConstraint.Pair] = fkConstraintList;
264                     }
265                     //
266                     // Theoretically, we can have more than one fk constraint between
267                     // the 2 tables (though, it is unlikely)
268                     //
269                     fkConstraintList.Add(fkConstraint);
270                 }
271             }
272
273             // Mark this entity container as already loaded
274             m_entityContainerMap[entityContainer] = entityContainer;
275         }
276         #endregion
277
278         #region constructors
279         internal ConstraintManager()
280         {
281             m_entityContainerMap = new Dictionary<md.EntityContainer, md.EntityContainer>();
282             m_parentChildRelationships = new Dictionary<ExtentPair, List<ForeignKeyConstraint>>();
283         }
284         #endregion
285
286         #region private state
287         private Dictionary<md.EntityContainer, md.EntityContainer> m_entityContainerMap;
288         private Dictionary<ExtentPair, List<ForeignKeyConstraint>> m_parentChildRelationships;
289         #endregion
290
291         #region private methods
292
293         /// <summary>
294         /// Is this relationship a binary relationship (ie) does it have exactly 2 end points?
295         /// 
296         /// This should ideally be a method supported by RelationType itself
297         /// </summary>
298         /// <param name="relationshipType"></param>
299         /// <returns>true, if this is a binary relationship</returns>
300         private static bool IsBinary(md.RelationshipType relationshipType)
301         {
302             int endCount = 0;
303             foreach(md.EdmMember member in relationshipType.Members)
304             {
305                 if (member is md.RelationshipEndMember)
306                 {
307                     endCount++;
308                     if (endCount > 2)
309                     {
310                         return false;
311                     }
312                 }
313             }
314             return (endCount == 2);
315         }
316         #endregion
317     }
318 }