File: System\Data\SqlClient\SqlGen\SqlSelectStatement.cs
Project: ndp\fx\src\DataEntity\System.Data.Entity.csproj (System.Data.Entity)
//---------------------------------------------------------------------
// <copyright file="SqlSelectStatement.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner  Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
 
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
    }
}