Imported Upstream version 4.0.0~alpha1

Former-commit-id: 806294f5ded97629b74c85c09952f2a74fe182d9
This commit is contained in:
Jo Shields
2015-04-07 09:35:12 +01:00
parent 283343f570
commit 3c1f479b9d
22469 changed files with 2931443 additions and 869343 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,159 @@
//---------------------------------------------------------------------
// <copyright file="CellPartioner.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Data.Common.Utils;
using System.Data.Mapping.ViewGeneration.Structures;
using System.Collections.Generic;
using System.Data.Mapping.ViewGeneration.Validation;
using System.Text;
using System.Data.Mapping.Update.Internal;
using System.Collections.ObjectModel;
using System.Data.Metadata.Edm;
namespace System.Data.Mapping.ViewGeneration
{
using CellGroup = Set<Cell>;
// This class is responsible for partitioning cells into groups of cells
// that are related and for which view generation needs to be done together
internal class CellPartitioner : InternalBase
{
#region Constructor
// effects: Creates a partitioner for cells with extra information
// about foreign key constraints
internal CellPartitioner(IEnumerable<Cell> cells, IEnumerable<ForeignConstraint> foreignKeyConstraints)
{
m_foreignKeyConstraints = foreignKeyConstraints;
m_cells = cells;
}
#endregion
#region Fields
private IEnumerable<Cell> m_cells;
private IEnumerable<ForeignConstraint> m_foreignKeyConstraints;
#endregion
#region Available Methods
// effects: Given a list of cells, segments them into multiple
// "groups" such that view generation (including validation) of one
// group can be done independently of another group. Returns the
// groups as a list (uses the foreign key information as well)
internal List<CellGroup> GroupRelatedCells()
{
// If two cells share the same C or S, we place them in the same group
// For each cell, determine the Cis and Sis that it refers
// to. For every Ci (Si), keep track of the cells that Ci is
// contained in. At the end, run through the Cis and Sis and do a
// "connected components" algorithm to determine partitions
// Now form a graph between different cells -- then compute the connected
// components in it
UndirectedGraph<Cell> graph = new UndirectedGraph<Cell>(EqualityComparer<Cell>.Default);
List<Cell> alreadyAddedCells = new List<Cell>();
// For each extent, add an edge between it and all previously
// added extents with which it overlaps
foreach (Cell cell in m_cells)
{
graph.AddVertex(cell);
// Add an edge from this cell to the already added cells
EntitySetBase firstCExtent = cell.CQuery.Extent;
EntitySetBase firstSExtent = cell.SQuery.Extent;
foreach (Cell existingCell in alreadyAddedCells)
{
EntitySetBase secondCExtent = existingCell.CQuery.Extent;
EntitySetBase secondSExtent = existingCell.SQuery.Extent;
// Add an edge between cell and existingCell if
// * They have the same C or S extent
// * They are linked via a foreign key between the S extents
// * They are linked via a relationship
bool sameExtent = secondCExtent.Equals(firstCExtent) || secondSExtent.Equals(firstSExtent);
bool linkViaForeignKey = OverlapViaForeignKeys(cell, existingCell);
bool linkViaRelationship = AreCellsConnectedViaRelationship(cell, existingCell);
if (sameExtent || linkViaForeignKey || linkViaRelationship)
{
graph.AddEdge(existingCell, cell);
}
}
alreadyAddedCells.Add(cell);
}
// Now determine the connected components of this graph
List<CellGroup> result = GenerateConnectedComponents(graph);
return result;
}
#endregion
#region Private Methods
// effects: Returns true iff cell1 is an extent at the end of cell2's
// relationship set or vice versa
private static bool AreCellsConnectedViaRelationship(Cell cell1, Cell cell2)
{
AssociationSet cRelationSet1 = cell1.CQuery.Extent as AssociationSet;
AssociationSet cRelationSet2 = cell2.CQuery.Extent as AssociationSet;
if (cRelationSet1 != null && MetadataHelper.IsExtentAtSomeRelationshipEnd(cRelationSet1, cell2.CQuery.Extent))
{
return true;
}
if (cRelationSet2 != null && MetadataHelper.IsExtentAtSomeRelationshipEnd(cRelationSet2, cell1.CQuery.Extent))
{
return true;
}
return false;
}
// effects: Given a graph of cell groups, returns a list of cellgroup
// such that each cellgroup contains all the cells that are in the
// same connected component
private static List<CellGroup> GenerateConnectedComponents(UndirectedGraph<Cell> graph)
{
KeyToListMap<int, Cell> groupMap = graph.GenerateConnectedComponents();
// Run through the list of groups and generate the merged groups
List<CellGroup> result = new List<CellGroup>();
foreach (int setNum in groupMap.Keys)
{
ReadOnlyCollection<Cell> cellsInComponent = groupMap.ListForKey(setNum);
CellGroup component = new CellGroup(cellsInComponent);
result.Add(component);
}
return result;
}
// effects: Returns true iff there is a foreign key constraint
// between cell1 and cell2's S extents
private bool OverlapViaForeignKeys(Cell cell1, Cell cell2)
{
EntitySetBase sExtent1 = cell1.SQuery.Extent;
EntitySetBase sExtent2 = cell2.SQuery.Extent;
foreach (ForeignConstraint constraint in m_foreignKeyConstraints)
{
if (sExtent1.Equals(constraint.ParentTable) && sExtent2.Equals(constraint.ChildTable) ||
sExtent2.Equals(constraint.ParentTable) && sExtent1.Equals(constraint.ChildTable))
{
return true;
}
}
return false;
}
#endregion
internal override void ToCompactString(StringBuilder builder)
{
Cell.CellsToBuilder(builder, m_cells);
}
}
}

View File

@@ -0,0 +1,192 @@
//---------------------------------------------------------------------
// <copyright file="ConfigViewGenerator.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Data.Common.Utils;
using System.Text;
using System.Diagnostics;
namespace System.Data.Mapping.ViewGeneration
{
internal enum ViewGenMode
{
GenerateAllViews = 0,
OfTypeViews,
OfTypeOnlyViews
}
internal enum ViewGenTraceLevel
{
None = 0,
ViewsOnly,
Normal,
Verbose
}
internal enum PerfType
{
InitialSetup = 0,
CellCreation,
KeyConstraint,
ViewgenContext,
UpdateViews,
DisjointConstraint,
PartitionConstraint,
DomainConstraint,
ForeignConstraint,
QueryViews,
BoolResolution,
Unsatisfiability,
ViewParsing,
}
/// <summary>
/// This class holds some configuration information for the view generation code.
/// </summary>
internal sealed class ConfigViewGenerator : InternalBase
{
#region Constructors
internal ConfigViewGenerator()
{
m_watch = new Stopwatch();
m_singleWatch = new Stopwatch();
int numEnums = Enum.GetNames(typeof(PerfType)).Length;
m_breakdownTimes = new TimeSpan[numEnums];
m_traceLevel = ViewGenTraceLevel.None;
m_generateUpdateViews = false;
StartWatch();
}
#endregion
#region Fields
private bool m_generateViewsForEachType;
private ViewGenTraceLevel m_traceLevel;
private readonly TimeSpan[] m_breakdownTimes;
private Stopwatch m_watch;
/// <summary>
/// To measure a single thing at a time.
/// </summary>
private Stopwatch m_singleWatch;
/// <summary>
/// Perf op being measured.
/// </summary>
private PerfType m_singlePerfOp;
private bool m_enableValidation = true;
private bool m_generateUpdateViews = true;
private bool m_generateEsql = false;
#endregion
#region Properties
/// <summary>
/// If true then view generation will produce eSQL, otherwise CQTs only.
/// </summary>
internal bool GenerateEsql
{
get { return m_generateEsql; }
set { m_generateEsql = value; }
}
/// <summary>
/// Callers can set elements in this list.
/// </summary>
internal TimeSpan[] BreakdownTimes
{
get { return m_breakdownTimes; }
}
internal ViewGenTraceLevel TraceLevel
{
get { return m_traceLevel; }
set { m_traceLevel = value; }
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal bool IsValidationEnabled
{
get { return m_enableValidation; }
set { m_enableValidation = value; }
}
internal bool GenerateUpdateViews
{
get { return m_generateUpdateViews; }
set { m_generateUpdateViews = value; }
}
internal bool GenerateViewsForEachType
{
get { return m_generateViewsForEachType; }
set { m_generateViewsForEachType = value; }
}
internal bool IsViewTracing
{
get { return IsTraceAllowed(ViewGenTraceLevel.ViewsOnly); }
}
internal bool IsNormalTracing
{
get { return IsTraceAllowed(ViewGenTraceLevel.Normal); }
}
internal bool IsVerboseTracing
{
get { return IsTraceAllowed(ViewGenTraceLevel.Verbose); }
}
#endregion
#region Methods
private void StartWatch()
{
m_watch.Start();
}
internal void StartSingleWatch(PerfType perfType)
{
m_singleWatch.Start();
m_singlePerfOp = perfType;
}
/// <summary>
/// Sets time for <paramref name="perfType"/> for the individual timer.
/// </summary>
internal void StopSingleWatch(PerfType perfType)
{
Debug.Assert(m_singlePerfOp == perfType, "Started op for different activity " + m_singlePerfOp + " -- not " + perfType);
TimeSpan timeElapsed = m_singleWatch.Elapsed;
int index = (int)perfType;
m_singleWatch.Stop();
m_singleWatch.Reset();
BreakdownTimes[index] = BreakdownTimes[index].Add(timeElapsed);
}
/// <summary>
/// Sets time for <paramref name="perfType"/> since the last call to <see cref="SetTimeForFinishedActivity"/>.
/// </summary>
/// <param name="perfType"></param>
internal void SetTimeForFinishedActivity(PerfType perfType)
{
TimeSpan timeElapsed = m_watch.Elapsed;
int index = (int)perfType;
BreakdownTimes[index] = BreakdownTimes[index].Add(timeElapsed);
m_watch.Reset();
m_watch.Start();
}
internal bool IsTraceAllowed(ViewGenTraceLevel traceLevel)
{
return TraceLevel >= traceLevel;
}
internal override void ToCompactString(StringBuilder builder)
{
StringUtil.FormatStringBuilder(builder, "Trace Switch: {0}", m_traceLevel);
}
#endregion
}
}

View File

@@ -0,0 +1,108 @@
//---------------------------------------------------------------------
// <copyright file="AliasedSlot.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Linq;
using System.Data.Mapping.ViewGeneration.Structures;
using System.Text;
using System.Diagnostics;
using System.Data.Common.CommandTrees;
using System.Data.Common.CommandTrees.ExpressionBuilder;
using System.Data.Common.Utils;
using System.Collections.Generic;
namespace System.Data.Mapping.ViewGeneration.CqlGeneration
{
/// <summary>
/// Encapsulates a slot in a particular cql block.
/// </summary>
internal sealed class QualifiedSlot : ProjectedSlot
{
#region Constructor
/// <summary>
/// Creates a qualified slot "block_alias.slot_alias"
/// </summary>
internal QualifiedSlot(CqlBlock block, ProjectedSlot slot)
{
Debug.Assert(block != null && slot != null, "Null input to QualifiedSlot constructor");
m_block = block;
m_slot = slot; // Note: slot can be another qualified slot.
}
#endregion
#region Fields
private readonly CqlBlock m_block;
private readonly ProjectedSlot m_slot;
#endregion
#region Methods
/// <summary>
/// Creates new <see cref="ProjectedSlot"/> that is qualified with <paramref name="block"/>.CqlAlias.
/// If current slot is composite (such as <see cref="CaseStatementProjectedSlot"/>, then this method recursively qualifies all parts
/// and returns a new deeply qualified slot (as opposed to <see cref="CqlBlock.QualifySlotWithBlockAlias"/>).
/// </summary>
internal override ProjectedSlot DeepQualify(CqlBlock block)
{
// We take the slot inside this and change the block
QualifiedSlot result = new QualifiedSlot(block, m_slot);
return result;
}
/// <summary>
/// Delegates alias generation to the leaf slot in the qualified chain.
/// </summary>
internal override string GetCqlFieldAlias(MemberPath outputMember)
{
// Keep looking inside the chain of qualified slots till we find a non-qualified slot and then get the alias name for it.
string result = GetOriginalSlot().GetCqlFieldAlias(outputMember);
return result;
}
/// <summary>
/// Walks the chain of <see cref="QualifiedSlot"/>s starting from the current one and returns the original slot.
/// </summary>
internal ProjectedSlot GetOriginalSlot()
{
ProjectedSlot slot = m_slot;
while (true)
{
QualifiedSlot qualifiedSlot = slot as QualifiedSlot;
if (qualifiedSlot == null)
{
break;
}
slot = qualifiedSlot.m_slot;
}
return slot;
}
internal string GetQualifiedCqlName(MemberPath outputMember)
{
return CqlWriter.GetQualifiedName(m_block.CqlAlias, GetCqlFieldAlias(outputMember));
}
internal override StringBuilder AsEsql(StringBuilder builder, MemberPath outputMember, string blockAlias, int indentLevel)
{
Debug.Assert(blockAlias == null || m_block.CqlAlias == blockAlias, "QualifiedSlot: blockAlias mismatch");
builder.Append(GetQualifiedCqlName(outputMember));
return builder;
}
internal override DbExpression AsCqt(DbExpression row, MemberPath outputMember)
{
return m_block.GetInput(row).Property(GetCqlFieldAlias(outputMember));
}
internal override void ToCompactString(StringBuilder builder)
{
StringUtil.FormatStringBuilder(builder, "{0} ", m_block.CqlAlias);
m_slot.ToCompactString(builder);
}
#endregion
}
}

View File

@@ -0,0 +1,99 @@
//---------------------------------------------------------------------
// <copyright file="BooleanProjectedSlot.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Data.Mapping.ViewGeneration.Structures;
using System.Text;
using System.Diagnostics;
using System.Data.Common.CommandTrees;
using System.Data.Common.CommandTrees.ExpressionBuilder;
using System.Data.Common.Utils;
namespace System.Data.Mapping.ViewGeneration.CqlGeneration
{
/// <summary>
/// This class represents slots for expressions over boolean variables, e.g., _from0, _from1, etc
/// </summary>
internal sealed class BooleanProjectedSlot : ProjectedSlot
{
#region Constructor
/// <summary>
/// Creates a boolean slot for expression that comes from originalCellNum, i.e.,
/// the value of the slot is <paramref name="expr"/> and the name is "_from{<paramref name="originalCellNum"/>}", e.g., _from2
/// </summary>
internal BooleanProjectedSlot(BoolExpression expr, CqlIdentifiers identifiers, int originalCellNum)
{
m_expr = expr;
m_originalCell = new CellIdBoolean(identifiers, originalCellNum);
Debug.Assert(!(expr.AsLiteral is CellIdBoolean) ||
BoolLiteral.EqualityComparer.Equals((CellIdBoolean)expr.AsLiteral, m_originalCell), "Cellid boolean for the slot and cell number disagree");
}
#endregion
#region Fields
/// <summary>
/// The actual value of the slot - could be <see cref="CellIdBoolean"/>!
/// </summary>
private readonly BoolExpression m_expr;
/// <summary>
/// A boolean corresponding to the original cell number (_from0)
/// </summary>
private readonly CellIdBoolean m_originalCell;
#endregion
#region Methods
/// <summary>
/// Returns "_from0", "_from1" etc. <paramref name="outputMember"/> is ignored.
/// </summary>
internal override string GetCqlFieldAlias(MemberPath outputMember)
{
return m_originalCell.SlotName;
}
internal override StringBuilder AsEsql(StringBuilder builder, MemberPath outputMember, string blockAlias, int indentLevel)
{
if (m_expr.IsTrue || m_expr.IsFalse)
{
// No Case statement for TRUE and FALSE
m_expr.AsEsql(builder, blockAlias);
}
else
{
// Produce "CASE WHEN boolExpr THEN True ELSE False END" in order to enforce the two-state boolean logic:
// if boolExpr returns the boolean Unknown, it gets converted to boolean False.
builder.Append("CASE WHEN ");
m_expr.AsEsql(builder, blockAlias);
builder.Append(" THEN True ELSE False END");
}
return builder;
}
internal override DbExpression AsCqt(DbExpression row, MemberPath outputMember)
{
if (m_expr.IsTrue || m_expr.IsFalse)
{
return m_expr.AsCqt(row);
}
else
{
// Produce "CASE WHEN boolExpr THEN True ELSE False END" in order to enforce the two-state boolean logic:
// if boolExpr returns the boolean Unknown, it gets converted to boolean False.
return DbExpressionBuilder.Case(new DbExpression[] { m_expr.AsCqt(row) }, new DbExpression[] { DbExpressionBuilder.True }, DbExpressionBuilder.False);
}
}
internal override void ToCompactString(StringBuilder builder)
{
StringUtil.FormatStringBuilder(builder, "<{0}, ", m_originalCell.SlotName);
m_expr.ToCompactString(builder);
builder.Append('>');
}
#endregion
}
}

View File

@@ -0,0 +1,96 @@
//---------------------------------------------------------------------
// <copyright file="CaseCqlBlock.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Data.Mapping.ViewGeneration.Structures;
using System.Text;
using System.Collections.Generic;
using System.Data.Common.CommandTrees;
using System.Data.Common.CommandTrees.ExpressionBuilder;
using System.Data.Common.Utils;
using System.Diagnostics;
namespace System.Data.Mapping.ViewGeneration.CqlGeneration
{
/// <summary>
/// A class to capture cql blocks responsible for case statements generating multiconstants, i.e., complex types, entities, discriminators, etc.
/// </summary>
internal sealed class CaseCqlBlock : CqlBlock
{
#region Constructors
/// <summary>
/// Creates a <see cref="CqlBlock"/> containing the case statememt for the <paramref name="caseSlot"/> and projecting other slots as is from its child (input). CqlBlock with SELECT (slots),
/// </summary>
/// <param name="caseSlot">indicates which slot in <paramref name="slots"/> corresponds to the case statement being generated by this block</param>
internal CaseCqlBlock(SlotInfo[] slots, int caseSlot, CqlBlock child, BoolExpression whereClause, CqlIdentifiers identifiers, int blockAliasNum)
: base(slots, new List<CqlBlock>(new CqlBlock[] { child }), whereClause, identifiers, blockAliasNum)
{
m_caseSlotInfo = slots[caseSlot];
}
#endregion
#region Fields
private readonly SlotInfo m_caseSlotInfo;
#endregion
#region Methods
internal override StringBuilder AsEsql(StringBuilder builder, bool isTopLevel, int indentLevel)
{
// The SELECT part
StringUtil.IndentNewLine(builder, indentLevel);
builder.Append("SELECT ");
if (isTopLevel)
{
builder.Append("VALUE ");
}
Debug.Assert(m_caseSlotInfo.OutputMember != null, "We only construct member slots, not boolean slots.");
builder.Append("-- Constructing ").Append(m_caseSlotInfo.OutputMember.LeafName);
Debug.Assert(Children.Count == 1, "CaseCqlBlock can have exactly one child.");
CqlBlock childBlock = Children[0];
base.GenerateProjectionEsql(builder, childBlock.CqlAlias, true, indentLevel, isTopLevel);
// The FROM part: FROM (ChildView) AS AliasName
builder.Append("FROM (");
childBlock.AsEsql(builder, false, indentLevel + 1);
StringUtil.IndentNewLine(builder, indentLevel);
builder.Append(") AS ").Append(childBlock.CqlAlias);
// Get the WHERE part only when the expression is not simply TRUE.
if (false == BoolExpression.EqualityComparer.Equals(this.WhereClause, BoolExpression.True))
{
StringUtil.IndentNewLine(builder, indentLevel);
builder.Append("WHERE ");
this.WhereClause.AsEsql(builder, childBlock.CqlAlias);
}
return builder;
}
internal override DbExpression AsCqt(bool isTopLevel)
{
Debug.Assert(m_caseSlotInfo.OutputMember != null, "We only construct real slots not boolean slots");
// The FROM part: FROM (childBlock)
Debug.Assert(Children.Count == 1, "CaseCqlBlock can have exactly one child.");
CqlBlock childBlock = this.Children[0];
DbExpression cqt = childBlock.AsCqt(false);
// Get the WHERE part only when the expression is not simply TRUE.
if (!BoolExpression.EqualityComparer.Equals(this.WhereClause, BoolExpression.True))
{
cqt = cqt.Where(row => this.WhereClause.AsCqt(row));
}
// The SELECT part.
return cqt.Select(row => GenerateProjectionCqt(row, isTopLevel));
}
#endregion
}
}

View File

@@ -0,0 +1,291 @@
//---------------------------------------------------------------------
// <copyright file="CqlBlock.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Linq;
using System.Data.Common.CommandTrees;
using System.Data.Common.CommandTrees.ExpressionBuilder;
using System.Data.Common.Utils;
using System.Collections.Generic;
using System.Data.Mapping.ViewGeneration.Structures;
using System.Collections.ObjectModel;
using System.Text;
using System.Diagnostics;
namespace System.Data.Mapping.ViewGeneration.CqlGeneration
{
/// <summary>
/// A class that holds an expression of the form "(SELECT .. FROM .. WHERE) AS alias".
/// Essentially, it allows generating Cql query in a localized manner, i.e., all global decisions about nulls, constants,
/// case statements, etc have already been made.
/// </summary>
internal abstract class CqlBlock : InternalBase
{
/// <summary>
/// Initializes a <see cref="CqlBlock"/> with the SELECT (<paramref name="slotInfos"/>), FROM (<paramref name="children"/>),
/// WHERE (<paramref name="whereClause"/>), AS (<paramref name="blockAliasNum"/>).
/// </summary>
protected CqlBlock(SlotInfo[] slotInfos, List<CqlBlock> children, BoolExpression whereClause, CqlIdentifiers identifiers, int blockAliasNum)
{
m_slots = new ReadOnlyCollection<SlotInfo>(slotInfos);
m_children = new ReadOnlyCollection<CqlBlock>(children);
m_whereClause = whereClause;
m_blockAlias = identifiers.GetBlockAlias(blockAliasNum);
}
#region Fields
/// <summary>
/// Essentially, SELECT. May be replaced with another collection after block construction.
/// </summary>
private ReadOnlyCollection<SlotInfo> m_slots;
/// <summary>
/// FROM inputs.
/// </summary>
private readonly ReadOnlyCollection<CqlBlock> m_children;
/// <summary>
/// WHERER.
/// </summary>
private readonly BoolExpression m_whereClause;
/// <summary>
/// Alias of the whole block for cql generation.
/// </summary>
private readonly string m_blockAlias;
/// <summary>
/// See <see cref="JoinTreeContext"/> for more info.
/// </summary>
private JoinTreeContext m_joinTreeContext;
#endregion
#region Properties
/// <summary>
/// Returns all the slots for this block (SELECT).
/// </summary>
internal ReadOnlyCollection<SlotInfo> Slots
{
get { return m_slots; }
set { m_slots = value; }
}
/// <summary>
/// Returns all the child (input) blocks of this block (FROM).
/// </summary>
protected ReadOnlyCollection<CqlBlock> Children
{
get { return m_children; }
}
/// <summary>
/// Returns the where clause of this block (WHERE).
/// </summary>
protected BoolExpression WhereClause
{
get { return m_whereClause; }
}
/// <summary>
/// Returns an alias for this block that can be used for "AS".
/// </summary>
internal string CqlAlias
{
get { return m_blockAlias; }
}
#endregion
#region Abstract Methods
/// <summary>
/// Returns a string corresponding to the eSQL representation of this block (and its children below).
/// </summary>
internal abstract StringBuilder AsEsql(StringBuilder builder, bool isTopLevel, int indentLevel);
/// <summary>
/// Returns a string corresponding to the CQT representation of this block (and its children below).
/// </summary>
internal abstract DbExpression AsCqt(bool isTopLevel);
#endregion
#region Methods
/// <summary>
/// For the given <paramref name="slotNum"/> creates a <see cref="QualifiedSlot"/> qualified with <see cref="CqlAlias"/> of the current block:
/// "<see cref="CqlAlias"/>.slot_alias"
/// </summary>
internal QualifiedSlot QualifySlotWithBlockAlias(int slotNum)
{
Debug.Assert(this.IsProjected(slotNum), StringUtil.FormatInvariant("Slot {0} that is to be qualified with the block alias is not projected in this block", slotNum));
var slotInfo = m_slots[slotNum];
return new QualifiedSlot(this, slotInfo.SlotValue);
}
internal ProjectedSlot SlotValue(int slotNum)
{
Debug.Assert(slotNum < m_slots.Count, "Slotnum too high");
return m_slots[slotNum].SlotValue;
}
internal MemberPath MemberPath(int slotNum)
{
Debug.Assert(slotNum < m_slots.Count, "Slotnum too high");
return m_slots[slotNum].OutputMember;
}
/// <summary>
/// Returns true iff <paramref name="slotNum"/> is being projected by this block.
/// </summary>
internal bool IsProjected(int slotNum)
{
Debug.Assert(slotNum < m_slots.Count, "Slotnum too high");
return m_slots[slotNum].IsProjected;
}
/// <summary>
/// Generates "A, B, C, ..." for all the slots in the block.
/// </summary>
protected void GenerateProjectionEsql(StringBuilder builder, string blockAlias, bool addNewLineAfterEachSlot, int indentLevel, bool isTopLevel)
{
bool isFirst = true;
foreach (SlotInfo slotInfo in Slots)
{
if (false == slotInfo.IsRequiredByParent)
{
// Ignore slots that are not needed
continue;
}
if (isFirst == false)
{
builder.Append(", ");
}
if (addNewLineAfterEachSlot)
{
StringUtil.IndentNewLine(builder, indentLevel + 1);
}
slotInfo.AsEsql(builder, blockAlias, indentLevel);
// Print the field alias for complex expressions that don't produce default alias.
// Don't print alias for qualified fields as they reproduce their alias.
// Don't print alias if it's a top level query using SELECT VALUE.
if (!isTopLevel && (!(slotInfo.SlotValue is QualifiedSlot) || slotInfo.IsEnforcedNotNull))
{
builder.Append(" AS ")
.Append(slotInfo.CqlFieldAlias);
}
isFirst = false;
}
if (addNewLineAfterEachSlot)
{
StringUtil.IndentNewLine(builder, indentLevel);
}
}
/// <summary>
/// Generates "NewRow(A, B, C, ...)" for all the slots in the block.
/// If <paramref name="isTopLevel"/>=true then generates "A" for the only slot that is marked as <see cref="SlotInfo.IsRequiredByParent"/>.
/// </summary>
protected DbExpression GenerateProjectionCqt(DbExpression row, bool isTopLevel)
{
if (isTopLevel)
{
Debug.Assert(this.Slots.Where(slot => slot.IsRequiredByParent).Count() == 1, "Top level projection must project only one slot.");
return this.Slots.Where(slot => slot.IsRequiredByParent).Single().AsCqt(row);
}
else
{
return DbExpressionBuilder.NewRow(
this.Slots.Where(slot => slot.IsRequiredByParent).Select(slot => new KeyValuePair<string, DbExpression>(slot.CqlFieldAlias, slot.AsCqt(row))));
}
}
/// <summary>
/// Initializes context positioning in the join tree that owns the <see cref="CqlBlock"/>.
/// For more info see <see cref="JoinTreeContext"/>.
/// </summary>
internal void SetJoinTreeContext(IList<string> parentQualifiers, string leafQualifier)
{
Debug.Assert(m_joinTreeContext == null, "Join tree context is already set.");
m_joinTreeContext = new JoinTreeContext(parentQualifiers, leafQualifier);
}
/// <summary>
/// Searches the input <paramref name="row"/> for the property that represents the current <see cref="CqlBlock"/>.
/// In all cases except JOIN, the <paramref name="row"/> is returned as is.
/// In case of JOIN, <paramref name="row"/>.JoinVarX.JoinVarY...blockVar is returned.
/// See <see cref="SetJoinTreeContext"/> for more info.
/// </summary>
internal DbExpression GetInput(DbExpression row)
{
return m_joinTreeContext != null ? m_joinTreeContext.FindInput(row) : row;
}
internal override void ToCompactString(StringBuilder builder)
{
for (int i = 0; i < m_slots.Count; i++)
{
StringUtil.FormatStringBuilder(builder, "{0}: ", i);
m_slots[i].ToCompactString(builder);
builder.Append(' ');
}
m_whereClause.ToCompactString(builder);
}
#endregion
#region JoinTreeContext
/// <summary>
/// The class represents a position of a <see cref="CqlBlock"/> in a join tree.
/// It is expected that the join tree is left-recursive (not balanced) and looks like this:
///
/// ___J___
/// / \
/// L3/ \R3
/// / \
/// __J__ \
/// / \ \
/// L2/ \R2 \
/// / \ \
/// _J_ \ \
/// / \ \ \
/// L1/ \R1 \ \
/// / \ \ \
/// CqlBlock1 CqlBlock2 CqlBlock3 CqlBlock4
///
/// Example of <see cref="JoinTreeContext"/>s for the <see cref="CqlBlock"/>s:
/// block# m_parentQualifiers m_indexInParentQualifiers m_leafQualifier FindInput(row) = ...
/// 1 (L2, L3) 0 L1 row.(L3.L2).L1
/// 2 (L2, L3) 0 R1 row.(L3.L2).R1
/// 3 (L2, L3) 1 R2 row.(L3).R2
/// 4 (L2, L3) 2 R3 row.().R3
///
/// </summary>
private sealed class JoinTreeContext
{
internal JoinTreeContext(IList<string> parentQualifiers, string leafQualifier)
{
Debug.Assert(parentQualifiers != null, "parentQualifiers != null");
Debug.Assert(leafQualifier != null, "leafQualifier != null");
m_parentQualifiers = parentQualifiers;
m_indexInParentQualifiers = parentQualifiers.Count;
m_leafQualifier = leafQualifier;
}
private readonly IList<string> m_parentQualifiers;
private readonly int m_indexInParentQualifiers;
private readonly string m_leafQualifier;
internal DbExpression FindInput(DbExpression row)
{
DbExpression cqt = row;
for (int i = m_parentQualifiers.Count - 1; i >= m_indexInParentQualifiers; --i)
{
cqt = cqt.Property(m_parentQualifiers[i]);
}
return cqt.Property(m_leafQualifier);
}
}
#endregion
}
}

View File

@@ -0,0 +1,105 @@
//---------------------------------------------------------------------
// <copyright file="CqlIdentifiers.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Data.Common.Utils;
using System.Text;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
namespace System.Data.Mapping.ViewGeneration.Structures
{
// This class is responsible for ensuring unique aliases for _from0, etc
// and block aliases T, T0, T1, etc
internal class CqlIdentifiers : InternalBase
{
#region Constructor
internal CqlIdentifiers()
{
m_identifiers = new Set<string>(StringComparer.Ordinal);
}
#endregion
#region Fields
private Set<string> m_identifiers;
#endregion
#region Methods
// effects: Given a number, returns _from<num> if it does not clashes with
// any identifier, else returns _from_<next>_<num> where <next> is the first number from 0
// where there is no clash
internal string GetFromVariable(int num)
{
return GetNonConflictingName("_from", num);
}
// effects: Given a number, returns T<num> if it does not clashes with
// any identifier, else returns T_<next>_<num> where <next> is the first number from 0
// where there is no clash
internal string GetBlockAlias(int num)
{
return GetNonConflictingName("T", num);
}
// effects: Given a number, returns T if it does not clashes with
// any identifier, else returns T_<next> where <next> is the first number from 0
// where there is no clash
internal string GetBlockAlias()
{
return GetNonConflictingName("T", -1);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
internal void AddIdentifier(string identifier)
{
m_identifiers.Add(identifier.ToLower(CultureInfo.InvariantCulture));
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
private string GetNonConflictingName(string prefix, int number)
{
// Do a case sensitive search but return the string that uses the
// original prefix
string result = number < 0 ? prefix : StringUtil.FormatInvariant("{0}{1}", prefix, number);
// Check if the prefix exists or not
if (m_identifiers.Contains(result.ToLower(CultureInfo.InvariantCulture)) == false)
{
return result;
}
// Go through integers and find the first one that does not clash
for (int count = 0; count < int.MaxValue; count++)
{
if (number < 0)
{
result = StringUtil.FormatInvariant("{0}_{1}", prefix, count);
}
else
{
result = StringUtil.FormatInvariant("{0}_{1}_{2}", prefix, count, number);
}
if (m_identifiers.Contains(result.ToLower(CultureInfo.InvariantCulture)) == false)
{
return result;
}
}
Debug.Fail("Found no unique _from till MaxValue?");
return null;
}
internal override void ToCompactString(StringBuilder builder)
{
m_identifiers.ToCompactString(builder);
}
#endregion
}
}

View File

@@ -0,0 +1,68 @@
//---------------------------------------------------------------------
// <copyright file="CqlWriter.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Text.RegularExpressions;
using System.Text;
using System.Data.Common.Utils;
using System.Data.Mapping.ViewGeneration.Utils;
using System.Data.Metadata.Edm;
namespace System.Data.Mapping.ViewGeneration.CqlGeneration
{
// This class contains helper methods needed for generating Cql
internal static class CqlWriter
{
#region Fields
private static readonly Regex s_wordIdentifierRegex = new Regex(@"^[_A-Za-z]\w*$", RegexOptions.ECMAScript | RegexOptions.Compiled);
#endregion
#region Helper Methods
// effects: Given a block name and a field in it -- returns a string
// of form "blockName.field". Does not perform any escaping
internal static string GetQualifiedName(string blockName, string field)
{
string result = StringUtil.FormatInvariant("{0}.{1}", blockName, field);
return result;
}
// effects: Modifies builder to contain an escaped version of type's name as "[namespace.typename]"
internal static void AppendEscapedTypeName(StringBuilder builder, EdmType type)
{
AppendEscapedName(builder, GetQualifiedName(type.NamespaceName, type.Name));
}
// effects: Modifies builder to contain an escaped version of "name1.name2" as "[name1].[name2]"
internal static void AppendEscapedQualifiedName(StringBuilder builder, string name1, string name2)
{
AppendEscapedName(builder, name1);
builder.Append('.');
AppendEscapedName(builder, name2);
}
// effects: Modifies builder to contain an escaped version of "name"
internal static void AppendEscapedName(StringBuilder builder, string name)
{
if (s_wordIdentifierRegex.IsMatch(name) && false == ExternalCalls.IsReservedKeyword(name))
{
// We do not need to escape the name if it is a simple name and it is not a keyword
builder.Append(name);
}
else
{
string newName = name.Replace("]", "]]");
builder.Append('[')
.Append(newName)
.Append(']');
}
}
#endregion
}
}

View File

@@ -0,0 +1,101 @@
//---------------------------------------------------------------------
// <copyright file="ExtentCqlBlock.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Text;
using System.Collections.Generic;
using System.Data.Mapping.ViewGeneration.Structures;
using System.Data.Common.CommandTrees;
using System.Data.Common.CommandTrees.ExpressionBuilder;
using System.Data.Common.Utils;
using System.Data.Metadata.Edm;
namespace System.Data.Mapping.ViewGeneration.CqlGeneration
{
/// <summary>
/// A class that represents leaf <see cref="CqlBlock"/>s in the <see cref="CqlBlock"/> tree.
/// </summary>
internal sealed class ExtentCqlBlock : CqlBlock
{
#region Constructors
/// <summary>
/// Creates an cql block representing the <paramref name="extent"/> (the FROM part).
/// SELECT is given by <paramref name="slots"/>, WHERE by <paramref name="whereClause"/> and AS by <paramref name="blockAliasNum"/>.
/// </summary>
internal ExtentCqlBlock(EntitySetBase extent,
CellQuery.SelectDistinct selectDistinct,
SlotInfo[] slots,
BoolExpression whereClause,
CqlIdentifiers identifiers,
int blockAliasNum)
: base(slots, EmptyChildren, whereClause, identifiers, blockAliasNum)
{
m_extent = extent;
m_nodeTableAlias = identifiers.GetBlockAlias();
m_selectDistinct = selectDistinct;
}
#endregion
#region Fields
private readonly EntitySetBase m_extent;
private readonly string m_nodeTableAlias;
private readonly CellQuery.SelectDistinct m_selectDistinct;
private static readonly List<CqlBlock> EmptyChildren = new List<CqlBlock>();
#endregion
#region Methods
internal override StringBuilder AsEsql(StringBuilder builder, bool isTopLevel, int indentLevel)
{
// The SELECT/DISTINCT part.
StringUtil.IndentNewLine(builder, indentLevel);
builder.Append("SELECT ");
if (m_selectDistinct == CellQuery.SelectDistinct.Yes)
{
builder.Append("DISTINCT ");
}
GenerateProjectionEsql(builder, m_nodeTableAlias, true, indentLevel, isTopLevel);
// Get the FROM part.
builder.Append("FROM ");
CqlWriter.AppendEscapedQualifiedName(builder, m_extent.EntityContainer.Name, m_extent.Name);
builder.Append(" AS ").Append(m_nodeTableAlias);
// Get the WHERE part only when the expression is not simply TRUE.
if (!BoolExpression.EqualityComparer.Equals(this.WhereClause, BoolExpression.True))
{
StringUtil.IndentNewLine(builder, indentLevel);
builder.Append("WHERE ");
this.WhereClause.AsEsql(builder, m_nodeTableAlias);
}
return builder;
}
internal override DbExpression AsCqt(bool isTopLevel)
{
// Get the FROM part.
DbExpression cqt = m_extent.Scan();
// Get the WHERE part only when the expression is not simply TRUE.
if (!BoolExpression.EqualityComparer.Equals(this.WhereClause, BoolExpression.True))
{
cqt = cqt.Where(row => this.WhereClause.AsCqt(row));
}
// The SELECT/DISTINCT part.
cqt = cqt.Select(row => GenerateProjectionCqt(row, isTopLevel));
if (m_selectDistinct == CellQuery.SelectDistinct.Yes)
{
cqt = cqt.Distinct();
}
return cqt;
}
#endregion
}
}

View File

@@ -0,0 +1,262 @@
//---------------------------------------------------------------------
// <copyright file="JoinCqlBlock.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Data.Common.CommandTrees;
using System.Data.Common.CommandTrees.ExpressionBuilder;
using System.Data.Common.Utils;
using System.Text;
using System.Collections.Generic;
using System.Data.Mapping.ViewGeneration.Structures;
using System.Diagnostics;
namespace System.Data.Mapping.ViewGeneration.CqlGeneration
{
/// <summary>
/// Represents to the various Join nodes in the view: IJ, LOJ, FOJ.
/// </summary>
internal sealed class JoinCqlBlock : CqlBlock
{
#region Constructor
/// <summary>
/// Creates a join block (type given by <paramref name="opType"/>) with SELECT (<paramref name="slotInfos"/>), FROM (<paramref name="children"/>),
/// ON (<paramref name="onClauses"/> - one for each child except 0th), WHERE (true), AS (<paramref name="blockAliasNum"/>).
/// </summary>
internal JoinCqlBlock(CellTreeOpType opType,
SlotInfo[] slotInfos,
List<CqlBlock> children,
List<OnClause> onClauses,
CqlIdentifiers identifiers,
int blockAliasNum)
: base(slotInfos, children, BoolExpression.True, identifiers, blockAliasNum)
{
m_opType = opType;
m_onClauses = onClauses;
}
#endregion
#region Fields
private readonly CellTreeOpType m_opType;
private readonly List<OnClause> m_onClauses;
#endregion
#region Methods
internal override StringBuilder AsEsql(StringBuilder builder, bool isTopLevel, int indentLevel)
{
// The SELECT part.
StringUtil.IndentNewLine(builder, indentLevel);
builder.Append("SELECT ");
GenerateProjectionEsql(
builder,
null, /* There is no single input, so the blockAlias is null. ProjectedSlot objects will have to carry their own input block info:
* see QualifiedSlot and QualifiedCellIdBoolean for more info. */
false,
indentLevel,
isTopLevel);
StringUtil.IndentNewLine(builder, indentLevel);
// The FROM part by joining all the children using ON Clauses.
builder.Append("FROM ");
int i = 0;
foreach (CqlBlock child in Children)
{
if (i > 0)
{
StringUtil.IndentNewLine(builder, indentLevel + 1);
builder.Append(OpCellTreeNode.OpToEsql(m_opType));
}
builder.Append(" (");
child.AsEsql(builder, false, indentLevel + 1);
builder.Append(") AS ")
.Append(child.CqlAlias);
// The ON part.
if (i > 0)
{
StringUtil.IndentNewLine(builder, indentLevel + 1);
builder.Append("ON ");
m_onClauses[i - 1].AsEsql(builder);
}
i++;
}
return builder;
}
internal override DbExpression AsCqt(bool isTopLevel)
{
// The FROM part:
// - build a tree of binary joins out of the inputs (this.Children).
// - update each child block with its relative position in the join tree,
// so that QualifiedSlot and QualifiedCellIdBoolean objects could find their
// designated block areas inside the cumulative join row passed into their AsCqt(row) method.
CqlBlock leftmostBlock = this.Children[0];
DbExpression left = leftmostBlock.AsCqt(false);
List<string> joinTreeCtxParentQualifiers = new List<string>();
for (int i = 1; i < this.Children.Count; ++i)
{
// Join the current left expression (a tree) to the current right block.
CqlBlock rightBlock = this.Children[i];
DbExpression right = rightBlock.AsCqt(false);
Func<DbExpression, DbExpression, DbExpression> joinConditionFunc = m_onClauses[i - 1].AsCqt;
DbJoinExpression join;
switch (m_opType)
{
case CellTreeOpType.FOJ:
join = left.FullOuterJoin(right, joinConditionFunc);
break;
case CellTreeOpType.IJ:
join = left.InnerJoin(right, joinConditionFunc);
break;
case CellTreeOpType.LOJ:
join = left.LeftOuterJoin(right, joinConditionFunc);
break;
default:
Debug.Fail("Unknown operator");
return null;
}
if (i == 1)
{
// Assign the joinTreeContext to the leftmost block.
leftmostBlock.SetJoinTreeContext(joinTreeCtxParentQualifiers, join.Left.VariableName);
}
else
{
// Update the joinTreeCtxParentQualifiers.
// Note that all blocks that already participate in the left expression tree share the same copy of the joinTreeContext.
joinTreeCtxParentQualifiers.Add(join.Left.VariableName);
}
// Assign the joinTreeContext to the right block.
rightBlock.SetJoinTreeContext(joinTreeCtxParentQualifiers, join.Right.VariableName);
left = join;
}
// The SELECT part.
return left.Select(row => GenerateProjectionCqt(row, false));
}
#endregion
/// <summary>
/// Represents a complete ON clause "slot1 == slot2 AND "slot3 == slot4" ... for two <see cref="JoinCqlBlock"/>s.
/// </summary>
internal sealed class OnClause : InternalBase
{
#region Constructor
internal OnClause()
{
m_singleClauses = new List<SingleClause>();
}
#endregion
#region Fields
private readonly List<SingleClause> m_singleClauses;
#endregion
#region Methods
/// <summary>
/// Adds an <see cref="SingleClause"/> element for a join of the form <paramref name="leftSlot"/> = <paramref name="rightSlot"/>.
/// </summary>
internal void Add(QualifiedSlot leftSlot, MemberPath leftSlotOutputMember, QualifiedSlot rightSlot, MemberPath rightSlotOutputMember)
{
SingleClause singleClause = new SingleClause(leftSlot, leftSlotOutputMember, rightSlot, rightSlotOutputMember);
m_singleClauses.Add(singleClause);
}
/// <summary>
/// Generates eSQL string of the form "LeftSlot1 = RightSlot1 AND LeftSlot2 = RightSlot2 AND ...
/// </summary>
internal StringBuilder AsEsql(StringBuilder builder)
{
bool isFirst = true;
foreach (SingleClause singleClause in m_singleClauses)
{
if (false == isFirst)
{
builder.Append(" AND ");
}
singleClause.AsEsql(builder);
isFirst = false;
}
return builder;
}
/// <summary>
/// Generates CQT of the form "LeftSlot1 = RightSlot1 AND LeftSlot2 = RightSlot2 AND ...
/// </summary>
internal DbExpression AsCqt(DbExpression leftRow, DbExpression rightRow)
{
DbExpression cqt = m_singleClauses[0].AsCqt(leftRow, rightRow);
for (int i = 1; i < m_singleClauses.Count; ++i)
{
cqt = cqt.And(m_singleClauses[i].AsCqt(leftRow, rightRow));
}
return cqt;
}
internal override void ToCompactString(StringBuilder builder)
{
builder.Append("ON ");
StringUtil.ToSeparatedString(builder, m_singleClauses, " AND ");
}
#endregion
#region SingleClause
/// <summary>
/// Represents an expression between slots of the form: LeftSlot = RightSlot
/// </summary>
private sealed class SingleClause : InternalBase
{
internal SingleClause(QualifiedSlot leftSlot, MemberPath leftSlotOutputMember, QualifiedSlot rightSlot, MemberPath rightSlotOutputMember)
{
m_leftSlot = leftSlot;
m_leftSlotOutputMember = leftSlotOutputMember;
m_rightSlot = rightSlot;
m_rightSlotOutputMember = rightSlotOutputMember;
}
#region Fields
private readonly QualifiedSlot m_leftSlot;
private readonly MemberPath m_leftSlotOutputMember;
private readonly QualifiedSlot m_rightSlot;
private readonly MemberPath m_rightSlotOutputMember;
#endregion
#region Methods
/// <summary>
/// Generates eSQL string of the form "leftSlot = rightSlot".
/// </summary>
internal StringBuilder AsEsql(StringBuilder builder)
{
builder.Append(m_leftSlot.GetQualifiedCqlName(m_leftSlotOutputMember))
.Append(" = ")
.Append(m_rightSlot.GetQualifiedCqlName(m_rightSlotOutputMember));
return builder;
}
/// <summary>
/// Generates CQT of the form "leftSlot = rightSlot".
/// </summary>
internal DbExpression AsCqt(DbExpression leftRow, DbExpression rightRow)
{
return m_leftSlot.AsCqt(leftRow, m_leftSlotOutputMember).Equal(m_rightSlot.AsCqt(rightRow, m_rightSlotOutputMember));
}
internal override void ToCompactString(StringBuilder builder)
{
m_leftSlot.ToCompactString(builder);
builder.Append(" = ");
m_rightSlot.ToCompactString(builder);
}
#endregion
}
#endregion
}
}
}

View File

@@ -0,0 +1,186 @@
//---------------------------------------------------------------------
// <copyright file="SlotInfo.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Data.Common.CommandTrees;
using System.Data.Common.CommandTrees.ExpressionBuilder;
using System.Data.Common.Utils;
using System.Data.Mapping.ViewGeneration.Structures;
using System.Text;
using System.Diagnostics;
namespace System.Data.Mapping.ViewGeneration.CqlGeneration
{
/// <summary>
/// A class that keeps track of slot information in a <see cref="CqlBlock"/>.
/// </summary>
internal sealed class SlotInfo : InternalBase
{
#region Constructor
/// <summary>
/// Creates a <see cref="SlotInfo"/> for a <see cref="CqlBlock"/> X with information about whether this slot is needed by X's parent
/// (<paramref name="isRequiredByParent"/>), whether X projects it (<paramref name="isProjected"/>) along with the slot value (<paramref name="slotValue"/>) and
/// the output member path (<paramref name="outputMember"/> (for regular/non-boolean slots) for the slot.
/// </summary>
internal SlotInfo(bool isRequiredByParent, bool isProjected, ProjectedSlot slotValue, MemberPath outputMember)
: this(isRequiredByParent, isProjected, slotValue, outputMember, false /* enforceNotNull */)
{ }
/// <summary>
/// Creates a <see cref="SlotInfo"/> for a <see cref="CqlBlock"/> X with information about whether this slot is needed by X's parent
/// (<paramref name="isRequiredByParent"/>), whether X projects it (<paramref name="isProjected"/>) along with the slot value (<paramref name="slotValue"/>) and
/// the output member path (<paramref name="outputMember"/> (for regular/non-boolean slots) for the slot.
/// </summary>
/// <param name="enforceNotNull">We need to ensure that _from variables are never null since view generation uses 2-valued boolean logic.
/// If <paramref name="enforceNotNull"/>=true, the generated Cql adds a condition (AND <paramref name="slotValue"/> NOT NULL).
/// This flag is used only for boolean slots.</param>
internal SlotInfo(bool isRequiredByParent, bool isProjected, ProjectedSlot slotValue, MemberPath outputMember, bool enforceNotNull)
{
m_isRequiredByParent = isRequiredByParent;
m_isProjected = isProjected;
m_slotValue = slotValue;
m_outputMember = outputMember;
m_enforceNotNull = enforceNotNull;
Debug.Assert(false == m_isRequiredByParent || m_slotValue != null, "Required slots cannot be null");
Debug.Assert(m_slotValue is QualifiedSlot ||
(m_slotValue == null && m_outputMember == null) || // unused boolean slot
(m_slotValue is BooleanProjectedSlot) == (m_outputMember == null),
"If slot is boolean slot, there is no member path for it and vice-versa");
}
#endregion
#region Fields
/// <summary>
/// If slot is required by the parent. Can be reset to false in <see cref="ResetIsRequiredByParent"/> method.
/// </summary>
private bool m_isRequiredByParent;
/// <summary>
/// If the node is capable of projecting this slot.
/// </summary>
private readonly bool m_isProjected;
/// <summary>
/// The slot represented by this <see cref="SlotInfo"/>.
/// </summary>
private readonly ProjectedSlot m_slotValue;
/// <summary>
/// The output member path of this slot.
/// </summary>
private readonly MemberPath m_outputMember;
/// <summary>
/// Whether to add AND NOT NULL to Cql.
/// </summary>
private readonly bool m_enforceNotNull;
#endregion
#region Properties
/// <summary>
/// Returns true iff this slot is required by the <see cref="CqlBlock"/>'s parent.
/// Can be reset to false by calling <see cref="ResetIsRequiredByParent"/> method.
/// </summary>
internal bool IsRequiredByParent
{
get { return m_isRequiredByParent; }
}
/// <summary>
/// Returns true iff this slot is projected by this <see cref="CqlBlock"/>.
/// </summary>
internal bool IsProjected
{
get { return m_isProjected; }
}
/// <summary>
/// Returns the output memberpath of this slot
/// </summary>
internal MemberPath OutputMember
{
get { return m_outputMember; }
}
/// <summary>
/// Returns the slot value corresponfing to this object.
/// </summary>
internal ProjectedSlot SlotValue
{
get { return m_slotValue; }
}
/// <summary>
/// Returns the Cql alias for this slot, e.g., "CPerson1_Pid", "_from0", etc
/// </summary>
internal string CqlFieldAlias
{
get
{
return m_slotValue != null ? m_slotValue.GetCqlFieldAlias(m_outputMember) : null;
}
}
/// <summary>
/// Returns true if Cql generated for the slot needs to have an extra AND IS NOT NULL condition.
/// </summary>
internal bool IsEnforcedNotNull
{
get { return m_enforceNotNull; }
}
#endregion
#region Methods
/// <summary>
/// Sets the <see cref="IsRequiredByParent"/> to false.
/// Note we don't have a setter because we don't want people to set this field to true after the object has been created.
/// </summary>
internal void ResetIsRequiredByParent()
{
m_isRequiredByParent = false;
}
/// <summary>
/// Generates eSQL representation of the slot. For different slots, the result is different, e.g., "_from0", "CPerson1.pid", "TREAT(....)".
/// </summary>
internal StringBuilder AsEsql(StringBuilder builder, string blockAlias, int indentLevel)
{
if (m_enforceNotNull)
{
builder.Append('(');
m_slotValue.AsEsql(builder, m_outputMember, blockAlias, indentLevel);
builder.Append(" AND ");
m_slotValue.AsEsql(builder, m_outputMember, blockAlias, indentLevel);
builder.Append(" IS NOT NULL)");
}
else
{
m_slotValue.AsEsql(builder, m_outputMember, blockAlias, indentLevel);
}
return builder;
}
/// <summary>
/// Generates CQT representation of the slot.
/// </summary>
internal DbExpression AsCqt(DbExpression row)
{
DbExpression cqt = m_slotValue.AsCqt(row, m_outputMember);
if (m_enforceNotNull)
{
cqt = cqt.And(cqt.IsNull().Not());
}
return cqt;
}
internal override void ToCompactString(StringBuilder builder)
{
if (m_slotValue != null)
{
builder.Append(CqlFieldAlias);
}
}
#endregion
}
}

View File

@@ -0,0 +1,69 @@
//---------------------------------------------------------------------
// <copyright file="UnionCqlBlock.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Collections.Generic;
using System.Text;
using System.Data.Mapping.ViewGeneration.Structures;
using System.Data.Common.CommandTrees;
using System.Data.Common.CommandTrees.ExpressionBuilder;
using System.Data.Common.Utils;
using System.Diagnostics;
namespace System.Data.Mapping.ViewGeneration.CqlGeneration
{
/// <summary>
/// Represents Union nodes in the <see cref="CqlBlock"/> tree.
/// </summary>
internal sealed class UnionCqlBlock : CqlBlock
{
#region Constructor
/// <summary>
/// Creates a union block with SELECT (<paramref name="slotInfos"/>), FROM (<paramref name="children"/>), WHERE (true), AS (<paramref name="blockAliasNum"/>).
/// </summary>
internal UnionCqlBlock(SlotInfo[] slotInfos, List<CqlBlock> children, CqlIdentifiers identifiers, int blockAliasNum)
: base(slotInfos, children, BoolExpression.True, identifiers, blockAliasNum)
{ }
#endregion
#region Methods
internal override StringBuilder AsEsql(StringBuilder builder, bool isTopLevel, int indentLevel)
{
Debug.Assert(this.Children.Count > 0, "UnionCqlBlock: Children collection must not be empty");
// Simply get the Cql versions of the children and add the union operator between them.
bool isFirst = true;
foreach (CqlBlock child in Children)
{
if (false == isFirst)
{
StringUtil.IndentNewLine(builder, indentLevel + 1);
builder.Append(OpCellTreeNode.OpToEsql(CellTreeOpType.Union));
}
isFirst = false;
builder.Append(" (");
child.AsEsql(builder, isTopLevel, indentLevel + 1);
builder.Append(')');
}
return builder;
}
internal override DbExpression AsCqt(bool isTopLevel)
{
Debug.Assert(this.Children.Count > 0, "UnionCqlBlock: Children collection must not be empty");
DbExpression cqt = this.Children[0].AsCqt(isTopLevel);
for (int i = 1; i < this.Children.Count; ++i)
{
cqt = cqt.UnionAll(this.Children[i].AsCqt(isTopLevel));
}
return cqt;
}
#endregion
}
}

View File

@@ -0,0 +1,464 @@
//---------------------------------------------------------------------
// <copyright file="CQLGenerator.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Data.Common;
using System.Data.Common.CommandTrees;
using System.Data.Common.Utils;
using System.Data.Mapping.ViewGeneration.Structures;
using System.Collections.Generic;
using System.Data.Mapping.ViewGeneration.CqlGeneration;
using System.Text;
using System.Diagnostics;
using System.Data.Metadata.Edm;
namespace System.Data.Mapping.ViewGeneration
{
/// <summary>
/// This class is responsible for generation of CQL after the cell merging process has been done.
/// </summary>
internal sealed class CqlGenerator : InternalBase
{
#region Constructor
/// <summary>
/// Given the generated <paramref name="view"/>, the <paramref name="caseStatements"/> for the multiconstant fields,
/// the <paramref name="projectedSlotMap"/> that maps different paths of the entityset (for which the view is being generated) to slot indexes in the view,
/// creates an object that is capable of generating the Cql for <paramref name="view"/>.
/// </summary>
internal CqlGenerator(CellTreeNode view,
Dictionary<MemberPath,
CaseStatement> caseStatements,
CqlIdentifiers identifiers,
MemberProjectionIndex projectedSlotMap,
int numCellsInView,
BoolExpression topLevelWhereClause,
StorageMappingItemCollection mappingItemCollection)
{
m_view = view;
m_caseStatements = caseStatements;
m_projectedSlotMap = projectedSlotMap;
m_numBools = numCellsInView; // We have that many booleans
m_topLevelWhereClause = topLevelWhereClause;
m_identifiers = identifiers;
m_mappingItemCollection = mappingItemCollection;
}
#endregion
#region Fields
/// <summary>
/// The generated view from the cells.
/// </summary>
private readonly CellTreeNode m_view;
/// <summary>
/// Case statements for the multiconstant fields.
/// </summary>
private readonly Dictionary<MemberPath, CaseStatement> m_caseStatements;
/// <summary>
/// Mapping from member paths to slot indexes.
/// </summary>
private MemberProjectionIndex m_projectedSlotMap;
/// <summary>
/// Number of booleans in the view, one per cell (from0, from1, etc...)
/// </summary>
private readonly int m_numBools;
/// <summary>
/// A counter used to generate aliases for blocks.
/// </summary>
private int m_currentBlockNum = 0;
private readonly BoolExpression m_topLevelWhereClause;
/// <summary>
/// Identifiers used in the Cql queries.
/// </summary>
private readonly CqlIdentifiers m_identifiers;
private readonly StorageMappingItemCollection m_mappingItemCollection;
#endregion
#region Properties
private int TotalSlots
{
get { return m_projectedSlotMap.Count + m_numBools; }
}
#endregion
#region CqlBlock generation methods for all node types
/// <summary>
/// Returns eSQL query that represents a query/update mapping view for the view information that was supplied in the constructor.
/// </summary>
internal string GenerateEsql()
{
// Generate a CqlBlock tree and then convert that to eSQL.
CqlBlock blockTree = GenerateCqlBlockTree();
// Create the string builder with 1K so that we don't have to
// keep growing it
StringBuilder builder = new StringBuilder(1024);
blockTree.AsEsql(builder, true, 1);
return builder.ToString();
}
/// <summary>
/// Returns Cqtl query that represents a query/update mapping view for the view information that was supplied in the constructor.
/// </summary>
internal DbQueryCommandTree GenerateCqt()
{
// Generate a CqlBlock tree and then convert that to CQT.
CqlBlock blockTree = GenerateCqlBlockTree();
DbExpression query = blockTree.AsCqt(true);
Debug.Assert(query != null, "Null CQT generated for query/update view.");
return DbQueryCommandTree.FromValidExpression(m_mappingItemCollection.Workspace, TargetPerspective.TargetPerspectiveDataSpace, query);
}
/// <summary>
/// Generates a <see cref="CqlBlock"/> tree that is capable of generating the actual Cql strings.
/// </summary>
private CqlBlock GenerateCqlBlockTree()
{
// Essentially, we create a block for each CellTreeNode in the
// tree and then we layer case statements on top of that view --
// one case statement for each multiconstant entry
// Dertmine the slots that are projected by the whole tree. Tell
// the children that they need to produce those slots somehow --
// if they don't have it, they can produce null
bool[] requiredSlots = GetRequiredSlots();
Debug.Assert(requiredSlots.Length == TotalSlots, "Wrong number of requiredSlots");
List<WithRelationship> withRelationships = new List<WithRelationship>();
CqlBlock viewBlock = m_view.ToCqlBlock(requiredSlots, m_identifiers, ref m_currentBlockNum, ref withRelationships);
// Handle case statements for multiconstant entries
// Right now, we have a simplication step that removes one of the
// entries and adds ELSE instead
foreach (CaseStatement statement in m_caseStatements.Values)
{
statement.Simplify();
}
// Generate the case statements and get the top level block which
// must correspond to the entity set
CqlBlock finalViewBlock = ConstructCaseBlocks(viewBlock, withRelationships);
return finalViewBlock;
}
private bool[] GetRequiredSlots()
{
bool[] requiredSlots = new bool[TotalSlots];
// union all slots that are required in case statements
foreach (CaseStatement caseStatement in m_caseStatements.Values)
{
GetRequiredSlotsForCaseMember(caseStatement.MemberPath, requiredSlots);
}
// For now, make sure that all booleans are required
// Reason: OUTER JOINs may introduce an extra CASE statement (in OpCellTreeNode.cs/GetJoinSlotInfo)
// if a member is projected in both inputs to the join.
// This case statement may use boolean variables that may not be marked as "required"
// The problem is that this decision is made _after_ CqlBlocks for children get produced (in OpCellTreeNode.cs/JoinToCqlBlock)
for (int i = TotalSlots - m_numBools; i < TotalSlots; i++)
{
requiredSlots[i] = true;
}
// Because of the above we don't need to harvest used booleans from the top-level WHERE clause
// m_topLevelWhereClause.GetRequiredSlots(m_projectedSlotMap, requiredSlots);
// Do we require the case statement member slot be produced by the inner queries?
foreach (CaseStatement caseStatement in m_caseStatements.Values)
{
bool notNeeded = !caseStatement.MemberPath.IsPartOfKey && // keys are required in inner queries for joins conditions
!caseStatement.DependsOnMemberValue; // if case statement returns its slot value as one of the options, then we need to produce it
if (notNeeded)
{
requiredSlots[m_projectedSlotMap.IndexOf(caseStatement.MemberPath)] = false;
}
}
return requiredSlots;
}
#endregion
#region Multiconstant CaseStatement methods
/// <summary>
/// Given the <paramref name="viewBlock"/> tree, generates the case statement blocks on top of it (using <see cref="m_caseStatements"/>) and returns the resulting tree.
/// One block per case statement is generated. Generated blocks are nested, with the <paramref name="viewBlock"/> is the innermost input.
/// </summary>
private CqlBlock ConstructCaseBlocks(CqlBlock viewBlock, IEnumerable<WithRelationship> withRelationships)
{
// Get the 0th slot only, i.e., the extent
bool[] topSlots = new bool[TotalSlots];
topSlots[0] = true;
// all booleans in the top-level WHERE clause are required and get bubbled up
// this makes some _fromX booleans be marked as 'required by parent'
m_topLevelWhereClause.GetRequiredSlots(m_projectedSlotMap, topSlots);
CqlBlock result = ConstructCaseBlocks(viewBlock, 0, topSlots, withRelationships);
return result;
}
/// <summary>
/// Given the <paramref name="viewBlock"/> tree generated by the cell merging process and the <paramref name="parentRequiredSlots"/>,
/// generates the block tree for the case statement at or past the startSlotNum, i.e., only for case statements that are beyond startSlotNum.
/// </summary>
private CqlBlock ConstructCaseBlocks(CqlBlock viewBlock, int startSlotNum, bool[] parentRequiredSlots, IEnumerable<WithRelationship> withRelationships)
{
int numMembers = m_projectedSlotMap.Count;
// Find the next slot for which we have a case statement, i.e.,
// which was in the multiconstants
int foundSlot = FindNextCaseStatementSlot(startSlotNum, parentRequiredSlots, numMembers);
if (foundSlot == -1)
{
// We have bottomed out - no more slots to generate cases for
// Just get the base view block
return viewBlock;
}
// Compute the requiredSlots for this member, i.e., what slots are needed to produce this member.
MemberPath thisMember = m_projectedSlotMap[foundSlot];
bool[] thisRequiredSlots = new bool[TotalSlots];
GetRequiredSlotsForCaseMember(thisMember, thisRequiredSlots);
Debug.Assert(thisRequiredSlots.Length == parentRequiredSlots.Length &&
thisRequiredSlots.Length == TotalSlots,
"Number of slots in array should not vary across blocks");
// Merge parent's requirements with this requirements
for (int i = 0; i < TotalSlots; i++)
{
// We do ask the children to generate the slot that we are
// producing if it is available
if (parentRequiredSlots[i])
{
thisRequiredSlots[i] = true;
}
}
// If current case statement depends on its slot value, then make sure the value is produced by the child block.
CaseStatement thisCaseStatement = m_caseStatements[thisMember];
thisRequiredSlots[foundSlot] = thisCaseStatement.DependsOnMemberValue;
// Recursively, determine the block tree for slots beyond foundSlot.
CqlBlock childBlock = ConstructCaseBlocks(viewBlock, foundSlot + 1, thisRequiredSlots, null);
// For each slot, create a SlotInfo object
SlotInfo[] slotInfos = CreateSlotInfosForCaseStatement(parentRequiredSlots, foundSlot, childBlock, thisCaseStatement, withRelationships);
m_currentBlockNum++;
// We have a where clause only at the top level
BoolExpression whereClause = startSlotNum == 0 ? m_topLevelWhereClause : BoolExpression.True;
if (startSlotNum == 0)
{
// only slot #0 is required by parent; reset all 'required by parent' booleans introduced above
for (int i = 1; i < slotInfos.Length; i++)
{
slotInfos[i].ResetIsRequiredByParent();
}
}
CaseCqlBlock result = new CaseCqlBlock(slotInfos, foundSlot, childBlock, whereClause, m_identifiers, m_currentBlockNum);
return result;
}
/// <summary>
/// Given the slot (<paramref name="foundSlot"/>) and its corresponding case statement (<paramref name="thisCaseStatement"/>),
/// generates the slotinfos for the cql block producing the case statement.
/// </summary>
private SlotInfo[] CreateSlotInfosForCaseStatement(bool[] parentRequiredSlots,
int foundSlot,
CqlBlock childBlock,
CaseStatement thisCaseStatement,
IEnumerable<WithRelationship> withRelationships)
{
int numSlotsAddedByChildBlock = childBlock.Slots.Count - TotalSlots;
SlotInfo[] slotInfos = new SlotInfo[TotalSlots + numSlotsAddedByChildBlock];
for (int slotNum = 0; slotNum < TotalSlots; slotNum++)
{
bool isProjected = childBlock.IsProjected(slotNum);
bool isRequiredByParent = parentRequiredSlots[slotNum];
ProjectedSlot slot = childBlock.SlotValue(slotNum);
MemberPath outputMember = GetOutputMemberPath(slotNum);
if (slotNum == foundSlot)
{
// We need a case statement instead for this slot that we
// are handling right now
Debug.Assert(isRequiredByParent, "Case result not needed by parent");
// Get a case statement with all slots replaced by aliases slots
CaseStatement newCaseStatement = thisCaseStatement.DeepQualify(childBlock);
slot = new CaseStatementProjectedSlot(newCaseStatement, withRelationships);
isProjected = true; // We are projecting this slot now
}
else if (isProjected && isRequiredByParent)
{
// We only alias something that is needed and is being projected by the child.
// It is a qualified slot into the child block.
slot = childBlock.QualifySlotWithBlockAlias(slotNum);
}
// For slots, if it is not required by the parent, we want to
// set the isRequiredByParent for this slot to be
// false. Furthermore, we do not want to introduce any "NULL
// AS something" at this stage for slots not being
// projected. So if the child does not project that slot, we
// declare it as not being required by the parent (if such a
// NULL was needed, it would have been pushed all the way
// down to a non-case block.
// Essentially, from a Case statement's parent perspective,
// it is saying "If you can produce a slot either by yourself
// or your children, please do. Otherwise, do not concoct anything"
SlotInfo slotInfo = new SlotInfo(isRequiredByParent && isProjected, isProjected, slot, outputMember);
slotInfos[slotNum] = slotInfo;
}
for (int i = TotalSlots; i < TotalSlots + numSlotsAddedByChildBlock; i++)
{
QualifiedSlot childAddedSlot = childBlock.QualifySlotWithBlockAlias(i);
slotInfos[i] = new SlotInfo(true, true, childAddedSlot, childBlock.MemberPath(i));
}
return slotInfos;
}
/// <summary>
/// Returns the next slot starting at <paramref name="startSlotNum"/> that is present in the <see cref="m_caseStatements"/>.
/// </summary>
private int FindNextCaseStatementSlot(int startSlotNum, bool[] parentRequiredSlots, int numMembers)
{
int foundSlot = -1;
// Simply go through the slots and check the m_caseStatements map
for (int slotNum = startSlotNum; slotNum < numMembers; slotNum++)
{
MemberPath member = m_projectedSlotMap[slotNum];
if (parentRequiredSlots[slotNum] && m_caseStatements.ContainsKey(member))
{
foundSlot = slotNum;
break;
}
}
return foundSlot;
}
/// <summary>
/// Returns an array of size <see cref="TotalSlots"/> which indicates the slots that are needed to constuct value at <paramref name="caseMemberPath"/>,
/// e.g., CPerson may need pid and name (say slots 2 and 5 - then bools[2] and bools[5] will be true.
/// </summary>
/// <param name="caseMemberPath">must be part of <see cref="m_caseStatements"/></param>
private void GetRequiredSlotsForCaseMember(MemberPath caseMemberPath, bool[] requiredSlots)
{
Debug.Assert(true == m_caseStatements.ContainsKey(caseMemberPath), "Constructing case for regular field?");
Debug.Assert(requiredSlots.Length == TotalSlots, "Invalid array size for populating required slots");
CaseStatement statement = m_caseStatements[caseMemberPath];
// Find the required slots from the when then clause conditions
// and values
bool requireThisSlot = false;
foreach (CaseStatement.WhenThen clause in statement.Clauses)
{
clause.Condition.GetRequiredSlots(m_projectedSlotMap, requiredSlots);
ProjectedSlot slot = clause.Value;
if (!(slot is ConstantProjectedSlot))
{
// If this slot is a scalar and a non-constant,
// we need the lower down blocks to generate it for us
requireThisSlot = true;
}
}
EdmType edmType = caseMemberPath.EdmType;
if (Helper.IsEntityType(edmType) || Helper.IsComplexType(edmType))
{
foreach (EdmType instantiatedType in statement.InstantiatedTypes)
{
foreach (EdmMember childMember in Helper.GetAllStructuralMembers(instantiatedType) )
{
int slotNum = GetSlotIndex(caseMemberPath, childMember);
requiredSlots[slotNum] = true;
}
}
}
else if (caseMemberPath.IsScalarType())
{
// A scalar does not need anything per se to be constructed
// unless it is referring to a field in the tree below, i.e., the THEN
// slot is not a constant slot
if (requireThisSlot)
{
int caseMemberSlotNum = m_projectedSlotMap.IndexOf(caseMemberPath);
requiredSlots[caseMemberSlotNum] = true;
}
}
else if (Helper.IsAssociationType(edmType))
{
// For an association, get the indices of the ends, e.g.,
// CProduct and CCategory in CProductCategory1
// Need just it's ends
AssociationSet associationSet = (AssociationSet)caseMemberPath.Extent;
AssociationType associationType = associationSet.ElementType;
foreach (AssociationEndMember endMember in associationType.AssociationEndMembers)
{
int slotNum = GetSlotIndex(caseMemberPath, endMember);
requiredSlots[slotNum] = true;
}
}
else
{
// For a reference, all we need are the keys
RefType refType = edmType as RefType;
Debug.Assert(refType != null, "What other non scalars do we have? Relation end must be a reference type");
EntityTypeBase refElementType = refType.ElementType;
// Go through all the members of elementType and get the key properties
EntitySet entitySet = MetadataHelper.GetEntitySetAtEnd((AssociationSet)caseMemberPath.Extent,
(AssociationEndMember)caseMemberPath.LeafEdmMember);
foreach (EdmMember entityMember in refElementType.KeyMembers)
{
int slotNum = GetSlotIndex(caseMemberPath, entityMember);
requiredSlots[slotNum] = true;
}
}
}
#endregion
#region Helper methods
/// <summary>
/// Given the <paramref name="slotNum"/>, returns the output member path that this slot contributes/corresponds to in the extent view.
/// If the slot corresponds to one of the boolean variables, returns null.
/// </summary>
private MemberPath GetOutputMemberPath(int slotNum)
{
return m_projectedSlotMap.GetMemberPath(slotNum, TotalSlots - m_projectedSlotMap.Count);
}
/// <summary>
/// Returns the slot index for the following member path: <paramref name="member"/>.<paramref name="child"/>, e.g., CPerson1.pid
/// </summary>
private int GetSlotIndex(MemberPath member, EdmMember child)
{
MemberPath fullMember = new MemberPath(member, child);
int index = m_projectedSlotMap.IndexOf(fullMember);
Debug.Assert(index != -1, "Couldn't locate " + fullMember.ToString() + " in m_projectedSlotMap");
return index;
}
#endregion
#region String methods
internal override void ToCompactString(StringBuilder builder)
{
builder.Append("View: ");
m_view.ToCompactString(builder);
builder.Append("ProjectedSlotMap: ");
m_projectedSlotMap.ToCompactString(builder);
builder.Append("Case statements: ");
foreach (MemberPath member in m_caseStatements.Keys)
{
CaseStatement statement = m_caseStatements[member];
statement.ToCompactString(builder);
builder.AppendLine();
}
}
#endregion
}
}

View File

@@ -0,0 +1,295 @@
//---------------------------------------------------------------------
// <copyright file="DiscriminatorMap.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Data.Common.CommandTrees;
using System.Data.Common.CommandTrees.Internal;
using System.Collections.Generic;
using System.Data.Metadata.Edm;
using System.Linq;
using System.Globalization;
using System.Diagnostics;
using System.Data.Common.Utils;
namespace System.Data.Mapping.ViewGeneration
{
/// <summary>
/// Describes top-level query mapping view projection of the form:
///
/// SELECT VALUE CASE
/// WHEN Discriminator = DiscriminatorValue1 THEN EntityType1(...)
/// WHEN Discriminator = DiscriminatorValue2 THEN EntityType2(...)
/// ...
///
/// Supports optimizing queries to leverage user supplied discriminator values
/// in TPH mappings rather than introducing our own. This avoids the need
/// to introduce a CASE statement in the store.
/// </summary>
internal class DiscriminatorMap
{
/// <summary>
/// Expression retrieving discriminator value from projection input.
/// </summary>
internal readonly DbPropertyExpression Discriminator;
/// <summary>
/// Map from discriminator values to implied entity type.
/// </summary>
internal readonly System.Collections.ObjectModel.ReadOnlyCollection<KeyValuePair<object, EntityType>> TypeMap;
/// <summary>
/// Map from entity property to expression generating value for that property. Note that
/// the expression must be the same for all types in discriminator map.
/// </summary>
internal readonly System.Collections.ObjectModel.ReadOnlyCollection<KeyValuePair<EdmProperty, DbExpression>> PropertyMap;
/// <summary>
/// Map from entity relproperty to expression generating value for that property. Note that
/// the expression must be the same for all types in discriminator map.
/// </summary>
internal readonly System.Collections.ObjectModel.ReadOnlyCollection<KeyValuePair<Query.InternalTrees.RelProperty, DbExpression>> RelPropertyMap;
/// <summary>
/// EntitySet to which the map applies.
/// </summary>
internal readonly EntitySet EntitySet;
private DiscriminatorMap(DbPropertyExpression discriminator,
List<KeyValuePair<object, EntityType>> typeMap,
Dictionary<EdmProperty, DbExpression> propertyMap,
Dictionary<Query.InternalTrees.RelProperty, DbExpression> relPropertyMap,
EntitySet entitySet)
{
this.Discriminator = discriminator;
this.TypeMap = typeMap.AsReadOnly();
this.PropertyMap = propertyMap.ToList().AsReadOnly();
this.RelPropertyMap = relPropertyMap.ToList().AsReadOnly();
this.EntitySet = entitySet;
}
/// <summary>
/// Determines whether the given query view matches the discriminator map pattern.
/// </summary>
internal static bool TryCreateDiscriminatorMap(EntitySet entitySet, DbExpression queryView, out DiscriminatorMap discriminatorMap)
{
discriminatorMap = null;
if (queryView.ExpressionKind != DbExpressionKind.Project) { return false; }
var project = (DbProjectExpression)queryView;
if (project.Projection.ExpressionKind != DbExpressionKind.Case) { return false; }
var caseExpression = (DbCaseExpression)project.Projection;
if (project.Projection.ResultType.EdmType.BuiltInTypeKind != BuiltInTypeKind.EntityType) { return false; }
// determine value domain by walking filter
if (project.Input.Expression.ExpressionKind != DbExpressionKind.Filter) { return false; }
var filterExpression = (DbFilterExpression)project.Input.Expression;
HashSet<object> discriminatorDomain = new HashSet<object>();
if (!ViewSimplifier.TryMatchDiscriminatorPredicate(filterExpression, (equalsExp, discriminatorValue) => discriminatorDomain.Add(discriminatorValue)))
{
return false;
}
var typeMap = new List<KeyValuePair<object, EntityType>>();
var propertyMap = new Dictionary<EdmProperty, DbExpression>();
var relPropertyMap = new Dictionary<Query.InternalTrees.RelProperty, DbExpression>();
var typeToRelPropertyMap = new Dictionary<EntityType, List<Query.InternalTrees.RelProperty>>();
DbPropertyExpression discriminator = null;
EdmProperty discriminatorProperty = null;
for (int i = 0; i < caseExpression.When.Count; i++)
{
var when = caseExpression.When[i];
var then = caseExpression.Then[i];
var projectionVariableName = project.Input.VariableName;
DbPropertyExpression currentDiscriminator;
object discriminatorValue;
if (!ViewSimplifier.TryMatchPropertyEqualsValue(when, projectionVariableName, out currentDiscriminator, out discriminatorValue)) { return false; }
// must be the same discriminator in every case
if (null == discriminatorProperty) { discriminatorProperty = (EdmProperty)currentDiscriminator.Property; }
else if (discriminatorProperty != currentDiscriminator.Property) { return false; }
discriminator = currentDiscriminator;
// right hand side must be entity type constructor
EntityType currentType;
if (!TryMatchEntityTypeConstructor(then, propertyMap, relPropertyMap, typeToRelPropertyMap, out currentType)) { return false; }
// remember type + discriminator value
typeMap.Add(new KeyValuePair<object, EntityType>(discriminatorValue, currentType));
// remove discriminator value from domain
discriminatorDomain.Remove(discriminatorValue);
}
// make sure only one member of discriminator domain remains...
if (1 != discriminatorDomain.Count) { return false; }
// check default case
EntityType elseType;
if (null == caseExpression.Else ||
!TryMatchEntityTypeConstructor(caseExpression.Else, propertyMap, relPropertyMap, typeToRelPropertyMap, out elseType)) { return false; }
typeMap.Add(new KeyValuePair<object, EntityType>(discriminatorDomain.Single(), elseType));
// Account for cases where some type in the hierarchy specifies a rel-property, but another
// type in the hierarchy does not
if (!CheckForMissingRelProperties(relPropertyMap, typeToRelPropertyMap))
{
return false;
}
// since the store may right-pad strings, ensure discriminator values are unique in their trimmed
// form
var discriminatorValues = typeMap.Select(map => map.Key);
int uniqueValueCount = discriminatorValues.Distinct(TrailingSpaceComparer.Instance).Count();
int valueCount = typeMap.Count;
if (uniqueValueCount != valueCount) { return false; }
discriminatorMap = new DiscriminatorMap(discriminator, typeMap, propertyMap, relPropertyMap, entitySet);
return true;
}
private static bool CheckForMissingRelProperties(
Dictionary<Query.InternalTrees.RelProperty, DbExpression> relPropertyMap,
Dictionary<EntityType, List<Query.InternalTrees.RelProperty>> typeToRelPropertyMap)
{
// Easily the lousiest implementation of this search.
// Check to see that for each relProperty that we see in the relPropertyMap
// (presumably because some type constructor specified it), every type for
// which that rel-property is specified *must* also have specified it.
// We don't need to check for equivalence here - because that's already been
// checked
foreach (Query.InternalTrees.RelProperty relProperty in relPropertyMap.Keys)
{
foreach (KeyValuePair<EntityType, List<Query.InternalTrees.RelProperty>> kv in typeToRelPropertyMap)
{
if (kv.Key.IsSubtypeOf(relProperty.FromEnd.TypeUsage.EdmType))
{
if (!kv.Value.Contains(relProperty))
{
return false;
}
}
}
}
return true;
}
private static bool TryMatchEntityTypeConstructor(DbExpression then,
Dictionary<EdmProperty, DbExpression> propertyMap,
Dictionary<Query.InternalTrees.RelProperty, DbExpression> relPropertyMap,
Dictionary<EntityType, List<Query.InternalTrees.RelProperty>> typeToRelPropertyMap,
out EntityType entityType)
{
if (then.ExpressionKind != DbExpressionKind.NewInstance)
{
entityType = null;
return false;
}
var constructor = (DbNewInstanceExpression)then;
entityType = (EntityType)constructor.ResultType.EdmType;
// process arguments to constructor (must be aligned across all case statements)
Debug.Assert(entityType.Properties.Count == constructor.Arguments.Count, "invalid new instance");
for (int j = 0; j < entityType.Properties.Count; j++)
{
var property = entityType.Properties[j];
var assignment = constructor.Arguments[j];
DbExpression existingAssignment;
if (propertyMap.TryGetValue(property, out existingAssignment))
{
if (!ExpressionsCompatible(assignment, existingAssignment)) { return false; }
}
else
{
propertyMap.Add(property, assignment);
}
}
// Now handle the rel properties
if (constructor.HasRelatedEntityReferences)
{
List<Query.InternalTrees.RelProperty> relPropertyList;
if (!typeToRelPropertyMap.TryGetValue(entityType, out relPropertyList))
{
relPropertyList = new List<System.Data.Query.InternalTrees.RelProperty>();
typeToRelPropertyMap[entityType] = relPropertyList;
}
foreach (DbRelatedEntityRef relatedRef in constructor.RelatedEntityReferences)
{
Query.InternalTrees.RelProperty relProperty = new System.Data.Query.InternalTrees.RelProperty((RelationshipType)relatedRef.TargetEnd.DeclaringType,
relatedRef.SourceEnd, relatedRef.TargetEnd);
DbExpression assignment = relatedRef.TargetEntityReference;
DbExpression existingAssignment;
if (relPropertyMap.TryGetValue(relProperty, out existingAssignment))
{
if (!ExpressionsCompatible(assignment, existingAssignment)) { return false; }
}
else
{
relPropertyMap.Add(relProperty, assignment);
}
relPropertyList.Add(relProperty);
}
}
return true;
}
/// <summary>
/// Utility method determining whether two expressions appearing within the same scope
/// are equivalent. May return false negatives, but no false positives. In other words,
///
/// x != y --> !ExpressionsCompatible(x, y)
///
/// but does not guarantee
///
/// x == y --> ExpressionsCompatible(x, y)
/// </summary>
private static bool ExpressionsCompatible(DbExpression x, DbExpression y)
{
if (x.ExpressionKind != y.ExpressionKind) { return false; }
switch (x.ExpressionKind)
{
case DbExpressionKind.Property:
{
var prop1 = (DbPropertyExpression)x;
var prop2 = (DbPropertyExpression)y;
return prop1.Property == prop2.Property &&
ExpressionsCompatible(prop1.Instance, prop2.Instance);
}
case DbExpressionKind.VariableReference:
return ((DbVariableReferenceExpression)x).VariableName ==
((DbVariableReferenceExpression)y).VariableName;
case DbExpressionKind.NewInstance:
{
var newX = (DbNewInstanceExpression)x;
var newY = (DbNewInstanceExpression)y;
if (!newX.ResultType.EdmType.EdmEquals(newY.ResultType.EdmType)) { return false; }
for (int i = 0; i < newX.Arguments.Count; i++)
{
if (!ExpressionsCompatible(newX.Arguments[i], newY.Arguments[i]))
{
return false;
}
}
return true;
}
case DbExpressionKind.Ref:
{
DbRefExpression refX = (DbRefExpression)x;
DbRefExpression refY = (DbRefExpression)y;
return (refX.EntitySet.EdmEquals(refY.EntitySet) &&
ExpressionsCompatible(refX.Argument, refY.Argument));
}
default:
// here come the false negatives...
return false;
}
}
}
}

View File

@@ -0,0 +1,326 @@
//---------------------------------------------------------------------
// <copyright file="GeneratedView.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
namespace System.Data.Mapping.ViewGeneration
{
using System.Collections.Generic;
using System.Data.Common.CommandTrees;
using System.Data.Common.CommandTrees.Internal;
using System.Data.Common.EntitySql;
using System.Data.Common.Utils;
using System.Data.Entity.Util;
using System.Data.Mapping.ViewGeneration.Utils;
using System.Data.Metadata.Edm;
using System.Data.Query.InternalTrees;
using System.Data.Query.PlanCompiler;
using System.Diagnostics;
using System.Text;
/// <summary>
/// Holds the view generated for a given OFTYPE(Extent, Type) combination.
/// </summary>
internal sealed class GeneratedView : InternalBase
{
#region Factory
/// <summary>
/// Creates generated view object for the combination of the <paramref name="extent"/> and the <paramref name="type"/>.
/// This constructor is used for regular cell-based view generation.
/// </summary>
internal static GeneratedView CreateGeneratedView(EntitySetBase extent,
EdmType type,
DbQueryCommandTree commandTree,
string eSQL,
StorageMappingItemCollection mappingItemCollection,
ConfigViewGenerator config)
{
// If config.GenerateEsql is specified, eSQL must be non-null.
// If config.GenerateEsql is false, commandTree is non-null except the case when loading pre-compiled eSQL views.
Debug.Assert(!config.GenerateEsql || !String.IsNullOrEmpty(eSQL), "eSQL must be specified");
DiscriminatorMap discriminatorMap = null;
if (commandTree != null)
{
commandTree = ViewSimplifier.SimplifyView(extent, commandTree);
// See if the view matches the "discriminated" pattern (allows simplification of generated store commands)
if (extent.BuiltInTypeKind == BuiltInTypeKind.EntitySet)
{
if (DiscriminatorMap.TryCreateDiscriminatorMap((EntitySet)extent, commandTree.Query, out discriminatorMap))
{
Debug.Assert(discriminatorMap != null, "discriminatorMap == null after it has been created");
}
}
}
return new GeneratedView(extent, type, commandTree, eSQL, discriminatorMap, mappingItemCollection, config);
}
/// <summary>
/// Creates generated view object for the combination of the <paramref name="extent"/> and the <paramref name="type"/>.
/// This constructor is used for FK association sets only.
/// </summary>
internal static GeneratedView CreateGeneratedViewForFKAssociationSet(EntitySetBase extent,
EdmType type,
DbQueryCommandTree commandTree,
StorageMappingItemCollection mappingItemCollection,
ConfigViewGenerator config)
{
return new GeneratedView(extent, type, commandTree, null, null, mappingItemCollection, config);
}
/// <summary>
/// Creates generated view object for the combination of the <paramref name="setMapping"/>.Set and the <paramref name="type"/>.
/// This constructor is used for user-defined query views only.
/// </summary>
internal static bool TryParseUserSpecifiedView(StorageSetMapping setMapping,
EntityTypeBase type,
string eSQL,
bool includeSubtypes,
StorageMappingItemCollection mappingItemCollection,
ConfigViewGenerator config,
/*out*/ IList<EdmSchemaError> errors,
out GeneratedView generatedView)
{
bool failed = false;
DbQueryCommandTree commandTree;
DiscriminatorMap discriminatorMap;
Exception parserException;
if (!GeneratedView.TryParseView(eSQL, true, setMapping.Set, mappingItemCollection, config, out commandTree, out discriminatorMap, out parserException))
{
EdmSchemaError error = new EdmSchemaError(System.Data.Entity.Strings.Mapping_Invalid_QueryView2(setMapping.Set.Name, parserException.Message),
(int)StorageMappingErrorCode.InvalidQueryView, EdmSchemaErrorSeverity.Error,
setMapping.EntityContainerMapping.SourceLocation, setMapping.StartLineNumber, setMapping.StartLinePosition, parserException);
errors.Add(error);
failed = true;
}
else
{
Debug.Assert(commandTree != null, "commandTree not set after parsing the view");
// Verify that all expressions appearing in the view are supported.
foreach (var error in ViewValidator.ValidateQueryView(commandTree, setMapping, type, includeSubtypes))
{
errors.Add(error);
failed = true;
}
// Verify that the result type of the query view is assignable to the element type of the entityset
CollectionType queryResultType = (commandTree.Query.ResultType.EdmType) as CollectionType;
if ((queryResultType == null) || (!setMapping.Set.ElementType.IsAssignableFrom(queryResultType.TypeUsage.EdmType)))
{
EdmSchemaError error = new EdmSchemaError(System.Data.Entity.Strings.Mapping_Invalid_QueryView_Type(setMapping.Set.Name),
(int)StorageMappingErrorCode.InvalidQueryViewResultType, EdmSchemaErrorSeverity.Error,
setMapping.EntityContainerMapping.SourceLocation, setMapping.StartLineNumber, setMapping.StartLinePosition);
errors.Add(error);
failed = true;
}
}
if (!failed)
{
generatedView = new GeneratedView(setMapping.Set, type, commandTree, eSQL, discriminatorMap, mappingItemCollection, config);
return true;
}
else
{
generatedView = null;
return false;
}
}
private GeneratedView(EntitySetBase extent,
EdmType type,
DbQueryCommandTree commandTree,
string eSQL,
DiscriminatorMap discriminatorMap,
StorageMappingItemCollection mappingItemCollection,
ConfigViewGenerator config)
{
// At least one of the commandTree or eSQL must be specified.
// Both are specified in the case of user-defined views.
Debug.Assert(commandTree != null || !String.IsNullOrEmpty(eSQL), "commandTree or eSQL must be specified");
m_extent = extent;
m_type = type;
m_commandTree = commandTree;
m_eSQL = eSQL;
m_discriminatorMap = discriminatorMap;
m_mappingItemCollection = mappingItemCollection;
m_config = config;
if (m_config.IsViewTracing)
{
StringBuilder trace = new StringBuilder(1024);
this.ToCompactString(trace);
Helpers.FormatTraceLine("CQL view for {0}", trace.ToString());
}
}
#endregion
#region Fields
private readonly EntitySetBase m_extent;
private readonly EdmType m_type;
private DbQueryCommandTree m_commandTree; //We cache CQTs for Update Views sicne that is the one update stack works of.
private readonly string m_eSQL;
private Node m_internalTreeNode; //we cache IQTs for Query Views since that is the one query stack works of.
private DiscriminatorMap m_discriminatorMap;
private readonly StorageMappingItemCollection m_mappingItemCollection;
private readonly ConfigViewGenerator m_config;
#endregion
#region Properties
internal string eSQL
{
get { return m_eSQL; }
}
#endregion
#region Methods
internal DbQueryCommandTree GetCommandTree()
{
if (m_commandTree == null)
{
Debug.Assert(!String.IsNullOrEmpty(m_eSQL), "m_eSQL must be initialized");
Exception parserException;
if (TryParseView(m_eSQL, false, m_extent, m_mappingItemCollection, m_config, out m_commandTree, out m_discriminatorMap, out parserException))
{
Debug.Assert(m_commandTree != null, "m_commandTree not set after parsing the view");
return m_commandTree;
}
else
{
throw new MappingException(System.Data.Entity.Strings.Mapping_Invalid_QueryView(m_extent.Name, parserException.Message));
}
}
return m_commandTree;
}
internal Node GetInternalTree(Command targetIqtCommand)
{
Debug.Assert(m_extent.EntityContainer.DataSpace == DataSpace.CSpace, "Internal Tree should be asked only for query view");
if (m_internalTreeNode == null)
{
DbQueryCommandTree tree = GetCommandTree();
// Convert this into an ITree first
Command itree = ITreeGenerator.Generate(tree, m_discriminatorMap);
// Pull out the root physical project-op, and copy this itree into our own itree
PlanCompiler.Assert(itree.Root.Op.OpType == OpType.PhysicalProject,
"Expected a physical projectOp at the root of the tree - found " + itree.Root.Op.OpType);
// #554756: VarVec enumerators are not cached on the shared Command instance.
itree.DisableVarVecEnumCaching();
m_internalTreeNode = itree.Root.Child0;
}
Debug.Assert(m_internalTreeNode != null, "m_internalTreeNode != null");
return OpCopier.Copy(targetIqtCommand, m_internalTreeNode);
}
/// <summary>
/// Given an extent and its corresponding view, invokes the parser to check if the view definition is syntactically correct.
/// Iff parsing succeeds: <paramref name="commandTree"/> and <paramref name="discriminatorMap"/> are set to the parse result and method returns true,
/// otherwise if parser has thrown a catchable exception, it is returned via <paramref name="parserException"/> parameter,
/// otherwise exception is re-thrown.
/// </summary>
private static bool TryParseView(string eSQL,
bool isUserSpecified,
EntitySetBase extent,
StorageMappingItemCollection mappingItemCollection,
ConfigViewGenerator config,
out DbQueryCommandTree commandTree,
out DiscriminatorMap discriminatorMap,
out Exception parserException)
{
commandTree = null;
discriminatorMap = null;
parserException = null;
// We do not catch any internal exceptions any more
config.StartSingleWatch(PerfType.ViewParsing);
try
{
// If it is a user specified view, allow all queries. Otherwise parse the view in the restricted mode.
ParserOptions.CompilationMode compilationMode = ParserOptions.CompilationMode.RestrictedViewGenerationMode;
if (isUserSpecified)
{
compilationMode = ParserOptions.CompilationMode.UserViewGenerationMode;
}
Debug.Assert(!String.IsNullOrEmpty(eSQL), "eSQL query is not specified");
commandTree = (DbQueryCommandTree)ExternalCalls.CompileView(eSQL, mappingItemCollection, compilationMode);
if (!isUserSpecified || AppSettings.SimplifyUserSpecifiedViews)
{
commandTree = ViewSimplifier.SimplifyView(extent, commandTree);
}
// See if the view matches the "discriminated" pattern (allows simplification of generated store commands)
if (extent.BuiltInTypeKind == BuiltInTypeKind.EntitySet)
{
if (DiscriminatorMap.TryCreateDiscriminatorMap((EntitySet)extent, commandTree.Query, out discriminatorMap))
{
Debug.Assert(discriminatorMap != null, "discriminatorMap == null after it has been created");
}
}
}
catch (Exception e)
{
// Catching all the exception types since Query parser seems to be throwing veriety of
// exceptions - EntityException, ArgumentException, ArgumentNullException etc.
if (EntityUtil.IsCatchableExceptionType(e))
{
parserException = e;
}
else
{
throw;
}
}
finally
{
config.StopSingleWatch(PerfType.ViewParsing);
}
Debug.Assert(commandTree != null || parserException != null, "Either commandTree or parserException is expected.");
// Note: m_commandTree might have been initialized by a previous call to this method, so in consequent calls it might occur that
// both m_commandTree and parserException are not null - this would mean that the last parse attempt failed, but m_commandTree value is
// preserved from the previous call.
return parserException == null;
}
#endregion
#region String Methods
internal override void ToCompactString(StringBuilder builder)
{
bool ofTypeView = m_type != m_extent.ElementType;
if (ofTypeView)
{
builder.Append("OFTYPE(");
}
builder.AppendFormat("{0}.{1}", m_extent.EntityContainer.Name, m_extent.Name);
if (ofTypeView)
{
builder.Append(", ").Append(m_type.Name).Append(')');
}
builder.AppendLine(" = ");
if (!String.IsNullOrEmpty(m_eSQL))
{
builder.Append(m_eSQL);
}
else
{
builder.Append(m_commandTree.Print());
}
}
#endregion
}
}

View File

@@ -0,0 +1,193 @@
//---------------------------------------------------------------------
// <copyright file="FragmentQuery.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Text;
using System.Data.Common.Utils;
using System.Data.Common.Utils.Boolean;
using System.Data.Mapping.ViewGeneration.Structures;
using System.Data.Metadata.Edm;
using System.Linq;
using System.Globalization;
namespace System.Data.Mapping.ViewGeneration.QueryRewriting
{
internal class FragmentQuery : ITileQuery
{
private BoolExpression m_fromVariable; // optional
private string m_label; // optional
private HashSet<MemberPath> m_attributes;
private BoolExpression m_condition;
public HashSet<MemberPath> Attributes
{
get { return m_attributes; }
}
public BoolExpression Condition
{
get { return m_condition; }
}
public static FragmentQuery Create(BoolExpression fromVariable, CellQuery cellQuery)
{
BoolExpression whereClause = cellQuery.WhereClause;
whereClause = whereClause.MakeCopy();
whereClause.ExpensiveSimplify();
return new FragmentQuery(null /*label*/, fromVariable, new HashSet<MemberPath>(cellQuery.GetProjectedMembers()), whereClause);
}
public static FragmentQuery Create(string label, RoleBoolean roleBoolean, CellQuery cellQuery)
{
BoolExpression whereClause = cellQuery.WhereClause.Create(roleBoolean);
whereClause = BoolExpression.CreateAnd(whereClause, cellQuery.WhereClause);
//return new FragmentQuery(label, null /* fromVariable */, new HashSet<MemberPath>(cellQuery.GetProjectedMembers()), whereClause);
// don't need any attributes
whereClause = whereClause.MakeCopy();
whereClause.ExpensiveSimplify();
return new FragmentQuery(label, null /* fromVariable */, new HashSet<MemberPath>(), whereClause);
}
public static FragmentQuery Create(IEnumerable<MemberPath> attrs, BoolExpression whereClause)
{
return new FragmentQuery(null /* no name */, null /* no fromVariable*/, attrs, whereClause);
}
public static FragmentQuery Create(BoolExpression whereClause)
{
return new FragmentQuery(null /* no name */, null /* no fromVariable*/, new MemberPath[] { }, whereClause);
}
internal FragmentQuery(string label, BoolExpression fromVariable, IEnumerable<MemberPath> attrs, BoolExpression condition)
{
m_label = label;
m_fromVariable = fromVariable;
m_condition = condition;
m_attributes = new HashSet<MemberPath>(attrs);
}
public BoolExpression FromVariable
{
get { return m_fromVariable; }
}
public string Description
{
get
{
string label = m_label;
if (label == null && m_fromVariable != null)
{
label = m_fromVariable.ToString();
}
return label;
}
}
public override string ToString()
{
// attributes
StringBuilder b = new StringBuilder();
foreach (MemberPath value in this.Attributes)
{
if (b.Length > 0) { b.Append(','); }
b.Append(value.ToString());
}
if (Description != null && Description != b.ToString())
{
return String.Format(CultureInfo.InvariantCulture, "{0}: [{1} where {2}]", Description, b, this.Condition);
}
else
{
return String.Format(CultureInfo.InvariantCulture, "[{0} where {1}]", b, this.Condition);
}
}
#region Static methods
// creates a condition member=value
internal static BoolExpression CreateMemberCondition(MemberPath path, Constant domainValue, MemberDomainMap domainMap)
{
if (domainValue is TypeConstant)
{
return BoolExpression.CreateLiteral(new TypeRestriction(new MemberProjectedSlot(path),
new Domain(domainValue, domainMap.GetDomain(path))), domainMap);
}
else
{
return BoolExpression.CreateLiteral(new ScalarRestriction(new MemberProjectedSlot(path),
new Domain(domainValue, domainMap.GetDomain(path))), domainMap);
}
}
internal static IEqualityComparer<FragmentQuery> GetEqualityComparer(FragmentQueryProcessor qp)
{
return new FragmentQueryEqualityComparer(qp);
}
#endregion
#region Equality Comparer
// Two queries are "equal" if they project the same set of attributes
// and their WHERE clauses are equivalent
private class FragmentQueryEqualityComparer : IEqualityComparer<FragmentQuery>
{
FragmentQueryProcessor _qp;
internal FragmentQueryEqualityComparer(FragmentQueryProcessor qp)
{
_qp = qp;
}
#region IEqualityComparer<FragmentQuery> Members
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2140:TransparentMethodsMustNotReferenceCriticalCode", Justification = "Based on Bug VSTS Pioneer #433188: IsVisibleOutsideAssembly is wrong on generic instantiations.")]
public bool Equals(FragmentQuery x, FragmentQuery y)
{
if (!x.Attributes.SetEquals(y.Attributes))
{
return false;
}
return _qp.IsEquivalentTo(x, y);
}
// Hashing a bit naive: it exploits syntactic properties,
// i.e., some semantically equivalent queries may produce different hash codes
// But that's fine for usage scenarios in QueryRewriter.cs
public int GetHashCode(FragmentQuery q)
{
int attrHashCode = 0;
foreach (MemberPath member in q.Attributes)
{
attrHashCode ^= MemberPath.EqualityComparer.GetHashCode(member);
}
int varHashCode = 0;
int constHashCode = 0;
foreach (MemberRestriction oneOf in q.Condition.MemberRestrictions)
{
varHashCode ^= MemberPath.EqualityComparer.GetHashCode(oneOf.RestrictedMemberSlot.MemberPath);
foreach (Constant constant in oneOf.Domain.Values)
{
constHashCode ^= Constant.EqualityComparer.GetHashCode(constant);
}
}
return attrHashCode * 13 + varHashCode * 7 + constHashCode;
}
#endregion
}
#endregion
}
}

View File

@@ -0,0 +1,216 @@
//---------------------------------------------------------------------
// <copyright file="FragmentQueryKB.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Text;
using System.Data.Common.Utils;
using System.Data.Common.Utils.Boolean;
using System.Data.Mapping.ViewGeneration.Structures;
using System.Data.Metadata.Edm;
using System.Linq;
namespace System.Data.Mapping.ViewGeneration.QueryRewriting
{
internal class FragmentQueryKB : KnowledgeBase<DomainConstraint<BoolLiteral, Constant>>
{
private BoolExpr<DomainConstraint<BoolLiteral, Constant>> _kbExpression = TrueExpr<DomainConstraint<BoolLiteral, Constant>>.Value;
internal override void AddFact(BoolExpr<DomainConstraint<BoolLiteral, Constant>> fact)
{
base.AddFact(fact);
_kbExpression = new AndExpr<DomainConstraint<BoolLiteral, Constant>>(_kbExpression, fact);
}
internal BoolExpr<DomainConstraint<BoolLiteral, Constant>> KbExpression
{
get { return _kbExpression; }
}
internal void CreateVariableConstraints(EntitySetBase extent, MemberDomainMap domainMap, EdmItemCollection edmItemCollection)
{
CreateVariableConstraintsRecursion(extent.ElementType, new MemberPath(extent), domainMap, edmItemCollection);
}
internal void CreateAssociationConstraints(EntitySetBase extent, MemberDomainMap domainMap, EdmItemCollection edmItemCollection)
{
AssociationSet assocSet = extent as AssociationSet;
if (assocSet != null)
{
BoolExpression assocSetExpr = BoolExpression.CreateLiteral(new RoleBoolean(assocSet), domainMap);
//Set of Keys for this Association Set
//need to key on EdmMember and EdmType because A, B subtype of C, can have the same id (EdmMember) that is defined in C.
HashSet<Pair<EdmMember, EntityType>> associationkeys = new HashSet<Pair<EdmMember, EntityType>>();
//foreach end, add each Key
foreach (var endMember in assocSet.ElementType.AssociationEndMembers)
{
EntityType type = (EntityType)((RefType)endMember.TypeUsage.EdmType).ElementType;
type.KeyMembers.All(member => associationkeys.Add(new Pair<EdmMember, EntityType>(member, type)) || true /* prevent early termination */);
}
foreach (AssociationSetEnd end in assocSet.AssociationSetEnds)
{
// construct type condition
HashSet<EdmType> derivedTypes = new HashSet<EdmType>();
derivedTypes.UnionWith(MetadataHelper.GetTypeAndSubtypesOf(end.CorrespondingAssociationEndMember.TypeUsage.EdmType, edmItemCollection, false));
BoolExpression typeCondition = CreateIsOfTypeCondition(new MemberPath(end.EntitySet),
derivedTypes, domainMap);
BoolExpression inRoleExpression = BoolExpression.CreateLiteral(new RoleBoolean(end), domainMap);
BoolExpression inSetExpression = BoolExpression.CreateAnd(
BoolExpression.CreateLiteral(new RoleBoolean(end.EntitySet), domainMap),
typeCondition);
// InRole -> (InSet AND type(Set)=T)
AddImplication(inRoleExpression.Tree, inSetExpression.Tree);
if (MetadataHelper.IsEveryOtherEndAtLeastOne(assocSet, end.CorrespondingAssociationEndMember))
{
AddImplication(inSetExpression.Tree, inRoleExpression.Tree);
}
// Add equivalence between association set an End/Role if necessary.
// Equivalence is added when a given association end's keys subsumes keys for
// all the other association end.
// For example: We have Entity Sets A[id1], B[id2, id3] and an association A_B between them.
// Ref Constraint A.id1 = B.id2
// In this case, the Association Set has Key <id1, id2, id3>
// id1 alone can not identify a unique tuple in the Association Set, but <id2, id3> can.
// Therefore we add a constraint: InSet(B) <=> InEnd(A_B.B)
if (MetadataHelper.DoesEndKeySubsumeAssociationSetKey(assocSet,
end.CorrespondingAssociationEndMember,
associationkeys))
{
AddEquivalence(inRoleExpression.Tree, assocSetExpr.Tree);
}
}
// add rules for referential constraints (borrowed from LeftCellWrapper.cs)
AssociationType assocType = assocSet.ElementType;
foreach (ReferentialConstraint constraint in assocType.ReferentialConstraints)
{
AssociationEndMember toEndMember = (AssociationEndMember)constraint.ToRole;
EntitySet toEntitySet = MetadataHelper.GetEntitySetAtEnd(assocSet, toEndMember);
// Check if the keys of the entitySet's are equal to what is specified in the constraint
// How annoying that KeyMembers returns EdmMember and not EdmProperty
IEnumerable<EdmMember> toProperties = Helpers.AsSuperTypeList<EdmProperty, EdmMember>(constraint.ToProperties);
if (Helpers.IsSetEqual(toProperties, toEntitySet.ElementType.KeyMembers, EqualityComparer<EdmMember>.Default))
{
// Now check that the FromEnd is 1..1 (only then will all the Addresses be present in the assoc set)
if (constraint.FromRole.RelationshipMultiplicity.Equals(RelationshipMultiplicity.One))
{
// Make sure that the ToEnd is not 0..* because then the schema is broken
Debug.Assert(constraint.ToRole.RelationshipMultiplicity.Equals(RelationshipMultiplicity.Many) == false);
// Equate the ends
BoolExpression inRoleExpression1 = BoolExpression.CreateLiteral(new RoleBoolean(assocSet.AssociationSetEnds[0]), domainMap);
BoolExpression inRoleExpression2 = BoolExpression.CreateLiteral(new RoleBoolean(assocSet.AssociationSetEnds[1]), domainMap);
AddEquivalence(inRoleExpression1.Tree, inRoleExpression2.Tree);
}
}
}
}
}
internal void CreateEquivalenceConstraintForOneToOneForeignKeyAssociation(AssociationSet assocSet, MemberDomainMap domainMap,
EdmItemCollection edmItemCollection)
{
AssociationType assocType = assocSet.ElementType;
foreach (ReferentialConstraint constraint in assocType.ReferentialConstraints)
{
AssociationEndMember toEndMember = (AssociationEndMember)constraint.ToRole;
AssociationEndMember fromEndMember = (AssociationEndMember)constraint.FromRole;
EntitySet toEntitySet = MetadataHelper.GetEntitySetAtEnd(assocSet, toEndMember);
EntitySet fromEntitySet = MetadataHelper.GetEntitySetAtEnd(assocSet, fromEndMember);
// Check if the keys of the entitySet's are equal to what is specified in the constraint
IEnumerable<EdmMember> toProperties = Helpers.AsSuperTypeList<EdmProperty, EdmMember>(constraint.ToProperties);
if (Helpers.IsSetEqual(toProperties, toEntitySet.ElementType.KeyMembers, EqualityComparer<EdmMember>.Default))
{
//make sure that the method called with a 1:1 association
Debug.Assert(constraint.FromRole.RelationshipMultiplicity.Equals(RelationshipMultiplicity.One));
Debug.Assert(constraint.ToRole.RelationshipMultiplicity.Equals(RelationshipMultiplicity.One));
// Create an Equivalence between the two Sets participating in this AssociationSet
BoolExpression fromSetExpression = BoolExpression.CreateLiteral(new RoleBoolean(fromEntitySet), domainMap);
BoolExpression toSetExpression = BoolExpression.CreateLiteral(new RoleBoolean(toEntitySet), domainMap);
AddEquivalence(fromSetExpression.Tree, toSetExpression.Tree);
}
}
}
private void CreateVariableConstraintsRecursion(EdmType edmType, MemberPath currentPath, MemberDomainMap domainMap, EdmItemCollection edmItemCollection)
{
// Add the types can member have, i.e., its type and its subtypes
HashSet<EdmType> possibleTypes = new HashSet<EdmType>();
possibleTypes.UnionWith(MetadataHelper.GetTypeAndSubtypesOf(edmType, edmItemCollection, true));
foreach (EdmType possibleType in possibleTypes)
{
// determine type domain
HashSet<EdmType> derivedTypes = new HashSet<EdmType>();
derivedTypes.UnionWith(MetadataHelper.GetTypeAndSubtypesOf(possibleType, edmItemCollection, false));
if (derivedTypes.Count != 0)
{
BoolExpression typeCondition = CreateIsOfTypeCondition(currentPath, derivedTypes, domainMap);
BoolExpression typeConditionComplement = BoolExpression.CreateNot(typeCondition);
if (false == typeConditionComplement.IsSatisfiable())
{
continue;
}
StructuralType structuralType = (StructuralType)possibleType;
foreach (EdmProperty childProperty in structuralType.GetDeclaredOnlyMembers<EdmProperty>())
{
MemberPath childPath = new MemberPath(currentPath, childProperty);
bool isScalar = MetadataHelper.IsNonRefSimpleMember(childProperty);
if (domainMap.IsConditionMember(childPath) || domainMap.IsProjectedConditionMember(childPath))
{
BoolExpression nullCondition;
List<Constant> childDomain = new List<Constant>(domainMap.GetDomain(childPath));
if (isScalar)
{
nullCondition = BoolExpression.CreateLiteral(new ScalarRestriction(new MemberProjectedSlot(childPath),
new Domain(ScalarConstant.Undefined, childDomain)), domainMap);
}
else
{
nullCondition = BoolExpression.CreateLiteral(new TypeRestriction(new MemberProjectedSlot(childPath),
new Domain(TypeConstant.Undefined, childDomain)), domainMap);
}
// Properties not occuring in type are UNDEFINED
AddEquivalence(typeConditionComplement.Tree, nullCondition.Tree);
}
// recurse into complex types
if (false == isScalar)
{
CreateVariableConstraintsRecursion(childPath.EdmType, childPath, domainMap, edmItemCollection);
}
}
}
}
}
private static BoolExpression CreateIsOfTypeCondition(MemberPath currentPath, IEnumerable<EdmType> derivedTypes, MemberDomainMap domainMap)
{
Domain typeDomain = new Domain(derivedTypes.Select(derivedType => (Constant)new TypeConstant(derivedType)), domainMap.GetDomain(currentPath));
BoolExpression typeCondition = BoolExpression.CreateLiteral(new TypeRestriction(new MemberProjectedSlot(currentPath), typeDomain), domainMap);
return typeCondition;
}
}
}

Some files were not shown because too many files have changed in this diff Show More