Update Reference Sources to .NET Framework 4.6.1
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / Mapping / ViewGeneration / Validation / ErrorPatternMatcher.cs
1 //---------------------------------------------------------------------
2 // <copyright file="ErrorPatternMatcher.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner [....]
7 // @backupOwner [....]
8 //---------------------------------------------------------------------
9
10 using System.Collections.Generic;
11 using System.Data.Common.Utils;
12 using System.Data.Entity;
13 using System.Data.Mapping.ViewGeneration.QueryRewriting;
14 using System.Data.Mapping.ViewGeneration.Structures;
15 using System.Data.Mapping.ViewGeneration.Utils;
16 using System.Data.Metadata.Edm;
17 using System.Diagnostics;
18 using System.Linq;
19 using System.Text;
20
21 namespace System.Data.Mapping.ViewGeneration.Validation
22 {
23     using CompositeCondition = Dictionary<MemberPath, Set<Constant>>;
24     delegate bool LCWComparer(FragmentQuery query1, FragmentQuery query2);
25
26     internal class ErrorPatternMatcher
27     {
28
29         private ViewgenContext m_viewgenContext;
30         private MemberDomainMap m_domainMap;
31         private IEnumerable<MemberPath> m_keyAttributes;
32         private ErrorLog m_errorLog;
33         private int m_originalErrorCount;
34         private const int NUM_PARTITION_ERR_TO_FIND = 5;
35
36
37         #region Constructor
38         private ErrorPatternMatcher(ViewgenContext context, MemberDomainMap domainMap, ErrorLog errorLog)
39         {
40             m_viewgenContext = context;
41             m_domainMap = domainMap;
42             m_keyAttributes = MemberPath.GetKeyMembers(context.Extent, domainMap);
43             m_errorLog = errorLog;
44             m_originalErrorCount = m_errorLog.Count;
45         }
46
47         public static bool FindMappingErrors(ViewgenContext context, MemberDomainMap domainMap, ErrorLog errorLog)
48         {
49             //Can't get here if Update Views have validation disabled
50             Debug.Assert(context.ViewTarget == ViewTarget.QueryView || context.Config.IsValidationEnabled);
51
52             if (context.ViewTarget == ViewTarget.QueryView && !context.Config.IsValidationEnabled)
53             {
54                 return false; // Rules for QV under no validation are different
55             }
56
57             ErrorPatternMatcher matcher = new ErrorPatternMatcher(context, domainMap, errorLog);
58
59             matcher.MatchMissingMappingErrors();
60             matcher.MatchConditionErrors();
61             matcher.MatchSplitErrors();
62
63             if (matcher.m_errorLog.Count == matcher.m_originalErrorCount)
64             {   //this will generate redundant errors if one of the above routine finds an error
65                 // so execute it only when we dont have any other errors
66                 matcher.MatchPartitionErrors();
67             }
68
69             if (matcher.m_errorLog.Count > matcher.m_originalErrorCount)
70             {
71                 ExceptionHelpers.ThrowMappingException(matcher.m_errorLog, matcher.m_viewgenContext.Config);
72             }
73
74             return false;
75         }
76         #endregion
77
78         #region Error Matching Routines
79
80
81         /// <summary>
82         /// Finds Types (possibly without any members) that have no mapping specified
83         /// </summary>
84         private void MatchMissingMappingErrors()
85         {
86             if (m_viewgenContext.ViewTarget == ViewTarget.QueryView)
87             {
88                 //Find all types for the given EntitySet
89                 Set<EdmType> unmapepdTypesInExtent = new Set<EdmType>(MetadataHelper.GetTypeAndSubtypesOf(m_viewgenContext.Extent.ElementType, m_viewgenContext.EdmItemCollection, false /*isAbstract*/));
90
91                 //Figure out which type has no Cell mapped to it
92                 foreach (var fragment in m_viewgenContext.AllWrappersForExtent)
93                 {
94                     foreach (Cell cell in fragment.Cells)
95                     {
96                         foreach (var restriction in cell.CQuery.Conditions)
97                         {
98                             foreach (var cellConst in restriction.Domain.Values)
99                             {
100                                 //if there is a mapping to this type...
101                                 TypeConstant typeConst = cellConst as TypeConstant;
102                                 if (typeConst != null)
103                                 {
104                                     unmapepdTypesInExtent.Remove(typeConst.EdmType);
105                                 }
106                             }
107                         }
108                     }
109                 }
110
111                 //We are left with a type that has no mapping
112                 if (unmapepdTypesInExtent.Count > 0)
113                 {
114                     //error unmapped type
115                     m_errorLog.AddEntry(new ErrorLog.Record(true, ViewGenErrorCode.ErrorPatternMissingMappingError,
116                             Strings.ViewGen_Missing_Type_Mapping(BuildCommaSeparatedErrorString<EdmType>(unmapepdTypesInExtent)), m_viewgenContext.AllWrappersForExtent, ""));
117                 }
118             }
119         }
120
121         private static bool HasNotNullCondition(CellQuery cellQuery, MemberPath member)
122         {
123             foreach (MemberRestriction condition in cellQuery.GetConjunctsFromWhereClause())
124             {
125                 if (condition.RestrictedMemberSlot.MemberPath.Equals(member))
126                 {
127                     if (condition.Domain.Values.Contains(Constant.NotNull))
128                     {
129                         return true;
130                     }
131
132                     //Not Null may have been optimized into NOT(1, 2, NULL). SO look into negated cell constants
133                     foreach (NegatedConstant negatedConst in condition.Domain.Values.Select(cellConstant => cellConstant as NegatedConstant).Where(negated => negated != null))
134                     {
135                         if (negatedConst.Elements.Contains(Constant.Null))
136                         {
137                             return true;
138                         }
139                     }
140                 }
141             }
142             return false;
143         }
144
145         private static bool IsMemberPartOfNotNullCondition(IEnumerable<LeftCellWrapper> wrappers, MemberPath leftMember, ViewTarget viewTarget)
146         {
147
148             foreach (var leftCellWrapper in wrappers)
149             {
150                 CellQuery leftCellQuery = leftCellWrapper.OnlyInputCell.GetLeftQuery(viewTarget);
151
152                 if (HasNotNullCondition(leftCellQuery, leftMember))
153                 {
154                     return true;
155                 }
156
157                 //Now figure out corresponding right side MemberPath
158                 CellQuery rightCellQuery = leftCellWrapper.OnlyInputCell.GetRightQuery(viewTarget);
159                 int indexOfMemberInProjection = leftCellQuery.GetProjectedMembers().TakeWhile(path => !path.Equals(leftMember)).Count();
160
161                 //Member with condition is projected, so check opposite CellQuery's condition
162                 if (indexOfMemberInProjection < leftCellQuery.GetProjectedMembers().Count())
163                 {
164                     MemberPath rightmember = ((MemberProjectedSlot)rightCellQuery.ProjectedSlotAt(indexOfMemberInProjection)).MemberPath;
165
166                     if (HasNotNullCondition(rightCellQuery, rightmember))
167                     {
168                         return true;
169                     }
170                 }
171
172             }
173             return false;
174         }
175
176         /// <summary>
177         /// Finds errors related to splitting Conditions
178         /// 1. Condition value is repeated across multiple types
179         /// 2. A Column/attribute is mapped but also used as a condition
180         /// </summary>
181         private void MatchConditionErrors()
182         {
183             List<LeftCellWrapper> leftCellWrappers = m_viewgenContext.AllWrappersForExtent;
184
185             //Stores violating Discriminator (condition member) so that we dont repeat the same error
186             Set<MemberPath> mappedConditionMembers = new Set<MemberPath>();
187
188             //Both of these data-structs help in finding duplicate conditions
189             Set<CompositeCondition> setOfconditions = new Set<CompositeCondition>(new ConditionComparer());
190             Dictionary<CompositeCondition, LeftCellWrapper> firstLCWForCondition = new Dictionary<CompositeCondition, LeftCellWrapper>(new ConditionComparer());
191
192             foreach (var leftCellWrapper in leftCellWrappers)
193             {
194                 CompositeCondition condMembersValues = new CompositeCondition();
195
196                 CellQuery cellQuery = leftCellWrapper.OnlyInputCell.GetLeftQuery(m_viewgenContext.ViewTarget);
197
198                 foreach (MemberRestriction condition in cellQuery.GetConjunctsFromWhereClause())
199                 {
200                     MemberPath memberPath = condition.RestrictedMemberSlot.MemberPath;
201
202                     if (!m_domainMap.IsConditionMember(memberPath))
203                     {
204                         continue;
205                     }
206
207                     ScalarRestriction scalarCond = condition as ScalarRestriction;
208                     //Check for mapping of Scalar member condition, ignore type conditions
209                     if (scalarCond != null &&
210                         !mappedConditionMembers.Contains(memberPath) && /* prevents duplicate errors */
211                         !leftCellWrapper.OnlyInputCell.CQuery.WhereClause.Equals(leftCellWrapper.OnlyInputCell.SQuery.WhereClause) && /* projection allowed when both conditions are equal */
212                         !IsMemberPartOfNotNullCondition(leftCellWrappers, memberPath, m_viewgenContext.ViewTarget))
213                     {
214                         //This member should not be mapped
215                         CheckThatConditionMemberIsNotMapped(memberPath, leftCellWrappers, mappedConditionMembers);
216                     }
217
218                     //If a not-null condition is specified on a nullable column,
219                     //check that the property it is mapped to in the fragment is non-nullable,
220                     //unless there is a not null condition on the property that is being mapped it self.
221                     //Otherwise return an error.
222                     if (m_viewgenContext.ViewTarget == ViewTarget.UpdateView)
223                     {
224                         if (scalarCond != null &&
225                             memberPath.IsNullable && IsMemberPartOfNotNullCondition(new LeftCellWrapper[] { leftCellWrapper }, memberPath, m_viewgenContext.ViewTarget))                        
226                         {
227                             MemberPath rightMemberPath = GetRightMemberPath(memberPath, leftCellWrapper);
228                             if (rightMemberPath != null && rightMemberPath.IsNullable &&
229                                 !IsMemberPartOfNotNullCondition(new LeftCellWrapper[] { leftCellWrapper }, rightMemberPath, m_viewgenContext.ViewTarget))
230                             {
231                                 m_errorLog.AddEntry(new ErrorLog.Record(true, ViewGenErrorCode.ErrorPatternConditionError,
232                                         Strings.Viewgen_ErrorPattern_NotNullConditionMappedToNullableMember(
233                                                 memberPath, rightMemberPath
234                                             ), leftCellWrapper.OnlyInputCell, ""));
235                             }
236                         }
237                     }
238
239                     //CheckForDuplicateConditionValue
240                     //discover a composite condition of the form {path1=x, path2=y, ...}
241                     foreach (var element in condition.Domain.Values)
242                     {
243                         Set<Constant> values;
244                         //if not in the dict, add it
245                         if (!condMembersValues.TryGetValue(memberPath, out values))
246                         {
247                             values = new Set<Constant>(Constant.EqualityComparer);
248                             condMembersValues.Add(memberPath, values);
249                         }
250                         values.Add(element);
251                     }
252
253                 } //foreach condition
254
255                 if (condMembersValues.Count > 0) //it is possible that there are no condition members
256                 {
257                     //Check if the composite condition has been encountered before
258                     if (setOfconditions.Contains(condMembersValues))
259                     {
260                         //Extents may be Equal on right side (e.g: by some form of Refconstraint)
261                         if (!RightSideEqual(firstLCWForCondition[condMembersValues], leftCellWrapper))
262                         {
263                             //error duplicate conditions
264                             m_errorLog.AddEntry(new ErrorLog.Record(true, ViewGenErrorCode.ErrorPatternConditionError,
265                                     Strings.Viewgen_ErrorPattern_DuplicateConditionValue(
266                                             BuildCommaSeparatedErrorString<MemberPath>(condMembersValues.Keys)
267                                         ),
268                                     ToIEnum(firstLCWForCondition[condMembersValues].OnlyInputCell, leftCellWrapper.OnlyInputCell), ""));
269                         }
270                     }
271                     else
272                     {
273                         setOfconditions.Add(condMembersValues);
274
275                         //Remember which cell the condition came from.. used for error reporting
276                         firstLCWForCondition.Add(condMembersValues, leftCellWrapper);
277                     }
278                 }
279             } //foreach fragment related to the Extent we are working on
280
281         }
282
283         private MemberPath GetRightMemberPath(MemberPath conditionMember,LeftCellWrapper leftCellWrapper)
284         {
285             CellQuery rightCellQuery = leftCellWrapper.OnlyInputCell.GetRightQuery(ViewTarget.QueryView);
286             var projectPositions = rightCellQuery.GetProjectedPositions(conditionMember);
287             //Make the case simple. If the member is mapped more than once in the same cell wrapper
288             //we are not going try and guess the pattern
289             if (projectPositions.Count != 1)
290             {
291                 return null;
292             }
293             int firstProjectedPosition = projectPositions.First();
294             CellQuery leftCellQuery = leftCellWrapper.OnlyInputCell.GetLeftQuery(ViewTarget.QueryView);
295             return ((MemberProjectedSlot)leftCellQuery.ProjectedSlotAt(firstProjectedPosition)).MemberPath;
296         }
297
298         /// <summary>
299         /// When we are dealing with an update view, this method
300         /// finds out if the given Table is mapped to different EntitySets
301         /// </summary>
302         private void MatchSplitErrors()
303         {
304             List<LeftCellWrapper> leftCellWrappers = m_viewgenContext.AllWrappersForExtent;
305
306             //Check that the given Table is mapped to only one EntitySet (avoid AssociationSets)
307             var nonAssociationWrappers = leftCellWrappers.Where(r => !(r.LeftExtent is AssociationSet) && !(r.RightCellQuery.Extent is AssociationSet));
308
309             if (m_viewgenContext.ViewTarget == ViewTarget.UpdateView && nonAssociationWrappers.Any())
310             {
311                 LeftCellWrapper firstLeftCWrapper = nonAssociationWrappers.First();
312                 EntitySetBase rightExtent = firstLeftCWrapper.RightCellQuery.Extent;
313
314                 foreach (var leftCellWrapper in nonAssociationWrappers)
315                 {
316                     //!(leftCellWrapper.RightCellQuery.Extent is AssociationSet) &&
317                     if (!leftCellWrapper.RightCellQuery.Extent.EdmEquals(rightExtent))
318                     {
319                         //A Table may be mapped to two extents but the extents may be Equal (by some form of Refconstraint)
320                         if (!RightSideEqual(leftCellWrapper, firstLeftCWrapper))
321                         {
322                             //Report Error
323                             m_errorLog.AddEntry(new ErrorLog.Record(true, ViewGenErrorCode.ErrorPatternSplittingError,
324                                 Strings.Viewgen_ErrorPattern_TableMappedToMultipleES(leftCellWrapper.LeftExtent.ToString(), leftCellWrapper.RightCellQuery.Extent.ToString(), rightExtent.ToString()),
325                                 leftCellWrapper.Cells.First(), ""));
326                         }
327                     }
328                 }
329             }
330         }
331
332         /// <summary>
333         /// Finds out whether fragments (partitions) violate constraints that would produce an invalid mapping.
334         /// We compare equality/disjointness/containment for all 2-combinations of fragments.
335         /// Error is reported if given relationship on S side is not maintained on the C side.
336         /// If we know nothing about S-side then any relationship on C side is valid.
337         /// </summary>
338         private void MatchPartitionErrors()
339         {
340             List<LeftCellWrapper> mappingFragments = m_viewgenContext.AllWrappersForExtent;
341
342             //for every 2-combination nC2  (n choose 2) 
343             int i = 0;
344             foreach (var fragment1 in mappingFragments)
345             {
346                 foreach (var fragment2 in mappingFragments.Skip(++i))
347                 {
348                     FragmentQuery rightFragmentQuery1 = CreateRightFragmentQuery(fragment1);
349                     FragmentQuery rightFragmentQuery2 = CreateRightFragmentQuery(fragment2);
350
351                     bool isSDisjoint = CompareS(ComparisonOP.IsDisjointFrom, m_viewgenContext, fragment1, fragment2, rightFragmentQuery1, rightFragmentQuery2);
352                     bool isCDisjoint = CompareC(ComparisonOP.IsDisjointFrom, m_viewgenContext, fragment1, fragment2, rightFragmentQuery1, rightFragmentQuery2);
353
354                     bool is1SubsetOf2_C;
355                     bool is2SubsetOf1_C;
356                     bool is1SubsetOf2_S;
357                     bool is2SubsetOf1_S;
358                     bool isSEqual;
359                     bool isCEqual;
360
361                     if (isSDisjoint)
362                     {
363                         if (isCDisjoint)
364                         {
365                             continue;
366                         }
367                         else
368                         {
369                             //Figure out more info for accurate message
370                             is1SubsetOf2_C = CompareC(ComparisonOP.IsContainedIn, m_viewgenContext, fragment1, fragment2, rightFragmentQuery1, rightFragmentQuery2);
371                             is2SubsetOf1_C = CompareC(ComparisonOP.IsContainedIn, m_viewgenContext, fragment2, fragment1, rightFragmentQuery2, rightFragmentQuery1);
372                             isCEqual = is1SubsetOf2_C && is2SubsetOf1_C;
373
374                             StringBuilder errorString = new StringBuilder();
375                             //error
376                             if (isCEqual) //equal
377                             {
378                                 //MSG:  These two fragments are disjoint on the S-side but equal on the C-side. 
379                                 //      Ensure disjointness on C-side by mapping them to different types within the same EntitySet
380                                 //      or by mapping them to the same type but with a C-side discriminator.
381                                 //TestCase (1)
382                                 errorString.Append(Strings.Viewgen_ErrorPattern_Partition_Disj_Eq);
383                             }
384                             else if (is1SubsetOf2_C || is2SubsetOf1_C)
385                             {
386                                 //Really overlap is not accurate term (should be contianed in or subset of), but its easiest to read.
387
388                                 if (CSideHasDifferentEntitySets(fragment1, fragment2))
389                                 {
390                                     //MSG:  These two fragments are disjoint on the S-side but overlap on the C-side via a Referential constraint.
391                                     //      Ensure disjointness on C-side by mapping them to different types within the same EntitySet
392                                     //      or by mapping them to the same type but with a C-side discriminator.
393
394                                     //TestCase (Not possible because all PKs must be mapped)
395                                     errorString.Append(Strings.Viewgen_ErrorPattern_Partition_Disj_Subs_Ref);
396                                 }
397                                 else
398                                 {
399                                     //MSG:  These two fragments are disjoint on the S-side but overlap on the C-side. 
400                                     //      Ensure disjointness on C-side. You may be using IsTypeOf() quantifier to 
401                                     //      map multiple types within one of these fragments.
402                                     //TestCase (2)
403                                     errorString.Append(Strings.Viewgen_ErrorPattern_Partition_Disj_Subs);
404                                 }
405                             }
406                             else //relationship is unknown
407                             {
408                                 //MSG:  These two fragments are disjoint on the S-side but not so on the C-side. 
409                                 //      Ensure disjointness on C-side by mapping them to different types within the same EntitySet
410                                 //      or by mapping them to the same type but with a C-side discriminator.
411
412                                 //TestCase (4)
413                                 errorString.Append(Strings.Viewgen_ErrorPattern_Partition_Disj_Unk);
414                             }
415
416                             m_errorLog.AddEntry(new ErrorLog.Record(true, ViewGenErrorCode.ErrorPatternInvalidPartitionError, errorString.ToString(), ToIEnum(fragment1.OnlyInputCell, fragment2.OnlyInputCell), ""));
417
418                             if (FoundTooManyErrors())
419                             {
420                                 return;
421                             }
422                         }
423                     }
424                     else
425                     {
426                         is1SubsetOf2_C = CompareC(ComparisonOP.IsContainedIn, m_viewgenContext, fragment1, fragment2, rightFragmentQuery1, rightFragmentQuery2);
427                         is2SubsetOf1_C = CompareC(ComparisonOP.IsContainedIn, m_viewgenContext, fragment2, fragment1, rightFragmentQuery2, rightFragmentQuery1);
428                     }
429                     is1SubsetOf2_S = CompareS(ComparisonOP.IsContainedIn, m_viewgenContext, fragment1, fragment2, rightFragmentQuery1, rightFragmentQuery2);
430                     is2SubsetOf1_S = CompareS(ComparisonOP.IsContainedIn, m_viewgenContext, fragment2, fragment1, rightFragmentQuery2, rightFragmentQuery1);
431
432                     isCEqual = is1SubsetOf2_C && is2SubsetOf1_C;
433                     isSEqual = is1SubsetOf2_S && is2SubsetOf1_S;
434
435
436                     if (isSEqual)
437                     {
438                         if (isCEqual) //c-side equal
439                         {
440                             continue;
441                         }
442                         else
443                         {
444                             //error
445                             StringBuilder errorString = new StringBuilder();
446
447                             if (isCDisjoint)
448                             {
449                                 //MSG:  These two fragments are equal on the S-side but disjoint on the C-side. 
450                                 //      Either partition the S-side by adding a condition or remove any C-side conditions along with resulting redundant mapping fragments.
451                                 //      You may also map these two disjoint C-side partitions to different tables.
452                                 //TestCase (5)
453                                 errorString.Append(Strings.Viewgen_ErrorPattern_Partition_Eq_Disj);
454                             }
455                             else if (is1SubsetOf2_C || is2SubsetOf1_C)
456                             {
457                                 if (CSideHasDifferentEntitySets(fragment1, fragment2))
458                                 {
459                                     //MSG:  These two fragments are equal on the S-side but overlap on the C-side. 
460                                     //      It is likely that you have not added Referential Integrity constriaint for all Key attributes of both EntitySets.
461                                     //      Doing so would ensure equality on the C-side.
462                                     //TestCase (Not possible, right?)
463                                     errorString.Append(Strings.Viewgen_ErrorPattern_Partition_Eq_Subs_Ref);
464                                 }
465                                 else
466                                 {
467                                     //MSG:  These two fragments are equal on the S-side but overlap on the C-side. 
468                                     //      If you are using IsTypeOf() quantifier ensure both mapping fragments capture same types on the C-side.
469                                     //      Otherwise you may have intended to partition the S-side.
470                                     //TestCase (6)
471
472                                     //Check for the specific case
473                                     //where there are mapping fragments with different types on C side
474                                     //mapped to same table on the Store side but not all the fragments have 
475                                     //a condition. Ignore the cases where any of the fragments have C side conditions.
476                                     if (fragment1.LeftExtent.Equals(fragment2.LeftExtent))
477                                     {
478                                         bool firstCellWrapperHasCondition;
479                                         List<EdmType> edmTypesForFirstCellWrapper;
480                                         bool secondCellWrapperHasCondition;
481                                         List<EdmType> edmTypesForSecondCellWrapper;
482                                         GetTypesAndConditionForWrapper(fragment1, out firstCellWrapperHasCondition, out edmTypesForFirstCellWrapper);
483                                         GetTypesAndConditionForWrapper(fragment2, out secondCellWrapperHasCondition, out edmTypesForSecondCellWrapper);
484                                         if (!firstCellWrapperHasCondition && !secondCellWrapperHasCondition)
485                                         {
486                                             if (((edmTypesForFirstCellWrapper.Except(edmTypesForSecondCellWrapper)).Count() != 0 )
487                                                 || ((edmTypesForSecondCellWrapper.Except(edmTypesForFirstCellWrapper)).Count() != 0 ))
488                                             {
489                                                 if (!CheckForStoreConditions(fragment1) || !CheckForStoreConditions(fragment2))
490                                                 {
491                                                     IEnumerable<string> edmTypesForErrorString = edmTypesForFirstCellWrapper.Select(it => it.FullName).Union(edmTypesForSecondCellWrapper.Select(it => it.FullName));
492                                                     m_errorLog.AddEntry(new ErrorLog.Record(true, ViewGenErrorCode.ErrorPatternConditionError,
493                                                 Strings.Viewgen_ErrorPattern_Partition_MultipleTypesMappedToSameTable_WithoutCondition(
494                                                         StringUtil.ToCommaSeparatedString(edmTypesForErrorString), fragment1.LeftExtent
495                                                     ), ToIEnum(fragment1.OnlyInputCell, fragment2.OnlyInputCell), ""));
496                                                     return;
497                                                 }
498                                             }
499                                         }
500                                     }
501
502                                     errorString.Append(Strings.Viewgen_ErrorPattern_Partition_Eq_Subs);
503                                 }
504                             }
505                             else //unknown
506                             {
507                                 //S-side equal, C-side Unknown
508                                 if (!IsQueryView() &&
509                                     (fragment1.OnlyInputCell.CQuery.Extent is AssociationSet ||
510                                      fragment2.OnlyInputCell.CQuery.Extent is AssociationSet))
511                                 {
512                                     //one side is an association set
513                                     errorString.Append(Strings.Viewgen_ErrorPattern_Partition_Eq_Unk_Association);
514                                 }
515                                 else
516                                 {
517
518                                     //MSG:  These two fragments are equal on the S-side but not so on the C-side. 
519                                     //      Try adding an Association with Referntial Integrity constraint if they are
520                                     //      mapped to different EntitySets in order to make theme equal on the C-side.
521                                     //TestCase (no need, Table mapped to multiple ES tests cover this scenario)
522                                     errorString.Append(Strings.Viewgen_ErrorPattern_Partition_Eq_Unk);
523                                 }
524                             }
525
526                             m_errorLog.AddEntry(new ErrorLog.Record(true, ViewGenErrorCode.ErrorPatternInvalidPartitionError, errorString.ToString(), ToIEnum(fragment1.OnlyInputCell, fragment2.OnlyInputCell), ""));
527
528                             if (FoundTooManyErrors())
529                             {
530                                 return;
531                             }
532                         }
533                     }
534                     else if (is1SubsetOf2_S || is2SubsetOf1_S) //proper subset - note: else if ensures inverse need not be checked
535                     {
536                         //C-side proper subset (c side must not be equal)
537                         if ((is1SubsetOf2_S && is1SubsetOf2_C == true && !(is2SubsetOf1_C == true)) || (is2SubsetOf1_S && is2SubsetOf1_C == true && !(is1SubsetOf2_C == true)))
538                         {
539                             continue;
540                         }
541                         else
542                         {   //error
543
544                             StringBuilder errorString = new StringBuilder();
545
546                             if (isCDisjoint)
547                             {
548                                 //MSG:  One of the fragments is a subset of the other on the S-side but they are disjoint on the C-side.
549                                 //      If you intended overlap on the S-side ensure they have similar relationship on teh C-side.
550                                 //      You may need to use IsTypeOf() quantifier or loosen conditions in one of the fragments.
551                                 //TestCase (9, 10)
552                                 errorString.Append(Strings.Viewgen_ErrorPattern_Partition_Sub_Disj);
553                             }
554                             else if (isCEqual) //equal
555                             {
556                                 //MSG:  One of the fragments is a subset of the other on the S-side but they are equal on the C-side.
557                                 //      If you intended overlap on the S-side ensure they have similar relationship on teh C-side.
558                                 //TestCase (10)
559
560
561                                 if (CSideHasDifferentEntitySets(fragment1, fragment2))
562                                 {
563                                     // If they are equal via a Referential integrity constraint try making one a subset of the other by
564                                     // not including all primary keys in the constraint.
565                                     //TestCase (Not possible)
566                                     errorString.Append(" " + Strings.Viewgen_ErrorPattern_Partition_Sub_Eq_Ref);
567                                 }
568                                 else
569                                 {
570                                     //      You may need to modify conditions in one of the fragments.
571                                     //TestCase (10)
572                                     errorString.Append(Strings.Viewgen_ErrorPattern_Partition_Sub_Eq);
573                                 }
574                             }
575                             else
576                             {   //unknown
577                                 //MSG:  One of the fragments is a subset of the other on the S-side but they are disjoint on the C-side.
578                                 //      If you intended overlap on the S-side ensure they have similar relationship on teh C-side.
579                                 //TestCase (no need, Table mapped to multiple ES tests cover this scenario)
580                                 errorString.Append(Strings.Viewgen_ErrorPattern_Partition_Sub_Unk);
581                             }
582
583                             m_errorLog.AddEntry(new ErrorLog.Record(true, ViewGenErrorCode.ErrorPatternInvalidPartitionError, errorString.ToString(), ToIEnum(fragment1.OnlyInputCell, fragment2.OnlyInputCell), ""));
584
585                             if (FoundTooManyErrors())
586                             {
587                                 return;
588                             }
589                         }
590                     }
591                     //else unknown relationship on the S-side
592                 }
593             }   //end looping over every 2-combination of fragment
594         }
595
596         /// <summary>
597         /// Gets the types on the Edm side mapped in this fragment wrapper.
598         /// It also returns an out parameter indicating whether there were any C side conditions.
599         /// </summary>
600         private void GetTypesAndConditionForWrapper(LeftCellWrapper wrapper, out bool hasCondition, out List<EdmType> edmTypes)
601         {
602             hasCondition = false;
603             edmTypes = new List<EdmType>();
604             //Figure out which type has no Cell mapped to it
605             foreach (Cell cell in wrapper.Cells)
606             {
607                 foreach (var restriction in cell.CQuery.Conditions)
608                 {
609                     foreach (var cellConst in restriction.Domain.Values)
610                     {
611                         //if there is a mapping to this type...
612                         TypeConstant typeConst = cellConst as TypeConstant;
613                         if (typeConst != null)
614                         {
615                             edmTypes.Add(typeConst.EdmType);
616                         }
617                         else
618                         {
619                             hasCondition = true;
620                         }
621                     }
622                 }
623             }
624         }
625
626         /// <summary>
627         /// Return true if there were any Store conditions on this cell wrapper.
628         /// </summary>
629         /// <param name="wrapper"></param>
630         /// <returns></returns>
631         private bool CheckForStoreConditions(LeftCellWrapper wrapper)
632         {
633             return wrapper.Cells.SelectMany(c => c.SQuery.Conditions).Any();
634         }
635
636
637         private void CheckThatConditionMemberIsNotMapped(MemberPath conditionMember, List<LeftCellWrapper> mappingFragments, Set<MemberPath> mappedConditionMembers)
638         {
639
640             //Make sure memberPath is not mapped (in any other cells)
641             foreach (var anotherFragment in mappingFragments)
642             {
643                 foreach (var anotherCell in anotherFragment.Cells)
644                 {
645                     CellQuery anotherCellQuery = anotherCell.GetLeftQuery(m_viewgenContext.ViewTarget);
646                     if (anotherCellQuery.GetProjectedMembers().Contains(conditionMember))
647                     {
648                         mappedConditionMembers.Add(conditionMember);
649                         //error condition memer is projected somewhere
650                         m_errorLog.AddEntry(new ErrorLog.Record(true, ViewGenErrorCode.ErrorPatternConditionError, Strings.Viewgen_ErrorPattern_ConditionMemberIsMapped(conditionMember.ToString()), anotherCell, ""));
651                     }
652                 }
653             }
654         }
655
656         #endregion
657
658         private bool FoundTooManyErrors()
659         {
660             return (m_errorLog.Count > m_originalErrorCount + NUM_PARTITION_ERR_TO_FIND);
661         }
662
663         #region Private Helpers
664
665         private string BuildCommaSeparatedErrorString<T>(IEnumerable<T> members)
666         {
667             StringBuilder builder = new StringBuilder();
668
669             var firstMember = members.First();
670             foreach (var member in members)
671             {
672                 if (!member.Equals(firstMember))
673                 {
674                     builder.Append(", ");
675                 }
676                 builder.Append("'" + member.ToString() + "'");
677             }
678             return builder.ToString();
679         }
680
681         private bool CSideHasDifferentEntitySets(LeftCellWrapper a, LeftCellWrapper b)
682         {
683             if (IsQueryView())
684             {
685                 return a.LeftExtent == b.LeftExtent;
686             }
687             else
688             {
689                 return a.RightCellQuery == b.RightCellQuery;
690             }
691         }
692
693         private bool CompareC(ComparisonOP op, ViewgenContext context, LeftCellWrapper leftWrapper1, LeftCellWrapper leftWrapper2, FragmentQuery rightQuery1, FragmentQuery rightQuery2)
694         {
695             return Compare(true /*lookingForCSide*/, op, context, leftWrapper1, leftWrapper2, rightQuery1, rightQuery2);
696         }
697
698         private bool CompareS(ComparisonOP op, ViewgenContext context, LeftCellWrapper leftWrapper1, LeftCellWrapper leftWrapper2, FragmentQuery rightQuery1, FragmentQuery rightQuery2)
699         {
700             return Compare(false/*lookingForCSide*/, op, context, leftWrapper1, leftWrapper2, rightQuery1, rightQuery2);
701         }
702
703         private bool Compare(bool lookingForC, ComparisonOP op, ViewgenContext context, LeftCellWrapper leftWrapper1, LeftCellWrapper leftWrapper2, FragmentQuery rightQuery1, FragmentQuery rightQuery2)
704         {
705             LCWComparer comparer;
706
707             if ((lookingForC && IsQueryView()) || (!lookingForC && !IsQueryView()))
708             {
709                 if (op == ComparisonOP.IsContainedIn)
710                 {
711                     comparer = context.LeftFragmentQP.IsContainedIn;
712                 }
713                 else if (op == ComparisonOP.IsDisjointFrom)
714                 {
715                     comparer = context.LeftFragmentQP.IsDisjointFrom;
716                 }
717                 else
718                 {
719                     Debug.Fail("Unexpected comparison operator, only IsDisjointFrom and IsContainedIn are expected");
720                     return false;
721                 }
722
723                 return comparer(leftWrapper1.FragmentQuery, leftWrapper2.FragmentQuery);
724             }
725             else
726             {
727                 if (op == ComparisonOP.IsContainedIn)
728                 {
729                     comparer = context.RightFragmentQP.IsContainedIn;
730                 }
731                 else if (op == ComparisonOP.IsDisjointFrom)
732                 {
733                     comparer = context.RightFragmentQP.IsDisjointFrom;
734                 }
735                 else
736                 {
737                     Debug.Fail("Unexpected comparison operator, only IsDisjointFrom and IsContainedIn are expected");
738                     return false;
739                 }
740
741                 return comparer(rightQuery1, rightQuery2);
742             }
743         }
744
745         private bool RightSideEqual(LeftCellWrapper wrapper1, LeftCellWrapper wrapper2)
746         {
747             FragmentQuery rightFragmentQuery1 = CreateRightFragmentQuery(wrapper1);
748             FragmentQuery rightFragmentQuery2 = CreateRightFragmentQuery(wrapper2);
749
750             return m_viewgenContext.RightFragmentQP.IsEquivalentTo(rightFragmentQuery1, rightFragmentQuery2);
751         }
752
753         private FragmentQuery CreateRightFragmentQuery(LeftCellWrapper wrapper)
754         {
755             return FragmentQuery.Create(wrapper.OnlyInputCell.CellLabel.ToString(), wrapper.CreateRoleBoolean(), wrapper.OnlyInputCell.GetRightQuery(m_viewgenContext.ViewTarget));
756         }
757
758         private IEnumerable<Cell> ToIEnum(Cell one, Cell two)
759         {
760             List<Cell> cells = new List<Cell>();
761             cells.Add(one);
762             cells.Add(two);
763             return cells;
764         }
765
766         private bool IsQueryView()
767         {
768             return (m_viewgenContext.ViewTarget == ViewTarget.QueryView);
769         }
770
771         #endregion
772
773         enum ComparisonOP
774         {
775             IsContainedIn,
776             IsDisjointFrom
777         }
778     }
779
780     class ConditionComparer : IEqualityComparer<Dictionary<MemberPath, Set<Constant>>>
781     {
782         public bool Equals(Dictionary<MemberPath, Set<Constant>> one, Dictionary<MemberPath, Set<Constant>> two)
783         {
784             Set<MemberPath> keysOfOne = new Set<MemberPath>(one.Keys, MemberPath.EqualityComparer);
785             Set<MemberPath> keysOfTwo = new Set<MemberPath>(two.Keys, MemberPath.EqualityComparer);
786
787             if (!keysOfOne.SetEquals(keysOfTwo))
788             {
789                 return false;
790             }
791
792             foreach (var member in keysOfOne)
793             {
794                 Set<Constant> constantsOfOne = one[member];
795                 Set<Constant> constantsOfTwo = two[member];
796
797                 if (!constantsOfOne.SetEquals(constantsOfTwo))
798                 {
799                     return false;
800                 }
801             }
802             return true;
803         }
804
805         public int GetHashCode(Dictionary<MemberPath, Set<Constant>> obj)
806         {
807             StringBuilder builder = new StringBuilder();
808             foreach (var key in obj.Keys)
809             {
810                 builder.Append(key.ToString());
811             }
812
813             return builder.ToString().GetHashCode();
814         }
815
816     }
817 }