497a193d435ed105a5a3f6ac0a87ad5b13355618
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / Mapping / ViewGeneration / Validator.cs
1 //---------------------------------------------------------------------
2 // <copyright file="Validator.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.Collections.ObjectModel;
12 using System.Data.Common.Utils;
13 using System.Data.Mapping.ViewGeneration.Structures;
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
20 namespace System.Data.Mapping.ViewGeneration
21 {
22
23     using System.Data.Entity;
24     using BasicSchemaConstraints = SchemaConstraints<BasicKeyConstraint>;
25     using ViewSchemaConstraints = SchemaConstraints<ViewKeyConstraint>;
26
27     // This class is responsible for validating the incoming cells for a schema
28     class CellGroupValidator
29     {
30
31         #region Constructor
32         // requires: cells are not normalized, i.e., no slot is null in the cell queries
33         // effects: Constructs a validator object that is capable of
34         // validating all the schema cells together
35         internal CellGroupValidator(IEnumerable<Cell> cells, ConfigViewGenerator config)
36         {
37             m_cells = cells;
38             m_config = config;
39             m_errorLog = new ErrorLog();
40         }
41         #endregion
42
43         #region Fields
44         private IEnumerable<Cell> m_cells;
45         private ConfigViewGenerator m_config;
46         private ErrorLog m_errorLog; // Keeps track of errors for this set of cells
47         private ViewSchemaConstraints m_cViewConstraints;
48         private ViewSchemaConstraints m_sViewConstraints;
49         #endregion
50
51         #region External Methods
52         // effects: Performs the validation of the cells in this and returns
53         // an error log of all the errors/warnings that were discovered
54         internal ErrorLog Validate()
55         {
56
57             // Check for errors not checked by "C-implies-S principle"
58             if (m_config.IsValidationEnabled)
59             {
60                 if (PerformSingleCellChecks() == false)
61                 {
62                     return m_errorLog;
63                 }
64             }
65             else //Note that Metadata loading guarantees that DISTINCT flag is not present
66             {    // when update views (and validation) is disabled
67
68                 if (CheckCellsWithDistinctFlag() == false)
69                 {
70                     return m_errorLog;
71                 }
72             }
73
74             BasicSchemaConstraints cConstraints = new BasicSchemaConstraints();
75             BasicSchemaConstraints sConstraints = new BasicSchemaConstraints();
76
77             // Construct intermediate "view relations" and the basic cell
78             // relations along with the basic constraints
79             ConstructCellRelationsWithConstraints(cConstraints, sConstraints);
80
81             if (m_config.IsVerboseTracing)
82             {
83                 // Trace Basic constraints
84                 Trace.WriteLine(String.Empty);
85                 Trace.WriteLine("C-Level Basic Constraints");
86                 Trace.WriteLine(cConstraints);
87                 Trace.WriteLine("S-Level Basic Constraints");
88                 Trace.WriteLine(sConstraints);
89             }
90
91             // Propagate the constraints
92             m_cViewConstraints = PropagateConstraints(cConstraints);
93             m_sViewConstraints = PropagateConstraints(sConstraints);
94
95             // Make some basic checks on the view and basic cell constraints
96             CheckConstraintSanity(cConstraints, sConstraints, m_cViewConstraints, m_sViewConstraints);
97
98             if (m_config.IsVerboseTracing)
99             {
100                 // Trace View constraints
101                 Trace.WriteLine(String.Empty);
102                 Trace.WriteLine("C-Level View Constraints");
103                 Trace.WriteLine(m_cViewConstraints);
104                 Trace.WriteLine("S-Level View Constraints");
105                 Trace.WriteLine(m_sViewConstraints);
106             }
107
108             // Check for implication
109             if (m_config.IsValidationEnabled)
110             {
111                 CheckImplication(m_cViewConstraints, m_sViewConstraints);
112             }
113             return m_errorLog;
114         }
115         #endregion
116
117         #region Basic Constraint Creation
118
119         // effects: Creates the base cell relation and view cell relations
120         // for each cellquery/cell. Also generates the C-Side and S-side
121         // basic constraints and stores them into cConstraints and
122         // sConstraints. Stores them in cConstraints and sConstraints
123         private void ConstructCellRelationsWithConstraints(BasicSchemaConstraints cConstraints,
124                                                            BasicSchemaConstraints sConstraints)
125         {
126
127             // Populate single cell constraints
128             int cellNumber = 0;
129             foreach (Cell cell in m_cells)
130             {
131                 // We have to create the ViewCellRelation so that the
132                 // BasicCellRelations can be created.
133                 cell.CreateViewCellRelation(cellNumber);
134                 BasicCellRelation cCellRelation = cell.CQuery.BasicCellRelation;
135                 BasicCellRelation sCellRelation = cell.SQuery.BasicCellRelation;
136                 // Populate the constraints for the C relation and the S Relation
137                 PopulateBaseConstraints(cCellRelation, cConstraints);
138                 PopulateBaseConstraints(sCellRelation, sConstraints);
139                 cellNumber++;
140             }
141
142             // Populate two-cell constraints, i.e., inclusion
143             foreach (Cell firstCell in m_cells)
144             {
145                 foreach (Cell secondCell in m_cells)
146                 {
147                     if (Object.ReferenceEquals(firstCell, secondCell))
148                     {
149                         // We do not want to set up self-inclusion constraints unnecessarily
150                         continue;
151                     }
152                 }
153             }
154         }
155
156         // effects: Generates the single-cell key+domain constraints for
157         // baseRelation and adds them to constraints
158         private static void PopulateBaseConstraints(BasicCellRelation baseRelation,
159                                                     BasicSchemaConstraints constraints)
160         {
161             // Populate key constraints
162             baseRelation.PopulateKeyConstraints(constraints);
163         }
164         #endregion
165
166         #region Constraint Propagation
167         // effects: Propagates baseConstraints derived from the cellrelations
168         // to the corresponding viewCellRelations and returns the list of
169         // propagated constraints
170         private static ViewSchemaConstraints PropagateConstraints(BasicSchemaConstraints baseConstraints)
171         {
172             ViewSchemaConstraints propagatedConstraints = new ViewSchemaConstraints();
173
174             // Key constraint propagation
175             foreach (BasicKeyConstraint keyConstraint in baseConstraints.KeyConstraints)
176             {
177                 ViewKeyConstraint viewConstraint = keyConstraint.Propagate();
178                 if (viewConstraint != null)
179                 {
180                     propagatedConstraints.Add(viewConstraint);
181                 }
182             }
183             return propagatedConstraints;
184         }
185         #endregion
186
187         #region Checking for Implication
188         // effects: Checks if all sViewConstraints are implied by the
189         // constraints in cViewConstraints. If some S-level constraints are
190         // not implied, adds errors/warnings to m_errorLog
191         private void CheckImplication(ViewSchemaConstraints cViewConstraints, ViewSchemaConstraints sViewConstraints)
192         {
193
194             // Check key constraints
195             // i.e., if S has a key <k1, k2>, C must have a key that is a subset of this
196             CheckImplicationKeyConstraints(cViewConstraints, sViewConstraints);
197             
198             // For updates, we need to ensure the following: for every
199             // extent E, table T pair, some key of E is implied by T's key
200
201             // Get all key constraints for each extent and each table
202             KeyToListMap<ExtentPair, ViewKeyConstraint> extentPairConstraints =
203                 new KeyToListMap<ExtentPair, ViewKeyConstraint>(EqualityComparer<ExtentPair>.Default);
204
205             foreach (ViewKeyConstraint cKeyConstraint in cViewConstraints.KeyConstraints)
206             {
207                 ExtentPair pair = new ExtentPair(cKeyConstraint.Cell.CQuery.Extent, cKeyConstraint.Cell.SQuery.Extent);
208                 extentPairConstraints.Add(pair, cKeyConstraint);
209             }
210
211             // Now check that we guarantee at least one constraint per
212             // extent/table pair
213             foreach (ExtentPair extentPair in extentPairConstraints.Keys)
214             {
215                 ReadOnlyCollection<ViewKeyConstraint> cKeyConstraints = extentPairConstraints.ListForKey(extentPair);
216                 bool sImpliesSomeC = false;
217                 // Go through all key constraints for the extent/table pair, and find one that S implies
218                 foreach (ViewKeyConstraint cKeyConstraint in cKeyConstraints)
219                 {
220                     foreach (ViewKeyConstraint sKeyConstraint in sViewConstraints.KeyConstraints)
221                     {
222                         if (sKeyConstraint.Implies(cKeyConstraint))
223                         {
224                             sImpliesSomeC = true;
225                             break; // The implication holds - so no problem
226                         }
227                     }
228                 }
229                 if (sImpliesSomeC == false)
230                 {
231                     // Indicate that at least one key must be ensured on the S-side
232                     m_errorLog.AddEntry(ViewKeyConstraint.GetErrorRecord(cKeyConstraints));
233                 }
234             }
235         }
236
237         // effects: Checks for key constraint implication problems from
238         // leftViewConstraints to rightViewConstraints. Adds errors/warning to m_errorLog 
239         private void CheckImplicationKeyConstraints(ViewSchemaConstraints leftViewConstraints,
240                                                     ViewSchemaConstraints rightViewConstraints)
241         {
242
243             // if cImpliesS is true, every rightKeyConstraint must be implied
244             // if it is false, at least one key constraint for each C-level
245             // extent must be implied
246
247             foreach (ViewKeyConstraint rightKeyConstraint in rightViewConstraints.KeyConstraints)
248             {
249                 // Go through all the left Side constraints and check for implication
250                 bool found = false;
251                 foreach (ViewKeyConstraint leftKeyConstraint in leftViewConstraints.KeyConstraints)
252                 {
253                     if (leftKeyConstraint.Implies(rightKeyConstraint))
254                     {
255                         found = true;
256                         break; // The implication holds - so no problem
257                     }
258                 }
259                 if (false == found)
260                 {
261                     // No C-side key constraint implies this S-level key constraint
262                     // Report a problem
263                     m_errorLog.AddEntry(ViewKeyConstraint.GetErrorRecord(rightKeyConstraint));
264                 }
265             }
266         }
267
268         #endregion
269
270         #region Miscellaneous checks
271
272
273         /// <summary>
274         /// Checks that if a DISTINCT operator exists between some C-Extent and S-Extent, there are no additional
275         /// mapping fragments between that C-Extent and S-Extent.
276         /// We need to enforce this because DISTINCT is not understood by viewgen machinery, and two fragments may be merged
277         /// despite one of them having DISTINCT.
278         /// </summary>
279         private bool CheckCellsWithDistinctFlag()
280         {
281
282             int errorLogSize = m_errorLog.Count;
283             foreach (Cell cell in m_cells)
284             {
285                 if (cell.SQuery.SelectDistinctFlag == CellQuery.SelectDistinct.Yes)
286                 {
287                     var cExtent = cell.CQuery.Extent;
288                     var sExtent = cell.SQuery.Extent;
289
290                     //There should be no other fragments mapping cExtent to sExtent
291                     var mapepdFragments = m_cells.Where(otherCell => otherCell != cell)
292                                         .Where(otherCell => otherCell.CQuery.Extent == cExtent && otherCell.SQuery.Extent == sExtent);
293
294                     if (mapepdFragments.Any())
295                     {
296                         var cellsToReport = Enumerable.Union(Enumerable.Repeat(cell, 1), mapepdFragments);
297                         ErrorLog.Record record = new ErrorLog.Record(true, ViewGenErrorCode.MultipleFragmentsBetweenCandSExtentWithDistinct,
298                             Strings.Viewgen_MultipleFragmentsBetweenCandSExtentWithDistinct(cExtent.Name, sExtent.Name), cellsToReport, String.Empty);
299                         m_errorLog.AddEntry(record);
300                     }
301                 }
302             }
303
304             return m_errorLog.Count == errorLogSize;
305         }
306         
307         
308         
309         
310         // effects: Check for problems in each cell that are not detected by the
311         // "C-constraints-imply-S-constraints" principle. If the check fails,
312         // adds relevant error info to m_errorLog and returns false. Else
313         // retrns true
314         private bool PerformSingleCellChecks()
315         {
316
317             int errorLogSize = m_errorLog.Count;
318             foreach (Cell cell in m_cells)
319             {
320                 // Check for duplication of element in a single cell name1, name2
321                 // -> name Could be done by implication but that would require
322                 // setting self-inclusion constraints etc That seems unnecessary
323
324                 // We need this check only for the C side. if we map cname1
325                 // and cmane2 to sname, that is a problem. But mapping sname1
326                 // and sname2 to cname is ok
327                 ErrorLog.Record error = cell.SQuery.CheckForDuplicateFields(cell.CQuery, cell);
328                 if (error != null)
329                 {
330                     m_errorLog.AddEntry(error);
331                 }
332
333                 // Check that the EntityKey and the Table key are mapped
334                 // (Key for association is all ends)
335                 error = cell.CQuery.VerifyKeysPresent(cell, Strings.ViewGen_EntitySetKey_Missing,
336                     Strings.ViewGen_AssociationSetKey_Missing, ViewGenErrorCode.KeyNotMappedForCSideExtent);
337
338                 if (error != null)
339                 {
340                     m_errorLog.AddEntry(error);
341                 }
342
343                 error = cell.SQuery.VerifyKeysPresent(cell, Strings.ViewGen_TableKey_Missing, null, ViewGenErrorCode.KeyNotMappedForTable);
344                 if (error != null)
345                 {
346                     m_errorLog.AddEntry(error);
347                 }
348
349                 // Check that if any side has a not-null constraint -- if so,
350                 // we must project that slot
351                 error = cell.CQuery.CheckForProjectedNotNullSlots(cell, m_cells.Where(c=> c.SQuery.Extent is AssociationSet));
352                 if (error != null)
353                 {
354                     m_errorLog.AddEntry(error);
355                 }
356                 error = cell.SQuery.CheckForProjectedNotNullSlots(cell, m_cells.Where(c => c.CQuery.Extent is AssociationSet));
357                 if (error != null)
358                 {
359                     m_errorLog.AddEntry(error);
360                 }
361             }
362             return m_errorLog.Count == errorLogSize;
363         }
364
365         // effects: Checks for some sanity issues between the basic and view constraints. Adds to m_errorLog if needed
366         [Conditional("DEBUG")]
367         private static void CheckConstraintSanity(BasicSchemaConstraints cConstraints, BasicSchemaConstraints sConstraints,
368                                            ViewSchemaConstraints cViewConstraints, ViewSchemaConstraints sViewConstraints)
369         {
370             Debug.Assert(cConstraints.KeyConstraints.Count() == cViewConstraints.KeyConstraints.Count(),
371                          "Mismatch in number of C basic and view key constraints");
372             Debug.Assert(sConstraints.KeyConstraints.Count() == sViewConstraints.KeyConstraints.Count(),
373                          "Mismatch in number of S basic and view key constraints");
374         }
375         #endregion
376
377         // Keeps track of two extent objects
378         private class ExtentPair
379         {
380             internal ExtentPair(EntitySetBase acExtent, EntitySetBase asExtent)
381             {
382                 cExtent = acExtent;
383                 sExtent = asExtent;
384             }
385             internal EntitySetBase cExtent;
386             internal EntitySetBase sExtent;
387
388             public override bool Equals(object obj)
389             {
390                 if (object.ReferenceEquals(this, obj))
391                 {
392                     return true;
393                 }
394                 ExtentPair pair = obj as ExtentPair;
395                 if (pair == null)
396                 {
397                     return false;
398                 }
399
400                 return pair.cExtent.Equals(cExtent) && pair.sExtent.Equals(sExtent);
401             }
402
403             public override int GetHashCode()
404             {
405                 return cExtent.GetHashCode() ^ sExtent.GetHashCode();
406             }
407         }
408
409
410     }
411
412 }