You've already forked linux-packaging-mono
Imported Upstream version 4.6.0.125
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
This commit is contained in:
parent
a569aebcfd
commit
e79aa3c0ed
@@ -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(); }
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
b09cca657435426ac0e2be61dcf07d98f1cd11a1
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user