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

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
}
}