1 //---------------------------------------------------------------------
2 // <copyright file="ViewGenerator.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
10 using System
.Data
.Common
.CommandTrees
;
11 using System
.Data
.Common
.Utils
;
12 using System
.Data
.Common
.Utils
.Boolean
;
13 using System
.Data
.Mapping
.ViewGeneration
.Structures
;
14 using System
.Data
.Mapping
.ViewGeneration
.Validation
;
15 using System
.Data
.Mapping
.ViewGeneration
.QueryRewriting
;
16 using System
.Collections
.Generic
;
18 using System
.Diagnostics
;
19 using System
.Data
.Mapping
.ViewGeneration
.Utils
;
20 using System
.Data
.Metadata
.Edm
;
23 namespace System
.Data
.Mapping
.ViewGeneration
25 using ViewSet
= KeyToListMap
<EntitySetBase
, GeneratedView
>;
26 using CellGroup
= Set
<Cell
>;
27 using WrapperBoolExpr
= BoolExpr
<LeftCellWrapper
>;
28 using WrapperTrueExpr
= TrueExpr
<LeftCellWrapper
>;
29 using WrapperFalseExpr
= FalseExpr
<LeftCellWrapper
>;
30 using WrapperNotExpr
= NotExpr
<LeftCellWrapper
>;
31 using WrapperOrExpr
= OrExpr
<LeftCellWrapper
>;
33 // This class is responsible for generating query or update mapping
34 // views from the initial cells.
35 internal class ViewGenerator
: InternalBase
38 private CellGroup m_cellGroup
; // The initial cells from which we produce views
39 private ConfigViewGenerator m_config
; // Configuration variables
40 private MemberDomainMap m_queryDomainMap
;
41 private MemberDomainMap m_updateDomainMap
;
42 private Dictionary
<EntitySetBase
, QueryRewriter
> m_queryRewriterCache
;
43 private List
<ForeignConstraint
> m_foreignKeyConstraints
;
44 private StorageEntityContainerMapping m_entityContainerMapping
;
47 #region Internal API - Only Gatekeeper calls it
49 // effects: Creates a ViewGenerator object that is capable of
50 // producing query or update mapping views given the relevant schema
52 internal ViewGenerator(CellGroup cellGroup
, ConfigViewGenerator config
,
53 List
<ForeignConstraint
> foreignKeyConstraints
,
54 StorageEntityContainerMapping entityContainerMapping
)
57 m_cellGroup
= cellGroup
;
59 m_queryRewriterCache
= new Dictionary
<EntitySetBase
, QueryRewriter
>();
60 m_foreignKeyConstraints
= foreignKeyConstraints
;
61 m_entityContainerMapping
= entityContainerMapping
;
63 Dictionary
<EntityType
, Set
<EntityType
>> inheritanceGraph
= MetadataHelper
.BuildUndirectedGraphOfTypes(entityContainerMapping
.StorageMappingItemCollection
.EdmItemCollection
);
64 SetConfiguration(entityContainerMapping
);
66 // We fix all the cells at this point
67 m_queryDomainMap
= new MemberDomainMap(ViewTarget
.QueryView
, m_config
.IsValidationEnabled
, cellGroup
, entityContainerMapping
.StorageMappingItemCollection
.EdmItemCollection
, m_config
, inheritanceGraph
);
68 m_updateDomainMap
= new MemberDomainMap(ViewTarget
.UpdateView
, m_config
.IsValidationEnabled
, cellGroup
, entityContainerMapping
.StorageMappingItemCollection
.EdmItemCollection
, m_config
, inheritanceGraph
);
70 // We now go and fix the queryDomain map so that it has all the
71 // values from the S-side as well -- this is needed for domain
72 // constraint propagation, i.e., values from the S-side get
73 // propagated to te oneOfConst on the C-side. So we better get
74 // the "possiblveValues" stuff to contain those constants as well
75 MemberDomainMap
.PropagateUpdateDomainToQueryDomain(cellGroup
, m_queryDomainMap
, m_updateDomainMap
);
77 UpdateWhereClauseForEachCell(cellGroup
, m_queryDomainMap
, m_updateDomainMap
, m_config
);
79 // We need to simplify cell queries, yet we don't want the conditions to disappear
80 // So, add an extra value to the domain, temporarily
81 MemberDomainMap queryOpenDomain
= m_queryDomainMap
.GetOpenDomain();
82 MemberDomainMap updateOpenDomain
= m_updateDomainMap
.GetOpenDomain();
84 // Make sure the WHERE clauses of the cells reflect the changes
85 foreach (Cell cell
in cellGroup
)
87 cell
.CQuery
.WhereClause
.FixDomainMap(queryOpenDomain
);
88 cell
.SQuery
.WhereClause
.FixDomainMap(updateOpenDomain
);
89 cell
.CQuery
.WhereClause
.ExpensiveSimplify();
90 cell
.SQuery
.WhereClause
.ExpensiveSimplify();
91 cell
.CQuery
.WhereClause
.FixDomainMap(m_queryDomainMap
);
92 cell
.SQuery
.WhereClause
.FixDomainMap(m_updateDomainMap
);
96 private void SetConfiguration(StorageEntityContainerMapping entityContainerMapping
)
98 m_config
.IsValidationEnabled
= entityContainerMapping
.Validate
;
99 m_config
.GenerateUpdateViews
= entityContainerMapping
.GenerateUpdateViews
;
102 // effects: Generates views for the particular cellgroup in this. Returns an
103 // error log describing the errors that were encountered (if none
104 // were encountered, the ErrorLog.Count is 0). Places the generated
106 internal ErrorLog
GenerateAllBidirectionalViews(ViewSet views
, CqlIdentifiers identifiers
)
109 // Allow missing attributes for now to make entity splitting run through
110 // we cannot do this for query views in general: need to obtain the exact enumerated domain
112 if (m_config
.IsNormalTracing
)
114 StringBuilder builder
= new StringBuilder();
115 Cell
.CellsToBuilder(builder
, m_cellGroup
);
116 Helpers
.StringTraceLine(builder
.ToString());
119 m_config
.SetTimeForFinishedActivity(PerfType
.CellCreation
);
120 // Check if the cellgroup is consistent and all known S constraints are
121 // satisified by the known C constraints
122 CellGroupValidator validator
= new CellGroupValidator(m_cellGroup
, m_config
);
123 ErrorLog errorLog
= validator
.Validate();
125 if (errorLog
.Count
> 0)
127 errorLog
.PrintTrace();
131 m_config
.SetTimeForFinishedActivity(PerfType
.KeyConstraint
);
133 // We generate update views first since they perform the main
135 if (m_config
.GenerateUpdateViews
)
137 errorLog
= GenerateDirectionalViews(ViewTarget
.UpdateView
, identifiers
, views
);
138 if (errorLog
.Count
> 0)
140 return errorLog
; // If we have discovered errors here, do not generate query views
144 // Make sure that the foreign key constraints are not violated
145 if (m_config
.IsValidationEnabled
)
147 CheckForeignKeyConstraints(errorLog
);
149 m_config
.SetTimeForFinishedActivity(PerfType
.ForeignConstraint
);
151 if (errorLog
.Count
> 0)
153 errorLog
.PrintTrace();
154 return errorLog
; // If we have discovered errors here, do not generate query views
157 // Query views - do not allow missing attributes
158 // For the S-side, we add NOT ... for each scalar constant so
159 // that if we have C, P in the mapping but the store has C, P, S,
160 // we can handle it in the query views
161 m_updateDomainMap
.ExpandDomainsToIncludeAllPossibleValues();
163 errorLog
= GenerateDirectionalViews(ViewTarget
.QueryView
, identifiers
, views
);
168 internal ErrorLog
GenerateQueryViewForSingleExtent(ViewSet views
, CqlIdentifiers identifiers
, EntitySetBase entity
, EntityTypeBase type
, ViewGenMode mode
)
170 Debug
.Assert(mode
!= ViewGenMode
.GenerateAllViews
);
172 if (m_config
.IsNormalTracing
)
174 StringBuilder builder
= new StringBuilder();
175 Cell
.CellsToBuilder(builder
, m_cellGroup
);
176 Helpers
.StringTraceLine(builder
.ToString());
179 // Check if the cellgroup is consistent and all known S constraints are
180 // satisified by the known C constraints
181 CellGroupValidator validator
= new CellGroupValidator(m_cellGroup
, m_config
);
182 ErrorLog errorLog
= validator
.Validate();
183 if (errorLog
.Count
> 0)
185 errorLog
.PrintTrace();
189 // Make sure that the foreign key constraints are not violated
190 if (m_config
.IsValidationEnabled
)
192 CheckForeignKeyConstraints(errorLog
);
195 if (errorLog
.Count
> 0)
197 errorLog
.PrintTrace();
198 return errorLog
; // If we have discovered errors here, do not generate query views
201 // For the S-side, we add NOT ... for each scalar constant so
202 // that if we have C, P in the mapping but the store has C, P, S,
203 // we can handle it in the query views
204 m_updateDomainMap
.ExpandDomainsToIncludeAllPossibleValues();
206 foreach (Cell cell
in m_cellGroup
)
208 cell
.SQuery
.WhereClause
.FixDomainMap(m_updateDomainMap
);
211 errorLog
= GenerateQueryViewForExtentAndType(m_entityContainerMapping
, identifiers
, views
, entity
, type
, mode
);
221 #region Private Methods
223 // effects: Given the extent cells and a map for the domains of all
224 // variables in it, fixes the cell constant domains of the where
225 // clauses in the left queries of cells (left is defined using viewTarget)
226 private static void UpdateWhereClauseForEachCell(IEnumerable
<Cell
> extentCells
, MemberDomainMap queryDomainMap
,
227 MemberDomainMap updateDomainMap
, ConfigViewGenerator config
)
229 foreach (Cell cell
in extentCells
)
231 cell
.CQuery
.UpdateWhereClause(queryDomainMap
);
232 cell
.SQuery
.UpdateWhereClause(updateDomainMap
);
235 // Fix enumerable domains - currently it is only applicable to boolean type. Note that it is
236 // not applicable to enumerated types since we allow any value of the underlying type of the enum type.
237 queryDomainMap
.ReduceEnumerableDomainToEnumeratedValues(ViewTarget
.QueryView
, config
);
238 updateDomainMap
.ReduceEnumerableDomainToEnumeratedValues(ViewTarget
.UpdateView
, config
);
242 private ErrorLog
GenerateQueryViewForExtentAndType(StorageEntityContainerMapping entityContainerMapping
, CqlIdentifiers identifiers
, ViewSet views
, EntitySetBase entity
, EntityTypeBase type
, ViewGenMode mode
)
244 Debug
.Assert(mode
!= ViewGenMode
.GenerateAllViews
);
246 // Keep track of the mapping exceptions that we have generated
247 ErrorLog errorLog
= new ErrorLog();
249 if (m_config
.IsViewTracing
)
251 Helpers
.StringTraceLine(String
.Empty
);
252 Helpers
.StringTraceLine(String
.Empty
);
253 Helpers
.FormatTraceLine("================= Generating {0} Query View for: {1} ===========================",
254 (mode
== ViewGenMode
.OfTypeViews
) ? "OfType" : "OfTypeOnly",
256 Helpers
.StringTraceLine(String
.Empty
);
257 Helpers
.StringTraceLine(String
.Empty
);
262 // (1) view generation (checks that extents are fully mapped)
263 ViewgenContext context
= CreateViewgenContext(entity
, ViewTarget
.QueryView
, identifiers
);
264 QueryRewriter queryRewriter
= GenerateViewsForExtentAndType(type
, context
, identifiers
, views
, mode
);
266 catch (InternalMappingException exception
)
268 // All exceptions have mapping errors in them
269 Debug
.Assert(exception
.ErrorLog
.Count
> 0, "Incorrectly created mapping exception");
270 errorLog
.Merge(exception
.ErrorLog
);
277 // requires: schema refers to C-side or S-side schema for the cells
278 // inside this. if schema.IsQueryView is true, the left side of cells refers
279 // to the C side (and vice-versa for the right side)
280 // effects: Generates the relevant views for the schema side and
281 // returns them. If allowMissingAttributes is true and attributes
282 // are missing on the schema side, substitutes them with NULL
283 // Modifies views to contain the generated views for different
284 // extents specified by cells and the the schemaContext
285 private ErrorLog
GenerateDirectionalViews(ViewTarget viewTarget
, CqlIdentifiers identifiers
, ViewSet views
)
287 bool isQueryView
= viewTarget
== ViewTarget
.QueryView
;
289 // Partition cells by extent.
290 KeyToListMap
<EntitySetBase
, Cell
> extentCellMap
= GroupCellsByExtent(m_cellGroup
, viewTarget
);
292 // Keep track of the mapping exceptions that we have generated
293 ErrorLog errorLog
= new ErrorLog();
295 // Generate views for each extent
296 foreach (EntitySetBase extent
in extentCellMap
.Keys
)
298 if (m_config
.IsViewTracing
)
300 Helpers
.StringTraceLine(String
.Empty
);
301 Helpers
.StringTraceLine(String
.Empty
);
302 Helpers
.FormatTraceLine("================= Generating {0} View for: {1} ===========================",
303 isQueryView
? "Query" : "Update", extent
.Name
);
304 Helpers
.StringTraceLine(String
.Empty
);
305 Helpers
.StringTraceLine(String
.Empty
);
309 // (1) view generation (checks that extents are fully mapped)
310 QueryRewriter queryRewriter
= GenerateDirectionalViewsForExtent(viewTarget
, extent
, identifiers
, views
);
312 // (2) validation for update views
313 if (viewTarget
== ViewTarget
.UpdateView
&&
314 m_config
.IsValidationEnabled
)
316 if (m_config
.IsViewTracing
)
318 Helpers
.StringTraceLine(String
.Empty
);
319 Helpers
.StringTraceLine(String
.Empty
);
320 Helpers
.FormatTraceLine("----------------- Validation for generated update view for: {0} -----------------",
322 Helpers
.StringTraceLine(String
.Empty
);
323 Helpers
.StringTraceLine(String
.Empty
);
326 RewritingValidator validator
= new RewritingValidator(queryRewriter
.ViewgenContext
, queryRewriter
.BasicView
);
327 validator
.Validate();
331 catch (InternalMappingException exception
)
333 // All exceptions have mapping errors in them
334 Debug
.Assert(exception
.ErrorLog
.Count
> 0,
335 "Incorrectly created mapping exception");
336 errorLog
.Merge(exception
.ErrorLog
);
343 // effects: Generates a view for an extent "extent" that belongs to
344 // schema "schema". extentCells are the cells for this extent.
345 // Adds the view corrsponding to the extent to "views"
346 private QueryRewriter
GenerateDirectionalViewsForExtent(ViewTarget viewTarget
, EntitySetBase extent
, CqlIdentifiers identifiers
, ViewSet views
)
349 // First normalize the cells in terms of multiconstants, etc
350 // and then generate the view for the extent
351 ViewgenContext context
= CreateViewgenContext(extent
, viewTarget
, identifiers
);
352 QueryRewriter queryRewriter
= null;
354 if (m_config
.GenerateViewsForEachType
)
356 // generate views for each OFTYPE(Extent, Type) combination
357 foreach (EdmType type
in MetadataHelper
.GetTypeAndSubtypesOf(extent
.ElementType
, m_entityContainerMapping
.StorageMappingItemCollection
.EdmItemCollection
, false /*includeAbstractTypes*/))
359 if (m_config
.IsViewTracing
&& false == type
.Equals(extent
.ElementType
))
361 Helpers
.FormatTraceLine("CQL View for {0} and type {1}", extent
.Name
, type
.Name
);
363 queryRewriter
= GenerateViewsForExtentAndType(type
, context
, identifiers
, views
, ViewGenMode
.OfTypeViews
);
368 // generate the view for Extent only
369 queryRewriter
= GenerateViewsForExtentAndType(extent
.ElementType
, context
, identifiers
, views
, ViewGenMode
.OfTypeViews
);
371 if (viewTarget
== ViewTarget
.QueryView
)
373 m_config
.SetTimeForFinishedActivity(PerfType
.QueryViews
);
377 m_config
.SetTimeForFinishedActivity(PerfType
.UpdateViews
);
380 // cache this rewriter (and context inside it) for future use in FK checking
381 m_queryRewriterCache
[extent
] = queryRewriter
;
382 return queryRewriter
;
385 // effects: Returns a context corresponding to extent (if one does not exist, creates one)
386 private ViewgenContext
CreateViewgenContext(EntitySetBase extent
, ViewTarget viewTarget
, CqlIdentifiers identifiers
)
388 QueryRewriter queryRewriter
;
389 if (!m_queryRewriterCache
.TryGetValue(extent
, out queryRewriter
))
391 // collect the cells that belong to this extent (just a few of them since we segment the mapping first)
392 var cellsForExtent
= m_cellGroup
.Where(c
=> c
.GetLeftQuery(viewTarget
).Extent
== extent
);
394 return new ViewgenContext(viewTarget
, extent
, cellsForExtent
, identifiers
, m_config
, m_queryDomainMap
, m_updateDomainMap
, m_entityContainerMapping
);
398 return queryRewriter
.ViewgenContext
;
403 private QueryRewriter
GenerateViewsForExtentAndType(EdmType generatedType
, ViewgenContext context
, CqlIdentifiers identifiers
, ViewSet views
, ViewGenMode mode
)
406 Debug
.Assert(mode
!= ViewGenMode
.GenerateAllViews
, "By definition this method can not handle generating views for all extents");
408 QueryRewriter queryRewriter
= new QueryRewriter(generatedType
, context
, mode
);
409 queryRewriter
.GenerateViewComponents();
411 // Get the basic view
412 CellTreeNode basicView
= queryRewriter
.BasicView
;
414 if (m_config
.IsNormalTracing
)
416 Helpers
.StringTrace("Basic View: ");
417 Helpers
.StringTraceLine(basicView
.ToString());
420 CellTreeNode simplifiedView
= GenerateSimplifiedView(basicView
, queryRewriter
.UsedCells
);
422 if (m_config
.IsNormalTracing
)
424 Helpers
.StringTraceLine(String
.Empty
);
425 Helpers
.StringTrace("Simplified View: ");
426 Helpers
.StringTraceLine(simplifiedView
.ToString());
429 CqlGenerator cqlGen
= new CqlGenerator(simplifiedView
,
430 queryRewriter
.CaseStatements
,
432 context
.MemberMaps
.ProjectedSlotMap
,
433 queryRewriter
.UsedCells
.Count
,
434 queryRewriter
.TopLevelWhereClause
,
435 m_entityContainerMapping
.StorageMappingItemCollection
);
438 DbQueryCommandTree commandTree
;
439 if (m_config
.GenerateEsql
)
441 eSQLView
= cqlGen
.GenerateEsql();
447 commandTree
= cqlGen
.GenerateCqt();
450 GeneratedView generatedView
= GeneratedView
.CreateGeneratedView(context
.Extent
, generatedType
, commandTree
, eSQLView
, m_entityContainerMapping
.StorageMappingItemCollection
, m_config
);
451 views
.Add(context
.Extent
, generatedView
);
453 return queryRewriter
;
456 private CellTreeNode
GenerateSimplifiedView(CellTreeNode basicView
, List
<LeftCellWrapper
> usedCells
)
458 Debug
.Assert(false == basicView
.IsEmptyRightFragmentQuery
, "Basic view is empty?");
460 // create 'joined' variables, one for each cell
461 // We know (say) that out of the 10 cells that we were given, only 7 (say) were
462 // needed to construct the view for this extent.
463 int numBoolVars
= usedCells
.Count
;
464 // We need the boolean expressions in Simplify. Precisely ont boolean expression is set to
465 // true in each cell query
467 for (int i
= 0; i
< numBoolVars
; i
++)
469 // In the ith cell, set its boolean to be true (i.e., ith boolean)
470 usedCells
[i
].RightCellQuery
.InitializeBoolExpressions(numBoolVars
, i
);
473 CellTreeNode simplifiedView
= CellTreeSimplifier
.MergeNodes(basicView
);
474 return simplifiedView
;
477 private void CheckForeignKeyConstraints(ErrorLog errorLog
)
479 foreach (ForeignConstraint constraint
in m_foreignKeyConstraints
)
481 QueryRewriter childRewriter
= null;
482 QueryRewriter parentRewriter
= null;
483 m_queryRewriterCache
.TryGetValue(constraint
.ChildTable
, out childRewriter
);
484 m_queryRewriterCache
.TryGetValue(constraint
.ParentTable
, out parentRewriter
);
485 constraint
.CheckConstraint(m_cellGroup
, childRewriter
, parentRewriter
, errorLog
, m_config
);
489 // effects: Given all the cells for a container, groups the cells by
490 // the left query's extent and returns a dictionary for it
491 private static KeyToListMap
<EntitySetBase
, Cell
> GroupCellsByExtent(IEnumerable
<Cell
> cells
, ViewTarget viewTarget
)
494 // Partition cells by extent -- extent is the top node in
495 // the tree. Even for compositions for now? CHANGE_Microsoft_FEATURE_COMPOSITION
496 KeyToListMap
<EntitySetBase
, Cell
> extentCellMap
=
497 new KeyToListMap
<EntitySetBase
, Cell
>(EqualityComparer
<EntitySetBase
>.Default
);
498 foreach (Cell cell
in cells
)
500 // Get the cell query and determine its extent
501 CellQuery cellQuery
= cell
.GetLeftQuery(viewTarget
);
502 extentCellMap
.Add(cellQuery
.Extent
, cell
);
504 return extentCellMap
;
509 #region String Methods
510 internal override void ToCompactString(StringBuilder builder
)
512 Cell
.CellsToBuilder(builder
, m_cellGroup
);