2015-04-07 09:35:12 +01:00
//---------------------------------------------------------------------
// <copyright file="QueryRewriter.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
namespace System.Data.Mapping.ViewGeneration.QueryRewriting
{
using System.Collections.Generic ;
using System.Data.Common.Utils ;
using System.Data.Entity ;
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 ;
using System.Text ;
/// <summary>
/// Uses query rewriting to determine the case statements, top-level WHERE clause, and the "used views"
/// for a given type to be generated.
///
/// Step 1: Method "EnsureIsFullyMapped" goes through the (C) schema metadata and checks whether the query for each
/// entity shape can be rewritten from the C fragment queries.
/// This step tracks the "used views" which will later be passed to "basic view generation" (i.e., creation of the FOJ/LOJ/IJ/Union relational expressions)
/// Step 2: GetCaseStatements constructs the required case statements and the top-level WHERE clause.
/// This may add some extra views to "used views".
/// Now we know what views are used overall.
/// Step 3: We remap _from variables to new _from variables that are renumbered for used views.
/// This is done to comply with the numbering scheme in the old algorithm - and to produce more readable views.
/// Step 4: From the constructed relational expression (OpCellTree), we can tell whether a top-level WHERE clause is needed or not.
/// (Usually, it's needed only in certain cases for OfType() views.)
/// </summary>
internal class QueryRewriter
{
#region Fields
// The following fields are copied from ViewGenContext
MemberPath _extentPath ;
MemberDomainMap _domainMap ;
ConfigViewGenerator _config ;
CqlIdentifiers _identifiers ;
ViewgenContext _context ;
// Keeps track of statistics
RewritingProcessor < Tile < FragmentQuery > > _qp ;
// Key attributes of the current extent in _extentPath
List < MemberPath > _keyAttributes ;
// Fragment queries, one per LeftCellWrapper
List < FragmentQuery > _fragmentQueries = new List < FragmentQuery > ( ) ;
List < Tile < FragmentQuery > > _views = new List < Tile < FragmentQuery > > ( ) ;
FragmentQuery _domainQuery ;
EdmType _generatedType ;
HashSet < FragmentQuery > _usedViews = new HashSet < FragmentQuery > ( ) ;
List < LeftCellWrapper > _usedCells = new List < LeftCellWrapper > ( ) ;
BoolExpression _topLevelWhereClause ;
CellTreeNode _basicView ;
Dictionary < MemberPath , CaseStatement > _caseStatements = new Dictionary < MemberPath , CaseStatement > ( ) ;
ErrorLog _errorLog = new ErrorLog ( ) ;
ViewGenMode _typesGenerationMode ;
#endregion
#region Static variables
static Tile < FragmentQuery > TrueViewSurrogate = CreateTile ( FragmentQuery . Create ( BoolExpression . True ) ) ;
#endregion
#region Constructor and main entry point
internal QueryRewriter ( EdmType generatedType , ViewgenContext context , ViewGenMode typesGenerationMode )
{
Debug . Assert ( typesGenerationMode ! = ViewGenMode . GenerateAllViews ) ;
_typesGenerationMode = typesGenerationMode ;
_context = context ;
_generatedType = generatedType ;
_domainMap = context . MemberMaps . LeftDomainMap ;
_config = context . Config ;
_identifiers = context . CqlIdentifiers ;
_qp = new RewritingProcessor < Tile < FragmentQuery > > ( new DefaultTileProcessor < FragmentQuery > ( context . LeftFragmentQP ) ) ;
_extentPath = new MemberPath ( context . Extent ) ;
_keyAttributes = new List < MemberPath > ( MemberPath . GetKeyMembers ( context . Extent , _domainMap ) ) ;
// populate _fragmentQueries and _views
foreach ( LeftCellWrapper leftCellWrapper in _context . AllWrappersForExtent )
{
FragmentQuery query = leftCellWrapper . FragmentQuery ;
Tile < FragmentQuery > tile = CreateTile ( query ) ;
_fragmentQueries . Add ( query ) ;
_views . Add ( tile ) ;
}
Debug . Assert ( _views . Count > 0 ) ;
AdjustMemberDomainsForUpdateViews ( ) ;
// must be done after adjusting domains
_domainQuery = GetDomainQuery ( FragmentQueries , generatedType ) ;
_usedViews = new HashSet < FragmentQuery > ( ) ;
}
// Generates the components used to assemble and validate the view:
// (1) case statements
// (2) top-level where clause
// (3) used cells
// (4) basic view CellTreeNode
// (5) dictionary<MemberValue, CellTreeNode> for validation
internal void GenerateViewComponents ( )
{
// make sure everything is mapped (for query views only)
EnsureExtentIsFullyMapped ( _usedViews ) ;
// (1) case statements
GenerateCaseStatements ( _domainMap . ConditionMembers ( _extentPath . Extent ) , _usedViews ) ;
AddTrivialCaseStatementsForConditionMembers ( ) ;
if ( _usedViews . Count = = 0 | | _errorLog . Count > 0 )
{
// can't continue: no view will be generated, further validation doesn't make sense
Debug . Assert ( _errorLog . Count > 0 ) ;
ExceptionHelpers . ThrowMappingException ( _errorLog , _config ) ;
}
// (2) top-level where clause
_topLevelWhereClause = GetTopLevelWhereClause ( _usedViews ) ;
// some tracing
if ( _context . ViewTarget = = ViewTarget . QueryView )
{
TraceVerbose ( "Used {0} views of {1} total for rewriting" , _usedViews . Count , _views . Count ) ;
}
PrintStatistics ( _qp ) ;
// (3) construct the final _from variables
_usedCells = RemapFromVariables ( ) ;
// (4) construct basic view
BasicViewGenerator basicViewGenerator = new BasicViewGenerator (
_context . MemberMaps . ProjectedSlotMap , _usedCells ,
_domainQuery , _context , _domainMap , _errorLog , _config ) ;
_basicView = basicViewGenerator . CreateViewExpression ( ) ;
// a top-level WHERE clause is needed only if the simplifiedView still contains extra tuples
bool noWhereClauseNeeded = _context . LeftFragmentQP . IsContainedIn ( _basicView . LeftFragmentQuery , _domainQuery ) ;
if ( noWhereClauseNeeded )
{
_topLevelWhereClause = BoolExpression . True ;
}
if ( _errorLog . Count > 0 )
{
ExceptionHelpers . ThrowMappingException ( _errorLog , _config ) ;
}
}
#endregion
#region Properties
internal ViewgenContext ViewgenContext
{
get { return _context ; }
}
internal Dictionary < MemberPath , CaseStatement > CaseStatements
{
get { return _caseStatements ; }
}
internal BoolExpression TopLevelWhereClause
{
get { return _topLevelWhereClause ; }
}
internal CellTreeNode BasicView
{
get
{
// create a copy so the original won't get modified when Simplifier.Simplify is called on it
return _basicView . MakeCopy ( ) ;
}
}
internal List < LeftCellWrapper > UsedCells
{
get { return _usedCells ; }
}
private IEnumerable < FragmentQuery > FragmentQueries
{
get { return _fragmentQueries ; }
}
#endregion
#region Main logic
private IEnumerable < Constant > GetDomain ( MemberPath currentPath )
{
if ( _context . ViewTarget = = ViewTarget . QueryView & & MemberPath . EqualityComparer . Equals ( currentPath , _extentPath ) )
{
IEnumerable < EdmType > types ;
if ( _typesGenerationMode = = ViewGenMode . OfTypeOnlyViews )
{
Debug . Assert ( ! Helper . IsRefType ( _generatedType ) ) ;
HashSet < EdmType > type = new HashSet < EdmType > ( ) ;
type . Add ( _generatedType ) ;
types = type ;
}
else
{
types = MetadataHelper . GetTypeAndSubtypesOf ( _generatedType , _context . EdmItemCollection , false /* don't include abstract types */ ) ;
}
return GetTypeConstants ( types ) ;
}
return _domainMap . GetDomain ( currentPath ) ;
}
// NULL/default and NOT(...) values in cell constant domains for update views may be unused.
// If we don't detect that and remove them, we can suboptimal (but still correct) update views.
// (For example, SProducts1 in NotNullCorrect.msl has an unused constant NOT("Camera", NULL), which results in a gratuitous join.
// That join could be eliminated due to 1:1 association on C side).
// To determine that a constant is unused, we first try to obtain the S-side rewriting for it.
// If that succeeds, we unfold C-queries, i.e., create OpCellTree for found rewritings,
// and check whether these are unsatisfiable.
// If they indeed are unsatisfiable, we eliminate the constants from the domainMap.
private void AdjustMemberDomainsForUpdateViews ( )
{
switch ( _context . ViewTarget )
{
case ViewTarget . UpdateView :
{
// materialize members in a list so we can modify _domainMap later on
List < MemberPath > members = new List < MemberPath > ( _domainMap . ConditionMembers ( _extentPath . Extent ) ) ;
foreach ( MemberPath currentPath in members )
{
// try to remove default value followed by negated value, in this order
IEnumerable < Constant > oldDomain = _domainMap . GetDomain ( currentPath ) ;
Constant defaultValue = oldDomain . FirstOrDefault ( domainValue = > IsDefaultValue ( domainValue , currentPath ) ) ;
if ( defaultValue ! = null )
{
RemoveUnusedValueFromStoreDomain ( defaultValue , currentPath ) ;
}
oldDomain = _domainMap . GetDomain ( currentPath ) ; // is case has changed
Constant negatedValue = oldDomain . FirstOrDefault ( domainValue = > domainValue is NegatedConstant ) ;
if ( negatedValue ! = null )
{
RemoveUnusedValueFromStoreDomain ( negatedValue , currentPath ) ;
}
}
break ;
}
}
}
private void RemoveUnusedValueFromStoreDomain ( Constant domainValue , MemberPath currentPath )
{
// construct WHERE clause for this value
BoolExpression domainWhereClause = CreateMemberCondition ( currentPath , domainValue ) ;
// get a rewriting for CASE statements by not requesting any attributes beyond key
Tile < FragmentQuery > caseRewriting ;
HashSet < FragmentQuery > outputUsedViews = new HashSet < FragmentQuery > ( ) ;
bool isUsedValue = false ;
if ( FindRewritingAndUsedViews ( _keyAttributes , domainWhereClause , outputUsedViews , out caseRewriting ) )
{
// check whether this rewriting is indeed satisfiable using C-side fragment views
// If we wanted to force retention of all negated constants, we could use:
// if (domainValue is NegatedCellConstant) { isUsedValue = true; } else {...}
CellTreeNode cellTree = TileToCellTree ( ( Tile < FragmentQuery > ) caseRewriting , _context ) ;
isUsedValue = ! cellTree . IsEmptyRightFragmentQuery ;
}
if ( ! isUsedValue )
{
Set < Constant > newDomain = new Set < Constant > ( _domainMap . GetDomain ( currentPath ) , Constant . EqualityComparer ) ;
newDomain . Remove ( domainValue ) ;
TraceVerbose ( "Shrunk domain of column {0} from {1} to {2}" , currentPath , _domainMap . GetDomain ( currentPath ) , newDomain ) ;
_domainMap . UpdateConditionMemberDomain ( currentPath , newDomain ) ;
// Update the WHERE clauses of all fragment queries
// Since these are pointers to the respective WHERE clauses in S-side cell queries, those get updated automatically
foreach ( FragmentQuery query in _fragmentQueries )
{
query . Condition . FixDomainMap ( _domainMap ) ;
}
}
}
// determine the domain query, i.e., the query that returns all keys of the extent to be populated
internal FragmentQuery GetDomainQuery ( IEnumerable < FragmentQuery > fragmentQueries , EdmType generatedType )
{
BoolExpression domainQueryCondition = null ;
if ( _context . ViewTarget = = ViewTarget . QueryView )
{
if ( generatedType = = null )
{
// domainQuery for entire extent: True
domainQueryCondition = BoolExpression . True ;
}
else // domainQuery for specific type: WHERE type(path) IS OF (Type)
{
//If Mode is OFTypeOnlyViews then don't get subtypes
IEnumerable < EdmType > derivedTypes ;
if ( _typesGenerationMode = = ViewGenMode . OfTypeOnlyViews )
{
Debug . Assert ( ! Helper . IsRefType ( _generatedType ) ) ;
HashSet < EdmType > type = new HashSet < EdmType > ( ) ;
type . Add ( _generatedType ) ;
derivedTypes = type ;
}
else
{
derivedTypes = MetadataHelper . GetTypeAndSubtypesOf ( generatedType , _context . EdmItemCollection , false /* don't include abstract types */ ) ;
}
Domain typeDomain = new Domain ( GetTypeConstants ( derivedTypes ) , _domainMap . GetDomain ( _extentPath ) ) ;
domainQueryCondition = BoolExpression . CreateLiteral ( new TypeRestriction ( new MemberProjectedSlot ( _extentPath ) , typeDomain ) , _domainMap ) ;
}
return FragmentQuery . Create ( _keyAttributes , domainQueryCondition ) ;
}
else // for update views, domain query = exposed tiles
{
IEnumerable < BoolExpression > whereClauses = from fragmentQuery in fragmentQueries
select fragmentQuery . Condition ;
BoolExpression exposedRegionCondition = BoolExpression . CreateOr ( whereClauses . ToArray ( ) ) ;
return FragmentQuery . Create ( _keyAttributes , exposedRegionCondition ) ;
}
}
// returns true when the case statement is completed
private bool AddRewritingToCaseStatement ( Tile < FragmentQuery > rewriting , CaseStatement caseStatement , MemberPath currentPath , Constant domainValue )
{
BoolExpression whenCondition = BoolExpression . True ;
// check whether the rewriting is always true or always false
// if it's always true, we don't need any other WHEN clauses in the case statement
// if it's always false, we don't need to add this WHEN clause to the case statement
// given: domainQuery is satisfied. Check (domainQuery -> rewriting)
bool isAlwaysTrue = _qp . IsContainedIn ( CreateTile ( _domainQuery ) , rewriting ) ;
bool isAlwaysFalse = _qp . IsDisjointFrom ( CreateTile ( _domainQuery ) , rewriting ) ;
Debug . Assert ( ! ( isAlwaysTrue & & isAlwaysFalse ) ) ;
if ( isAlwaysFalse )
{
return false ; // don't need an unsatisfiable WHEN clause
}
if ( isAlwaysTrue )
{
Debug . Assert ( caseStatement . Clauses . Count = = 0 ) ;
}
ProjectedSlot projectedSlot ;
if ( domainValue . HasNotNull ( ) )
{
projectedSlot = new MemberProjectedSlot ( currentPath ) ;
}
else
{
projectedSlot = new ConstantProjectedSlot ( domainValue , currentPath ) ;
}
if ( ! isAlwaysTrue )
{
whenCondition = TileToBoolExpr ( ( Tile < FragmentQuery > ) rewriting ) ;
}
else
{
whenCondition = BoolExpression . True ;
}
caseStatement . AddWhenThen ( whenCondition , projectedSlot ) ;
return isAlwaysTrue ;
}
// make sure that we can find a rewriting for each possible entity shape appearing in an extent
// Possible optimization for OfType view generation:
// Cache "used views" for each (currentPath, domainValue) combination
private void EnsureConfigurationIsFullyMapped ( MemberPath currentPath ,
BoolExpression currentWhereClause ,
HashSet < FragmentQuery > outputUsedViews ,
ErrorLog errorLog )
{
foreach ( Constant domainValue in GetDomain ( currentPath ) )
{
if ( domainValue = = Constant . Undefined )
{
continue ; // no point in trying to recover a situation that can never happen
}
TraceVerbose ( "REWRITING FOR {0}={1}" , currentPath , domainValue ) ;
// construct WHERE clause for this value
BoolExpression domainAddedWhereClause = CreateMemberCondition ( currentPath , domainValue ) ;
// AND the current where clause to it
BoolExpression domainWhereClause = BoolExpression . CreateAnd ( currentWhereClause , domainAddedWhereClause ) ;
// first check whether we can recover instances of this type - don't care about the attributes - to produce a helpful error message
Tile < FragmentQuery > rewriting ;
if ( false = = FindRewritingAndUsedViews ( _keyAttributes , domainWhereClause , outputUsedViews , out rewriting ) )
{
if ( ! ErrorPatternMatcher . FindMappingErrors ( _context , _domainMap , _errorLog ) )
{
StringBuilder builder = new StringBuilder ( ) ;
string extentName = StringUtil . FormatInvariant ( "{0}" , _extentPath ) ;
BoolExpression whereClause = rewriting . Query . Condition ;
whereClause . ExpensiveSimplify ( ) ;
if ( whereClause . RepresentsAllTypeConditions )
{
string tableString = Strings . ViewGen_Extent ;
builder . AppendLine ( Strings . ViewGen_Cannot_Recover_Types ( tableString , extentName ) ) ;
}
else
{
string entitiesString = Strings . ViewGen_Entities ;
builder . AppendLine ( Strings . ViewGen_Cannot_Disambiguate_MultiConstant ( entitiesString , extentName ) ) ;
}
RewritingValidator . EntityConfigurationToUserString ( whereClause , builder ) ;
ErrorLog . Record record = new ErrorLog . Record ( true , ViewGenErrorCode . AmbiguousMultiConstants , builder . ToString ( ) , _context . AllWrappersForExtent , String . Empty ) ;
errorLog . AddEntry ( record ) ;
}
}
else
{
TypeConstant typeConstant = domainValue as TypeConstant ;
if ( typeConstant ! = null )
{
// we are enumerating types
EdmType edmType = typeConstant . EdmType ;
// If can recover the type, make sure can get all the necessary attributes (key is included for EntityTypes)
List < MemberPath > nonConditionalAttributes = GetNonConditionalScalarMembers ( edmType , currentPath , _domainMap ) . Union ( GetNonConditionalComplexMembers ( edmType , currentPath , _domainMap ) ) . ToList ( ) ;
IEnumerable < MemberPath > notCoverdAttributes ;
if ( nonConditionalAttributes . Count > 0 & &
! FindRewritingAndUsedViews ( nonConditionalAttributes , domainWhereClause , outputUsedViews , out rewriting , out notCoverdAttributes ) )
{
//Error: No mapping specified for some attributes
// remove keys
nonConditionalAttributes = new List < MemberPath > ( nonConditionalAttributes . Where ( a = > ! a . IsPartOfKey ) ) ;
Debug . Assert ( nonConditionalAttributes . Count > 0 , "Must have caught key-only case earlier" ) ;
AddUnrecoverableAttributesError ( notCoverdAttributes , domainAddedWhereClause , errorLog ) ;
}
else
{
// recurse into complex members
foreach ( MemberPath complexMember in GetConditionalComplexMembers ( edmType , currentPath , _domainMap ) )
{
EnsureConfigurationIsFullyMapped ( complexMember , domainWhereClause , outputUsedViews , errorLog ) ;
}
// recurse into scalar members
foreach ( MemberPath scalarMember in GetConditionalScalarMembers ( edmType , currentPath , _domainMap ) )
{
EnsureConfigurationIsFullyMapped ( scalarMember , domainWhereClause , outputUsedViews , errorLog ) ;
}
}
}
}
}
}
private static List < String > GetTypeBasedMemberPathList ( IEnumerable < MemberPath > nonConditionalScalarAttributes )
{
Debug . Assert ( nonConditionalScalarAttributes ! = null ) ;
List < String > typeBasedMembers = new List < string > ( ) ;
foreach ( MemberPath memberPath in nonConditionalScalarAttributes )
{
EdmMember member = memberPath . LeafEdmMember ;
typeBasedMembers . Add ( member . DeclaringType . Name + "." + member ) ;
}
return typeBasedMembers ;
}
private void AddUnrecoverableAttributesError ( IEnumerable < MemberPath > attributes , BoolExpression domainAddedWhereClause , ErrorLog errorLog )
{
StringBuilder builder = new StringBuilder ( ) ;
string extentName = StringUtil . FormatInvariant ( "{0}" , _extentPath ) ;
string tableString = Strings . ViewGen_Extent ;
string attributesString = StringUtil . ToCommaSeparatedString ( GetTypeBasedMemberPathList ( attributes ) ) ;
builder . AppendLine ( Strings . ViewGen_Cannot_Recover_Attributes ( attributesString , tableString , extentName ) ) ;
RewritingValidator . EntityConfigurationToUserString ( domainAddedWhereClause , builder ) ;
ErrorLog . Record record = new ErrorLog . Record ( true , ViewGenErrorCode . AttributesUnrecoverable , builder . ToString ( ) , _context . AllWrappersForExtent , String . Empty ) ;
errorLog . AddEntry ( record ) ;
}
private void GenerateCaseStatements ( IEnumerable < MemberPath > members ,
HashSet < FragmentQuery > outputUsedViews )
{
// Compute right domain query - non-simplified version of "basic view"
// It is used below to check whether we need a default value in a case statement
IEnumerable < LeftCellWrapper > usedCells = _context . AllWrappersForExtent . Where ( w = > _usedViews . Contains ( w . FragmentQuery ) ) ;
CellTreeNode rightDomainQuery = new OpCellTreeNode (
_context , CellTreeOpType . Union ,
usedCells . Select ( wrapper = > new LeafCellTreeNode ( _context , wrapper ) ) . ToArray ( ) ) ;
foreach ( MemberPath currentPath in members )
{
// Add the types can member have, i.e., its type and its subtypes
List < Constant > domain = GetDomain ( currentPath ) . ToList ( ) ;
CaseStatement caseStatement = new CaseStatement ( currentPath ) ;
Tile < FragmentQuery > unionCaseRewriting = null ;
// optimization for domain = {NULL, NOT_NULL}
// Create a single case: WHEN True THEN currentPath
// Reason: if the WHEN condition is not satisfied (say because of LOJ), then currentPath = NULL
bool needCaseStatement =
! ( domain . Count = = 2 & &
domain . Contains ( Constant . Null , Constant . EqualityComparer ) & &
domain . Contains ( Constant . NotNull , Constant . EqualityComparer ) ) ;
{
// go over the domain
foreach ( Constant domainValue in domain )
{
if ( domainValue = = Constant . Undefined & & _context . ViewTarget = = ViewTarget . QueryView )
{
// we cannot assume closed domain for query views;
// if obtaining undefined is possible, we need to account for that
caseStatement . AddWhenThen ( BoolExpression . False /* arbitrary condition */ ,
new ConstantProjectedSlot ( Constant . Undefined , currentPath ) ) ;
continue ;
}
TraceVerbose ( "CASE STATEMENT FOR {0}={1}" , currentPath , domainValue ) ;
// construct WHERE clause for this value
FragmentQuery memberConditionQuery = CreateMemberConditionQuery ( currentPath , domainValue ) ;
Tile < FragmentQuery > caseRewriting ;
if ( FindRewritingAndUsedViews ( memberConditionQuery . Attributes , memberConditionQuery . Condition , outputUsedViews , out caseRewriting ) )
{
if ( _context . ViewTarget = = ViewTarget . UpdateView )
{
unionCaseRewriting = ( unionCaseRewriting ! = null ) ? _qp . Union ( unionCaseRewriting , caseRewriting ) : caseRewriting ;
}
if ( needCaseStatement )
{
bool isAlwaysTrue = AddRewritingToCaseStatement ( caseRewriting , caseStatement , currentPath , domainValue ) ;
if ( isAlwaysTrue )
{
break ;
}
}
}
else
{
if ( ! IsDefaultValue ( domainValue , currentPath ) )
{
Debug . Assert ( _context . ViewTarget = = ViewTarget . UpdateView | | ! _config . IsValidationEnabled ) ;
if ( ! ErrorPatternMatcher . FindMappingErrors ( _context , _domainMap , _errorLog ) )
{
StringBuilder builder = new StringBuilder ( ) ;
string extentName = StringUtil . FormatInvariant ( "{0}" , _extentPath ) ;
string objectString = _context . ViewTarget = = ViewTarget . QueryView ?
Strings . ViewGen_Entities : Strings . ViewGen_Tuples ;
if ( _context . ViewTarget = = ViewTarget . QueryView )
{
builder . AppendLine ( Strings . Viewgen_CannotGenerateQueryViewUnderNoValidation ( extentName ) ) ;
}
else
{
builder . AppendLine ( Strings . ViewGen_Cannot_Disambiguate_MultiConstant ( objectString , extentName ) ) ;
}
RewritingValidator . EntityConfigurationToUserString ( memberConditionQuery . Condition , builder , _context . ViewTarget = = ViewTarget . UpdateView ) ;
ErrorLog . Record record = new ErrorLog . Record ( true , ViewGenErrorCode . AmbiguousMultiConstants , builder . ToString ( ) , _context . AllWrappersForExtent , String . Empty ) ;
_errorLog . AddEntry ( record ) ;
}
}
}
}
}
if ( _errorLog . Count = = 0 )
{
// for update views, add WHEN True THEN defaultValue
// which will ultimately be translated into a (possibly implicit) ELSE clause
if ( _context . ViewTarget = = ViewTarget . UpdateView & & needCaseStatement )
{
AddElseDefaultToCaseStatement ( currentPath , caseStatement , domain , rightDomainQuery , unionCaseRewriting ) ;
}
if ( caseStatement . Clauses . Count > 0 )
{
TraceVerbose ( "{0}" , caseStatement . ToString ( ) ) ;
_caseStatements [ currentPath ] = caseStatement ;
}
}
}
}
private void AddElseDefaultToCaseStatement ( MemberPath currentPath , CaseStatement caseStatement , List < Constant > domain ,
CellTreeNode rightDomainQuery , Tile < FragmentQuery > unionCaseRewriting )
{
Debug . Assert ( _context . ViewTarget = = ViewTarget . UpdateView , "Used for update views only" ) ;
Constant defaultValue ;
bool hasDefaultValue = Domain . TryGetDefaultValueForMemberPath ( currentPath , out defaultValue ) ;
if ( false = = hasDefaultValue | | false = = domain . Contains ( defaultValue ) )
{
Debug . Assert ( unionCaseRewriting ! = null , "No union of rewritings for case statements" ) ;
CellTreeNode unionTree = TileToCellTree ( unionCaseRewriting , _context ) ;
FragmentQuery configurationNeedsDefault = _context . RightFragmentQP . Difference ( rightDomainQuery . RightFragmentQuery , unionTree . RightFragmentQuery ) ;
if ( _context . RightFragmentQP . IsSatisfiable ( configurationNeedsDefault ) )
{
if ( hasDefaultValue )
{
caseStatement . AddWhenThen ( BoolExpression . True , new ConstantProjectedSlot ( defaultValue , currentPath ) ) ;
}
else
{
configurationNeedsDefault . Condition . ExpensiveSimplify ( ) ;
StringBuilder builder = new StringBuilder ( ) ;
builder . AppendLine ( Strings . ViewGen_No_Default_Value_For_Configuration ( currentPath . PathToString ( false /* for alias */ ) ) ) ;
RewritingValidator . EntityConfigurationToUserString ( configurationNeedsDefault . Condition , builder ) ;
_errorLog . AddEntry ( new ErrorLog . Record ( true , ViewGenErrorCode . NoDefaultValue , builder . ToString ( ) , _context . AllWrappersForExtent , String . Empty ) ) ;
}
}
}
}
// construct top-level WHERE clause
private BoolExpression GetTopLevelWhereClause ( HashSet < FragmentQuery > outputUsedViews )
{
BoolExpression topLevelWhereClause = BoolExpression . True ;
if ( _context . ViewTarget = = ViewTarget . QueryView )
{
// check whether a top-level query is needed
if ( ! _domainQuery . Condition . IsTrue )
{
Tile < FragmentQuery > topLevelRewriting ;
if ( FindRewritingAndUsedViews ( _keyAttributes , _domainQuery . Condition , outputUsedViews , out topLevelRewriting ) )
{
topLevelWhereClause = TileToBoolExpr ( topLevelRewriting ) ;
topLevelWhereClause . ExpensiveSimplify ( ) ;
}
else
{
Debug . Fail ( "Can't happen if EnsureExtentIsFullyMapped succeeded" ) ;
}
}
}
return topLevelWhereClause ;
}
// This makes sure that the mapping describes how to store all C-side data,
// i.e., the view given by C-side cell queries is injective
internal void EnsureExtentIsFullyMapped ( HashSet < FragmentQuery > outputUsedViews )
{
if ( _context . ViewTarget = = ViewTarget . QueryView & & _config . IsValidationEnabled )
{
// Run the check below for OfType views too so we can determine
// what views are used (low overhead due to caching of rewritings)
EnsureConfigurationIsFullyMapped ( _extentPath , BoolExpression . True , outputUsedViews , _errorLog ) ;
if ( _errorLog . Count > 0 )
{
ExceptionHelpers . ThrowMappingException ( _errorLog , _config ) ;
}
}
else
{
if ( _config . IsValidationEnabled )
{
// Ensure that non-nullable, no-default attributes are always populated properly
foreach ( MemberPath memberPath in _context . MemberMaps . ProjectedSlotMap . Members )
{
Constant defaultConstant ;
if ( memberPath . IsScalarType ( ) & &
! memberPath . IsPartOfKey & &
! _domainMap . IsConditionMember ( memberPath ) & &
! Domain . TryGetDefaultValueForMemberPath ( memberPath , out defaultConstant ) )
{
HashSet < MemberPath > attributes = new HashSet < MemberPath > ( _keyAttributes ) ;
attributes . Add ( memberPath ) ;
foreach ( LeftCellWrapper leftCellWrapper in _context . AllWrappersForExtent )
{
FragmentQuery fragmentQuery = leftCellWrapper . FragmentQuery ;
FragmentQuery tileQuery = new FragmentQuery ( fragmentQuery . Description , fragmentQuery . FromVariable ,
attributes , fragmentQuery . Condition ) ;
Tile < FragmentQuery > noNullToAvoid = CreateTile ( FragmentQuery . Create ( _keyAttributes , BoolExpression . CreateNot ( fragmentQuery . Condition ) ) ) ;
Tile < FragmentQuery > noNullRewriting ;
IEnumerable < MemberPath > notCoveredAttributes ;
if ( ! RewriteQuery ( CreateTile ( tileQuery ) , noNullToAvoid , /*_views,*/ out noNullRewriting , out notCoveredAttributes , false /* isRelaxed */ ) )
{
// force error
Domain . GetDefaultValueForMemberPath ( memberPath , new LeftCellWrapper [ ] { leftCellWrapper } , _config ) ;
}
}
}
}
}
// find a rewriting for each tile
// some of the views may be redundant and unused
foreach ( Tile < FragmentQuery > toFill in _views )
{
Tile < FragmentQuery > rewriting ;
Tile < FragmentQuery > toAvoid = CreateTile ( FragmentQuery . Create ( _keyAttributes , BoolExpression . CreateNot ( toFill . Query . Condition ) ) ) ;
IEnumerable < MemberPath > notCoveredAttributes ;
bool found = RewriteQuery ( toFill , toAvoid , out rewriting , out notCoveredAttributes , true /* isRelaxed */ ) ;
//Must be able to find the rewriting since the query is one of the views
// otherwise it means condition on the fragment is not satisfiable
if ( ! found )
{
LeftCellWrapper fragment = _context . AllWrappersForExtent . First ( lcr = > lcr . FragmentQuery . Equals ( toFill . Query ) ) ;
Debug . Assert ( fragment ! = null ) ;
ErrorLog . Record record = new ErrorLog . Record ( true , ViewGenErrorCode . ImpopssibleCondition , Strings . Viewgen_QV_RewritingNotFound ( fragment . RightExtent . ToString ( ) ) , fragment . Cells , String . Empty ) ;
_errorLog . AddEntry ( record ) ;
}
else
{
outputUsedViews . UnionWith ( rewriting . GetNamedQueries ( ) ) ;
}
}
}
}
// Modifies _caseStatements and _topLevelWhereClause
private List < LeftCellWrapper > RemapFromVariables ( )
{
List < LeftCellWrapper > usedCells = new List < LeftCellWrapper > ( ) ;
// remap CellIdBooleans appearing in WHEN clauses and in topLevelWhereClause so the first used cell = 0, second = 1, etc.
// This ordering is exploited in CQL generation
int newNumber = 0 ;
Dictionary < BoolLiteral , BoolLiteral > literalRemap = new Dictionary < BoolLiteral , BoolLiteral > ( BoolLiteral . EqualityIdentifierComparer ) ;
foreach ( LeftCellWrapper leftCellWrapper in _context . AllWrappersForExtent )
{
if ( _usedViews . Contains ( leftCellWrapper . FragmentQuery ) )
{
usedCells . Add ( leftCellWrapper ) ;
int oldNumber = leftCellWrapper . OnlyInputCell . CellNumber ;
if ( newNumber ! = oldNumber )
{
literalRemap [ new CellIdBoolean ( _identifiers , oldNumber ) ] = new CellIdBoolean ( _identifiers , newNumber ) ;
}
newNumber + + ;
}
}
if ( literalRemap . Count > 0 )
{
// Remap _from literals in WHERE clause
_topLevelWhereClause = _topLevelWhereClause . RemapLiterals ( literalRemap ) ;
// Remap _from literals in case statements
Dictionary < MemberPath , CaseStatement > newCaseStatements = new Dictionary < MemberPath , CaseStatement > ( ) ;
foreach ( var entry in _caseStatements )
{
CaseStatement newCaseStatement = new CaseStatement ( entry . Key ) ;
Debug . Assert ( entry . Value . ElseValue = = null ) ;
foreach ( CaseStatement . WhenThen clause in entry . Value . Clauses )
{
newCaseStatement . AddWhenThen ( clause . Condition . RemapLiterals ( literalRemap ) , clause . Value ) ;
}
newCaseStatements [ entry . Key ] = newCaseStatement ;
}
_caseStatements = newCaseStatements ;
}
return usedCells ;
}
// for backward compatibility: add (WHEN True THEN Type) for non-scalar types
internal void AddTrivialCaseStatementsForConditionMembers ( )
{
for ( int memberNum = 0 ; memberNum < _context . MemberMaps . ProjectedSlotMap . Count ; memberNum + + )
{
MemberPath memberPath = _context . MemberMaps . ProjectedSlotMap [ memberNum ] ;
if ( ! memberPath . IsScalarType ( ) & & ! _caseStatements . ContainsKey ( memberPath ) )
{
Constant typeConstant = new TypeConstant ( memberPath . EdmType ) ;
{
CaseStatement caseStmt = new CaseStatement ( memberPath ) ;
caseStmt . AddWhenThen ( BoolExpression . True , new ConstantProjectedSlot ( typeConstant , memberPath ) ) ;
_caseStatements [ memberPath ] = caseStmt ;
}
}
}
}
#endregion
#region Computing rewriting
// Find rewriting for query SELECT <attributes> WHERE <whereClause> FROM _extentPath
// and add view appearing in rewriting to outputUsedViews
private bool FindRewritingAndUsedViews ( IEnumerable < MemberPath > attributes , BoolExpression whereClause ,
HashSet < FragmentQuery > outputUsedViews , out Tile < FragmentQuery > rewriting )
{
IEnumerable < MemberPath > notCoveredAttributes ;
return FindRewritingAndUsedViews ( attributes , whereClause , outputUsedViews , out rewriting ,
out notCoveredAttributes ) ;
}
// Find rewriting for query SELECT <attributes> WHERE <whereClause> FROM _extentPath
// and add view appearing in rewriting to outputUsedViews
private bool FindRewritingAndUsedViews ( IEnumerable < MemberPath > attributes , BoolExpression whereClause ,
HashSet < FragmentQuery > outputUsedViews , out Tile < FragmentQuery > rewriting ,
out IEnumerable < MemberPath > notCoveredAttributes )
{
if ( FindRewriting ( attributes , whereClause , out rewriting , out notCoveredAttributes ) )
{
outputUsedViews . UnionWith ( rewriting . GetNamedQueries ( ) ) ;
return true ;
}
return false ;
}
// Find rewriting for query SELECT <attributes> WHERE <whereClause> FROM _extentPath
private bool FindRewriting ( IEnumerable < MemberPath > attributes , BoolExpression whereClause ,
out Tile < FragmentQuery > rewriting , out IEnumerable < MemberPath > notCoveredAttributes )
{
Tile < FragmentQuery > toFill = CreateTile ( FragmentQuery . Create ( attributes , whereClause ) ) ;
Debug . Assert ( toFill . Query . Attributes . Count > 0 , "Query has no attributes?" ) ;
Tile < FragmentQuery > toAvoid = CreateTile ( FragmentQuery . Create ( _keyAttributes , BoolExpression . CreateNot ( whereClause ) ) ) ;
bool isRelaxed = ( _context . ViewTarget = = ViewTarget . UpdateView ) ;
bool found = RewriteQuery ( toFill , toAvoid , out rewriting , out notCoveredAttributes , isRelaxed ) ;
Debug . Assert ( ! found | | rewriting . GetNamedQueries ( ) . All ( q = > q ! = TrueViewSurrogate . Query ) ,
"TrueViewSurrogate should have been substituted" ) ;
return found ;
}
private bool RewriteQuery ( Tile < FragmentQuery > toFill , Tile < FragmentQuery > toAvoid , out Tile < FragmentQuery > rewriting , out IEnumerable < MemberPath > notCoveredAttributes ,
bool isRelaxed )
{
notCoveredAttributes = new List < MemberPath > ( ) ;
// first, find a rewriting for WHERE clause only
FragmentQuery toFillQuery = toFill . Query ;
if ( _context . TryGetCachedRewriting ( toFillQuery , out rewriting ) )
{
TraceVerbose ( "Cached rewriting {0}: {1}" , toFill , rewriting ) ;
return true ; // query with attributes is already cached
}
// Filter the relevant views. These may include a TrueSurrogate view
IEnumerable < Tile < FragmentQuery > > relevantViews = GetRelevantViews ( toFillQuery , isRelaxed ) ;
FragmentQuery originalToFillQuery = toFillQuery ;
if ( ! RewriteQueryCached ( CreateTile ( FragmentQuery . Create ( toFillQuery . Condition ) ) , toAvoid , relevantViews , out rewriting ) )
{
if ( isRelaxed )
{
// don't give up quite yet
toFillQuery = FragmentQuery . Create ( toFillQuery . Attributes , BoolExpression . CreateAndNot ( toFillQuery . Condition , rewriting . Query . Condition ) ) ;
if ( _qp . IsEmpty ( CreateTile ( toFillQuery ) ) | |
! RewriteQueryCached ( CreateTile ( FragmentQuery . Create ( toFillQuery . Condition ) ) , toAvoid , relevantViews , out rewriting ) )
{
return false ; // finally give up
}
}
else
{
return false ;
}
}
if ( toFillQuery . Attributes . Count = = 0 )
{
// return w/o trying to remove TrueSurrogate from view - it's an attribute-less view
// we keep TrueSurrogate there because it may be expanded in various ways for
// different projected attributes
return true ;
}
// now we have the rewriting for WHERE
Dictionary < MemberPath , FragmentQuery > attributeConditions = new Dictionary < MemberPath , FragmentQuery > ( ) ;
foreach ( MemberPath attribute in NonKeys ( toFillQuery . Attributes ) )
{
attributeConditions [ attribute ] = toFillQuery ;
}
if ( attributeConditions . Count = = 0 | | CoverAttributes ( ref rewriting , toFillQuery , attributeConditions ) )
{
GetUsedViewsAndRemoveTrueSurrogate ( ref rewriting ) ;
_context . SetCachedRewriting ( originalToFillQuery , rewriting ) ;
return true ; // all attributes are covered
}
else if ( isRelaxed )
{
// re-initialize attributeConditions by subtracting the remaining attributes to cover
foreach ( MemberPath attribute in NonKeys ( toFillQuery . Attributes ) )
{
FragmentQuery remainingCondition ;
if ( attributeConditions . TryGetValue ( attribute , out remainingCondition ) )
{
attributeConditions [ attribute ] = FragmentQuery . Create ( BoolExpression . CreateAndNot ( toFillQuery . Condition , remainingCondition . Condition ) ) ;
}
else
{
attributeConditions [ attribute ] = toFillQuery ;
}
}
if ( CoverAttributes ( ref rewriting , toFillQuery , attributeConditions ) )
{
GetUsedViewsAndRemoveTrueSurrogate ( ref rewriting ) ;
_context . SetCachedRewriting ( originalToFillQuery , rewriting ) ;
return true ;
}
}
notCoveredAttributes = attributeConditions . Keys ;
return false ;
}
// input views may contain TrueSurrogate
private bool RewriteQueryCached ( Tile < FragmentQuery > toFill , Tile < FragmentQuery > toAvoid ,
IEnumerable < Tile < FragmentQuery > > views , out Tile < FragmentQuery > rewriting )
{
Debug . Assert ( toFill . Query . Attributes . Count = = 0 , "This method is used for attribute-less queries only" ) ;
if ( ! _context . TryGetCachedRewriting ( toFill . Query , out rewriting ) )
{
bool hasRewriting = _qp . RewriteQuery ( toFill , toAvoid , views , out rewriting ) ;
TraceVerbose ( "Computed rewriting {0}: {1}" , toFill , rewriting ) ;
if ( hasRewriting )
{
_context . SetCachedRewriting ( toFill . Query , rewriting ) ;
}
return hasRewriting ;
}
TraceVerbose ( "Cached rewriting {0}: {1}" , toFill , rewriting ) ;
return true ;
}
private bool CoverAttributes ( ref Tile < FragmentQuery > rewriting , FragmentQuery toFillQuery ,
Dictionary < MemberPath , FragmentQuery > attributeConditions )
{
// first, account for already used views
HashSet < FragmentQuery > usedViews = new HashSet < FragmentQuery > ( rewriting . GetNamedQueries ( ) ) ;
Debug . Assert ( usedViews . Count > 0 ) ;
//List<FragmentQuery> usedViewsList = new List<FragmentQuery>(usedViews);
//usedViewsList.Sort(FragmentQuery.GetComparer(toFillQuery.Attributes));
foreach ( FragmentQuery view in usedViews )
{
foreach ( MemberPath projectedAttribute in NonKeys ( view . Attributes ) )
{
CoverAttribute ( projectedAttribute , view , attributeConditions , toFillQuery ) ;
}
if ( attributeConditions . Count = = 0 )
{
return true ; // we are done
}
}
// still need to fill some attributes
Tile < FragmentQuery > attributeTile = null ;
foreach ( FragmentQuery view in _fragmentQueries )
{
foreach ( MemberPath projectedAttribute in NonKeys ( view . Attributes ) )
{
if ( CoverAttribute ( projectedAttribute , view , attributeConditions , toFillQuery ) )
{
attributeTile = ( attributeTile = = null ) ? CreateTile ( view ) : _qp . Union ( attributeTile , CreateTile ( view ) ) ;
}
}
if ( attributeConditions . Count = = 0 )
{
break ; // we are done!
}
}
if ( attributeConditions . Count = = 0 )
{
// yes, we covered all attributes
Debug . Assert ( attributeTile ! = null ) ;
rewriting = _qp . Join ( rewriting , attributeTile ) ;
return true ;
}
else
{
// create rewriting that we couldn't satisfy
return false ; // couldn't cover some attribute(s)
}
}
// returns true if the view is useful for covering the projected attribute
private bool CoverAttribute ( MemberPath projectedAttribute , FragmentQuery view , Dictionary < MemberPath , FragmentQuery > attributeConditions , FragmentQuery toFillQuery )
{
FragmentQuery currentAttributeCondition ;
if ( attributeConditions . TryGetValue ( projectedAttribute , out currentAttributeCondition ) )
{
currentAttributeCondition = FragmentQuery . Create ( BoolExpression . CreateAndNot ( currentAttributeCondition . Condition , view . Condition ) ) ;
if ( _qp . IsEmpty ( CreateTile ( currentAttributeCondition ) ) )
{
// this attribute is covered! remove it from the list
attributeConditions . Remove ( projectedAttribute ) ;
}
else
{
attributeConditions [ projectedAttribute ] = currentAttributeCondition ;
}
return true ;
}
return false ;
}
private IEnumerable < Tile < FragmentQuery > > GetRelevantViews ( FragmentQuery query , bool isRelaxed )
{
// Step 1:
// Determine connected and directly/indirectly connected variables
// Directly connected variables: those that appear in query's WHERE clause
// Indirectly connected variables: directly connected variables + variables in all views that contain directly connected variables
// Disconnected variables: those that appear in some view's WHERE clause but are not indirectly connected
Set < MemberPath > connectedVariables = GetVariables ( query ) ;
// Step 2:
// Take a union of all views that contain connected variables
// If it evaluates to True, we can discard all other views; no special True-view is needed
// Otherwise:
// If isRelaxed == false:
// Take a union of all views. If it yields True, than assume that True-view is available.
// Later, try to pick a smaller subset (instead of all views) once we know that attributes are needed
// If isRelaxed == true:
// Discard all views that don't contain connected variables; assume that True-view is available
Tile < FragmentQuery > unionOfConnectedViews = null ;
List < Tile < FragmentQuery > > connectedViews = new List < Tile < FragmentQuery > > ( ) ;
Tile < FragmentQuery > firstTrueView = null ;
foreach ( Tile < FragmentQuery > tile in _views )
{
// notice: this is a syntactic check. We assume that if the variable is not present in the condition,
// its value is unrestricted (which in general may not be true because the KB may have e.g., X=1 => Y=1,
// so even if condition on Y is absent, the view would still be relevant
if ( GetVariables ( tile . Query ) . Overlaps ( connectedVariables ) )
{
unionOfConnectedViews = ( unionOfConnectedViews = = null ) ? tile : _qp . Union ( unionOfConnectedViews , tile ) ;
connectedViews . Add ( tile ) ;
}
else if ( IsTrue ( tile . Query ) & & firstTrueView = = null )
{
firstTrueView = tile ; // don't add True views; only one of them might be needed, if at all
}
}
if ( unionOfConnectedViews ! = null & &
IsTrue ( unionOfConnectedViews . Query ) ) // the collected views give us "True"
{
return connectedViews ;
}
if ( firstTrueView = = null )
{
// can we obtain True at all?
Tile < FragmentQuery > unionTile = null ;
foreach ( FragmentQuery view in _fragmentQueries )
{
unionTile = ( unionTile = = null ) ? CreateTile ( view ) : _qp . Union ( unionTile , CreateTile ( view ) ) ;
if ( IsTrue ( unionTile . Query ) )
{
// yes, we can; use a surrogate view - replace it later
firstTrueView = TrueViewSurrogate ;
break ;
}
}
}
if ( firstTrueView ! = null ) // the collected views don't give us True, but
{
connectedViews . Add ( firstTrueView ) ;
return connectedViews ;
}
// Step 3:
// For each indirectly-connected variable x:
// Union all views that contain x. The condition on x must disappear, i.e., union must imply that x is in Domain(x)
// That is, the union must be equivalent to the expression in which all conditions on x have been eliminated.
// If that's not the case (i.e., can't get rid of x), remove all these views from consideration.
return _views ;
}
private HashSet < FragmentQuery > GetUsedViewsAndRemoveTrueSurrogate ( ref Tile < FragmentQuery > rewriting )
{
HashSet < FragmentQuery > usedViews = new HashSet < FragmentQuery > ( rewriting . GetNamedQueries ( ) ) ;
if ( ! usedViews . Contains ( TrueViewSurrogate . Query ) )
{
return usedViews ; // no surrogate
}
// remove the surrogate
usedViews . Remove ( TrueViewSurrogate . Query ) ;
// first, try to union usedViews to see whether we can get True
Tile < FragmentQuery > unionTile = null ;
IEnumerable < FragmentQuery > usedFollowedByUnusedViews = usedViews . Concat ( _fragmentQueries ) ;
foreach ( FragmentQuery view in usedFollowedByUnusedViews )
{
unionTile = ( unionTile = = null ) ? CreateTile ( view ) : _qp . Union ( unionTile , CreateTile ( view ) ) ;
usedViews . Add ( view ) ;
if ( IsTrue ( unionTile . Query ) )
{
// we found a true rewriting
rewriting = rewriting . Replace ( TrueViewSurrogate , unionTile ) ;
return usedViews ;
}
}
// now we either found the rewriting or we can just take all views because we are in relaxed mode for update views
Debug . Fail ( "Shouldn't happen" ) ;
return usedViews ;
}
#endregion
#region Helper methods
private BoolExpression CreateMemberCondition ( MemberPath path , Constant domainValue )
{
return FragmentQuery . CreateMemberCondition ( path , domainValue , _domainMap ) ;
}
private FragmentQuery CreateMemberConditionQuery ( MemberPath currentPath , Constant domainValue )
{
return CreateMemberConditionQuery ( currentPath , domainValue , _keyAttributes , _domainMap ) ;
}
internal static FragmentQuery CreateMemberConditionQuery ( MemberPath currentPath , Constant domainValue ,
IEnumerable < MemberPath > keyAttributes , MemberDomainMap domainMap )
{
// construct WHERE clause for this value
BoolExpression domainWhereClause = FragmentQuery . CreateMemberCondition ( currentPath , domainValue , domainMap ) ;
// get a rewriting for CASE statements by not requesting any attributes beyond key
IEnumerable < MemberPath > attributes = keyAttributes ;
if ( domainValue is NegatedConstant )
{
// we need the attribute value
attributes = keyAttributes . Concat ( new MemberPath [ ] { currentPath } ) ;
}
return FragmentQuery . Create ( attributes , domainWhereClause ) ;
}
private static TileNamed < FragmentQuery > CreateTile ( FragmentQuery query )
{
return new TileNamed < FragmentQuery > ( query ) ;
}
private static IEnumerable < Constant > GetTypeConstants ( IEnumerable < EdmType > types )
{
foreach ( EdmType type in types )
{
yield return new TypeConstant ( type ) ;
}
}
private static IEnumerable < MemberPath > GetNonConditionalScalarMembers ( EdmType edmType , MemberPath currentPath , MemberDomainMap domainMap )
{
return currentPath . GetMembers ( edmType , true /* isScalar */ , false /* isConditional */ , null /* isPartOfKey */ , domainMap ) ;
}
private static IEnumerable < MemberPath > GetConditionalComplexMembers ( EdmType edmType , MemberPath currentPath , MemberDomainMap domainMap )
{
return currentPath . GetMembers ( edmType , false /* isScalar */ , true /* isConditional */ , null /* isPartOfKey */ , domainMap ) ;
}
private static IEnumerable < MemberPath > GetNonConditionalComplexMembers ( EdmType edmType , MemberPath currentPath , MemberDomainMap domainMap )
{
return currentPath . GetMembers ( edmType , false /* isScalar */ , false /* isConditional */ , null /* isPartOfKey */ , domainMap ) ;
}
private static IEnumerable < MemberPath > GetConditionalScalarMembers ( EdmType edmType , MemberPath currentPath , MemberDomainMap domainMap )
{
return currentPath . GetMembers ( edmType , true /* isScalar */ , true /* isConditional */ , null /* isPartOfKey */ , domainMap ) ;
}
private IEnumerable < MemberPath > NonKeys ( IEnumerable < MemberPath > attributes )
{
return attributes . Where ( attr = > ! attr . IsPartOfKey ) ;
}
// allows us to check whether a found rewriting is satisfiable
// by taking into account the "other side" of mapping constraints
// (Ultimately, should produce a CQT and use general-purpose query containment)
internal static CellTreeNode TileToCellTree ( Tile < FragmentQuery > tile , ViewgenContext context )
{
if ( tile . OpKind = = TileOpKind . Named )
{
FragmentQuery view = ( ( TileNamed < FragmentQuery > ) tile ) . NamedQuery ;
LeftCellWrapper leftCellWrapper = context . AllWrappersForExtent . First ( w = > w . FragmentQuery = = view ) ;
return new LeafCellTreeNode ( context , leftCellWrapper ) ;
}
CellTreeOpType opType ;
switch ( tile . OpKind )
{
case TileOpKind . Join : opType = CellTreeOpType . IJ ; break ;
case TileOpKind . AntiSemiJoin : opType = CellTreeOpType . LASJ ; break ;
case TileOpKind . Union : opType = CellTreeOpType . Union ; break ;
default :
Debug . Fail ( "unexpected" ) ;
return null ;
}
return new OpCellTreeNode ( context , opType ,
TileToCellTree ( tile . Arg1 , context ) ,
TileToCellTree ( tile . Arg2 , context ) ) ;
}
private static BoolExpression TileToBoolExpr ( Tile < FragmentQuery > tile )
{
switch ( tile . OpKind )
{
case TileOpKind . Named :
FragmentQuery view = ( ( TileNamed < FragmentQuery > ) tile ) . NamedQuery ;
if ( view . Condition . IsAlwaysTrue ( ) )
{
return BoolExpression . True ;
}
else
{
Debug . Assert ( view . FromVariable ! = null ) ;
return view . FromVariable ;
}
case TileOpKind . Join :
return BoolExpression . CreateAnd ( TileToBoolExpr ( tile . Arg1 ) , TileToBoolExpr ( tile . Arg2 ) ) ;
case TileOpKind . AntiSemiJoin :
return BoolExpression . CreateAnd ( TileToBoolExpr ( tile . Arg1 ) , BoolExpression . CreateNot ( TileToBoolExpr ( tile . Arg2 ) ) ) ;
case TileOpKind . Union :
return BoolExpression . CreateOr ( TileToBoolExpr ( tile . Arg1 ) , TileToBoolExpr ( tile . Arg2 ) ) ;
default :
Debug . Fail ( "unexpected" ) ;
return null ;
}
}
private static bool IsDefaultValue ( Constant domainValue , MemberPath path )
{
if ( domainValue . IsNull ( ) & & path . IsNullable )
{
return true ;
}
if ( path . DefaultValue ! = null )
{
ScalarConstant scalarConstant = domainValue as ScalarConstant ;
return scalarConstant . Value = = path . DefaultValue ;
}
return false ;
}
// Returns MemberPaths which have conditions in the where clause
// Filters out all trivial conditions (e.g., num=1 where dom(num)={1})
// i.e., where all constants from the domain are contained in range
private Set < MemberPath > GetVariables ( FragmentQuery query )
{
IEnumerable < MemberPath > memberVariables =
from domainConstraint in query . Condition . VariableConstraints
where domainConstraint . Variable . Identifier is MemberRestriction & &
false = = domainConstraint . Variable . Domain . All ( constant = > domainConstraint . Range . Contains ( constant ) )
select ( ( MemberRestriction ) domainConstraint . Variable . Identifier ) . RestrictedMemberSlot . MemberPath ;
return new Set < MemberPath > ( memberVariables , MemberPath . EqualityComparer ) ;
}
private bool IsTrue ( FragmentQuery query )
{
return ! _context . LeftFragmentQP . IsSatisfiable ( FragmentQuery . Create ( BoolExpression . CreateNot ( query . Condition ) ) ) ;
}
[Conditional("DEBUG")]
private void PrintStatistics ( RewritingProcessor < Tile < FragmentQuery > > qp )
{
int numSATChecks ;
int numIntersection ;
int numDifference ;
int numUnion ;
int numErrors ;
qp . GetStatistics ( out numSATChecks , out numIntersection , out numUnion , out numDifference , out numErrors ) ;
TraceVerbose ( "{0} containment checks, {4} set operations ({1} intersections + {2} unions + {3} differences)" ,
numSATChecks , numIntersection , numUnion , numDifference ,
numIntersection + numUnion + numDifference ) ;
TraceVerbose ( "{0} errors" , numErrors ) ;
}
[Conditional("DEBUG")]
internal void TraceVerbose ( string msg , params object [ ] parameters )
{
if ( _config . IsVerboseTracing )
{
Helpers . FormatTraceLine ( msg , parameters ) ;
}
}
#endregion
}
}