1 //---------------------------------------------------------------------
2 // <copyright file="LeftCellWrapper.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.Collections.Generic;
11 using System.Data.Common.Utils;
12 using System.Text;
13 using System.Linq;
14 using System.Diagnostics;
15 using System.Collections.ObjectModel;
16 using System.Data.Metadata.Edm;
17 using System.Data.Common.Utils.Boolean;
18 using System.Data.Mapping.ViewGeneration.Validation;
19 using System.Data.Mapping.ViewGeneration.QueryRewriting;
20 
21 namespace System.Data.Mapping.ViewGeneration.Structures
22 {
23 
24     // This class essentially stores a cell but in a special form. When we
25     // are generating a view for an extent, we denote the extent's side (C or
26     // S) as the "left side" and the side being used in the view as the right
27     // side. For example, in query views, the C side is the left side.
28     //
29     // Each LeftCellWrapper is a cell of the form:
30     // Project[A1,...,An] (Select[var IN {domain}] (Extent)) = Expr
31     // Where
32     // - "domain" is a set of multiconstants that correspond to the different
33     //   variable values allowed for the cell query
34     // - A1 ... An are denoted by Attributes in this and corresponds to
35     //   the list of attributes that are projected
36     // - Extent is the extent for which th view is being generated
37     // - Expr is the expression on the other side to produce the left side of
38     //   the cell
39     internal class LeftCellWrapper : InternalBase
40     {
41 
42         #region Fields
43         internal static readonly IEqualityComparer<LeftCellWrapper> BoolEqualityComparer = new BoolWrapperComparer();
44 
45         private Set<MemberPath> m_attributes;// project: attributes computed by
46 
47         // Expr (projected attributes that get set)
48         private MemberMaps m_memberMaps;
49         private CellQuery m_leftCellQuery; // expression that computes this portion
50         private CellQuery m_rightCellQuery; // expression that computes this portion
51 
52         private HashSet<Cell> m_mergedCells; // Cells that this LeftCellWrapper (MergedCell) wraps.
53                                              // At first it starts off with a single cell and during cell merging
54                                              // cells from both LeftCellWrappers are concatenated.
55         private ViewTarget m_viewTarget;
56         private FragmentQuery m_leftFragmentQuery; // Fragment query corresponding to the left cell query of the cell
57 
58         internal static readonly IComparer<LeftCellWrapper> Comparer = new LeftCellWrapperComparer();
59         internal static readonly IComparer<LeftCellWrapper> OriginalCellIdComparer = new CellIdComparer();
60         #endregion
61 
62 
63         #region Constructor
64         // effects: Creates a LeftCellWrapper of the form:
65         // Project[attrs] (Select[var IN {domain}] (Extent)) = cellquery
66         // memberMaps is the set of maps used for producing the query or update views
LeftCellWrapper(ViewTarget viewTarget, Set<MemberPath> attrs, FragmentQuery fragmentQuery, CellQuery leftCellQuery, CellQuery rightCellQuery, MemberMaps memberMaps, IEnumerable<Cell> inputCells)67         internal LeftCellWrapper(ViewTarget viewTarget, Set<MemberPath> attrs,
68                                  FragmentQuery fragmentQuery,
69                                  CellQuery leftCellQuery, CellQuery rightCellQuery, MemberMaps memberMaps, IEnumerable<Cell> inputCells)
70         {
71             m_leftFragmentQuery = fragmentQuery;
72             m_rightCellQuery = rightCellQuery;
73             m_leftCellQuery = leftCellQuery;
74             m_attributes = attrs;
75             m_viewTarget = viewTarget;
76             m_memberMaps = memberMaps;
77             m_mergedCells = new HashSet<Cell>(inputCells);
78         }
79 
LeftCellWrapper(ViewTarget viewTarget, Set<MemberPath> attrs, FragmentQuery fragmentQuery, CellQuery leftCellQuery, CellQuery rightCellQuery, MemberMaps memberMaps, Cell inputCell)80         internal LeftCellWrapper(ViewTarget viewTarget, Set<MemberPath> attrs,
81                                  FragmentQuery fragmentQuery,
82                                  CellQuery leftCellQuery, CellQuery rightCellQuery, MemberMaps memberMaps, Cell inputCell)
83             : this(viewTarget, attrs, fragmentQuery, leftCellQuery, rightCellQuery, memberMaps, Enumerable.Repeat(inputCell, 1)) { }
84 
85         #endregion
86 
87 
88 
89         #region Properties
90 
91         internal FragmentQuery FragmentQuery
92         {
93             get { return m_leftFragmentQuery; }
94         }
95 
96         // effects: Returns the projected fields on the left side
97         internal Set<MemberPath> Attributes
98         {
99             get { return m_attributes; }
100         }
101 
102         // effects: Returns the original cell number from which the wrapper came
103         internal string OriginalCellNumberString
104         {
105             get
106             {
107                 return StringUtil.ToSeparatedString(m_mergedCells.Select(cell => cell.CellNumberAsString), "+", "");
108             }
109         }
110 
111         // effects: Returns the right domain map associated with the right query
112         internal MemberDomainMap RightDomainMap
113         {
114             get { return m_memberMaps.RightDomainMap; }
115         }
116 
117         [Conditional("DEBUG")]
AssertHasUniqueCell()118         internal void AssertHasUniqueCell()
119         {
120             Debug.Assert(m_mergedCells.Count == 1);
121         }
122 
123         internal IEnumerable<Cell> Cells
124         {
125             get { return m_mergedCells; }
126         }
127 
128         // requires: There is only one input cell in this
129         // effects: Returns the  input cell provided to view generation as part of the mapping
130         internal Cell OnlyInputCell
131         {
132             get
133             {
134                 AssertHasUniqueCell();
135                 return m_mergedCells.First();
136             }
137         }
138 
139         // effects: Returns the right CellQuery
140         internal CellQuery RightCellQuery
141         {
142             get { return m_rightCellQuery; }
143         }
144 
145         internal CellQuery LeftCellQuery
146         {
147             get { return m_leftCellQuery; }
148         }
149 
150 
151         // effects: Returns the extent for which the wrapper was built
152         internal EntitySetBase LeftExtent
153         {
154             get
155             {
156                 return m_mergedCells.First().GetLeftQuery(m_viewTarget).Extent;
157             }
158         }
159 
160         // effects: Returns the extent of the right cellquery
161         internal EntitySetBase RightExtent
162         {
163             get
164             {
165                 EntitySetBase result = m_rightCellQuery.Extent;
166                 Debug.Assert(result != null, "Bad root value in join tree");
167                 return result;
168             }
169         }
170 
171         #endregion
172 
173         #region Methods
174 
175         // effects: Yields the input cells in wrappers
GetInputCellsForWrappers(IEnumerable<LeftCellWrapper> wrappers)176         internal static IEnumerable<Cell> GetInputCellsForWrappers(IEnumerable<LeftCellWrapper> wrappers)
177         {
178             foreach (LeftCellWrapper wrapper in wrappers)
179             {
180                 foreach (Cell cell in wrapper.m_mergedCells)
181                 {
182                     yield return cell;
183                 }
184             }
185         }
186 
187         // effects: Creates a boolean variable representing the right extent or association end
CreateRoleBoolean()188         internal RoleBoolean CreateRoleBoolean()
189         {
190             if (RightExtent is AssociationSet)
191             {
192                 Set<AssociationEndMember> ends = GetEndsForTablePrimaryKey();
193                 if (ends.Count == 1)
194                 {
195                     AssociationSetEnd setEnd = ((AssociationSet)RightExtent).AssociationSetEnds[ends.First().Name];
196                     return new RoleBoolean(setEnd);
197                 }
198             }
199             return new RoleBoolean(RightExtent);
200         }
201 
202         // effects: Given a set of wrappers, returns a string that contains the list of extents in the
203         // rightcellQueries of the wrappers
GetExtentListAsUserString(IEnumerable<LeftCellWrapper> wrappers)204         internal static string GetExtentListAsUserString(IEnumerable<LeftCellWrapper> wrappers)
205         {
206             Set<EntitySetBase> extents = new Set<EntitySetBase>(EqualityComparer<EntitySetBase>.Default);
207             foreach (LeftCellWrapper wrapper in wrappers)
208             {
209                 extents.Add(wrapper.RightExtent);
210             }
211 
212             StringBuilder builder = new StringBuilder();
213             bool isFirst = true;
214             foreach (EntitySetBase extent in extents)
215             {
216                 if (isFirst == false)
217                 {
218                     builder.Append(", ");
219                 }
220                 isFirst = false;
221                 builder.Append(extent.Name);
222             }
223             return builder.ToString();
224         }
225 
ToFullString(StringBuilder builder)226         internal override void ToFullString(StringBuilder builder)
227         {
228             builder.Append("P[");
229             StringUtil.ToSeparatedString(builder, m_attributes, ",");
230             builder.Append("] = ");
231             m_rightCellQuery.ToFullString(builder);
232         }
233 
234         // effects: Modifies stringBuilder to contain the view corresponding
235         // to the right cellquery
ToCompactString(StringBuilder stringBuilder)236         internal override void ToCompactString(StringBuilder stringBuilder)
237         {
238             stringBuilder.Append(OriginalCellNumberString);
239         }
240 
241         // effects: Writes m_cellWrappers to builder
WrappersToStringBuilder(StringBuilder builder, List<LeftCellWrapper> wrappers, string header)242         internal static void WrappersToStringBuilder(StringBuilder builder, List<LeftCellWrapper> wrappers,
243                                                      string header)
244         {
245             builder.AppendLine()
246                    .Append(header)
247                    .AppendLine();
248             // Sort them according to the original cell number
249             LeftCellWrapper[] cellWrappers = wrappers.ToArray();
250             Array.Sort(cellWrappers, LeftCellWrapper.OriginalCellIdComparer);
251 
252             foreach (LeftCellWrapper wrapper in cellWrappers)
253             {
254                 wrapper.ToCompactString(builder);
255                 builder.Append(" = ");
256                 wrapper.ToFullString(builder);
257                 builder.AppendLine();
258             }
259         }
260 
261 
262         // requires: RightCellQuery.Extent corresponds to a relationship set
263         // effects: Returns the ends to which the key of the corresponding
264         // table (i.e., the left query) maps to in the relationship set. For
265         // example, if RightCellQuery.Extent is OrderOrders and it maps to
266         // <oid, otherOid> of table SOrders with key oid, this returns the
267         // end to which oid is mapped. Similarly, if we have a link table
268         // with the whole key mapped to two ends of the association set, it
269         // returns both ends
GetEndsForTablePrimaryKey()270         private Set<AssociationEndMember> GetEndsForTablePrimaryKey()
271         {
272             CellQuery rightQuery = RightCellQuery;
273             Set<AssociationEndMember> result = new Set<AssociationEndMember>(EqualityComparer<AssociationEndMember>.Default);
274             // Get the key slots for the table (they are in the slotMap) and
275             // check for that slot on the C-side
276             foreach (int keySlot in m_memberMaps.ProjectedSlotMap.KeySlots)
277             {
278                 MemberProjectedSlot slot = (MemberProjectedSlot)rightQuery.ProjectedSlotAt(keySlot);
279                 MemberPath path = slot.MemberPath;
280                 // See what end it maps to in the relationSet
281                 AssociationEndMember endMember = (AssociationEndMember)path.RootEdmMember;
282                 Debug.Assert(endMember != null, "Element in path before scalar path is not end property?");
283                 result.Add(endMember);
284             }
285             Debug.Assert(result != null, "No end found for keyslots of table?");
286             return result;
287         }
288 
289 
GetLeftSideMappedSlotForRightSideMember(MemberPath member)290         internal MemberProjectedSlot GetLeftSideMappedSlotForRightSideMember(MemberPath member)
291         {
292             int projectedPosition = RightCellQuery.GetProjectedPosition(new MemberProjectedSlot(member));
293             if (projectedPosition == -1)
294             {
295                 return null;
296             }
297 
298             ProjectedSlot slot = LeftCellQuery.ProjectedSlotAt(projectedPosition);
299 
300             if (slot == null || slot is ConstantProjectedSlot)
301             {
302                 return null;
303             }
304 
305             return slot as MemberProjectedSlot;
306         }
307 
GetRightSideMappedSlotForLeftSideMember(MemberPath member)308         internal MemberProjectedSlot GetRightSideMappedSlotForLeftSideMember(MemberPath member)
309         {
310             int projectedPosition = LeftCellQuery.GetProjectedPosition(new MemberProjectedSlot(member));
311             if (projectedPosition == -1)
312             {
313                 return null;
314             }
315 
316             ProjectedSlot slot = RightCellQuery.ProjectedSlotAt(projectedPosition);
317 
318             if (slot == null || slot is ConstantProjectedSlot)
319             {
320                 return null;
321             }
322 
323             return slot as MemberProjectedSlot;
324         }
325 
GetCSideMappedSlotForSMember(MemberPath member)326         internal MemberProjectedSlot GetCSideMappedSlotForSMember(MemberPath member)
327         {
328             if (m_viewTarget == ViewTarget.QueryView)
329             {
330                 return GetLeftSideMappedSlotForRightSideMember(member);
331             }
332             else
333             {
334                 return GetRightSideMappedSlotForLeftSideMember(member);
335             }
336         }
337 
338         #endregion
339 
340         #region Equality Comparer class
341         // This class compares wrappers based on the Right Where Clause and
342         // Extent -- needed for the boolean engine
343         private class BoolWrapperComparer : IEqualityComparer<LeftCellWrapper>
344         {
345 
Equals(LeftCellWrapper left, LeftCellWrapper right)346             public bool Equals(LeftCellWrapper left, LeftCellWrapper right)
347             {
348                 // Quick check with references
349                 if (object.ReferenceEquals(left, right))
350                 {
351                     // Gets the Null and Undefined case as well
352                     return true;
353                 }
354                 // One of them is non-null at least
355                 if (left == null || right == null)
356                 {
357                     return false;
358                 }
359                 // Both are non-null at this point
360                 bool whereClauseEqual = BoolExpression.EqualityComparer.Equals(left.RightCellQuery.WhereClause,
361                                                                                right.RightCellQuery.WhereClause);
362 
363                 return left.RightExtent.Equals(right.RightExtent) && whereClauseEqual;
364             }
365 
GetHashCode(LeftCellWrapper wrapper)366             public int GetHashCode(LeftCellWrapper wrapper)
367             {
368                 return BoolExpression.EqualityComparer.GetHashCode(wrapper.RightCellQuery.WhereClause) ^ wrapper.RightExtent.GetHashCode();
369             }
370         }
371         #endregion
372 
373         #region Comparer
374         // A class that compares two cell wrappers. Useful for guiding heuristics
375         // and to ensure that the largest selection domain (i.e., the number of
376         // multiconstants in "mc in {...}") is first in the list
377         private class LeftCellWrapperComparer : IComparer<LeftCellWrapper>
378         {
379 
Compare(LeftCellWrapper x, LeftCellWrapper y)380             public int Compare(LeftCellWrapper x, LeftCellWrapper y)
381             {
382 
383                 // More attributes first -- so that we get most attributes
384                 // with very few intersections (when we use the sortings for
385                 // that). When we are subtracting, attributes are not important
386 
387                 // Use FragmentQuery's attributes instead of LeftCellWrapper's original attributes in the comparison
388                 // since the former might have got extended to include all attributes whose value is determined
389                 // by the WHERE clause (e.g., if we have WHERE ProductName='Camera' we can assume ProductName is projected)
390 
391                 if (x.FragmentQuery.Attributes.Count > y.FragmentQuery.Attributes.Count)
392                 {
393                     return -1;
394                 }
395                 else if (x.FragmentQuery.Attributes.Count < y.FragmentQuery.Attributes.Count)
396                 {
397                     return 1;
398                 }
399                 // Since the sort may not be stable, we use the original cell number string to break the tie
400                 return String.CompareOrdinal(x.OriginalCellNumberString, y.OriginalCellNumberString);
401             }
402         }
403 
404         // A class that compares two cell wrappers based on original cell number
405         internal class CellIdComparer : IComparer<LeftCellWrapper>
406         {
407 
Compare(LeftCellWrapper x, LeftCellWrapper y)408             public int Compare(LeftCellWrapper x, LeftCellWrapper y)
409             {
410                 return StringComparer.Ordinal.Compare(x.OriginalCellNumberString, y.OriginalCellNumberString);
411             }
412         }
413 
414         #endregion
415     }
416 }
417