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