File: System\Data\Mapping\ViewGeneration\CqlGeneration\JoinCqlBlock.cs
Project: ndp\fx\src\DataEntity\System.Data.Entity.csproj (System.Data.Entity)
//---------------------------------------------------------------------
// <copyright file="JoinCqlBlock.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
 
using System.Data.Common.CommandTrees;
using System.Data.Common.CommandTrees.ExpressionBuilder;
using System.Data.Common.Utils;
using System.Text;
using System.Collections.Generic;
using System.Data.Mapping.ViewGeneration.Structures;
using System.Diagnostics;
 
namespace System.Data.Mapping.ViewGeneration.CqlGeneration
{
    /// <summary>
    /// Represents to the various Join nodes in the view: IJ, LOJ, FOJ.
    /// </summary>
    internal sealed class JoinCqlBlock : CqlBlock
    {
        #region Constructor
        /// <summary>
        /// Creates a join block (type given by <paramref name="opType"/>) with SELECT (<paramref name="slotInfos"/>), FROM (<paramref name="children"/>),
        /// ON (<paramref name="onClauses"/> - one for each child except 0th), WHERE (true), AS (<paramref name="blockAliasNum"/>).
        /// </summary>
        internal JoinCqlBlock(CellTreeOpType opType,
                              SlotInfo[] slotInfos,
                              List<CqlBlock> children,
                              List<OnClause> onClauses,
                              CqlIdentifiers identifiers,
                              int blockAliasNum)
            : base(slotInfos, children, BoolExpression.True, identifiers, blockAliasNum)
        {
            m_opType = opType;
            m_onClauses = onClauses;
        }
        #endregion
 
        #region Fields
        private readonly CellTreeOpType m_opType;
        private readonly List<OnClause> m_onClauses;
        #endregion
 
        #region Methods
        internal override StringBuilder AsEsql(StringBuilder builder, bool isTopLevel, int indentLevel)
        {
            // The SELECT part.
            StringUtil.IndentNewLine(builder, indentLevel);
            builder.Append("SELECT ");
            GenerateProjectionEsql(
                builder,
                null, /* There is no single input, so the blockAlias is null. ProjectedSlot objects will have to carry their own input block info:
                       * see QualifiedSlot and QualifiedCellIdBoolean for more info. */
                false,
                indentLevel,
                isTopLevel);
            StringUtil.IndentNewLine(builder, indentLevel);
 
            // The FROM part by joining all the children using ON Clauses.
            builder.Append("FROM ");
            int i = 0;
            foreach (CqlBlock child in Children)
            {
                if (i > 0)
                {
                    StringUtil.IndentNewLine(builder, indentLevel + 1);
                    builder.Append(OpCellTreeNode.OpToEsql(m_opType));
                }
                builder.Append(" (");
                child.AsEsql(builder, false, indentLevel + 1);
                builder.Append(") AS ")
                       .Append(child.CqlAlias);
 
                // The ON part.
                if (i > 0)
                {
                    StringUtil.IndentNewLine(builder, indentLevel + 1);
                    builder.Append("ON ");
                    m_onClauses[i - 1].AsEsql(builder);
                }
                i++;
            }
            return builder;
        }
 
        internal override DbExpression AsCqt(bool isTopLevel)
        {
            // The FROM part:
            //  - build a tree of binary joins out of the inputs (this.Children).
            //  - update each child block with its relative position in the join tree, 
            //    so that QualifiedSlot and QualifiedCellIdBoolean objects could find their 
            //    designated block areas inside the cumulative join row passed into their AsCqt(row) method.
            CqlBlock leftmostBlock = this.Children[0];
            DbExpression left = leftmostBlock.AsCqt(false);
            List<string> joinTreeCtxParentQualifiers = new List<string>();
            for (int i = 1; i < this.Children.Count; ++i)
            {
                // Join the current left expression (a tree) to the current right block.
                CqlBlock rightBlock = this.Children[i];
                DbExpression right = rightBlock.AsCqt(false);
                Func<DbExpression, DbExpression, DbExpression> joinConditionFunc = m_onClauses[i - 1].AsCqt;
                DbJoinExpression join;
                switch (m_opType)
                {
                    case CellTreeOpType.FOJ:
                        join = left.FullOuterJoin(right, joinConditionFunc);
                        break;
                    case CellTreeOpType.IJ:
                        join = left.InnerJoin(right, joinConditionFunc);
                        break;
                    case CellTreeOpType.LOJ:
                        join = left.LeftOuterJoin(right, joinConditionFunc);
                        break;
                    default:
                        Debug.Fail("Unknown operator");
                        return null;
                }
 
                if (i == 1)
                {
                    // Assign the joinTreeContext to the leftmost block.
                    leftmostBlock.SetJoinTreeContext(joinTreeCtxParentQualifiers, join.Left.VariableName);
                }
                else
                {
                    // Update the joinTreeCtxParentQualifiers.
                    // Note that all blocks that already participate in the left expression tree share the same copy of the joinTreeContext.
                    joinTreeCtxParentQualifiers.Add(join.Left.VariableName);
                }
 
                // Assign the joinTreeContext to the right block.
                rightBlock.SetJoinTreeContext(joinTreeCtxParentQualifiers, join.Right.VariableName);
 
                left = join;
            }
 
            // The SELECT part.
            return left.Select(row => GenerateProjectionCqt(row, false));
        }
        #endregion
 
        /// <summary>
        /// Represents a complete ON clause "slot1 == slot2 AND "slot3 == slot4" ... for two <see cref="JoinCqlBlock"/>s.
        /// </summary>
        internal sealed class OnClause : InternalBase
        {
            #region Constructor
            internal OnClause()
            {
                m_singleClauses = new List<SingleClause>();
            }
            #endregion
 
            #region Fields
            private readonly List<SingleClause> m_singleClauses;
            #endregion
 
            #region Methods
            /// <summary>
            /// Adds an <see cref="SingleClause"/> element for a join of the form <paramref name="leftSlot"/> = <paramref name="rightSlot"/>.
            /// </summary>
            internal void Add(QualifiedSlot leftSlot, MemberPath leftSlotOutputMember, QualifiedSlot rightSlot, MemberPath rightSlotOutputMember)
            {
                SingleClause singleClause = new SingleClause(leftSlot, leftSlotOutputMember, rightSlot, rightSlotOutputMember);
                m_singleClauses.Add(singleClause);
            }
 
            /// <summary>
            /// Generates eSQL string of the form "LeftSlot1 = RightSlot1 AND LeftSlot2 = RightSlot2 AND ...
            /// </summary>
            internal StringBuilder AsEsql(StringBuilder builder)
            {
                bool isFirst = true;
                foreach (SingleClause singleClause in m_singleClauses)
                {
                    if (false == isFirst)
                    {
                        builder.Append(" AND ");
                    }
                    singleClause.AsEsql(builder);
                    isFirst = false;
                }
                return builder;
            }
 
            /// <summary>
            /// Generates CQT of the form "LeftSlot1 = RightSlot1 AND LeftSlot2 = RightSlot2 AND ...
            /// </summary>
            internal DbExpression AsCqt(DbExpression leftRow, DbExpression rightRow)
            {
                DbExpression cqt = m_singleClauses[0].AsCqt(leftRow, rightRow);
                for (int i = 1; i < m_singleClauses.Count; ++i)
                {
                    cqt = cqt.And(m_singleClauses[i].AsCqt(leftRow, rightRow));
                }
                return cqt;
            }
 
            internal override void ToCompactString(StringBuilder builder)
            {
                builder.Append("ON ");
                StringUtil.ToSeparatedString(builder, m_singleClauses, " AND ");
            }
            #endregion
 
            #region SingleClause
            /// <summary>
            /// Represents an expression between slots of the form: LeftSlot = RightSlot
            /// </summary>
            private sealed class SingleClause : InternalBase
            {
                internal SingleClause(QualifiedSlot leftSlot, MemberPath leftSlotOutputMember, QualifiedSlot rightSlot, MemberPath rightSlotOutputMember)
                {
                    m_leftSlot = leftSlot;
                    m_leftSlotOutputMember = leftSlotOutputMember;
                    m_rightSlot = rightSlot;
                    m_rightSlotOutputMember = rightSlotOutputMember;
                }
 
                #region Fields
                private readonly QualifiedSlot m_leftSlot;
                private readonly MemberPath m_leftSlotOutputMember;
                private readonly QualifiedSlot m_rightSlot;
                private readonly MemberPath m_rightSlotOutputMember;
                #endregion
 
                #region Methods
                /// <summary>
                /// Generates eSQL string of the form "leftSlot = rightSlot".
                /// </summary>
                internal StringBuilder AsEsql(StringBuilder builder)
                {
                    builder.Append(m_leftSlot.GetQualifiedCqlName(m_leftSlotOutputMember))
                           .Append(" = ")
                           .Append(m_rightSlot.GetQualifiedCqlName(m_rightSlotOutputMember));
                    return builder;
                }
 
                /// <summary>
                /// Generates CQT of the form "leftSlot = rightSlot".
                /// </summary>
                internal DbExpression AsCqt(DbExpression leftRow, DbExpression rightRow)
                {
                    return m_leftSlot.AsCqt(leftRow, m_leftSlotOutputMember).Equal(m_rightSlot.AsCqt(rightRow, m_rightSlotOutputMember));
                }
 
                internal override void ToCompactString(StringBuilder builder)
                {
                    m_leftSlot.ToCompactString(builder);
                    builder.Append(" = ");
                    m_rightSlot.ToCompactString(builder);
                }
                #endregion
            }
            #endregion
        }
    }
}