1 //---------------------------------------------------------------------
2 // <copyright file="Validator.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
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;
20 namespace System.Data.Mapping.ViewGeneration
23 using System.Data.Entity;
24 using BasicSchemaConstraints = SchemaConstraints<BasicKeyConstraint>;
25 using ViewSchemaConstraints = SchemaConstraints<ViewKeyConstraint>;
27 // This class is responsible for validating the incoming cells for a schema
28 class CellGroupValidator
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)
39 m_errorLog = new ErrorLog();
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;
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()
57 // Check for errors not checked by "C-implies-S principle"
58 if (m_config.IsValidationEnabled)
60 if (PerformSingleCellChecks() == false)
65 else //Note that Metadata loading guarantees that DISTINCT flag is not present
66 { // when update views (and validation) is disabled
68 if (CheckCellsWithDistinctFlag() == false)
74 BasicSchemaConstraints cConstraints = new BasicSchemaConstraints();
75 BasicSchemaConstraints sConstraints = new BasicSchemaConstraints();
77 // Construct intermediate "view relations" and the basic cell
78 // relations along with the basic constraints
79 ConstructCellRelationsWithConstraints(cConstraints, sConstraints);
81 if (m_config.IsVerboseTracing)
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);
91 // Propagate the constraints
92 m_cViewConstraints = PropagateConstraints(cConstraints);
93 m_sViewConstraints = PropagateConstraints(sConstraints);
95 // Make some basic checks on the view and basic cell constraints
96 CheckConstraintSanity(cConstraints, sConstraints, m_cViewConstraints, m_sViewConstraints);
98 if (m_config.IsVerboseTracing)
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);
108 // Check for implication
109 if (m_config.IsValidationEnabled)
111 CheckImplication(m_cViewConstraints, m_sViewConstraints);
117 #region Basic Constraint Creation
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)
127 // Populate single cell constraints
129 foreach (Cell cell in m_cells)
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);
142 // Populate two-cell constraints, i.e., inclusion
143 foreach (Cell firstCell in m_cells)
145 foreach (Cell secondCell in m_cells)
147 if (Object.ReferenceEquals(firstCell, secondCell))
149 // We do not want to set up self-inclusion constraints unnecessarily
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)
161 // Populate key constraints
162 baseRelation.PopulateKeyConstraints(constraints);
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)
172 ViewSchemaConstraints propagatedConstraints = new ViewSchemaConstraints();
174 // Key constraint propagation
175 foreach (BasicKeyConstraint keyConstraint in baseConstraints.KeyConstraints)
177 ViewKeyConstraint viewConstraint = keyConstraint.Propagate();
178 if (viewConstraint != null)
180 propagatedConstraints.Add(viewConstraint);
183 return propagatedConstraints;
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)
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);
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
201 // Get all key constraints for each extent and each table
202 KeyToListMap<ExtentPair, ViewKeyConstraint> extentPairConstraints =
203 new KeyToListMap<ExtentPair, ViewKeyConstraint>(EqualityComparer<ExtentPair>.Default);
205 foreach (ViewKeyConstraint cKeyConstraint in cViewConstraints.KeyConstraints)
207 ExtentPair pair = new ExtentPair(cKeyConstraint.Cell.CQuery.Extent, cKeyConstraint.Cell.SQuery.Extent);
208 extentPairConstraints.Add(pair, cKeyConstraint);
211 // Now check that we guarantee at least one constraint per
213 foreach (ExtentPair extentPair in extentPairConstraints.Keys)
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)
220 foreach (ViewKeyConstraint sKeyConstraint in sViewConstraints.KeyConstraints)
222 if (sKeyConstraint.Implies(cKeyConstraint))
224 sImpliesSomeC = true;
225 break; // The implication holds - so no problem
229 if (sImpliesSomeC == false)
231 // Indicate that at least one key must be ensured on the S-side
232 m_errorLog.AddEntry(ViewKeyConstraint.GetErrorRecord(cKeyConstraints));
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)
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
247 foreach (ViewKeyConstraint rightKeyConstraint in rightViewConstraints.KeyConstraints)
249 // Go through all the left Side constraints and check for implication
251 foreach (ViewKeyConstraint leftKeyConstraint in leftViewConstraints.KeyConstraints)
253 if (leftKeyConstraint.Implies(rightKeyConstraint))
256 break; // The implication holds - so no problem
261 // No C-side key constraint implies this S-level key constraint
263 m_errorLog.AddEntry(ViewKeyConstraint.GetErrorRecord(rightKeyConstraint));
270 #region Miscellaneous checks
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.
279 private bool CheckCellsWithDistinctFlag()
282 int errorLogSize = m_errorLog.Count;
283 foreach (Cell cell in m_cells)
285 if (cell.SQuery.SelectDistinctFlag == CellQuery.SelectDistinct.Yes)
287 var cExtent = cell.CQuery.Extent;
288 var sExtent = cell.SQuery.Extent;
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);
294 if (mapepdFragments.Any())
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);
304 return m_errorLog.Count == errorLogSize;
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
314 private bool PerformSingleCellChecks()
317 int errorLogSize = m_errorLog.Count;
318 foreach (Cell cell in m_cells)
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
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);
330 m_errorLog.AddEntry(error);
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);
340 m_errorLog.AddEntry(error);
343 error = cell.SQuery.VerifyKeysPresent(cell, Strings.ViewGen_TableKey_Missing, null, ViewGenErrorCode.KeyNotMappedForTable);
346 m_errorLog.AddEntry(error);
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));
354 m_errorLog.AddEntry(error);
356 error = cell.SQuery.CheckForProjectedNotNullSlots(cell, m_cells.Where(c => c.CQuery.Extent is AssociationSet));
359 m_errorLog.AddEntry(error);
362 return m_errorLog.Count == errorLogSize;
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)
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");
377 // Keeps track of two extent objects
378 private class ExtentPair
380 internal ExtentPair(EntitySetBase acExtent, EntitySetBase asExtent)
385 internal EntitySetBase cExtent;
386 internal EntitySetBase sExtent;
388 public override bool Equals(object obj)
390 if (object.ReferenceEquals(this, obj))
394 ExtentPair pair = obj as ExtentPair;
400 return pair.cExtent.Equals(cExtent) && pair.sExtent.Equals(sExtent);
403 public override int GetHashCode()
405 return cExtent.GetHashCode() ^ sExtent.GetHashCode();