Imported Upstream version 4.6.0.125

Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
This commit is contained in:
Xamarin Public Jenkins (auto-signing)
2016-08-03 10:59:49 +00:00
parent a569aebcfd
commit e79aa3c0ed
17047 changed files with 3137615 additions and 392334 deletions

View File

@@ -0,0 +1,208 @@
//------------------------------------------------------------------------------
// <copyright file="IDbSpatialValue.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner willa
// @backupOwner [....]
//------------------------------------------------------------------------------
using System.Data.Spatial;
using System.Data.Metadata.Edm;
namespace System.Data.SqlClient.Internal
{
/// <summary>
/// Adapter interface to make working with instances of <see cref="DbGeometry"/> or <see cref="DbGeography"/> easier.
/// Implementing types wrap instances of DbGeography/DbGeometry and allow them to be consumed in a common way.
/// This interface is implemented by wrapping types for two reasons:
/// 1. The DbGeography/DbGeometry classes cannot directly implement internal interfaces because their members are virtual (behavior is not guaranteed).
/// 2. The wrapping types ensure that instances of IDbSpatialValue handle the <see cref="NotImplementedException"/>s thrown
/// by any unimplemented members of derived DbGeography/DbGeometry types that correspond to the properties and methods declared in the interface.
/// </summary>
internal interface IDbSpatialValue
{
bool IsGeography { get; }
PrimitiveTypeKind PrimitiveType { get; }
object ProviderValue { get; }
int? CoordinateSystemId { get; }
string WellKnownText { get; }
byte[] WellKnownBinary { get; }
string GmlString { get; }
Exception NotSqlCompatible();
}
internal static class IDbSpatialValueExtensionMethods
{
/// <summary>
/// Returns an instance of <see cref="IDbSpatialValue"/> that wraps the specified <see cref="DbGeography"/> value.
/// IDbSpatialValue members are guaranteed not to throw the <see cref="NotImplementedException"/>s caused by unimplemented members of their wrapped values.
/// </summary>
/// <param name="geographyValue">The geography instance to wrap</param>
/// <returns>An instance of <see cref="IDbSpatialValue"/> that wraps the specified geography value</returns>
internal static IDbSpatialValue AsSpatialValue(this DbGeography geographyValue)
{
if (geographyValue == null)
{
return null;
}
return new DbGeographyAdapter(geographyValue);
}
/// <summary>
/// Returns an instance of <see cref="IDbSpatialValue"/> that wraps the specified <see cref="DbGeometry"/> value.
/// IDbSpatialValue members are guaranteed not to throw the <see cref="NotImplementedException"/>s caused by unimplemented members of their wrapped values.
/// </summary>
/// <param name="geometryValue">The geometry instance to wrap</param>
/// <returns>An instance of <see cref="IDbSpatialValue"/> that wraps the specified geometry value</returns>
internal static IDbSpatialValue AsSpatialValue(this DbGeometry geometryValue)
{
if (geometryValue == null)
{
return null;
}
return new DbGeometryAdapter(geometryValue);
}
}
internal struct DbGeographyAdapter : IDbSpatialValue
{
private readonly DbGeography value;
internal DbGeographyAdapter(DbGeography geomValue)
{
this.value = geomValue;
}
private TResult NullIfNotImplemented<TResult>(Func<DbGeography, TResult> accessor)
where TResult : class
{
try
{
return accessor(this.value);
}
catch (NotImplementedException)
{
return null;
}
}
private int? NullIfNotImplemented(Func<DbGeography, int> accessor)
{
try
{
return accessor(this.value);
}
catch (NotImplementedException)
{
return null;
}
}
public bool IsGeography { get { return true; } }
public PrimitiveTypeKind PrimitiveType { get { return PrimitiveTypeKind.Geography; } }
public object ProviderValue
{
get { return NullIfNotImplemented(geog => geog.ProviderValue); }
}
public int? CoordinateSystemId
{
get { return NullIfNotImplemented(geog => geog.CoordinateSystemId); }
}
public string WellKnownText
{
get
{
return NullIfNotImplemented(geog => geog.AsTextIncludingElevationAndMeasure())
?? NullIfNotImplemented(geog => geog.AsText()); // better than nothing if the provider doesn't support AsTextIncludingElevationAndMeasure
}
}
public byte[] WellKnownBinary
{
get { return NullIfNotImplemented(geog => geog.AsBinary()); }
}
public string GmlString
{
get { return NullIfNotImplemented(geog => geog.AsGml()); }
}
public Exception NotSqlCompatible() { return EntityUtil.GeographyValueNotSqlCompatible(); }
}
internal struct DbGeometryAdapter : IDbSpatialValue
{
private readonly DbGeometry value;
internal DbGeometryAdapter(DbGeometry geomValue)
{
this.value = geomValue;
}
private TResult NullIfNotImplemented<TResult>(Func<DbGeometry, TResult> accessor)
where TResult : class
{
try
{
return accessor(this.value);
}
catch (NotImplementedException)
{
return null;
}
}
private int? NullIfNotImplemented(Func<DbGeometry, int> accessor)
{
try
{
return accessor(this.value);
}
catch (NotImplementedException)
{
return null;
}
}
public bool IsGeography { get { return false; } }
public PrimitiveTypeKind PrimitiveType { get { return PrimitiveTypeKind.Geometry; } }
public object ProviderValue
{
get { return NullIfNotImplemented(geom => geom.ProviderValue); }
}
public int? CoordinateSystemId
{
get { return NullIfNotImplemented(geom => geom.CoordinateSystemId); }
}
public string WellKnownText
{
get
{
return NullIfNotImplemented(geom => geom.AsTextIncludingElevationAndMeasure())
?? NullIfNotImplemented(geom => geom.AsText()); // better than nothing if the provider doesn't support AsTextIncludingElevationAndMeasure
}
}
public byte[] WellKnownBinary
{
get { return NullIfNotImplemented(geom => geom.AsBinary()); }
}
public string GmlString
{
get { return NullIfNotImplemented(geom => geom.AsGml()); }
}
public Exception NotSqlCompatible() { return EntityUtil.GeometryValueNotSqlCompatible(); }
}
}

View File

@@ -0,0 +1,46 @@
//---------------------------------------------------------------------
// <copyright file="ISqlFragment.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner katicad
// @backupOwner sheetgu
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Data.SqlClient;
using System.Data.Metadata.Edm;
using System.Data.Common.CommandTrees;
namespace System.Data.SqlClient.SqlGen
{
/// <summary>
/// Represents the sql fragment for any node in the query tree.
/// </summary>
/// <remarks>
/// The nodes in a query tree produce various kinds of sql
/// <list type="bullet">
/// <item>A select statement.</item>
/// <item>A reference to an extent. (symbol)</item>
/// <item>A raw string.</item>
/// </list>
/// We have this interface to allow for a common return type for the methods
/// in the expression visitor <see cref="DbExpressionVisitor{T}"/>
///
/// Add the endd of translation, the sql fragments are converted into real strings.
/// </remarks>
internal interface ISqlFragment
{
/// <summary>
/// Write the string represented by this fragment into the stream.
/// </summary>
/// <param name="writer">The stream that collects the strings.</param>
/// <param name="sqlGenerator">Context information used for renaming.
/// The global lists are used to generated new names without collisions.</param>
void WriteSql(SqlWriter writer, SqlGenerator sqlGenerator);
}
}

View File

@@ -0,0 +1,101 @@
//---------------------------------------------------------------------
// <copyright file="JoinSymbol.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Data.SqlClient;
using System.Data.Metadata.Edm;
using System.Data.Common.CommandTrees;
namespace System.Data.SqlClient.SqlGen
{
/// <summary>
/// A Join symbol is a special kind of Symbol.
/// It has to carry additional information
/// <list type="bullet">
/// <item>ColumnList for the list of columns in the select clause if this
/// symbol represents a sql select statement. This is set by <see cref="SqlGenerator.AddDefaultColumns"/>. </item>
/// <item>ExtentList is the list of extents in the select clause.</item>
/// <item>FlattenedExtentList - if the Join has multiple extents flattened at the
/// top level, we need this information to ensure that extent aliases are renamed
/// correctly in <see cref="SqlSelectStatement.WriteSql"/></item>
/// <item>NameToExtent has all the extents in ExtentList as a dictionary.
/// This is used by <see cref="SqlGenerator.Visit(DbPropertyExpression)"/> to flatten
/// record accesses.</item>
/// <item>IsNestedJoin - is used to determine whether a JoinSymbol is an
/// ordinary join symbol, or one that has a corresponding SqlSelectStatement.</item>
/// </list>
///
/// All the lists are set exactly once, and then used for lookups/enumerated.
/// </summary>
internal sealed class JoinSymbol : Symbol
{
private List<Symbol> columnList;
internal List<Symbol> ColumnList
{
get
{
if (null == columnList)
{
columnList = new List<Symbol>();
}
return columnList;
}
set { columnList = value; }
}
private List<Symbol> extentList;
internal List<Symbol> ExtentList
{
get { return extentList; }
}
private List<Symbol> flattenedExtentList;
internal List<Symbol> FlattenedExtentList
{
get
{
if (null == flattenedExtentList)
{
flattenedExtentList = new List<Symbol>();
}
return flattenedExtentList;
}
set { flattenedExtentList = value; }
}
private Dictionary<string, Symbol> nameToExtent;
internal Dictionary<string, Symbol> NameToExtent
{
get { return nameToExtent; }
}
private bool isNestedJoin;
internal bool IsNestedJoin
{
get { return isNestedJoin; }
set { isNestedJoin = value; }
}
public JoinSymbol(string name, TypeUsage type, List<Symbol> extents)
: base(name, type)
{
extentList = new List<Symbol>(extents.Count);
nameToExtent = new Dictionary<string, Symbol>(extents.Count, StringComparer.OrdinalIgnoreCase);
foreach (Symbol symbol in extents)
{
this.nameToExtent[symbol.Name] = symbol;
this.ExtentList.Add(symbol);
}
}
}
}

View File

@@ -0,0 +1,76 @@
//---------------------------------------------------------------------
// <copyright file="OptionalColumn.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace System.Data.SqlClient.SqlGen
{
/// <summary>
/// Represents a column in a select list that should be printed only if it is later used.
/// Such columns get added by <see cref="SqlGenerator.AddDefaultColumns"/>.
/// The SymbolUsageManager associated with the OptionalColumn has the information whether the column
/// has been used based on its symbol.
/// </summary>
internal sealed class OptionalColumn
{
#region Private State
private readonly SymbolUsageManager m_usageManager;
// The SqlBuilder that contains the column building blocks (e.g: "c.X as X1")
private readonly SqlBuilder m_builder = new SqlBuilder();
// The symbol representing the optional column
private readonly Symbol m_symbol;
#endregion
#region Internal Methods
/// <summary>
/// Append to the "fragment" representing this column
/// </summary>
internal void Append(object s)
{
m_builder.Append(s);
}
internal void MarkAsUsed()
{
this.m_usageManager.MarkAsUsed(this.m_symbol);
}
#endregion
#region Constructor
internal OptionalColumn(SymbolUsageManager usageManager, Symbol symbol)
{
this.m_usageManager = usageManager;
this.m_symbol = symbol;
}
#endregion
#region Internal members
/// <summary>
/// Writes that fragment that represents the optional column
/// if the usage manager says it is used.
/// </summary>
/// <param name="writer"></param>
/// <param name="sqlGenerator"></param>
public bool WriteSqlIfUsed(SqlWriter writer, SqlGenerator sqlGenerator, string separator)
{
if (m_usageManager.IsUsed(m_symbol))
{
writer.Write(separator);
m_builder.WriteSql(writer, sqlGenerator);
return true;
}
return false;
}
#endregion
}
}

View File

@@ -0,0 +1,474 @@
//---------------------------------------------------------------------
// <copyright file="Sql8ExpressionRewriter.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Data.Common;
using System.Data.Common.CommandTrees;
using System.Data.Common.CommandTrees.Internal;
using System.Data.Metadata.Edm;
using System.Data.Common.CommandTrees.ExpressionBuilder;
namespace System.Data.SqlClient.SqlGen
{
/// <summary>
/// Rewrites an expression tree to make it suitable for translation to SQL appropriate for SQL Server 2000
/// In particular, it replaces expressions that are not directly supported on SQL Server 2000
/// with alternative translations. The following expressions are translated:
/// <list type="bullet">
/// <item><see cref="DbExceptExpression"/></item>
/// <item><see cref="DbIntersectExpression"/></item>
/// <item><see cref="DbSkipExpression"/></item>
/// </list>
///
/// The other expressions are copied unmodified.
/// The new expression belongs to a new query command tree.
/// </summary>
internal class Sql8ExpressionRewriter : DbExpressionRebinder
{
#region Entry Point
/// <summary>
/// The only entry point.
/// Rewrites the given tree by replacing expressions that are not directly supported on SQL Server 2000
/// with alterntive translations.
/// </summary>
/// <param name="originalTree">The tree to rewrite</param>
/// <returns>The new tree</returns>
internal static DbQueryCommandTree Rewrite(DbQueryCommandTree originalTree)
{
Debug.Assert(originalTree != null, "OriginalTree is null");
Sql8ExpressionRewriter rewriter = new Sql8ExpressionRewriter(originalTree.MetadataWorkspace);
DbExpression newQuery = rewriter.VisitExpression(originalTree.Query);
return DbQueryCommandTree.FromValidExpression(originalTree.MetadataWorkspace, originalTree.DataSpace, newQuery);
}
#endregion
#region Constructor
/// <summary>
/// Private Constructor.
/// </summary>
/// <param name="metadata"></param>
private Sql8ExpressionRewriter(MetadataWorkspace metadata)
:base(metadata)
{
}
#endregion
#region DbExpressionVisitor<DbExpression> Members
/// <summary>
/// <see cref="TransformIntersectOrExcept(DbExpression left, DbExpression right, DbExpressionKind expressionKind, IList<DbPropertyExpression> sortExpressionsOverLeft, string sortExpressionsBindingVariableName)"/>
/// </summary>
/// <param name="e"></param>
/// <returns></returns>
public override DbExpression Visit(DbExceptExpression e)
{
return TransformIntersectOrExcept(VisitExpression(e.Left), VisitExpression(e.Right), DbExpressionKind.Except);
}
/// <summary>
/// <see cref="TransformIntersectOrExcept(DbExpression left, DbExpression right, DbExpressionKind expressionKind, IList<DbPropertyExpression> sortExpressionsOverLeft, string sortExpressionsBindingVariableName)"/>
/// </summary>
/// <param name="e"></param>
/// <returns></returns>
public override DbExpression Visit(DbIntersectExpression e)
{
return TransformIntersectOrExcept(VisitExpression(e.Left), VisitExpression(e.Right), DbExpressionKind.Intersect);
}
/// <summary>
/// Logicaly, <see cref="DbSkipExpression"/> translates to:
/// SELECT Y.x1, Y.x2, ..., Y.xn
/// FROM (
/// SELECT X.x1, X.x2, ..., X.xn,
/// FROM input AS X
/// EXCEPT
/// SELECT TOP(count) Z.x1, Z.x2, ..., Z.xn
/// FROM input AS Z
/// ORDER BY sk1, sk2, ...
/// ) AS Y
/// ORDER BY sk1, sk2, ...
///
/// Here, input refers to the input of the <see cref="DbSkipExpression"/>, and count to the count property of the <see cref="DbSkipExpression"/>.
/// The implementation of EXCEPT is non-duplicate eliminating, and does equality comparison only over the
/// equality comparable columns of the input.
///
/// This corresponds to the following expression tree:
///
/// SORT
/// |
/// NON-DISTINCT EXCEPT (specially translated, <see cref="TransformIntersectOrExcept(DbExpression left, DbExpression right, DbExpressionKind expressionKind, IList<DbPropertyExpression> sortExpressionsOverLeft, string sortExpressionsBindingVariableName)"/>
/// |
/// | - Left: clone of input
/// | - Right:
/// |
/// Limit
/// |
/// | - Limit: Count
/// | - Input
/// |
/// Sort
/// |
/// input
///
/// </summary>
/// <param name="e"></param>
/// <returns></returns>
public override DbExpression Visit(DbSkipExpression e)
{
//Build the right input of the except
DbExpression rightInput = VisitExpressionBinding(e.Input).Sort(VisitSortOrder(e.SortOrder)).Limit(VisitExpression(e.Count));
//Build the left input for the except
DbExpression leftInput = VisitExpression(e.Input.Expression); //Another copy of the input
IList<DbSortClause> sortOrder = VisitSortOrder(e.SortOrder); //Another copy of the sort order
// Create a list of the sort expressions to be used for translating except
IList<DbPropertyExpression> sortExpressions = new List<DbPropertyExpression>(e.SortOrder.Count);
foreach (DbSortClause sortClause in sortOrder)
{
//We only care about property expressions, not about constants
if (sortClause.Expression.ExpressionKind == DbExpressionKind.Property)
{
sortExpressions.Add((DbPropertyExpression)sortClause.Expression);
}
}
DbExpression exceptExpression = TransformIntersectOrExcept(leftInput, rightInput, DbExpressionKind.Skip, sortExpressions, e.Input.VariableName);
DbExpression result = exceptExpression.BindAs(e.Input.VariableName).Sort(sortOrder);
return result;
}
#endregion
#region DbExpressionVisitor<DbExpression> Member Helpers
/// <summary>
/// This method is invoked when tranforming <see cref="DbIntersectExpression"/> and <see cref="DbExceptExpression"/> by doing comparison over all input columns.
/// <see cref="TransformIntersectOrExcept(DbExpression left, DbExpression right, DbExpressionKind expressionKind, IList<DbPropertyExpression> sortExpressionsOverLeft, string sortExpressionsBindingVariableName)"/>
/// </summary>
/// <param name="left"></param>
/// <param name="right"></param>
/// <param name="expressionKind"></param>
/// <returns></returns>
private DbExpression TransformIntersectOrExcept(DbExpression left, DbExpression right, DbExpressionKind expressionKind)
{
return TransformIntersectOrExcept( left, right, expressionKind, null, null);
}
/// <summary>
/// This method is used for translating <see cref="DbIntersectExpression"/> and <see cref="DbExceptExpression"/>,
/// and for translating the "Except" part of <see cref="DbSkipExpression"/>.
/// into the follwoing expression:
///
/// A INTERSECT B, A EXCEPT B
///
/// (DISTINCT)
/// |
/// FILTER
/// |
/// | - Input: A
/// | - Predicate:(NOT)
/// |
/// ANY
/// |
/// | - Input: B
/// | - Predicate: (B.b1 = A.a1 or (B.b1 is null and A.a1 is null))
/// AND (B.b2 = A.a2 or (B.b2 is null and A.a2 is null))
/// AND ...
/// AND (B.bn = A.an or (B.bn is null and A.an is null)))
///
/// Here, A corresponds to right and B to left.
/// (NOT) is present when transforming Except
/// for the purpose of translating <see cref="DbExceptExpression"/> or <see cref="DbSkipExpression"/>.
/// (DISTINCT) is present when transforming for the purpose of translating
/// <see cref="DbExceptExpression"/> or <see cref="DbIntersectExpression"/>.
///
/// For <see cref="DbSkipExpression"/>, the input to ANY is caped with project which projects out only
/// the columns represented in the sortExpressionsOverLeft list and only these are used in the predicate.
/// This is because we want to support skip over input with non-equal comarable columns and we have no way to recognize these.
/// </summary>
/// <param name="left"></param>
/// <param name="right"></param>
/// <param name="expressionKind"></param>
/// <param name="sortExpressionsOverLeft">note that this list gets destroyed by this method</param>
/// <param name="sortExpressionsBindingVariableName"></param>
/// <returns></returns>
private DbExpression TransformIntersectOrExcept(DbExpression left, DbExpression right, DbExpressionKind expressionKind, IList<DbPropertyExpression> sortExpressionsOverLeft, string sortExpressionsBindingVariableName)
{
bool negate = (expressionKind == DbExpressionKind.Except) || (expressionKind == DbExpressionKind.Skip);
bool distinct = (expressionKind == DbExpressionKind.Except) || (expressionKind == DbExpressionKind.Intersect);
DbExpressionBinding leftInputBinding = left.Bind();
DbExpressionBinding rightInputBinding = right.Bind();
IList<DbPropertyExpression> leftFlattenedProperties = new List<DbPropertyExpression>();
IList<DbPropertyExpression> rightFlattenedProperties = new List<DbPropertyExpression>();
FlattenProperties(leftInputBinding.Variable, leftFlattenedProperties);
FlattenProperties(rightInputBinding.Variable, rightFlattenedProperties);
//For Skip, we need to ignore any columns that are not in the original sort list. We can recognize these by comparing the left flattened properties and
// the properties in the list sortExpressionsOverLeft
// If any such columns exist, we need to add an additional project, to keep the rest of the columns from being projected, as if any among these
// are non equal comparable, SQL Server 2000 throws.
if (expressionKind == DbExpressionKind.Skip)
{
if (RemoveNonSortProperties(leftFlattenedProperties, rightFlattenedProperties, sortExpressionsOverLeft, leftInputBinding.VariableName, sortExpressionsBindingVariableName))
{
rightInputBinding = CapWithProject(rightInputBinding, rightFlattenedProperties);
}
}
Debug.Assert(leftFlattenedProperties.Count == rightFlattenedProperties.Count, "The left and the right input to INTERSECT or EXCEPT have a different number of properties");
Debug.Assert(leftFlattenedProperties.Count != 0, "The inputs to INTERSECT or EXCEPT have no properties");
//Build the predicate for the quantifier:
// (B.b1 = A.a1 or (B.b1 is null and A.a1 is null))
// AND (B.b2 = A.a2 or (B.b2 is null and A.a2 is null))
// AND ...
// AND (B.bn = A.an or (B.bn is null and A.an is null)))
DbExpression existsPredicate = null;
for (int i = 0; i < leftFlattenedProperties.Count; i++)
{
//A.ai == B.bi
DbExpression equalsExpression = leftFlattenedProperties[i].Equal(rightFlattenedProperties[i]);
//A.ai is null AND B.bi is null
DbExpression leftIsNullExpression = leftFlattenedProperties[i].IsNull();
DbExpression rightIsNullExpression = rightFlattenedProperties[i].IsNull();
DbExpression bothNullExpression = leftIsNullExpression.And(rightIsNullExpression);
DbExpression orExpression = equalsExpression.Or(bothNullExpression);
if (i == 0)
{
existsPredicate = orExpression;
}
else
{
existsPredicate = existsPredicate.And(orExpression);
}
}
//Build the quantifier
DbExpression quantifierExpression = rightInputBinding.Any(existsPredicate);
DbExpression filterPredicate;
//Negate if needed
if (negate)
{
filterPredicate = quantifierExpression.Not();
}
else
{
filterPredicate = quantifierExpression;
}
//Build the filter
DbExpression result = leftInputBinding.Filter(filterPredicate);
//Apply distinct in needed
if (distinct)
{
result = result.Distinct();
}
return result;
}
/// <summary>
/// Adds the flattened properties on the input to the flattenedProperties list.
/// </summary>
/// <param name="input"></param>
/// <param name="flattenedProperties"></param>
private void FlattenProperties(DbExpression input, IList<DbPropertyExpression> flattenedProperties)
{
IList<EdmProperty> properties = TypeHelpers.GetProperties(input.ResultType);
Debug.Assert(properties.Count != 0, "No nested properties when FlattenProperties called?");
for (int i = 0; i < properties.Count; i++)
{
DbExpression propertyInput = input;
DbPropertyExpression propertyExpression = propertyInput.Property(properties[i]);
if (TypeSemantics.IsPrimitiveType(properties[i].TypeUsage))
{
flattenedProperties.Add(propertyExpression);
}
else
{
Debug.Assert(TypeSemantics.IsEntityType(properties[i].TypeUsage) || TypeSemantics.IsRowType(properties[i].TypeUsage),
"The input to FlattenProperties is not of EntityType or RowType?");
FlattenProperties(propertyExpression, flattenedProperties);
}
}
}
/// <summary>
/// Helper method for <see cref="TransformIntersectOrExcept(DbExpression left, DbExpression right, DbExpressionKind expressionKind, IList<DbPropertyExpression> sortExpressionsOverLeft, string sortExpressionsBindingVariableName)"/>
/// Removes all pairs of property expressions from list1 and list2, for which the property expression in list1
/// does not have a 'matching' property expression in list2.
/// The lists list1 and list2 are known to not create duplicate, and the purpose of the sortList is just for this method.
/// Thus, to optimize the match process, we remove the seen property expressions from the sort list in <see cref="HasMatchInList"/>
/// when iterating both list simultaneously.
/// </summary>
/// <param name="list1"></param>
/// <param name="list2"></param>
/// <param name="sortList"></param>
/// <param name="list1BindingVariableName"></param>
/// <param name="sortExpressionsBindingVariableName"></param>
/// <returns></returns>
private static bool RemoveNonSortProperties(IList<DbPropertyExpression> list1, IList<DbPropertyExpression> list2, IList<DbPropertyExpression> sortList, string list1BindingVariableName, string sortExpressionsBindingVariableName)
{
bool result = false;
for (int i = list1.Count - 1; i >= 0; i--)
{
if (!HasMatchInList(list1[i], sortList, list1BindingVariableName, sortExpressionsBindingVariableName))
{
list1.RemoveAt(i);
list2.RemoveAt(i);
result = true;
}
}
return result;
}
/// <summary>
/// Helper method for <see cref="RemoveNonSortProperties"/>
/// Checks whether expr has a 'match' in the given list of property expressions.
/// If it does, the matching expression is removed form the list, to speed up future matching.
/// </summary>
/// <param name="expr"></param>
/// <param name="sortList"></param>
/// <param name="exprBindingVariableName"></param>
/// <param name="sortExpressionsBindingVariableName"></param>
/// <returns></returns>
private static bool HasMatchInList(DbPropertyExpression expr, IList<DbPropertyExpression> list, string exprBindingVariableName, string listExpressionsBindingVariableName)
{
for (int i=0; i<list.Count; i++)
{
if (AreMatching(expr, list[i], exprBindingVariableName, listExpressionsBindingVariableName))
{
// This method is used for matching element of two list without duplicates,
// thus if match is found, remove it from the list, to speed up future matching.
list.RemoveAt(i);
return true;
}
}
return false;
}
/// <summary>
/// Determines whether two expressions match.
/// They match if they are of the shape
/// expr1 -> DbPropertyExpression(... (DbPropertyExpression(DbVariableReferenceExpression(expr1BindingVariableName), nameX), ..., name1)
/// expr1 -> DbPropertyExpression(... (DbPropertyExpression(DbVariableReferenceExpression(expr2BindingVariableName), nameX), ..., name1),
///
/// i.e. if they only differ in the name of the binding.
/// </summary>
/// <param name="expr1"></param>
/// <param name="expr2"></param>
/// <param name="expr1BindingVariableName"></param>
/// <param name="expr2BindingVariableName"></param>
/// <returns></returns>
private static bool AreMatching(DbPropertyExpression expr1, DbPropertyExpression expr2, string expr1BindingVariableName, string expr2BindingVariableName)
{
if (expr1.Property.Name != expr2.Property.Name)
{
return false;
}
if (expr1.Instance.ExpressionKind != expr2.Instance.ExpressionKind)
{
return false;
}
if (expr1.Instance.ExpressionKind == DbExpressionKind.Property)
{
return AreMatching((DbPropertyExpression)expr1.Instance, (DbPropertyExpression)expr2.Instance, expr1BindingVariableName, expr2BindingVariableName);
}
DbVariableReferenceExpression instance1 = (DbVariableReferenceExpression)expr1.Instance;
DbVariableReferenceExpression instance2 = (DbVariableReferenceExpression)expr2.Instance;
return (String.Equals(instance1.VariableName, expr1BindingVariableName, StringComparison.Ordinal)
&& String.Equals(instance2.VariableName, expr2BindingVariableName, StringComparison.Ordinal));
}
/// <summary>
/// Helper method for <see cref="TransformIntersectOrExcept(DbExpression left, DbExpression right, DbExpressionKind expressionKind, IList<DbPropertyExpression> sortExpressionsOverLeft, string sortExpressionsBindingVariableName)"/>
/// Creates a <see cref="DbProjectExpression"/> over the given inputBinding that projects out the given flattenedProperties.
/// and updates the flattenedProperties to be over the newly created project.
/// </summary>
/// <param name="inputBinding"></param>
/// <param name="flattenedProperties"></param>
/// <returns>An <see cref="DbExpressionBinding"/> over the newly created <see cref="DbProjectExpression"/></returns>
private DbExpressionBinding CapWithProject(DbExpressionBinding inputBinding, IList<DbPropertyExpression> flattenedProperties)
{
List<KeyValuePair<string, DbExpression>> projectColumns = new List<KeyValuePair<string, DbExpression>>(flattenedProperties.Count);
//List of all the columnNames used in the projection.
Dictionary<string, int> columnNames = new Dictionary<string, int>(flattenedProperties.Count);
foreach (DbPropertyExpression pe in flattenedProperties)
{
//There may be conflicting property names, thus we may need to rename.
string name = pe.Property.Name;
int i;
if (columnNames.TryGetValue(name, out i))
{
string newName;
do
{
++i;
newName = name + i.ToString(System.Globalization.CultureInfo.InvariantCulture);
} while (columnNames.ContainsKey(newName));
columnNames[name] = i;
name = newName;
}
// Add this column name to list of known names so that there are no subsequent
// collisions
columnNames[name] = 0;
projectColumns.Add(new KeyValuePair<string, DbExpression>(name, pe));
}
//Build the project
DbExpression rowExpr = DbExpressionBuilder.NewRow(projectColumns);
DbProjectExpression projectExpression = inputBinding.Project(rowExpr);
//Create the new inputBinding
DbExpressionBinding resultBinding = projectExpression.Bind();
//Create the list of flattenedProperties over the new project
flattenedProperties.Clear();
RowType rowExprType = (RowType)rowExpr.ResultType.EdmType;
foreach (KeyValuePair<string, DbExpression> column in projectColumns)
{
EdmProperty prop = rowExprType.Properties[column.Key];
flattenedProperties.Add(resultBinding.Variable.Property(prop));
}
return resultBinding;
}
#endregion
}
}

View File

@@ -0,0 +1,109 @@
//---------------------------------------------------------------------
// <copyright file="SqlBuilder.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Data.SqlClient;
using System.Data.Metadata.Edm;
using System.Data.Common.CommandTrees;
namespace System.Data.SqlClient.SqlGen
{
/// <summary>
/// This class is like StringBuilder. While traversing the tree for the first time,
/// we do not know all the strings that need to be appended e.g. things that need to be
/// renamed, nested select statements etc. So, we use a builder that can collect
/// all kinds of sql fragments.
/// </summary>
internal class SqlBuilder : ISqlFragment
{
private List<object> _sqlFragments;
private List<object> sqlFragments
{
get
{
if (null == _sqlFragments)
{
_sqlFragments = new List<object>();
}
return _sqlFragments;
}
}
/// <summary>
/// Add an object to the list - we do not verify that it is a proper sql fragment
/// since this is an internal method.
/// </summary>
/// <param name="s"></param>
public void Append(object s)
{
Debug.Assert(s != null);
sqlFragments.Add(s);
}
/// <summary>
/// This is to pretty print the SQL. The writer <see cref="SqlWriter.Write"/>
/// needs to know about new lines so that it can add the right amount of
/// indentation at the beginning of lines.
/// </summary>
public void AppendLine()
{
sqlFragments.Add("\r\n");
}
/// <summary>
/// Whether the builder is empty. This is used by the <see cref="SqlGenerator.Visit(DbProjectExpression)"/>
/// to determine whether a sql statement can be reused.
/// </summary>
public virtual bool IsEmpty
{
get { return ((null == _sqlFragments) || (0 == _sqlFragments.Count)); }
}
#region ISqlFragment Members
/// <summary>
/// We delegate the writing of the fragment to the appropriate type.
/// </summary>
/// <param name="writer"></param>
/// <param name="sqlGenerator"></param>
public virtual void WriteSql(SqlWriter writer, SqlGenerator sqlGenerator)
{
if (null != _sqlFragments)
{
foreach (object o in _sqlFragments)
{
string str = (o as String);
if (null != str)
{
writer.Write(str);
}
else
{
ISqlFragment sqlFragment = (o as ISqlFragment);
if (null != sqlFragment)
{
sqlFragment.WriteSql(writer, sqlGenerator);
}
else
{
throw new InvalidOperationException();
}
}
}
}
}
#endregion
}
}

View File

@@ -0,0 +1 @@
b09cca657435426ac0e2be61dcf07d98f1cd11a1

View File

@@ -0,0 +1,185 @@
//---------------------------------------------------------------------
// <copyright file="SqlSelectClauseBuilder.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace System.Data.SqlClient.SqlGen
{
/// <summary>
/// This class is used for building the SELECT clause of a Sql Statement
/// It is used to gather information about required and optional columns
/// and whether TOP and DISTINCT should be specified.
///
/// The underlying SqlBuilder is used for gathering the required columns.
///
/// The list of OptionalColumns is used for gathering the optional columns.
/// Whether a given OptionalColumn should be written is known only after the entire
/// command tree has been processed.
///
/// The IsDistinct property indicates that we want distinct columns.
/// This is given out of band, since the input expression to the select clause
/// may already have some columns projected out, and we use append-only SqlBuilders.
/// The DISTINCT is inserted when we finally write the object into a string.
///
/// Also, we have a Top property, which is non-null if the number of results should
/// be limited to certain number. It is given out of band for the same reasons as DISTINCT.
///
/// </summary>
internal class SqlSelectClauseBuilder : SqlBuilder
{
#region Fields and Properties
private List<OptionalColumn> m_optionalColumns;
internal void AddOptionalColumn(OptionalColumn column)
{
if (m_optionalColumns == null)
{
m_optionalColumns = new List<OptionalColumn>();
}
m_optionalColumns.Add(column);
}
private TopClause m_top;
internal TopClause Top
{
get { return m_top; }
set
{
Debug.Assert(m_top == null, "SqlSelectStatement.Top has already been set");
m_top = value;
}
}
/// <summary>
/// Do we need to add a DISTINCT at the beginning of the SELECT
/// </summary>
internal bool IsDistinct
{
get;
set;
}
/// <summary>
/// Whether any columns have been specified.
/// </summary>
public override bool IsEmpty
{
get { return (base.IsEmpty) && (this.m_optionalColumns == null || this.m_optionalColumns.Count == 0); }
}
private readonly Func<bool> m_isPartOfTopMostStatement;
#endregion
#region Constructor
internal SqlSelectClauseBuilder(Func<bool> isPartOfTopMostStatement)
{
this.m_isPartOfTopMostStatement = isPartOfTopMostStatement;
}
#endregion
#region ISqlFragment Members
/// <summary>
/// Writes the string representing the Select statement:
///
/// SELECT (DISTINCT) (TOP topClause) (optionalColumns) (requiredColumns)
///
/// If Distinct is specified or this is part of a top most statement
/// all optional columns are marked as used.
///
/// Optional columns are only written if marked as used.
/// In addition, if no required columns are specified and no optional columns are
/// marked as used, the first optional column is written.
///
/// </summary>
/// <param name="writer"></param>
/// <param name="sqlGenerator"></param>
public override void WriteSql(SqlWriter writer, SqlGenerator sqlGenerator)
{
writer.Write("SELECT ");
if (IsDistinct)
{
writer.Write("DISTINCT ");
}
if (this.Top != null)
{
this.Top.WriteSql(writer, sqlGenerator);
}
if (this.IsEmpty)
{
Debug.Assert(false); // we have removed all possibilities of SELECT *.
writer.Write("*");
}
else
{
//Print the optional columns if any
bool printedAny = WriteOptionalColumns(writer, sqlGenerator);
if (!base.IsEmpty)
{
if (printedAny)
{
writer.Write(", ");
}
base.WriteSql(writer, sqlGenerator);
}
//If no optional columns were printed and there were no other columns,
// print at least the first optional column
else if (!printedAny)
{
this.m_optionalColumns[0].MarkAsUsed();
m_optionalColumns[0].WriteSqlIfUsed(writer, sqlGenerator, "");
}
}
}
#endregion
#region Private Helper Methods
/// <summary>
/// Writes the optional columns that are used.
/// If this is the topmost statement or distict is specifed as part of the same statement
/// all optoinal columns are written.
/// </summary>
/// <param name="writer"></param>
/// <param name="sqlGenerator"></param>
/// <returns>Whether at least one column got written</returns>
private bool WriteOptionalColumns(SqlWriter writer, SqlGenerator sqlGenerator)
{
if (this.m_optionalColumns == null)
{
return false;
}
if (m_isPartOfTopMostStatement() || IsDistinct)
{
foreach (OptionalColumn column in this.m_optionalColumns)
{
column.MarkAsUsed();
}
}
string separator = "";
bool printedAny = false;
foreach (OptionalColumn column in this.m_optionalColumns)
{
if (column.WriteSqlIfUsed(writer, sqlGenerator, separator))
{
printedAny = true;
separator = ", ";
}
}
return printedAny;
}
#endregion
}
}

View File

@@ -0,0 +1,287 @@
//---------------------------------------------------------------------
// <copyright file="SqlSelectStatement.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Data.Common.CommandTrees;
namespace System.Data.SqlClient.SqlGen
{
/// <summary>
/// A SqlSelectStatement represents a canonical SQL SELECT statement.
/// It has fields for the 5 main clauses
/// <list type="number">
/// <item>SELECT</item>
/// <item>FROM</item>
/// <item>WHERE</item>
/// <item>GROUP BY</item>
/// <item>ORDER BY</item>
/// </list>
/// We do not have HAVING, since the CQT does not have such a node.
/// Each of the fields is a SqlBuilder, so we can keep appending SQL strings
/// or other fragments to build up the clause.
///
/// The FromExtents contains the list of inputs in use for the select statement.
/// There is usually just one element in this - Select statements for joins may
/// temporarily have more than one.
///
/// If the select statement is created by a Join node, we maintain a list of
/// all the extents that have been flattened in the join in AllJoinExtents
/// <example>
/// in J(j1= J(a,b), c)
/// FromExtents has 2 nodes JoinSymbol(name=j1, ...) and Symbol(name=c)
/// AllJoinExtents has 3 nodes Symbol(name=a), Symbol(name=b), Symbol(name=c)
/// </example>
///
/// If any expression in the non-FROM clause refers to an extent in a higher scope,
/// we add that extent to the OuterExtents list. This list denotes the list
/// of extent aliases that may collide with the aliases used in this select statement.
/// It is set by <see cref="SqlGenerator.Visit(DbVariableReferenceExpression)"/>.
/// An extent is an outer extent if it is not one of the FromExtents.
///
///
/// </summary>
internal sealed class SqlSelectStatement : ISqlFragment
{
/// <summary>
/// Whether the columns ouput by this sql statement were renamed from what given in the command tree.
/// </summary>
internal bool OutputColumnsRenamed
{
get;
set;
}
/// <summary>
/// A dictionary of output columns
/// </summary>
internal Dictionary<string, Symbol> OutputColumns
{
get;
set;
}
internal List<Symbol> AllJoinExtents
{
get;
// We have a setter as well, even though this is a list,
// since we use this field only in special cases.
set;
}
private List<Symbol> fromExtents;
internal List<Symbol> FromExtents
{
get
{
if (null == fromExtents)
{
fromExtents = new List<Symbol>();
}
return fromExtents;
}
}
private Dictionary<Symbol, bool> outerExtents;
internal Dictionary<Symbol, bool> OuterExtents
{
get
{
if (null == outerExtents)
{
outerExtents = new Dictionary<Symbol, bool>();
}
return outerExtents;
}
}
private readonly SqlSelectClauseBuilder select;
internal SqlSelectClauseBuilder Select
{
get { return select; }
}
private readonly SqlBuilder from = new SqlBuilder();
internal SqlBuilder From
{
get { return from; }
}
private SqlBuilder where;
internal SqlBuilder Where
{
get
{
if (null == where)
{
where = new SqlBuilder();
}
return where;
}
}
private SqlBuilder groupBy;
internal SqlBuilder GroupBy
{
get
{
if (null == groupBy)
{
groupBy = new SqlBuilder();
}
return groupBy;
}
}
private SqlBuilder orderBy;
public SqlBuilder OrderBy
{
get
{
if (null == orderBy)
{
orderBy = new SqlBuilder();
}
return orderBy;
}
}
//indicates whether it is the top most select statement,
// if not Order By should be omitted unless there is a corresponding TOP
internal bool IsTopMost
{
get;
set;
}
#region Internal Constructor
internal SqlSelectStatement()
{
this.select = new SqlSelectClauseBuilder(delegate() { return this.IsTopMost; });
}
#endregion
#region ISqlFragment Members
/// <summary>
/// Write out a SQL select statement as a string.
/// We have to
/// <list type="number">
/// <item>Check whether the aliases extents we use in this statement have
/// to be renamed.
/// We first create a list of all the aliases used by the outer extents.
/// For each of the FromExtents( or AllJoinExtents if it is non-null),
/// rename it if it collides with the previous list.
/// </item>
/// <item>Write each of the clauses (if it exists) as a string</item>
/// </list>
/// </summary>
/// <param name="writer"></param>
/// <param name="sqlGenerator"></param>
public void WriteSql(SqlWriter writer, SqlGenerator sqlGenerator)
{
#region Check if FROM aliases need to be renamed
// Create a list of the aliases used by the outer extents
// JoinSymbols have to be treated specially.
List<string> outerExtentAliases = null;
if ((null != outerExtents) && (0 < outerExtents.Count))
{
foreach (Symbol outerExtent in outerExtents.Keys)
{
JoinSymbol joinSymbol = outerExtent as JoinSymbol;
if (joinSymbol != null)
{
foreach (Symbol symbol in joinSymbol.FlattenedExtentList)
{
if (null == outerExtentAliases) { outerExtentAliases = new List<string>(); }
outerExtentAliases.Add(symbol.NewName);
}
}
else
{
if (null == outerExtentAliases) { outerExtentAliases = new List<string>(); }
outerExtentAliases.Add(outerExtent.NewName);
}
}
}
// An then rename each of the FromExtents we have
// If AllJoinExtents is non-null - it has precedence.
// The new name is derived from the old name - we append an increasing int.
List<Symbol> extentList = this.AllJoinExtents ?? this.fromExtents;
if (null != extentList)
{
foreach (Symbol fromAlias in extentList)
{
if ((null != outerExtentAliases) && outerExtentAliases.Contains(fromAlias.Name))
{
int i = sqlGenerator.AllExtentNames[fromAlias.Name];
string newName;
do
{
++i;
newName = fromAlias.Name + i.ToString(System.Globalization.CultureInfo.InvariantCulture);
}
while (sqlGenerator.AllExtentNames.ContainsKey(newName));
sqlGenerator.AllExtentNames[fromAlias.Name] = i;
fromAlias.NewName = newName;
// Add extent to list of known names (although i is always incrementing, "prefix11" can
// eventually collide with "prefix1" when it is extended)
sqlGenerator.AllExtentNames[newName] = 0;
}
// Add the current alias to the list, so that the extents
// that follow do not collide with me.
if (null == outerExtentAliases) { outerExtentAliases = new List<string>(); }
outerExtentAliases.Add(fromAlias.NewName);
}
}
#endregion
// Increase the indent, so that the Sql statement is nested by one tab.
writer.Indent += 1; // ++ can be confusing in this context
this.select.WriteSql(writer, sqlGenerator);
writer.WriteLine();
writer.Write("FROM ");
this.From.WriteSql(writer, sqlGenerator);
if ((null != this.where) && !this.Where.IsEmpty)
{
writer.WriteLine();
writer.Write("WHERE ");
this.Where.WriteSql(writer, sqlGenerator);
}
if ((null != this.groupBy) && !this.GroupBy.IsEmpty)
{
writer.WriteLine();
writer.Write("GROUP BY ");
this.GroupBy.WriteSql(writer, sqlGenerator);
}
if ((null != this.orderBy) && !this.OrderBy.IsEmpty && (this.IsTopMost || this.Select.Top != null))
{
writer.WriteLine();
writer.Write("ORDER BY ");
this.OrderBy.WriteSql(writer, sqlGenerator);
}
--writer.Indent;
}
#endregion
}
}

View File

@@ -0,0 +1,87 @@
//---------------------------------------------------------------------
// <copyright file="SqlWriter.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Data.SqlClient;
using System.Data.Metadata.Edm;
using System.Data.Common.CommandTrees;
namespace System.Data.SqlClient.SqlGen
{
/// <summary>
/// This extends StringWriter primarily to add the ability to add an indent
/// to each line that is written out.
/// </summary>
class SqlWriter : StringWriter
{
// We start at -1, since the first select statement will increment it to 0.
int indent = -1;
/// <summary>
/// The number of tabs to be added at the beginning of each new line.
/// </summary>
internal int Indent
{
get { return indent; }
set { indent = value; }
}
bool atBeginningOfLine = true;
/// <summary>
///
/// </summary>
/// <param name="b"></param>
public SqlWriter(StringBuilder b)
: base(b, System.Globalization.CultureInfo.InvariantCulture)
// I don't think the culture matters, but FxCop wants something
{
}
/// <summary>
/// Reset atBeginningofLine if we detect the newline string.
/// <see cref="SqlBuilder.AppendLine"/>
/// Add as many tabs as the value of indent if we are at the
/// beginning of a line.
/// </summary>
/// <param name="value"></param>
public override void Write(string value)
{
if (value == "\r\n")
{
base.WriteLine();
atBeginningOfLine = true;
}
else
{
if (atBeginningOfLine)
{
if (indent > 0)
{
base.Write(new string('\t', indent));
}
atBeginningOfLine = false;
}
base.Write(value);
}
}
/// <summary>
///
/// </summary>
public override void WriteLine()
{
base.WriteLine();
atBeginningOfLine = true;
}
}
}

View File

@@ -0,0 +1,174 @@
//---------------------------------------------------------------------
// <copyright file="Symbol.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Data.SqlClient;
using System.Data.Metadata.Edm;
using System.Data.Common.CommandTrees;
namespace System.Data.SqlClient.SqlGen
{
/// <summary>
/// <see cref="SymbolTable"/>
/// This class represents an extent/nested select statement,
/// or a column.
///
/// The important fields are Name, Type and NewName.
/// NewName starts off the same as Name, and is then modified as necessary.
///
///
/// The rest are used by special symbols.
/// e.g. NeedsRenaming is used by columns to indicate that a new name must
/// be picked for the column in the second phase of translation.
///
/// IsUnnest is used by symbols for a collection expression used as a from clause.
/// This allows <see cref="SqlGenerator.AddFromSymbol(SqlSelectStatement, string, Symbol, bool)"/> to add the column list
/// after the alias.
///
/// </summary>
internal class Symbol : ISqlFragment
{
/// <summary>
/// Used to track the columns originating from this Symbol when it is used
/// in as a from extent in a SqlSelectStatement with a Join or as a From Extent
/// in a Join Symbol.
/// </summary>
private Dictionary<string, Symbol> columns;
internal Dictionary<string, Symbol> Columns
{
get
{
if (null == columns)
{
columns = new Dictionary<string, Symbol>(StringComparer.OrdinalIgnoreCase);
}
return columns;
}
}
/// <summary>
/// Used to track the output columns of a SqlSelectStatement it represents
/// </summary>
private Dictionary<string, Symbol> outputColumns;
internal Dictionary<string, Symbol> OutputColumns
{
get
{
if (null == outputColumns)
{
outputColumns = new Dictionary<string, Symbol>(StringComparer.OrdinalIgnoreCase);
}
return outputColumns;
}
}
private bool needsRenaming;
internal bool NeedsRenaming
{
get { return needsRenaming; }
set { needsRenaming = value; }
}
private bool outputColumnsRenamed;
internal bool OutputColumnsRenamed
{
get { return outputColumnsRenamed; }
set { outputColumnsRenamed = value; }
}
private string name;
public string Name
{
get { return name; }
}
private string newName;
public string NewName
{
get { return newName; }
set { newName = value; }
}
private TypeUsage type;
internal TypeUsage Type
{
get { return type; }
set { type = value; }
}
public Symbol(string name, TypeUsage type)
{
this.name = name;
this.newName = name;
this.Type = type;
}
/// <summary>
/// Use this constructor if the symbol represents a SqlStatement for which the output columns need to be tracked.
/// </summary>
/// <param name="name"></param>
/// <param name="type"></param>
/// <param name="outputColumns"></param>
/// <param name="outputColumnsRenamed"></param>
public Symbol(string name, TypeUsage type, Dictionary<string, Symbol> outputColumns, bool outputColumnsRenamed)
{
this.name = name;
this.newName = name;
this.Type = type;
this.outputColumns = outputColumns;
this.OutputColumnsRenamed = outputColumnsRenamed;
}
#region ISqlFragment Members
/// <summary>
/// Write this symbol out as a string for sql. This is just
/// the new name of the symbol (which could be the same as the old name).
///
/// We rename columns here if necessary.
/// </summary>
/// <param name="writer"></param>
/// <param name="sqlGenerator"></param>
public void WriteSql(SqlWriter writer, SqlGenerator sqlGenerator)
{
if (this.NeedsRenaming)
{
int i;
if (sqlGenerator.AllColumnNames.TryGetValue(this.NewName, out i))
{
string newNameCandidate;
do
{
++i;
newNameCandidate = this.NewName + i.ToString(System.Globalization.CultureInfo.InvariantCulture);
} while (sqlGenerator.AllColumnNames.ContainsKey(newNameCandidate));
sqlGenerator.AllColumnNames[this.NewName] = i;
this.NewName = newNameCandidate;
}
// Add this column name to list of known names so that there are no subsequent
// collisions
sqlGenerator.AllColumnNames[this.NewName] = 0;
// Prevent it from being renamed repeatedly.
this.NeedsRenaming = false;
}
writer.Write(SqlGenerator.QuoteIdentifier(this.NewName));
}
#endregion
}
}

View File

@@ -0,0 +1,58 @@
//---------------------------------------------------------------------
// <copyright file="SymbolPair.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Data.SqlClient;
using System.Data.Metadata.Edm;
using System.Data.Common.CommandTrees;
namespace System.Data.SqlClient.SqlGen
{
/// <summary>
/// The SymbolPair exists to solve the record flattening problem.
/// <see cref="SqlGenerator.Visit(DbPropertyExpression)"/>
/// Consider a property expression D(v, "j3.j2.j1.a.x")
/// where v is a VarRef, j1, j2, j3 are joins, a is an extent and x is a columns.
/// This has to be translated eventually into {j'}.{x'}
///
/// The source field represents the outermost SqlStatement representing a join
/// expression (say j2) - this is always a Join symbol.
///
/// The column field keeps moving from one join symbol to the next, until it
/// stops at a non-join symbol.
///
/// This is returned by <see cref="SqlGenerator.Visit(DbPropertyExpression)"/>,
/// but never makes it into a SqlBuilder.
/// </summary>
class SymbolPair : ISqlFragment
{
public Symbol Source;
public Symbol Column;
public SymbolPair(Symbol source, Symbol column)
{
this.Source = source;
this.Column = column;
}
#region ISqlFragment Members
public void WriteSql(SqlWriter writer, SqlGenerator sqlGenerator)
{
// Symbol pair should never be part of a SqlBuilder.
Debug.Assert(false);
}
#endregion
}
}

View File

@@ -0,0 +1,68 @@
//---------------------------------------------------------------------
// <copyright file="SymbolTable.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Data.SqlClient;
using System.Data.Metadata.Edm;
using System.Data.Common.CommandTrees;
namespace System.Data.SqlClient.SqlGen
{
/// <summary>
/// The symbol table is quite primitive - it is a stack with a new entry for
/// each scope. Lookups search from the top of the stack to the bottom, until
/// an entry is found.
///
/// The symbols are of the following kinds
/// <list type="bullet">
/// <item><see cref="Symbol"/> represents tables (extents/nested selects/unnests)</item>
/// <item><see cref="JoinSymbol"/> represents Join nodes</item>
/// <item><see cref="Symbol"/> columns.</item>
/// </list>
///
/// Symbols represent names <see cref="SqlGenerator.Visit(DbVariableReferenceExpression)"/> to be resolved,
/// or things to be renamed.
/// </summary>
internal sealed class SymbolTable
{
private List<Dictionary<string, Symbol>> symbols = new List<Dictionary<string, Symbol>>();
internal void EnterScope()
{
symbols.Add(new Dictionary<string, Symbol>(StringComparer.OrdinalIgnoreCase));
}
internal void ExitScope()
{
symbols.RemoveAt(symbols.Count - 1);
}
internal void Add(string name, Symbol value)
{
symbols[symbols.Count - 1][name] = value;
}
internal Symbol Lookup(string name)
{
for (int i = symbols.Count - 1; i >= 0; --i)
{
if (symbols[i].ContainsKey(name))
{
return symbols[i][name];
}
}
return null;
}
}
}

View File

@@ -0,0 +1,78 @@
//---------------------------------------------------------------------
// <copyright file="SymbolUsageManager.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
namespace System.Data.SqlClient.SqlGen
{
/// <summary>
/// Used for wrapping a boolean value as an object.
/// </summary>
internal class BoolWrapper
{
internal bool Value {get; set;}
internal BoolWrapper()
{
this.Value = false;
}
}
/// <summary>
/// Tracks the usage of symbols.
/// When registering a symbol with the usage manager if an input symbol is specified,
/// than the usage of the two is 'connected' - if one ever gets marked as used,
/// the other one becomes 'used' too.
/// </summary>
internal class SymbolUsageManager
{
private readonly Dictionary<Symbol, BoolWrapper> optionalColumnUsage = new Dictionary<Symbol, BoolWrapper>();
internal bool ContainsKey(Symbol key)
{
return optionalColumnUsage.ContainsKey(key);
}
internal bool TryGetValue(Symbol key, out bool value)
{
BoolWrapper wrapper;
if (optionalColumnUsage.TryGetValue(key, out wrapper))
{
value = wrapper.Value;
return true;
}
value = false;
return false;
}
internal void Add(Symbol sourceSymbol, Symbol symbolToAdd)
{
BoolWrapper wrapper;
if (sourceSymbol == null || !this.optionalColumnUsage.TryGetValue(sourceSymbol, out wrapper))
{
wrapper = new BoolWrapper();
}
this.optionalColumnUsage.Add(symbolToAdd, wrapper);
}
internal void MarkAsUsed(Symbol key)
{
if (optionalColumnUsage.ContainsKey(key))
{
optionalColumnUsage[key].Value = true;
}
}
internal bool IsUsed(Symbol key)
{
return optionalColumnUsage[key].Value;
}
}
}

View File

@@ -0,0 +1,107 @@
//---------------------------------------------------------------------
// <copyright file="TopClause.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text;
using System.Data.SqlClient;
using System.Data.Metadata.Edm;
using System.Data.Common.CommandTrees;
namespace System.Data.SqlClient.SqlGen
{
/// <summary>
/// TopClause represents the a TOP expression in a SqlSelectStatement.
/// It has a count property, which indicates how many TOP rows should be selected and a
/// boolen WithTies property.
/// </summary>
class TopClause : ISqlFragment
{
ISqlFragment topCount;
bool withTies;
/// <summary>
/// Do we need to add a WITH_TIES to the top statement
/// </summary>
internal bool WithTies
{
get { return withTies; }
}
/// <summary>
/// How many top rows should be selected.
/// </summary>
internal ISqlFragment TopCount
{
get { return topCount; }
}
/// <summary>
/// Creates a TopClause with the given topCount and withTies.
/// </summary>
/// <param name="topCount"></param>
/// <param name="withTies"></param>
internal TopClause(ISqlFragment topCount, bool withTies)
{
this.topCount = topCount;
this.withTies = withTies;
}
/// <summary>
/// Creates a TopClause with the given topCount and withTies.
/// </summary>
/// <param name="topCount"></param>
/// <param name="withTies"></param>
internal TopClause(int topCount, bool withTies)
{
SqlBuilder sqlBuilder = new SqlBuilder();
sqlBuilder.Append(topCount.ToString(CultureInfo.InvariantCulture));
this.topCount = sqlBuilder;
this.withTies = withTies;
}
#region ISqlFragment Members
/// <summary>
/// Write out the TOP part of sql select statement
/// It basically writes TOP (X) [WITH TIES].
/// The brackets around X are ommited for Sql8.
/// </summary>
/// <param name="writer"></param>
/// <param name="sqlGenerator"></param>
public void WriteSql(SqlWriter writer, SqlGenerator sqlGenerator)
{
writer.Write("TOP ");
if (sqlGenerator.SqlVersion != SqlVersion.Sql8)
{
writer.Write("(");
}
this.TopCount.WriteSql(writer, sqlGenerator);
if (sqlGenerator.SqlVersion != SqlVersion.Sql8)
{
writer.Write(")");
}
writer.Write(" ");
if (this.WithTies)
{
writer.Write("WITH TIES ");
}
}
#endregion
}
}

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