Imported Upstream version 3.6.0

Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
This commit is contained in:
Jo Shields
2014-08-13 10:39:27 +01:00
commit a575963da9
50588 changed files with 8155799 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
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 SampleEntityFrameworkProvider
{
/// <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}"/>
///
/// At the end 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,93 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
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 SampleEntityFrameworkProvider
{
/// <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,101 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
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 SampleEntityFrameworkProvider
{
/// <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 sealed 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 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 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 @@
004b900742bebae9f8e3fdc3c947da685939f397

View File

@@ -0,0 +1,308 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
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 SampleEntityFrameworkProvider
{
/// <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 it does not correspond to anything in the DbCommandTree.
/// Each of the fields is a SqlBuilder, so we can keep appending SQL strings
/// or other fragments to build up the clause.
///
/// We have a IsDistinct property to indicate that we want distict 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.
///
/// 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
{
private bool isDistinct;
/// <summary>
/// Do we need to add a DISTINCT at the beginning of the SELECT
/// </summary>
internal bool IsDistinct
{
get { return isDistinct; }
set { isDistinct = value; }
}
private List<Symbol> allJoinExtents;
internal List<Symbol> AllJoinExtents
{
get { return allJoinExtents; }
// We have a setter as well, even though this is a list,
// since we use this field only in special cases.
set { allJoinExtents = value; }
}
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 TopClause top;
internal TopClause Top
{
get { return top; }
set
{
Debug.Assert(top == null, "SqlSelectStatement.Top has already been set");
top = value;
}
}
private SqlBuilder select = new SqlBuilder();
internal SqlBuilder Select
{
get { return select; }
}
private 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
private bool isTopMost;
internal bool IsTopMost
{
get { return this.isTopMost; }
set { this.isTopMost = value; }
}
#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
writer.Write("SELECT ");
if (IsDistinct)
{
writer.Write("DISTINCT ");
}
if (this.Top != null)
{
this.Top.WriteSql(writer, sqlGenerator);
}
if ((null == this.select) || this.Select.IsEmpty)
{
Debug.Assert(false); // we have removed all possibilities of SELECT *.
writer.Write("*");
}
else
{
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.Top != null))
{
writer.WriteLine();
writer.Write("ORDER BY ");
this.OrderBy.WriteSql(writer, sqlGenerator);
}
--writer.Indent;
}
#endregion
}
}

View File

@@ -0,0 +1,78 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
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 SampleEntityFrameworkProvider
{
/// <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)
{
}
/// <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,116 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
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 SampleEntityFrameworkProvider
{
/// <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>
class Symbol : ISqlFragment
{
private Dictionary<string, Symbol> columns = new Dictionary<string, Symbol>(StringComparer.CurrentCultureIgnoreCase);
internal Dictionary<string, Symbol> Columns
{
get { return columns; }
}
private bool needsRenaming = false;
internal bool NeedsRenaming
{
get { return needsRenaming; }
set { needsRenaming = value; }
}
bool isUnnest = false;
internal bool IsUnnest
{
get { return isUnnest; }
set { isUnnest = value; }
}
string name;
public string Name
{
get { return name; }
}
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;
}
#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)
{
string newName;
int i = sqlGenerator.AllColumnNames[this.NewName];
do
{
++i;
newName = this.Name + i.ToString(System.Globalization.CultureInfo.InvariantCulture);
} while (sqlGenerator.AllColumnNames.ContainsKey(newName));
sqlGenerator.AllColumnNames[this.NewName] = i;
// Prevent it from being renamed repeatedly.
this.NeedsRenaming = false;
this.NewName = newName;
// Add this column name to list of known names so that there are no subsequent
// collisions
sqlGenerator.AllColumnNames[newName] = 0;
}
writer.Write(SqlGenerator.QuoteIdentifier(this.NewName));
}
#endregion
}
}

View File

@@ -0,0 +1,50 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
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 SampleEntityFrameworkProvider
{
/// <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,60 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
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 SampleEntityFrameworkProvider
{
/// <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,88 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
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 SampleEntityFrameworkProvider
{
/// <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].
/// </summary>
/// <param name="writer"></param>
/// <param name="sqlGenerator"></param>
public void WriteSql(SqlWriter writer, SqlGenerator sqlGenerator)
{
writer.Write("TOP (");
this.TopCount.WriteSql(writer, sqlGenerator);
writer.Write(")");
writer.Write(" ");
if (this.WithTies)
{
writer.Write("WITH TIES ");
}
}
#endregion
}
}