File: System\Data\SqlClient\SqlGen\Sql8ConformanceChecker.cs
Project: ndp\fx\src\DataEntity\System.Data.Entity.csproj (System.Data.Entity)
//---------------------------------------------------------------------
// <copyright file="Sql8SupportabilityVisitor.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner  Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
 
namespace System.Data.SqlClient.SqlGen
{
    using System;
    using System.Collections.Generic;
    using System.Data.Common.CommandTrees;
 
    /// <summary>
    /// The Sql8ConformanceChecker walks a DbExpression tree and determines whether 
    /// it should be rewritten in order to be translated to SQL appropriate for SQL Server 2000.
    /// The tree should be rewritten if it contains any of the following expressions:
    /// <list type="bullet">
    /// <item><see cref="DbExceptExpression"/></item>
    /// <item><see cref="DbIntersectExpression"/></item>
    /// <item><see cref="DbSkipExpression"/></item>
    /// </list>
    /// 
    /// Also, it throws if it determines that the tree can not 
    /// be translated into SQL appropriate for SQL Server 2000.
    /// This happens if:
    /// <list type="bullet">
    /// <item>The tree contains <see cref="DbApplyExpression"/></item>
    /// <item>The tree contains <see cref="DbLimitExpression"/> with property Limit of type <see cref="DbParameterReferenceExpression"/></item>    
    /// <item>The tree contains <see cref="DbSkipExpression"/> with property Count of type <see cref="DbParameterReferenceExpression"/></item>
     /// </list>
    /// 
    /// The visitor only checks for expressions for which the support differs between SQL Server 2000 and SQL Server 2005,
    /// but does not check/throw for expressions that are not supported for both providers.
    /// 
    /// Implementation note: In the cases when the visitor encounters an expression that requires rewrite, 
    /// it still needs to walk its structure in case something below it is not supported and needs to throw.
    /// 
    /// </summary>
    internal class Sql8ConformanceChecker : DbExpressionVisitor<bool>
    {
 
        #region 'Public' API
        /// <summary>
        /// The entry point
        /// </summary>
        /// <param name="expr"></param>
        /// <returns>True if the tree needs to be rewriten, false otherwise</returns>
        internal static bool NeedsRewrite(DbExpression expr)
        {
            Sql8ConformanceChecker checker = new Sql8ConformanceChecker();
            return expr.Accept(checker);
        }
        #endregion
 
        #region Constructor
        /// <summary>
        /// Default Constructor
        /// </summary>
        private Sql8ConformanceChecker()
        {
        }
        #endregion
 
        #region Visitor Helpers
 
        /// <summary>
        /// Default handling for DbUnaryExpression-derived classes. Simply visits its argument
        /// </summary>
        /// <param name="expr">The DbUnaryExpression to visit</param>
        /// <returns></returns>
        private bool VisitUnaryExpression(DbUnaryExpression expr)
        {
            return VisitExpression(expr.Argument);
        }
 
        /// <summary>
        /// Default handling for DbBinaryExpression-derived classes. Visits both arguments.
        /// </summary>
        /// <param name="expr">The DbBinaryExpression to visit</param>
        /// <returns></returns>
        private bool VisitBinaryExpression(DbBinaryExpression expr)
        {
            bool leftNeedsRewrite = VisitExpression(expr.Left);
            bool rightNeedsRewrite = VisitExpression(expr.Right);
            return leftNeedsRewrite || rightNeedsRewrite;
        }
 
        /// <summary>
        /// Used for <see cref="VisitList"/>
        /// </summary>
        /// <typeparam name="TElementType"></typeparam>
        /// <param name="element"></param>
        /// <returns></returns>
        private delegate bool ListElementHandler<TElementType>(TElementType element);
 
        /// <summary>
        /// Walks the structure
        /// </summary>
        /// <param name="aggregate"></param>
        /// <returns></returns>
        private bool VisitAggregate(DbAggregate aggregate)
        {
            return VisitExpressionList(aggregate.Arguments);
        }
 
        /// <summary>
        /// DbExpressionBinding handler
        /// </summary>
        /// <param name="expressionBinding"></param>
        /// <returns></returns>
        private bool VisitExpressionBinding(DbExpressionBinding expressionBinding)
        {
            return VisitExpression(expressionBinding.Expression);
        }
 
        /// <summary>
        /// Used as handler for expressions
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        private bool VisitExpression(DbExpression expression)
        {
            if (expression == null)
            {
                return false;
            }
            return expression.Accept(this);
        }
 
        /// <summary>
        /// Used as handler for SortClauses
        /// </summary>
        /// <param name="sortClause"></param>
        /// <returns></returns>
        private bool VisitSortClause(DbSortClause sortClause)
        {
            return VisitExpression(sortClause.Expression);
        }
 
        /// <summary>
        /// Helper method for iterating a list
        /// </summary>
        /// <typeparam name="TElementType"></typeparam>
        /// <param name="handler"></param>
        /// <param name="list"></param>
        /// <returns></returns>
        private static bool VisitList<TElementType>(ListElementHandler<TElementType> handler, IList<TElementType> list)
        {
            bool result = false;
 
            foreach (TElementType element in list)
            {
                bool localResult = handler(element);
                result = result || localResult;
            }
            return result;
        }
 
        /// <summary>
        /// Handing for list of <see cref="DbExpressionBinding"/>s.
        /// </summary>
        /// <param name="list"></param>
        /// <returns></returns>
        private bool VisitAggregateList(IList<DbAggregate> list)
        {
            return VisitList<DbAggregate>(VisitAggregate, list);
        }
 
        /// <summary>
        /// Handing for list of <see cref="DbExpressionBinding"/>s.
        /// </summary>
        /// <param name="list"></param>
        /// <returns></returns>
        private bool VisitExpressionBindingList(IList<DbExpressionBinding> list)
        {
            return VisitList<DbExpressionBinding>(VisitExpressionBinding, list);
        }
 
        /// <summary>
        /// Handing for list of <see cref="DbExpression"/>s.
        /// </summary>
        /// <param name="list"></param>
        /// <returns></returns>
        private bool VisitExpressionList(IList<DbExpression> list)
        {
            return VisitList<DbExpression>(VisitExpression, list);
        }
 
        /// <summary>
        /// Handling for list of <see cref="DbSortClause"/>s.
        /// </summary>
        /// <param name="list"></param>
        /// <returns></returns>
        private bool VisitSortClauseList(IList<DbSortClause> list)
        {
            return VisitList<DbSortClause>(VisitSortClause, list);
        }
        #endregion
 
        #region DbExpressionVisitor Members
 
        /// <summary>
        /// Called when an <see cref="DbExpression"/> of an otherwise unrecognized type is encountered.
        /// </summary>
        /// <param name="expression">The expression</param>
        /// <returns></returns>
        /// <exception cref="NotSupportedException">Always thrown if this method is called, since it indicates that <paramref name="expression"/> is of an unsupported type</exception>
        public override bool Visit(DbExpression expression)
        {
            throw EntityUtil.NotSupported(System.Data.Entity.Strings.Cqt_General_UnsupportedExpression(expression.GetType().FullName));
        }
 
        /// <summary>
        /// <see cref="VisitBinaryExpression"/>
        /// </summary>
        /// <param name="expression">The DbAndExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbAndExpression expression)
        {
            return VisitBinaryExpression(expression);
        }
 
        /// <summary>
        /// Not supported on SQL Server 2000.
        /// </summary>
        /// <param name="expression">The DbApplyExpression that is being visited.</param>
        /// <exception cref="NotSupportedException">Always</exception>
        public override bool Visit(DbApplyExpression expression)
        {
            throw EntityUtil.NotSupported(System.Data.Entity.Strings.SqlGen_ApplyNotSupportedOnSql8);
        }
 
        /// <summary>
        /// Default handling for DbArithmeticExpression. Visits all arguments.
        /// </summary>
        /// <param name="expression">The DbArithmeticExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbArithmeticExpression expression)
        {
            return VisitExpressionList(expression.Arguments);
        }
 
        /// <summary>
        /// Walks the strucutre 
        /// </summary>
        /// <param name="expression">The DbCaseExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbCaseExpression expression)
        {
            bool whenNeedsRewrite = VisitExpressionList(expression.When);
            bool thenNeedsRewrite = VisitExpressionList(expression.Then);
            bool elseNeedsRewrite = VisitExpression(expression.Else);
            return whenNeedsRewrite || thenNeedsRewrite || elseNeedsRewrite;
        }
 
        /// <summary>
        /// <see cref="VisitUnaryExpression"/>
        /// </summary>
        /// <param name="expression">The DbCastExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbCastExpression expression)
        {
            return VisitUnaryExpression(expression);
        }
 
        /// <summary>
        /// <see cref="VisitBinaryExpression"/>
        /// </summary>
        /// <param name="expression">The DbComparisonExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbComparisonExpression expression)
        {
            return VisitBinaryExpression(expression);
        }
 
        /// <summary>
        /// Returns false
        /// </summary>
        /// <param name="expression">The DbConstantExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbConstantExpression expression)
        {
            return false;
        }
 
        /// <summary>
        /// Walks the structure
        /// </summary>
        /// <param name="expression">The DbCrossJoinExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbCrossJoinExpression expression)
        {
            return VisitExpressionBindingList(expression.Inputs);
        }
 
        /// <summary>
        /// <see cref="VisitUnaryExpression"/>
        /// </summary>
        /// <param name="expression">The DeRefExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbDerefExpression expression)
        {
            return VisitUnaryExpression(expression);
        }
 
        /// <summary>
        /// <see cref="VisitUnaryExpression"/>
        /// </summary>
        /// <param name="expression">The DbDistinctExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbDistinctExpression expression)
        {
            return VisitUnaryExpression(expression);
        }
 
        /// <summary>
        /// <see cref="VisitUnaryExpression"/>
        /// </summary>
        /// <param name="expression">The DbElementExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbElementExpression expression)
        {
            return VisitUnaryExpression(expression);
        }
 
        /// <summary>
        /// <see cref="VisitUnaryExpression"/>
        /// </summary>
        /// <param name="expression">The DbEntityRefExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbEntityRefExpression expression)
        {
            return VisitUnaryExpression(expression);
        }
 
        /// <summary>
        /// Returns true, the tree needs to be rewritten.
        /// </summary>
        /// <param name="expression">The DbExceptExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbExceptExpression expression)
        {
            //Walk the structure in case a non-supported construct is encountered 
            VisitExpression(expression.Left);
            VisitExpression(expression.Right);
            return true;
        }
 
        /// <summary>
        /// Walks the structure
        /// </summary>
        /// <param name="expression">The DbFilterExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbFilterExpression expression)
        {
            bool inputNeedsRewrite = VisitExpressionBinding(expression.Input);
            bool predicateNeedsRewrite = VisitExpression(expression.Predicate);
            return inputNeedsRewrite || predicateNeedsRewrite;
        }
 
        /// <summary>
        /// Visits the arguments
        /// </summary>
        /// <param name="expression">The DbFunctionExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbFunctionExpression expression)
        {
            return VisitExpressionList(expression.Arguments);
        }
 
        /// <summary>
        /// Visits the arguments and lambda body
        /// </summary>
        /// <param name="expression">The DbLambdaExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbLambdaExpression expression)
        {
            bool argumentsNeedRewrite = VisitExpressionList(expression.Arguments);
            bool bodyNeedsRewrite = VisitExpression(expression.Lambda.Body);
 
            return argumentsNeedRewrite || bodyNeedsRewrite;
        }
 
        /// <summary>
        /// Walks the structure
        /// </summary>
        /// <param name="expression">The DbExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbGroupByExpression expression)
        {
            bool inputNeedsRewrite = VisitExpression(expression.Input.Expression);
            bool keysNeedRewrite = VisitExpressionList(expression.Keys);
            bool aggregatesNeedRewrite = VisitAggregateList(expression.Aggregates);
 
            return inputNeedsRewrite || keysNeedRewrite || aggregatesNeedRewrite;
        }
 
        /// <summary>
        /// Returns true.
        /// </summary>
        /// <param name="expression">The DbIntersectExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbIntersectExpression expression)
        {
            //Walk the structure in case a non-supported construct is encountered 
            VisitExpression(expression.Left);
            VisitExpression(expression.Right);
            return true;
        }
 
        /// <summary>
        /// <see cref="VisitUnaryExpression"/>
        /// </summary>
        /// <param name="expression">The DbIsEmptyExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbIsEmptyExpression expression)
        {
            return VisitUnaryExpression(expression);
        }
 
        /// <summary>
        /// <see cref="VisitUnaryExpression"/>
        /// </summary>
        /// <param name="expression">The DbIsNullExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbIsNullExpression expression)
        {
            return VisitUnaryExpression(expression);
        }
 
        /// <summary>
        /// <see cref="VisitUnaryExpression"/>
        /// </summary>
        /// <param name="expression">The DbIsOfExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbIsOfExpression expression)
        {
            return VisitUnaryExpression(expression);
        }
 
        /// <summary>
        /// Walks the structure
        /// </summary>
        /// <param name="expression">The DbJoinExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbJoinExpression expression)
        {
            bool leftNeedsRewrite = VisitExpressionBinding(expression.Left);
            bool rightNeedsRewrite = VisitExpressionBinding(expression.Right);
            bool conditionNeedsRewrite = VisitExpression(expression.JoinCondition);
            return leftNeedsRewrite || rightNeedsRewrite || conditionNeedsRewrite;
        }
 
        /// <summary>
        /// Walks the structure
        /// </summary>
        /// <param name="expression">The DbLikeExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbLikeExpression expression)
        {
            bool argumentNeedsRewrite = VisitExpression(expression.Argument);
            bool patternNeedsRewrite = VisitExpression(expression.Pattern);
            bool excapeNeedsRewrite = VisitExpression(expression.Escape);
            return argumentNeedsRewrite || patternNeedsRewrite || excapeNeedsRewrite;
        }
 
        /// <summary>
        /// Walks the structure
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        /// <exception cref="NotSupportedException">expression.Limit is DbParameterReferenceExpression</exception>
        public override bool Visit(DbLimitExpression expression)
        {
            if (expression.Limit is DbParameterReferenceExpression)
            {
                throw EntityUtil.NotSupported(System.Data.Entity.Strings.SqlGen_ParameterForLimitNotSupportedOnSql8);
            }
 
            return VisitExpression(expression.Argument);
        }
 
 
#if METHOD_EXPRESSION
        /// <summary>
        /// Visitor pattern method for <see cref="MethodExpression"/>.
        /// </summary>
        /// <param name="expression">The MethodExpression that is being visited.</param>
        /// <exception cref="ArgumentNullException"><paramref name="expression"/> is null</exception>
        public override bool Visit(MethodExpression expression)
        {
            bool result = VisitExpressionList(expression.Arguments);
            
            if (expression.Instance != null)
            {
               bool instanceNeedsRewrite =  VisitExpression(expression.Instance);
                result = result || instanceNeedsRewrite;
            }
            return result;
        }
#endif
 
        /// <summary>
        /// Walks the arguments
        /// </summary>
        /// <param name="expression">The DbNewInstanceExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbNewInstanceExpression expression)
        {
            return VisitExpressionList(expression.Arguments);
        }
 
        /// <summary>
        /// <see cref="VisitUnaryExpression"/>
        /// </summary>
        /// <param name="expression">The DbNotExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbNotExpression expression)
        {
            return VisitUnaryExpression(expression);
        }
 
        /// <summary>
        /// Returns false        
        /// </summary>
        /// <param name="expression">The DbNullExpression that is being visited.</param>
        /// <returns>false</returns>
        public override bool Visit(DbNullExpression expression)
        {
            return false;
        }
 
        /// <summary>
        /// <see cref="VisitUnaryExpression"/>
        /// </summary>
        /// <param name="expression">The DbOfTypeExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbOfTypeExpression expression)
        {
            return VisitUnaryExpression(expression);
        }
 
        /// <summary>
        /// <see cref="VisitBinaryExpression"/>
        /// </summary>
        /// <param name="expression">The DbOrExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbOrExpression expression)
        {
            return VisitBinaryExpression(expression);
        }
 
        /// <summary>
        /// Returns false        
        /// </summary>
        /// <param name="expression">The DbParameterReferenceExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbParameterReferenceExpression expression)
        {
            return false;
        }
 
        /// <summary>
        /// Walks the structure
        /// </summary>
        /// <param name="expression">The DbProjectExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbProjectExpression expression)
        {
            bool inputNeedsRewrite = VisitExpressionBinding(expression.Input);
            bool projectionNeedsRewrite = VisitExpression(expression.Projection);
            return inputNeedsRewrite || projectionNeedsRewrite;
        }
 
        /// <summary>
        /// Returns false        
        /// </summary>
        /// <param name="expression">The DbPropertyExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbPropertyExpression expression)
        {
            return VisitExpression(expression.Instance);
        }
 
        /// <summary>
        /// Walks the structure
        /// </summary>
        /// <param name="expression">The DbQuantifierExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbQuantifierExpression expression)
        {
            bool inputNeedsRewrite = VisitExpressionBinding(expression.Input);
            bool predicateNeedsRewrite = VisitExpression(expression.Predicate);
            return inputNeedsRewrite || predicateNeedsRewrite;
        }
 
        /// <summary>
        /// <see cref="VisitUnaryExpression"/>
        /// </summary>
        /// <param name="expression">The DbRefExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbRefExpression expression)
        {
            return VisitUnaryExpression(expression);
        }
 
        /// <summary>
        /// <see cref="VisitUnaryExpression"/>
        /// </summary>
        /// <param name="expression">The DbRefKeyExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbRefKeyExpression expression)
        {
            return VisitUnaryExpression(expression);
        }
 
        /// <summary>
        /// Walks the structure
        /// </summary>
        /// <param name="expression">The DbRelationshipNavigationExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbRelationshipNavigationExpression expression)
        {
            return VisitExpression(expression.NavigationSource);
        }
 
        /// <summary>
        /// Returns false;
        /// </summary>
        /// <param name="expression">The DbScanExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbScanExpression expression)
        {
            return false;
        }
 
        /// <summary>
        /// Resturns true
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        /// <exception cref="NotSupportedException">expression.Count is DbParameterReferenceExpression</exception>
        public override bool Visit(DbSkipExpression expression)
        {
            if (expression.Count is DbParameterReferenceExpression)
            {
                throw EntityUtil.NotSupported(System.Data.Entity.Strings.SqlGen_ParameterForSkipNotSupportedOnSql8);
            }
 
            //Walk the structure in case a non-supported construct is encountered 
            VisitExpressionBinding(expression.Input);
            VisitSortClauseList(expression.SortOrder);
            VisitExpression(expression.Count);
 
            return true;
        }
 
        /// <summary>
        /// Walks the structure
        /// </summary>
        /// <param name="expression">The DbSortExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbSortExpression expression)
        {
            bool inputNeedsRewrite = VisitExpressionBinding(expression.Input);
            bool sortClauseNeedsRewrite = VisitSortClauseList(expression.SortOrder);
            return inputNeedsRewrite || sortClauseNeedsRewrite;
        }
 
        /// <summary>
        /// <see cref="VisitUnaryExpression"/>
        /// </summary>
        /// <param name="expression">The DbTreatExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbTreatExpression expression)
        {
            return VisitUnaryExpression(expression);
        }
 
        /// <summary>
        /// <see cref="VisitBinaryExpression"/>
        /// </summary>
        /// <param name="expression">The DbUnionAllExpression that is being visited.</param>
        /// <returns></returns>
        public override bool Visit(DbUnionAllExpression expression)
        {
            return VisitBinaryExpression(expression);
        }
 
        /// <summary>
        /// Returns false        
        /// </summary>
        /// <param name="expression">The DbVariableReferenceExpression that is being visited.</param>
        /// <returns>false</returns>
        public override bool Visit(DbVariableReferenceExpression expression)
        {
            return false;
        }
                
        #endregion
    }
}