|
//---------------------------------------------------------------------
// <copyright file="Validator.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Common.Utils;
using System.Data.Mapping.ViewGeneration.Structures;
using System.Data.Mapping.ViewGeneration.Utils;
using System.Data.Mapping.ViewGeneration.Validation;
using System.Data.Metadata.Edm;
using System.Diagnostics;
using System.Linq;
namespace System.Data.Mapping.ViewGeneration
{
using System.Data.Entity;
using BasicSchemaConstraints = SchemaConstraints<BasicKeyConstraint>;
using ViewSchemaConstraints = SchemaConstraints<ViewKeyConstraint>;
// This class is responsible for validating the incoming cells for a schema
class CellGroupValidator
{
#region Constructor
// requires: cells are not normalized, i.e., no slot is null in the cell queries
// effects: Constructs a validator object that is capable of
// validating all the schema cells together
internal CellGroupValidator(IEnumerable<Cell> cells, ConfigViewGenerator config)
{
m_cells = cells;
m_config = config;
m_errorLog = new ErrorLog();
}
#endregion
#region Fields
private IEnumerable<Cell> m_cells;
private ConfigViewGenerator m_config;
private ErrorLog m_errorLog; // Keeps track of errors for this set of cells
private ViewSchemaConstraints m_cViewConstraints;
private ViewSchemaConstraints m_sViewConstraints;
#endregion
#region External Methods
// effects: Performs the validation of the cells in this and returns
// an error log of all the errors/warnings that were discovered
internal ErrorLog Validate()
{
// Check for errors not checked by "C-implies-S principle"
if (m_config.IsValidationEnabled)
{
if (PerformSingleCellChecks() == false)
{
return m_errorLog;
}
}
else //Note that Metadata loading guarantees that DISTINCT flag is not present
{ // when update views (and validation) is disabled
if (CheckCellsWithDistinctFlag() == false)
{
return m_errorLog;
}
}
BasicSchemaConstraints cConstraints = new BasicSchemaConstraints();
BasicSchemaConstraints sConstraints = new BasicSchemaConstraints();
// Construct intermediate "view relations" and the basic cell
// relations along with the basic constraints
ConstructCellRelationsWithConstraints(cConstraints, sConstraints);
if (m_config.IsVerboseTracing)
{
// Trace Basic constraints
Trace.WriteLine(String.Empty);
Trace.WriteLine("C-Level Basic Constraints");
Trace.WriteLine(cConstraints);
Trace.WriteLine("S-Level Basic Constraints");
Trace.WriteLine(sConstraints);
}
// Propagate the constraints
m_cViewConstraints = PropagateConstraints(cConstraints);
m_sViewConstraints = PropagateConstraints(sConstraints);
// Make some basic checks on the view and basic cell constraints
CheckConstraintSanity(cConstraints, sConstraints, m_cViewConstraints, m_sViewConstraints);
if (m_config.IsVerboseTracing)
{
// Trace View constraints
Trace.WriteLine(String.Empty);
Trace.WriteLine("C-Level View Constraints");
Trace.WriteLine(m_cViewConstraints);
Trace.WriteLine("S-Level View Constraints");
Trace.WriteLine(m_sViewConstraints);
}
// Check for implication
if (m_config.IsValidationEnabled)
{
CheckImplication(m_cViewConstraints, m_sViewConstraints);
}
return m_errorLog;
}
#endregion
#region Basic Constraint Creation
// effects: Creates the base cell relation and view cell relations
// for each cellquery/cell. Also generates the C-Side and S-side
// basic constraints and stores them into cConstraints and
// sConstraints. Stores them in cConstraints and sConstraints
private void ConstructCellRelationsWithConstraints(BasicSchemaConstraints cConstraints,
BasicSchemaConstraints sConstraints)
{
// Populate single cell constraints
int cellNumber = 0;
foreach (Cell cell in m_cells)
{
// We have to create the ViewCellRelation so that the
// BasicCellRelations can be created.
cell.CreateViewCellRelation(cellNumber);
BasicCellRelation cCellRelation = cell.CQuery.BasicCellRelation;
BasicCellRelation sCellRelation = cell.SQuery.BasicCellRelation;
// Populate the constraints for the C relation and the S Relation
PopulateBaseConstraints(cCellRelation, cConstraints);
PopulateBaseConstraints(sCellRelation, sConstraints);
cellNumber++;
}
// Populate two-cell constraints, i.e., inclusion
foreach (Cell firstCell in m_cells)
{
foreach (Cell secondCell in m_cells)
{
if (Object.ReferenceEquals(firstCell, secondCell))
{
// We do not want to set up self-inclusion constraints unnecessarily
continue;
}
}
}
}
// effects: Generates the single-cell key+domain constraints for
// baseRelation and adds them to constraints
private static void PopulateBaseConstraints(BasicCellRelation baseRelation,
BasicSchemaConstraints constraints)
{
// Populate key constraints
baseRelation.PopulateKeyConstraints(constraints);
}
#endregion
#region Constraint Propagation
// effects: Propagates baseConstraints derived from the cellrelations
// to the corresponding viewCellRelations and returns the list of
// propagated constraints
private static ViewSchemaConstraints PropagateConstraints(BasicSchemaConstraints baseConstraints)
{
ViewSchemaConstraints propagatedConstraints = new ViewSchemaConstraints();
// Key constraint propagation
foreach (BasicKeyConstraint keyConstraint in baseConstraints.KeyConstraints)
{
ViewKeyConstraint viewConstraint = keyConstraint.Propagate();
if (viewConstraint != null)
{
propagatedConstraints.Add(viewConstraint);
}
}
return propagatedConstraints;
}
#endregion
#region Checking for Implication
// effects: Checks if all sViewConstraints are implied by the
// constraints in cViewConstraints. If some S-level constraints are
// not implied, adds errors/warnings to m_errorLog
private void CheckImplication(ViewSchemaConstraints cViewConstraints, ViewSchemaConstraints sViewConstraints)
{
// Check key constraints
// i.e., if S has a key <k1, k2>, C must have a key that is a subset of this
CheckImplicationKeyConstraints(cViewConstraints, sViewConstraints);
// For updates, we need to ensure the following: for every
// extent E, table T pair, some key of E is implied by T's key
// Get all key constraints for each extent and each table
KeyToListMap<ExtentPair, ViewKeyConstraint> extentPairConstraints =
new KeyToListMap<ExtentPair, ViewKeyConstraint>(EqualityComparer<ExtentPair>.Default);
foreach (ViewKeyConstraint cKeyConstraint in cViewConstraints.KeyConstraints)
{
ExtentPair pair = new ExtentPair(cKeyConstraint.Cell.CQuery.Extent, cKeyConstraint.Cell.SQuery.Extent);
extentPairConstraints.Add(pair, cKeyConstraint);
}
// Now check that we guarantee at least one constraint per
// extent/table pair
foreach (ExtentPair extentPair in extentPairConstraints.Keys)
{
ReadOnlyCollection<ViewKeyConstraint> cKeyConstraints = extentPairConstraints.ListForKey(extentPair);
bool sImpliesSomeC = false;
// Go through all key constraints for the extent/table pair, and find one that S implies
foreach (ViewKeyConstraint cKeyConstraint in cKeyConstraints)
{
foreach (ViewKeyConstraint sKeyConstraint in sViewConstraints.KeyConstraints)
{
if (sKeyConstraint.Implies(cKeyConstraint))
{
sImpliesSomeC = true;
break; // The implication holds - so no problem
}
}
}
if (sImpliesSomeC == false)
{
// Indicate that at least one key must be ensured on the S-side
m_errorLog.AddEntry(ViewKeyConstraint.GetErrorRecord(cKeyConstraints));
}
}
}
// effects: Checks for key constraint implication problems from
// leftViewConstraints to rightViewConstraints. Adds errors/warning to m_errorLog
private void CheckImplicationKeyConstraints(ViewSchemaConstraints leftViewConstraints,
ViewSchemaConstraints rightViewConstraints)
{
// if cImpliesS is true, every rightKeyConstraint must be implied
// if it is false, at least one key constraint for each C-level
// extent must be implied
foreach (ViewKeyConstraint rightKeyConstraint in rightViewConstraints.KeyConstraints)
{
// Go through all the left Side constraints and check for implication
bool found = false;
foreach (ViewKeyConstraint leftKeyConstraint in leftViewConstraints.KeyConstraints)
{
if (leftKeyConstraint.Implies(rightKeyConstraint))
{
found = true;
break; // The implication holds - so no problem
}
}
if (false == found)
{
// No C-side key constraint implies this S-level key constraint
// Report a problem
m_errorLog.AddEntry(ViewKeyConstraint.GetErrorRecord(rightKeyConstraint));
}
}
}
#endregion
#region Miscellaneous checks
/// <summary>
/// Checks that if a DISTINCT operator exists between some C-Extent and S-Extent, there are no additional
/// mapping fragments between that C-Extent and S-Extent.
/// We need to enforce this because DISTINCT is not understood by viewgen machinery, and two fragments may be merged
/// despite one of them having DISTINCT.
/// </summary>
private bool CheckCellsWithDistinctFlag()
{
int errorLogSize = m_errorLog.Count;
foreach (Cell cell in m_cells)
{
if (cell.SQuery.SelectDistinctFlag == CellQuery.SelectDistinct.Yes)
{
var cExtent = cell.CQuery.Extent;
var sExtent = cell.SQuery.Extent;
//There should be no other fragments mapping cExtent to sExtent
var mapepdFragments = m_cells.Where(otherCell => otherCell != cell)
.Where(otherCell => otherCell.CQuery.Extent == cExtent && otherCell.SQuery.Extent == sExtent);
if (mapepdFragments.Any())
{
var cellsToReport = Enumerable.Union(Enumerable.Repeat(cell, 1), mapepdFragments);
ErrorLog.Record record = new ErrorLog.Record(true, ViewGenErrorCode.MultipleFragmentsBetweenCandSExtentWithDistinct,
Strings.Viewgen_MultipleFragmentsBetweenCandSExtentWithDistinct(cExtent.Name, sExtent.Name), cellsToReport, String.Empty);
m_errorLog.AddEntry(record);
}
}
}
return m_errorLog.Count == errorLogSize;
}
// effects: Check for problems in each cell that are not detected by the
// "C-constraints-imply-S-constraints" principle. If the check fails,
// adds relevant error info to m_errorLog and returns false. Else
// retrns true
private bool PerformSingleCellChecks()
{
int errorLogSize = m_errorLog.Count;
foreach (Cell cell in m_cells)
{
// Check for duplication of element in a single cell name1, name2
// -> name Could be done by implication but that would require
// setting self-inclusion constraints etc That seems unnecessary
// We need this check only for the C side. if we map cname1
// and cmane2 to sname, that is a problem. But mapping sname1
// and sname2 to cname is ok
ErrorLog.Record error = cell.SQuery.CheckForDuplicateFields(cell.CQuery, cell);
if (error != null)
{
m_errorLog.AddEntry(error);
}
// Check that the EntityKey and the Table key are mapped
// (Key for association is all ends)
error = cell.CQuery.VerifyKeysPresent(cell, Strings.ViewGen_EntitySetKey_Missing,
Strings.ViewGen_AssociationSetKey_Missing, ViewGenErrorCode.KeyNotMappedForCSideExtent);
if (error != null)
{
m_errorLog.AddEntry(error);
}
error = cell.SQuery.VerifyKeysPresent(cell, Strings.ViewGen_TableKey_Missing, null, ViewGenErrorCode.KeyNotMappedForTable);
if (error != null)
{
m_errorLog.AddEntry(error);
}
// Check that if any side has a not-null constraint -- if so,
// we must project that slot
error = cell.CQuery.CheckForProjectedNotNullSlots(cell, m_cells.Where(c=> c.SQuery.Extent is AssociationSet));
if (error != null)
{
m_errorLog.AddEntry(error);
}
error = cell.SQuery.CheckForProjectedNotNullSlots(cell, m_cells.Where(c => c.CQuery.Extent is AssociationSet));
if (error != null)
{
m_errorLog.AddEntry(error);
}
}
return m_errorLog.Count == errorLogSize;
}
// effects: Checks for some sanity issues between the basic and view constraints. Adds to m_errorLog if needed
[Conditional("DEBUG")]
private static void CheckConstraintSanity(BasicSchemaConstraints cConstraints, BasicSchemaConstraints sConstraints,
ViewSchemaConstraints cViewConstraints, ViewSchemaConstraints sViewConstraints)
{
Debug.Assert(cConstraints.KeyConstraints.Count() == cViewConstraints.KeyConstraints.Count(),
"Mismatch in number of C basic and view key constraints");
Debug.Assert(sConstraints.KeyConstraints.Count() == sViewConstraints.KeyConstraints.Count(),
"Mismatch in number of S basic and view key constraints");
}
#endregion
// Keeps track of two extent objects
private class ExtentPair
{
internal ExtentPair(EntitySetBase acExtent, EntitySetBase asExtent)
{
cExtent = acExtent;
sExtent = asExtent;
}
internal EntitySetBase cExtent;
internal EntitySetBase sExtent;
public override bool Equals(object obj)
{
if (object.ReferenceEquals(this, obj))
{
return true;
}
ExtentPair pair = obj as ExtentPair;
if (pair == null)
{
return false;
}
return pair.cExtent.Equals(cExtent) && pair.sExtent.Equals(sExtent);
}
public override int GetHashCode()
{
return cExtent.GetHashCode() ^ sExtent.GetHashCode();
}
}
}
}
|