Update Reference Sources to .NET Framework 4.6.1
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / Mapping / ViewGeneration / Structures / CellQuery.cs
1 //---------------------------------------------------------------------
2 // <copyright file="CellQuery.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.Collections.ObjectModel;
12 using System.Data.Common.Utils;
13 using System.Data.Mapping.ViewGeneration.CqlGeneration;
14 using System.Data.Mapping.ViewGeneration.Utils;
15 using System.Data.Mapping.ViewGeneration.Validation;
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.Structures
22 {
23     using System.Data.Entity;
24     using AttributeSet = Set<MemberPath>;
25
26     /// <summary>
27     /// This class stores the C or S query. For example, 
28     /// (C) SELECT (p type Person) AS D1, p.pid, p.name FROM p in P WHERE D1 
29     /// (S) SELECT True AS D1, pid, name FROM SPerson WHERE D1
30     /// 
31     /// The cell query is stored in a "factored" manner for ease of
32     /// cell-merging and cell manipulation. It contains:
33     /// * Projection: A sequence of slots and a sequence of boolean slots (one
34     ///   for each cell in the extent)
35     /// * A From part represented as a Join tree
36     /// * A where clause 
37     /// </summary>
38     internal class CellQuery : InternalBase
39     {
40         #region Fields
41         /// <summary>
42         /// Whether query has a 'SELECT DISTINCT' on top.
43         /// </summary>
44         internal enum SelectDistinct
45         {
46             Yes,
47             No
48         }
49
50         // The boolean expressions that essentially capture the type information
51         // Fixed-size list; NULL in the list means 'unused'
52         private List<BoolExpression> m_boolExprs;
53         // The fields including the key fields
54         // May contain NULLs - means 'not in the projection'
55         private ProjectedSlot[] m_projectedSlots;
56         // where clause: An expression formed using the boolExprs
57         private BoolExpression m_whereClause;
58         private BoolExpression m_originalWhereClause; // m_originalWhereClause is not changed
59         
60         private SelectDistinct m_selectDistinct;
61         // The from part of the query
62         private MemberPath m_extentMemberPath;
63         // The basic cell relation for all slots in this
64         private BasicCellRelation m_basicCellRelation;
65         #endregion
66
67         #region Constructors
68         // effects: Creates a cell query with the given projection (slots),
69         // from part (joinTreeRoot) and the predicate (whereClause)
70         // Used for cell creation
71         internal CellQuery(List<ProjectedSlot> slots, BoolExpression whereClause, MemberPath rootMember, SelectDistinct eliminateDuplicates)
72             : this(slots.ToArray(), whereClause, new List<BoolExpression>(), eliminateDuplicates, rootMember)
73         {
74         }
75
76
77
78         // effects: Given all the fields, just sets them. 
79         internal CellQuery(ProjectedSlot[] projectedSlots,
80                           BoolExpression whereClause,
81                           List<BoolExpression> boolExprs,
82                           SelectDistinct elimDupl, MemberPath rootMember)
83         {
84             
85             m_boolExprs = boolExprs;
86             m_projectedSlots = projectedSlots;
87             m_whereClause = whereClause;
88             m_originalWhereClause = whereClause;
89             m_selectDistinct = elimDupl;
90             m_extentMemberPath = rootMember;
91         }
92
93         /// <summary>
94         /// Copy Constructor
95         /// </summary>
96         internal CellQuery(CellQuery source)
97         {
98             this.m_basicCellRelation = source.m_basicCellRelation;
99             this.m_boolExprs = source.m_boolExprs;
100             this.m_selectDistinct = source.m_selectDistinct;
101             this.m_extentMemberPath = source.m_extentMemberPath;
102             this.m_originalWhereClause = source.m_originalWhereClause;
103             this.m_projectedSlots = source.m_projectedSlots;
104             this.m_whereClause = source.m_whereClause;
105         }
106
107         // effects: Given an existing cellquery, makes a new one based on it
108         // but uses the slots as specified with newSlots
109         private CellQuery(CellQuery existing, ProjectedSlot[] newSlots) :
110             this(newSlots, existing.m_whereClause, existing.m_boolExprs,
111                  existing.m_selectDistinct, existing.m_extentMemberPath)
112         {
113         }
114         #endregion
115
116         #region Properties
117
118         internal SelectDistinct SelectDistinctFlag
119         {
120             get { return m_selectDistinct; }
121         }
122
123         // effects: Returns the top levelextent corresponding to this cell query
124         internal EntitySetBase Extent
125         {
126             get
127             {
128                 EntitySetBase extent = m_extentMemberPath.Extent as EntitySetBase;
129                 Debug.Assert(extent != null, "JoinTreeRoot in cellquery must be an extent");
130                 return extent;
131             }
132         }
133
134         // effects: Returns the number of slots projected in the query
135         internal int NumProjectedSlots
136         {
137             get { return m_projectedSlots.Length; }
138         }
139
140         internal ProjectedSlot[] ProjectedSlots
141         {
142             get { return m_projectedSlots; }
143         }
144
145         internal List<BoolExpression> BoolVars
146         {
147             get { return m_boolExprs; }
148         }
149
150         // effects: Returns the number of boolean expressions projected in the query
151         internal int NumBoolVars
152         {
153             get { return m_boolExprs.Count; }
154         }
155
156         internal BoolExpression WhereClause
157         {
158             get { return m_whereClause; }
159         }
160
161         // effects: Returns the root of the join tree
162         internal MemberPath SourceExtentMemberPath
163         {
164             get { return m_extentMemberPath; }
165         }
166
167         // effects: Returns the relation that contains all the slots present
168         // in this cell query
169         internal BasicCellRelation BasicCellRelation
170         {
171             get
172             {
173                 Debug.Assert(m_basicCellRelation != null, "BasicCellRelation must be created first");
174                 return m_basicCellRelation;
175             }
176         }
177
178         /// <summary>
179         /// [WARNING}
180         /// After cell merging boolean expression can (most likely) have disjunctions (OR node)
181         /// to represent the condition that a tuple came from either of the merged cells.
182         /// In this case original where clause IS MERGED CLAUSE with OR!!!
183         /// So don't call this after merging. It'll throw or debug assert from within GetConjunctsFromWC()
184         /// </summary>
185         internal IEnumerable<MemberRestriction> Conditions
186         {
187             get { return GetConjunctsFromOriginalWhereClause(); }
188         }
189
190         #endregion
191
192         #region ProjectedSlots related methods
193         // effects: Returns the slotnum projected slot
194         internal ProjectedSlot ProjectedSlotAt(int slotNum)
195         {
196             Debug.Assert(slotNum < m_projectedSlots.Length, "Slot number too high");
197             return m_projectedSlots[slotNum];
198         }
199
200         // requires: All slots in this are join tree slots
201         // This method is called for an S-side query
202         // cQuery is the corresponding C-side query in the cell
203         // sourceCell is the original cell for "this" and cQuery
204         // effects: Checks if any of the columns in "this" are mapped to multiple properties in cQuery. If so,
205         // returns an error record about the duplicated slots
206         internal ErrorLog.Record CheckForDuplicateFields(CellQuery cQuery, Cell sourceCell)
207         {
208             // slotMap stores the slots on the S-side and the
209             // C-side properties that it maps to
210             KeyToListMap<MemberProjectedSlot, int> slotMap = new KeyToListMap<MemberProjectedSlot, int>(ProjectedSlot.EqualityComparer);
211
212             // Note that this does work for self-association. In the manager
213             // employee example, ManagerId and EmployeeId from the SEmployee
214             // table map to the two ends -- Manager.ManagerId and
215             // Employee.EmployeeId in the C Space
216
217             for (int i = 0; i < m_projectedSlots.Length; i++)
218             {
219                 ProjectedSlot projectedSlot = m_projectedSlots[i];
220                 MemberProjectedSlot slot = projectedSlot as MemberProjectedSlot;
221                 Debug.Assert(slot != null, "All slots for this method must be JoinTreeSlots");
222                 slotMap.Add(slot, i);
223             }
224
225             StringBuilder builder = null;
226
227             // Now determine the entries that have more than one integer per slot
228             bool isErrorSituation = false;
229
230             foreach (MemberProjectedSlot slot in slotMap.Keys)
231             {
232                 ReadOnlyCollection<int> indexes = slotMap.ListForKey(slot);
233                 Debug.Assert(indexes.Count >= 1, "Each slot must have one index at least");
234
235                 if (indexes.Count > 1 &&
236                     cQuery.AreSlotsEquivalentViaRefConstraints(indexes) == false)
237                 {
238                     // The column is mapped to more than one property and it
239                     // failed the "association corresponds to referential
240                     // constraints" check
241
242                     isErrorSituation = true;
243                     if (builder == null)
244                     {
245                         builder = new StringBuilder(System.Data.Entity.Strings.ViewGen_Duplicate_CProperties(Extent.Name));
246                         builder.AppendLine();
247                     }
248                     StringBuilder tmpBuilder = new StringBuilder();
249                     for (int i = 0; i < indexes.Count; i++)
250                     {
251                         int index = indexes[i];
252                         if (i != 0)
253                         {
254                             tmpBuilder.Append(", ");
255                         }
256                         // The slot must be a JoinTreeSlot. If it isn't it is an internal error
257                         MemberProjectedSlot cSlot = (MemberProjectedSlot)cQuery.m_projectedSlots[index];
258                         tmpBuilder.Append(cSlot.ToUserString());
259                     }
260                     builder.AppendLine(Strings.ViewGen_Duplicate_CProperties_IsMapped(slot.ToUserString(), tmpBuilder.ToString()));
261                 }
262             }
263
264             if (false == isErrorSituation)
265             {
266                 return null;
267             }
268
269             ErrorLog.Record record = new ErrorLog.Record(true, ViewGenErrorCode.DuplicateCPropertiesMapped, builder.ToString(), sourceCell, String.Empty);
270             return record;
271         }
272
273         // requires: "this" is a query on the C-side
274         // and cSideSlotIndexes corresponds to the indexes
275         // (into "this") that the slot is being mapped into
276         // cSideSlotIndexes.Count > 1 - that is, a particular column in "this"'s corresponding S-Query 
277         // has been mapped to more than one property in "this"
278         //
279         // effects: Checks that the multiple mappings on the C-side are
280         // backed by an appropriate Referential constraint
281         // If a column is mapped to two properties <A, B> in a single cell:
282         // (a) Must be an association
283         // (b) The two properties must be on opposite ends of the association
284         // (c) The association must have a RI constraint
285         // (d) Ordinal[A] == Ordinal[B] in the RI constraint
286         // (c) and (d) can be stated as - the slots are equivalent, i.e.,
287         // kept equal via an RI constraint
288         private bool AreSlotsEquivalentViaRefConstraints(ReadOnlyCollection<int> cSideSlotIndexes)
289         {
290
291             // Check (a): Must be an association
292             AssociationSet assocSet = Extent as AssociationSet;
293             if (assocSet == null)
294             {
295                 return false;
296             }
297
298             // Check (b): The two properties must be on opposite ends of the association
299             // There better be exactly two properties!
300             Debug.Assert(cSideSlotIndexes.Count > 1, "Method called when no duplicate mapping");
301             if (cSideSlotIndexes.Count > 2)
302             {
303                 return false;
304             }
305
306             // They better be join tree slots (if they are mapped!) and map to opposite ends 
307             MemberProjectedSlot slot0 = (MemberProjectedSlot)m_projectedSlots[cSideSlotIndexes[0]];
308             MemberProjectedSlot slot1 = (MemberProjectedSlot)m_projectedSlots[cSideSlotIndexes[1]];
309
310             return slot0.MemberPath.IsEquivalentViaRefConstraint(slot1.MemberPath);
311         }
312
313         // requires: The Where clause satisfies the same requirements a GetConjunctsFromWhereClause
314         // effects: For each slot that has a NotNull condition in the where
315         // clause, checks if it is projected. If all such slots are
316         // projected, returns null. Else returns an error record
317         internal ErrorLog.Record CheckForProjectedNotNullSlots(Cell sourceCell, IEnumerable<Cell> associationSets)
318         {
319             StringBuilder builder = new StringBuilder();
320             bool foundError = false;
321
322             foreach (MemberRestriction restriction in Conditions)
323             {
324                 if (restriction.Domain.ContainsNotNull())
325                 {
326                     MemberProjectedSlot slot = MemberProjectedSlot.GetSlotForMember(m_projectedSlots, restriction.RestrictedMemberSlot.MemberPath);
327                     if (slot == null) //member with not null condition is not mapped in this extent
328                     {
329                         bool missingMapping = true;
330                         if(Extent is EntitySet)
331                         {
332                             bool isCQuery = sourceCell.CQuery == this;
333                             ViewTarget target = isCQuery ? ViewTarget.QueryView : ViewTarget.UpdateView;
334                             CellQuery rightCellQuery = isCQuery? sourceCell.SQuery : sourceCell.CQuery;
335                             
336                             //Find out if there is an association mapping but only if the current Not Null condition is on an EntitySet
337                             EntitySet rightExtent = rightCellQuery.Extent as EntitySet;
338                             if (rightExtent != null)
339                             {
340                                 List<AssociationSet> associations = MetadataHelper.GetAssociationsForEntitySet(rightCellQuery.Extent as EntitySet);
341                                 foreach (var association in associations.Where(association => association.AssociationSetEnds.Any(end => ( end.CorrespondingAssociationEndMember.RelationshipMultiplicity == RelationshipMultiplicity.One && 
342                                     (MetadataHelper.GetOppositeEnd(end).EntitySet.EdmEquals(rightExtent))))))
343                                 {
344                                     foreach (var associationCell in associationSets.Where(c => c.GetRightQuery(target).Extent.EdmEquals(association)))
345                                     {
346                                         if (MemberProjectedSlot.GetSlotForMember(associationCell.GetLeftQuery(target).ProjectedSlots, restriction.RestrictedMemberSlot.MemberPath) != null)
347                                         {
348                                             missingMapping = false;
349                                         }
350                                     }
351                                 }
352                             }
353                         }
354
355                         if (missingMapping)
356                         {
357                             // condition of NotNull and slot not being projected
358                             builder.AppendLine(System.Data.Entity.Strings.ViewGen_NotNull_No_Projected_Slot(
359                                                restriction.RestrictedMemberSlot.MemberPath.PathToString(false)));
360                             foundError = true;
361                         }
362                     }
363                 }
364             }
365             if (false == foundError)
366             {
367                 return null;
368             }
369             ErrorLog.Record record = new ErrorLog.Record(true, ViewGenErrorCode.NotNullNoProjectedSlot, builder.ToString(), sourceCell, String.Empty);
370             return record;
371         }
372
373         internal void FixMissingSlotAsDefaultConstant(int slotNumber, ConstantProjectedSlot slot)
374         {
375             Debug.Assert(m_projectedSlots[slotNumber] == null, "Another attempt to plug in a default value");
376             m_projectedSlots[slotNumber] = slot;
377         }
378
379         // requires: projectedSlotMap which contains a mapping of the fields
380         // for "this" to integers 
381         // effects: Align the fields of this cell query using the
382         // projectedSlotMap and generates a new query into newMainQuery
383         // Based on the re-aligned fields in this, re-aligns the
384         // corresponding fields in otherQuery as well and modifies
385         // newOtherQuery to contain it
386         // Example:
387         //    input:  Proj[A,B,"5"] = Proj[F,"7",G]
388         //            Proj[C,B]     = Proj[H,I]
389         //            projectedSlotMap: A -> 0, B -> 1, C -> 2
390         //   output:  Proj[A,B,null] = Proj[F,"7",null]
391         //            Proj[null,B,C] = Proj[null,I,H]
392         internal void CreateFieldAlignedCellQueries(CellQuery otherQuery, MemberProjectionIndex projectedSlotMap,
393                                                     out CellQuery newMainQuery, out CellQuery newOtherQuery)
394         {
395
396             // mainSlots and otherSlots hold the new slots for two queries
397             int numAlignedSlots = projectedSlotMap.Count;
398             ProjectedSlot[] mainSlots = new ProjectedSlot[numAlignedSlots];
399             ProjectedSlot[] otherSlots = new ProjectedSlot[numAlignedSlots];
400
401             // Go through the slots for this query and find the new slot for them
402             for (int i = 0; i < m_projectedSlots.Length; i++)
403             {
404
405                 MemberProjectedSlot slot = m_projectedSlots[i] as MemberProjectedSlot;
406                 Debug.Assert(slot != null, "All slots during cell normalization must field slots");
407                 // Get the the ith slot's variable and then get the
408                 // new slot number from the field map
409                 int newSlotNum = projectedSlotMap.IndexOf(slot.MemberPath);
410                 Debug.Assert(newSlotNum >= 0, "Field projected but not in projectedSlotMap");
411                 mainSlots[newSlotNum] = m_projectedSlots[i];
412                 otherSlots[newSlotNum] = otherQuery.m_projectedSlots[i];
413
414                 // We ignore constants -- note that this is not the
415                 // isHighpriority or discriminator case.  An example of this
416                 // is when (say) Address does not have zip but USAddress
417                 // does.  Then the constraint looks like Pi_NULL, A, B(E) =
418                 // Pi_x, y, z(S)
419
420                 // We don't care about this null in the view generation of
421                 // the left side. Note that this could happen in inheritance
422                 // or in cases when say the S side has 20 fields but the C
423                 // side has only 3 - the other 17 are null or default.
424
425                 // NOTE: We allow such constants only on the C side and not
426                 // ont the S side. Otherwise, we can have a situation Pi_A,
427                 // B, C(E) = Pi_5, y, z(S) Then someone can set A to 7 and we
428                 // will not roundtrip. We check for this in validation
429             }
430
431             // Make the new cell queries with the new slots
432             newMainQuery = new CellQuery(this, mainSlots);
433             newOtherQuery = new CellQuery(otherQuery, otherSlots);
434         }
435
436         // requires: All slots in this are null or non-constants
437         // effects: Returns the non-null slots of this
438         internal AttributeSet GetNonNullSlots()
439         {
440             AttributeSet attributes = new AttributeSet(MemberPath.EqualityComparer);
441             foreach (ProjectedSlot projectedSlot in m_projectedSlots)
442             {
443                 // null means 'unused' slot -- we ignore those
444                 if (projectedSlot != null)
445                 {
446                     MemberProjectedSlot projectedVar = projectedSlot as MemberProjectedSlot;
447                     Debug.Assert(projectedVar != null, "Projected slot must not be a constant");
448                     attributes.Add(projectedVar.MemberPath);
449                 }
450             }
451             return attributes;
452         }
453
454         // effects: Returns an error record if the keys of the extent/associationSet being mapped  are
455         // present in the projected slots of this query. Returns null
456         // otherwise. ownerCell indicates the cell that owns this and
457         // resourceString is a resource used for error messages
458         internal ErrorLog.Record VerifyKeysPresent(Cell ownerCell, Func<object, object, string> formatEntitySetMessage,
459             Func<object, object, object, string> formatAssociationSetMessage, ViewGenErrorCode errorCode)
460         {
461             List<MemberPath> prefixes = new List<MemberPath>(1);
462             // Keep track of the key corresponding to each prefix
463             List<ExtentKey> keys = new List<ExtentKey>(1);
464
465             if (Extent is EntitySet)
466             {
467                 // For entity set just get the full path of the key properties
468                 MemberPath prefix = new MemberPath(Extent);
469                 prefixes.Add(prefix);
470                 EntityType entityType = (EntityType)Extent.ElementType;
471                 List<ExtentKey> entitySetKeys = ExtentKey.GetKeysForEntityType(prefix, entityType);
472                 Debug.Assert(entitySetKeys.Count == 1, "Currently, we only support primary keys");
473                 keys.Add(entitySetKeys[0]);
474
475             }
476             else
477             {
478                 AssociationSet relationshipSet = (AssociationSet)Extent;
479                 // For association set, get the full path of the key
480                 // properties of each end
481
482                 foreach (AssociationSetEnd relationEnd in relationshipSet.AssociationSetEnds)
483                 {
484                     AssociationEndMember assocEndMember = relationEnd.CorrespondingAssociationEndMember;
485                     MemberPath prefix = new MemberPath(relationshipSet, assocEndMember);
486                     prefixes.Add(prefix);
487                     List<ExtentKey> endKeys = ExtentKey.GetKeysForEntityType(prefix,
488                                                                              MetadataHelper.GetEntityTypeForEnd(assocEndMember));
489                     Debug.Assert(endKeys.Count == 1, "Currently, we only support primary keys");
490                     keys.Add(endKeys[0]);
491                 }
492             }
493
494             for (int i = 0; i < prefixes.Count; i++)
495             {
496                 MemberPath prefix = prefixes[i];
497                 // Get all or none key slots that are being projected in this cell query
498                 List<MemberProjectedSlot> keySlots = MemberProjectedSlot.GetKeySlots(GetMemberProjectedSlots(), prefix);
499                 if (keySlots == null)
500                 {
501                     ExtentKey key = keys[i];
502                     string message;
503                     if (Extent is EntitySet)
504                     {
505                         string keyPropertiesString = MemberPath.PropertiesToUserString(key.KeyFields, true);
506                         message = formatEntitySetMessage(keyPropertiesString, Extent.Name);
507                     }
508                     else
509                     {
510                         string endName = prefix.RootEdmMember.Name;
511                         string keyPropertiesString = MemberPath.PropertiesToUserString(key.KeyFields, false);
512                         message = formatAssociationSetMessage(keyPropertiesString, endName, Extent.Name);
513                     }
514                     ErrorLog.Record error = new ErrorLog.Record(true, errorCode, message, ownerCell, String.Empty);
515                     return error;
516                 }
517             }
518             return null;
519         }
520
521         internal IEnumerable<MemberPath> GetProjectedMembers()
522         {
523             foreach (MemberProjectedSlot slot in this.GetMemberProjectedSlots())
524             {
525                 yield return slot.MemberPath;
526             }
527         }
528
529         // effects: Returns the fields in this, i.e., not constants or null slots
530         private IEnumerable<MemberProjectedSlot> GetMemberProjectedSlots()
531         {
532             foreach (ProjectedSlot slot in m_projectedSlots)
533             {
534                 MemberProjectedSlot memberSlot = slot as MemberProjectedSlot;
535                 if (memberSlot != null)
536                 {
537                     yield return memberSlot;
538                 }
539             }
540         }
541
542         // effects: Returns the fields that are used in the query (both projected and non-projected)
543         // Output list is a copy, i.e., can be modified by the caller
544         internal List<MemberProjectedSlot> GetAllQuerySlots()
545         {
546             HashSet<MemberProjectedSlot> slots = new HashSet<MemberProjectedSlot>(GetMemberProjectedSlots());
547             slots.Add(new MemberProjectedSlot(SourceExtentMemberPath));
548             foreach (var restriction in Conditions)
549             {
550                 slots.Add(restriction.RestrictedMemberSlot);
551             }
552             return new List<MemberProjectedSlot>(slots);
553         }
554
555         // effects: returns the index at which this slot appears in the projection
556         // or -1 if it is not projected
557         internal int GetProjectedPosition(MemberProjectedSlot slot)
558         {
559             for (int i = 0; i < m_projectedSlots.Length; i++)
560             {
561                 if (MemberProjectedSlot.EqualityComparer.Equals(slot, m_projectedSlots[i]))
562                 {
563                     return i;
564                 }
565             }
566             return -1;
567         }
568
569         // effects: returns the List of indexes at which this member appears in the projection
570         // or empty list if it is not projected
571         internal List<int> GetProjectedPositions(MemberPath member)
572         {
573             List<int> pathIndexes = new List<int>();
574             for (int i = 0; i < m_projectedSlots.Length; i++)
575             {
576                 MemberProjectedSlot slot = m_projectedSlots[i] as MemberProjectedSlot;
577                 if (slot != null && MemberPath.EqualityComparer.Equals(member, slot.MemberPath))
578                 {
579                     pathIndexes.Add(i);
580                 }
581             }
582             return pathIndexes;
583         }
584
585         // effects: Determines the slot numbers for members in cellQuery
586         // Returns a set of those paths in the same order as paths. If even
587         // one of the path entries is not projected in the cellquery, returns null
588         internal List<int> GetProjectedPositions(IEnumerable<MemberPath> paths)
589         {
590             List<int> pathIndexes = new List<int>();
591             foreach (MemberPath member in paths)
592             {
593                 // Get the index in checkQuery and add to pathIndexes
594                 List<int> slotIndexes = GetProjectedPositions(member);
595                 Debug.Assert(slotIndexes != null);
596                 if (slotIndexes.Count == 0)
597                 { // member is not projected
598                     return null;
599                 }
600                 Debug.Assert(slotIndexes.Count == 1, "Expecting the path to be projected only once");
601                 pathIndexes.Add(slotIndexes[0]);
602             }
603             return pathIndexes;
604         }
605
606         // effects : Return the slot numbers for members in Cell Query that 
607         //           represent the association end member passed in.
608         internal List<int> GetAssociationEndSlots(AssociationEndMember endMember)
609         {
610             List<int> slotIndexes = new List<int>();
611             Debug.Assert(this.Extent is AssociationSet);
612             for (int i = 0; i < m_projectedSlots.Length; i++)
613             {
614                 MemberProjectedSlot slot = m_projectedSlots[i] as MemberProjectedSlot;
615                 if (slot != null && slot.MemberPath.RootEdmMember.Equals(endMember))
616                 {
617                     slotIndexes.Add(i);
618                 }
619             }
620             return slotIndexes;
621         }
622
623         // effects: Determines the slot numbers for members in cellQuery
624         // Returns a set of those paths in the same order as paths. If even
625         // one of the path entries is not projected in the cellquery, returns null
626         // If a path is projected more than once, than we choose the one from the
627         // slotsToSearchFrom domain. 
628         internal List<int> GetProjectedPositions(IEnumerable<MemberPath> paths, List<int> slotsToSearchFrom)
629         {
630             List<int> pathIndexes = new List<int>();
631             foreach (MemberPath member in paths)
632             {
633                 // Get the index in checkQuery and add to pathIndexes
634                 List<int> slotIndexes = GetProjectedPositions(member);
635                 Debug.Assert(slotIndexes != null);
636                 if (slotIndexes.Count == 0)
637                 { // member is not projected
638                     return null;
639                 }
640                 int slotIndex = -1;
641                 if (slotIndexes.Count > 1)
642                 {
643                     for (int i = 0; i < slotIndexes.Count; i++)
644                     {
645                         if (slotsToSearchFrom.Contains(slotIndexes[i]))
646                         {
647                             Debug.Assert(slotIndex == -1, "Should be projected only once");
648                             slotIndex = slotIndexes[i];
649                         }
650                     }
651                     if (slotIndex == -1)
652                     {
653                         return null;
654                     }
655                 }
656                 else
657                 {
658                     slotIndex = slotIndexes[0];
659                 }
660                 pathIndexes.Add(slotIndex);
661             }
662             return pathIndexes;
663         }
664
665
666         // requires: The CellConstantDomains in the OneOfConsts of the where
667         // clause are partially done
668         // effects: Given the domains of different variables in domainMap,
669         // fixes the whereClause of this such that all the
670         // CellConstantDomains in OneOfConsts are complete
671         internal void UpdateWhereClause(MemberDomainMap domainMap)
672         {
673             List<BoolExpression> atoms = new List<BoolExpression>();
674             foreach (BoolExpression atom in WhereClause.Atoms)
675             {
676                 BoolLiteral literal = atom.AsLiteral;
677                 MemberRestriction restriction = literal as MemberRestriction;
678                 Debug.Assert(restriction != null, "All bool literals must be OneOfConst at this point");
679                 // The oneOfConst needs to be fixed with the new possible values from the domainMap.
680                 IEnumerable<Constant> possibleValues = domainMap.GetDomain(restriction.RestrictedMemberSlot.MemberPath);
681                 MemberRestriction newOneOf = restriction.CreateCompleteMemberRestriction(possibleValues);
682
683                 // Prevent optimization of single constraint e.g: "300 in (300)"
684                 // But we want to optimize type constants e.g: "category in (Category)"
685                 // To prevent optimization of bool expressions we add a Sentinel OneOF
686
687                 ScalarRestriction scalarConst = restriction as ScalarRestriction;
688                 bool addSentinel =
689                     scalarConst != null &&
690                     !scalarConst.Domain.Contains(Constant.Null) &&
691                     !scalarConst.Domain.Contains(Constant.NotNull) &&
692                     !scalarConst.Domain.Contains(Constant.Undefined);
693
694                 if (addSentinel)
695                 {
696                     domainMap.AddSentinel(newOneOf.RestrictedMemberSlot.MemberPath);
697                 }
698
699                 atoms.Add(BoolExpression.CreateLiteral(newOneOf, domainMap));
700
701                 if (addSentinel)
702                 {
703                     domainMap.RemoveSentinel(newOneOf.RestrictedMemberSlot.MemberPath);
704                 }
705
706             }
707             // We create a new whereClause that has the memberDomainMap set
708             if (atoms.Count > 0)
709             {
710                 m_whereClause = BoolExpression.CreateAnd(atoms.ToArray());
711             }
712         }
713         #endregion
714
715         #region BooleanExprs related Methods
716         // effects: Returns a boolean expression corresponding to the
717         // "varNum" boolean in this.
718         internal BoolExpression GetBoolVar(int varNum)
719         {
720             return m_boolExprs[varNum];
721         }
722
723         // effects: Initalizes the booleans of this cell query to be
724         // true. Creates numBoolVars booleans and sets the cellNum boolean to true
725         internal void InitializeBoolExpressions(int numBoolVars, int cellNum)
726         {
727             //Debug.Assert(m_boolExprs.Count == 0, "Overwriting existing booleans");
728             m_boolExprs = new List<BoolExpression>(numBoolVars);
729             for (int i = 0; i < numBoolVars; i++)
730             {
731                 m_boolExprs.Add(null);
732             }
733             Debug.Assert(cellNum < numBoolVars, "Trying to set boolean with too high an index");
734             m_boolExprs[cellNum] = BoolExpression.True;
735         }
736
737         #endregion
738         #region WhereClause related methods
739         // requires: The current whereClause corresponds to "True", "OneOfConst" or "
740         // "OneOfConst AND ... AND OneOfConst"
741         // effects: Yields all the conjuncts (OneOfConsts) in this (i.e., if the whereClause is
742         // just True, yields nothing
743         internal IEnumerable<MemberRestriction> GetConjunctsFromWhereClause()
744         {
745             return GetConjunctsFromWhereClause(m_whereClause);
746         }
747
748         internal IEnumerable<MemberRestriction> GetConjunctsFromOriginalWhereClause()
749         {
750             return GetConjunctsFromWhereClause(m_originalWhereClause);
751         }
752
753
754         private IEnumerable<MemberRestriction> GetConjunctsFromWhereClause(BoolExpression whereClause)
755         {
756             foreach (BoolExpression boolExpr in whereClause.Atoms)
757             {
758                 if (boolExpr.IsTrue)
759                 {
760                     continue;
761                 }
762                 MemberRestriction result = boolExpr.AsLiteral as MemberRestriction;
763                 Debug.Assert(result != null, "Atom must be restriction");
764                 yield return result;
765             }
766         }
767
768         // requires: whereClause is of the form specified in GetConjunctsFromWhereClause
769         // effects: Converts the whereclause to a user-readable string
770         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
771         internal void WhereClauseToUserString(StringBuilder builder, MetadataWorkspace workspace)
772         {
773             bool isFirst = true;
774             foreach (MemberRestriction restriction in GetConjunctsFromWhereClause())
775             {
776                 if (isFirst == false)
777                 {
778                     builder.Append(System.Data.Entity.Strings.ViewGen_AND);
779                 }
780                 restriction.ToUserString(false, builder, workspace);
781             }
782         }
783         #endregion
784
785         #region Full CellQuery methods
786         // effects: Determines all the identifiers used in this and adds them to identifiers
787         internal void GetIdentifiers(CqlIdentifiers identifiers)
788         {
789             foreach (ProjectedSlot projectedSlot in m_projectedSlots)
790             {
791                 MemberProjectedSlot slot = projectedSlot as MemberProjectedSlot;
792                 if (slot != null)
793                 {
794                     slot.MemberPath.GetIdentifiers(identifiers);
795                 }
796             }
797             m_extentMemberPath.GetIdentifiers(identifiers);
798         }
799
800         internal void CreateBasicCellRelation(ViewCellRelation viewCellRelation)
801         {
802             List<MemberProjectedSlot> slots = GetAllQuerySlots();
803             // Create a base cell relation that has all the scalar slots of this
804             m_basicCellRelation = new BasicCellRelation(this, viewCellRelation, slots);
805         }
806
807         
808
809         #endregion
810
811         #region String Methods
812         // effects: Modifies stringBuilder to contain a string representation
813         // of the cell query in terms of the original cells that are being used
814         internal override void ToCompactString(StringBuilder stringBuilder)
815         {
816             // This could be a simplified view where a number of cells
817             // got merged or it could be one of the original booleans. So
818             // determine their numbers using the booleans in m_cellWrapper
819             List<BoolExpression> boolExprs = m_boolExprs;
820             int i = 0;
821             bool first = true;
822             foreach (BoolExpression boolExpr in boolExprs)
823             {
824                 if (boolExpr != null)
825                 {
826                     if (false == first)
827                     {
828                         stringBuilder.Append(",");
829                     }
830                     else
831                     {
832                         stringBuilder.Append("[");
833                     }
834                     StringUtil.FormatStringBuilder(stringBuilder, "C{0}", i);
835                     first = false;
836                 }
837                 i++;
838             }
839             if (true == first)
840             {
841                 // No booleans, i.e., no compact representation. Use full string to avoid empty output
842                 ToFullString(stringBuilder);
843             }
844             else
845             {
846                 stringBuilder.Append("]");
847             }
848         }
849
850         internal override void ToFullString(StringBuilder builder)
851         {
852             builder.Append("SELECT ");
853
854             if (m_selectDistinct == SelectDistinct.Yes)
855             {
856                 builder.Append("DISTINCT ");
857             }
858
859             StringUtil.ToSeparatedString(builder, m_projectedSlots, ", ", "_");
860
861             if (m_boolExprs.Count > 0)
862             {
863                 builder.Append(", Bool[");
864                 StringUtil.ToSeparatedString(builder, m_boolExprs, ", ", "_");
865                 builder.Append("]");
866             }
867
868             builder.Append(" FROM ");
869             m_extentMemberPath.ToFullString(builder);
870
871             if (false == m_whereClause.IsTrue)
872             {
873                 builder.Append(" WHERE ");
874                 m_whereClause.ToFullString(builder);
875             }
876         }
877
878         public override string ToString()
879         {
880             return ToFullString();
881         }
882
883         // eSQL representation of cell query
884         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
885         internal string ToESqlString()
886         {
887             StringBuilder builder = new StringBuilder();
888             builder.Append("\n\tSELECT ");
889
890             if (m_selectDistinct == SelectDistinct.Yes)
891             {
892                 builder.Append("DISTINCT ");
893             }
894
895             foreach (ProjectedSlot ps in m_projectedSlots)
896             {
897                 MemberProjectedSlot jtn = ps as MemberProjectedSlot;
898                 StructuralType st = jtn.MemberPath.LeafEdmMember.DeclaringType;
899                 StringBuilder sb = new StringBuilder();
900                 jtn.MemberPath.AsEsql(sb, "e");
901                 builder.AppendFormat("{0}, ", sb.ToString());
902             }
903             //remove the extra-comma after the last slot         
904             builder.Remove(builder.Length - 2, 2);
905
906             builder.Append("\n\tFROM ");
907             EntitySetBase extent = m_extentMemberPath.Extent;
908             CqlWriter.AppendEscapedQualifiedName(builder, extent.EntityContainer.Name, extent.Name);
909             builder.Append(" AS e");
910
911             if (m_whereClause.IsTrue == false)
912             {
913                 builder.Append("\n\tWHERE ");
914
915                 StringBuilder qbuilder = new StringBuilder();
916                 m_whereClause.AsEsql(qbuilder, "e");
917
918                 builder.Append(qbuilder.ToString());
919             }
920             builder.Append("\n    ");
921
922             return builder.ToString();
923         }
924
925         #endregion
926
927     }
928
929 }