File: SqlClient\Query\SqlBinder.cs
Project: ndp\fx\src\DLinq\Dlinq\System.Data.Linq.csproj (System.Data.Linq)
using System.Collections.Generic;
using System.Data.Linq.Mapping;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
 
namespace System.Data.Linq.SqlClient {
 
    /// <summary>
    /// Binds MemberAccess
    /// Prefetches deferrable expressions (SqlLink) if necessary
    /// Translates structured object comparision (EQ, NE) into memberwise comparison
    /// Translates shared expressions (SqlSharedExpression, SqlSharedExpressionRef)
    /// Optimizes out simple redundant operations : 
    ///     XXX OR TRUE ==> TRUE
    ///     XXX AND FALSE ==> FALSE
    ///     NON-NULL EQ NULL ==> FALSE
    ///     NON-NULL NEQ NULL ==> TRUE
    /// </summary>
 
    internal class SqlBinder {
        SqlColumnizer columnizer;
        Visitor visitor;
        SqlFactory sql;
        Func<SqlNode, SqlNode> prebinder;
 
        bool optimizeLinkExpansions = true;
        bool simplifyCaseStatements = true;
 
        internal SqlBinder(Translator translator, SqlFactory sqlFactory, MetaModel model, DataLoadOptions shape, SqlColumnizer columnizer, bool canUseOuterApply) {
            this.sql = sqlFactory;
            this.columnizer = columnizer;
            this.visitor = new Visitor(this, translator, this.columnizer, this.sql, model, shape, canUseOuterApply);
        }
 
        internal Func<SqlNode, SqlNode> PreBinder {
            get { return this.prebinder; }
            set { this.prebinder = value; }
        }
 
        private SqlNode Prebind(SqlNode node) {
            if (this.prebinder != null) {
                node = this.prebinder(node);
            }
            return node;
        }
 
        class LinkOptimizationScope {
            Dictionary<object, SqlExpression> map;
            LinkOptimizationScope previous;
 
            internal LinkOptimizationScope(LinkOptimizationScope previous) {
                this.previous = previous;
            }
            internal void Add(object linkId, SqlExpression expr) {
                if (this.map == null) {
                    this.map = new Dictionary<object,SqlExpression>();
                }
                this.map.Add(linkId, expr);
            }
            internal bool TryGetValue(object linkId, out SqlExpression expr) {
                expr = null;
                return (this.map != null && this.map.TryGetValue(linkId, out expr)) ||
                       (this.previous != null && this.previous.TryGetValue(linkId, out expr));
            }
        }
 
        internal SqlNode Bind(SqlNode node) {
            node = Prebind(node);
            node = this.visitor.Visit(node);
            return node;
        }
 
        internal bool OptimizeLinkExpansions {
            get { return this.optimizeLinkExpansions; }
            set { this.optimizeLinkExpansions = value; }
        }
 
        internal bool SimplifyCaseStatements {
            get { return this.simplifyCaseStatements; }
            set { this.simplifyCaseStatements = value; }
        }
 
        [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification="These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")]
        class Visitor : SqlVisitor {
            SqlBinder binder;
            Translator translator;
            SqlFactory sql;
            TypeSystemProvider typeProvider;
            SqlExpander expander;
            SqlColumnizer columnizer;
            SqlAggregateChecker aggregateChecker;
            SqlSelect currentSelect;
            SqlAlias currentAlias;
            Dictionary<SqlAlias, SqlAlias> outerAliasMap;
            LinkOptimizationScope linkMap;
            MetaModel model;
            HashSet<MetaType> alreadyIncluded;
            DataLoadOptions shape;
            bool disableInclude;
            bool inGroupBy;
            bool canUseOuterApply;
 
            internal Visitor(SqlBinder binder, Translator translator, SqlColumnizer columnizer, SqlFactory sqlFactory, MetaModel model, DataLoadOptions shape, bool canUseOuterApply) {
                this.binder = binder;
                this.translator = translator;
                this.columnizer = columnizer;
                this.sql = sqlFactory;
                this.typeProvider = sqlFactory.TypeProvider;
                this.expander = new SqlExpander(this.sql);
                this.aggregateChecker = new SqlAggregateChecker();
                this.linkMap = new LinkOptimizationScope(null);
                this.outerAliasMap = new Dictionary<SqlAlias, SqlAlias>();
                this.model = model;
                this.shape = shape;
                this.canUseOuterApply = canUseOuterApply;
            }
 
            internal override SqlExpression VisitExpression(SqlExpression expr) {
                return this.ConvertToExpression(this.Visit(expr));
            }
 
            internal override SqlNode VisitIncludeScope(SqlIncludeScope scope) {
                this.alreadyIncluded = new HashSet<MetaType>();
                try {
                    return this.Visit(scope.Child); // Strip the include scope so SqlBinder will be idempotent.
                }
                finally {
                    this.alreadyIncluded = null;
                }
            }
 
            internal override SqlUserQuery VisitUserQuery(SqlUserQuery suq) {
                this.disableInclude = true;
                return base.VisitUserQuery(suq);
            }
 
            internal SqlExpression FetchExpression(SqlExpression expr) {
                return this.ConvertToExpression(this.ConvertToFetchedExpression(this.ConvertLinks(this.VisitExpression(expr))));
            }
 
            internal override SqlExpression VisitFunctionCall(SqlFunctionCall fc) {
                for (int i = 0, n = fc.Arguments.Count; i < n; i++) {
                    fc.Arguments[i] = this.FetchExpression(fc.Arguments[i]);
                }
                return fc;
            }
 
            internal override SqlExpression VisitLike(SqlLike like) {
                like.Expression = this.FetchExpression(like.Expression);
                like.Pattern = this.FetchExpression(like.Pattern);
                return base.VisitLike(like);
            }
 
            internal override SqlExpression VisitGrouping(SqlGrouping g) {
                g.Key = this.FetchExpression(g.Key);
                g.Group = this.FetchExpression(g.Group);
                return g;
            }
 
            internal override SqlExpression VisitMethodCall(SqlMethodCall mc) {
                mc.Object = this.FetchExpression(mc.Object);
                for (int i = 0, n = mc.Arguments.Count; i < n; i++) {
                    mc.Arguments[i] = this.FetchExpression(mc.Arguments[i]);
                }
                return mc;
            }
 
            [SuppressMessage("Microsoft.Maintainability", "CA1505:AvoidUnmaintainableCode", Justification = "These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")]
            [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")]
            internal override SqlExpression VisitBinaryOperator(SqlBinary bo) {
                // Below we translate comparisons with constant NULL to either IS NULL or IS NOT NULL.
                // We only want to do this if the type of the binary expression is not nullable.
                switch (bo.NodeType) {
                    case SqlNodeType.EQ:
                    case SqlNodeType.EQ2V:
                        if (this.IsConstNull(bo.Left) && !TypeSystem.IsNullableType(bo.ClrType)) {
                            return this.VisitUnaryOperator(this.sql.Unary(SqlNodeType.IsNull, bo.Right, bo.SourceExpression));
                        }
                        else if (this.IsConstNull(bo.Right) && !TypeSystem.IsNullableType(bo.ClrType)) {
                            return this.VisitUnaryOperator(this.sql.Unary(SqlNodeType.IsNull, bo.Left, bo.SourceExpression));
                        }
                        break;
                    case SqlNodeType.NE:
                    case SqlNodeType.NE2V:
                        if (this.IsConstNull(bo.Left) && !TypeSystem.IsNullableType(bo.ClrType)) {
                            return this.VisitUnaryOperator(this.sql.Unary(SqlNodeType.IsNotNull, bo.Right, bo.SourceExpression));
                        }
                        else if (this.IsConstNull(bo.Right) && !TypeSystem.IsNullableType(bo.ClrType)) {
                            return this.VisitUnaryOperator(this.sql.Unary(SqlNodeType.IsNotNull, bo.Left, bo.SourceExpression));
                        }
                        break;
                }
                
                bo.Left = this.VisitExpression(bo.Left);
                bo.Right = this.VisitExpression(bo.Right);
 
                switch (bo.NodeType) {
                    case SqlNodeType.EQ:
                    case SqlNodeType.EQ2V:   
                    case SqlNodeType.NE:
                    case SqlNodeType.NE2V: {
                        SqlValue vLeft = bo.Left as SqlValue;
                        SqlValue vRight = bo.Right as SqlValue;
                        bool leftIsBool = vLeft!=null && vLeft.Value is bool;
                        bool rightIsBool = vRight!=null && vRight.Value is bool;
                        if (leftIsBool || rightIsBool) {
                            bool equal = bo.NodeType != SqlNodeType.NE && bo.NodeType != SqlNodeType.NE2V;
                            bool isTwoValue = bo.NodeType == SqlNodeType.EQ2V || bo.NodeType == SqlNodeType.NE2V;
                            SqlNodeType negator = isTwoValue ? SqlNodeType.Not2V : SqlNodeType.Not;
                            if (leftIsBool && !rightIsBool) {
                                bool value = (bool)vLeft.Value;
                                if (value^equal) {
                                    return VisitUnaryOperator(new SqlUnary(negator, bo.ClrType, bo.SqlType, sql.DoNotVisitExpression(bo.Right), bo.SourceExpression));
                                }
                                if (bo.Right.ClrType==typeof(bool)) { // If the other side is nullable bool then this expression is already a reasonable way to handle three-values
                                    return bo.Right;
                                }
                            }
                            else if (!leftIsBool && rightIsBool) {
                                bool value = (bool)vRight.Value;
                                if (value^equal) {                               
                                    return VisitUnaryOperator(new SqlUnary(negator, bo.ClrType, bo.SqlType, sql.DoNotVisitExpression(bo.Left), bo.SourceExpression));
                                }
                                if (bo.Left.ClrType==typeof(bool)) { // If the other side is nullable bool then this expression is already a reasonable way to handle three-values
                                    return bo.Left;
                                }                                
                            } else if (leftIsBool && rightIsBool) {
                                // Here, both left and right are bools.
                                bool leftValue = (bool)vLeft.Value;
                                bool rightValue = (bool)vRight.Value;
                                
                                if (equal) {
                                    return sql.ValueFromObject(leftValue==rightValue, false, bo.SourceExpression);
                                } else {
                                    return sql.ValueFromObject(leftValue!=rightValue, false, bo.SourceExpression);
                                }
                            }
                        }
                        break;
                    }
                } 
                
                switch (bo.NodeType) {
                    case SqlNodeType.And: {
                            SqlValue vLeft = bo.Left as SqlValue;
                            SqlValue vRight = bo.Right as SqlValue;
                            if (vLeft != null && vRight == null) {
                                if (vLeft.Value != null && (bool)vLeft.Value) {
                                    return bo.Right;
                                }
                                return sql.ValueFromObject(false, false, bo.SourceExpression);
                            }
                            else if (vLeft == null && vRight != null) {
                                if (vRight.Value != null && (bool)vRight.Value) {
                                    return bo.Left;
                                }
                                return sql.ValueFromObject(false, false, bo.SourceExpression);
                            }
                            else if (vLeft != null && vRight != null) {
                                return sql.ValueFromObject((bool)(vLeft.Value ?? false) && (bool)(vRight.Value ?? false), false, bo.SourceExpression);
                            }
                            break;
                        }
 
                    case SqlNodeType.Or: {
                            SqlValue vLeft = bo.Left as SqlValue;
                            SqlValue vRight = bo.Right as SqlValue;
                            if (vLeft != null && vRight == null) {
                                if (vLeft.Value != null && !(bool)vLeft.Value) {
                                    return bo.Right;
                                }
                                return sql.ValueFromObject(true, false, bo.SourceExpression);
                            }
                            else if (vLeft == null && vRight != null) {
                                if (vRight.Value != null && !(bool)vRight.Value) {
                                    return bo.Left;
                                }
                                return sql.ValueFromObject(true, false, bo.SourceExpression);
                            }
                            else if (vLeft != null && vRight != null) {
                                return sql.ValueFromObject((bool)(vLeft.Value ?? false) || (bool)(vRight.Value ?? false), false, bo.SourceExpression);
                            }
                            break;
                        }
 
                    case SqlNodeType.EQ:
                    case SqlNodeType.NE:
                    case SqlNodeType.EQ2V:
                    case SqlNodeType.NE2V: {
                        SqlExpression translated = this.translator.TranslateLinkEquals(bo);
                        if (translated != bo) {
                            return this.VisitExpression(translated);
                        }
                        break;
                    }
                }
 
                bo.Left = this.ConvertToFetchedExpression(bo.Left);
                bo.Right = this.ConvertToFetchedExpression(bo.Right);
 
                switch (bo.NodeType) {
                    case SqlNodeType.EQ:
                    case SqlNodeType.NE:
                    case SqlNodeType.EQ2V:
                    case SqlNodeType.NE2V:
                        SqlExpression translated = this.translator.TranslateEquals(bo);
                        if (translated != bo) {
                            return this.VisitExpression(translated);
                        }
 
                        // Special handling for typeof(Type) nodes. Reduce to a static check if possible;
                        // strip SqlDiscriminatedType if possible;
                        if (typeof(Type).IsAssignableFrom(bo.Left.ClrType)) {
                            SqlExpression left = TypeSource.GetTypeSource(bo.Left);
                            SqlExpression right = TypeSource.GetTypeSource(bo.Right);
 
                            MetaType[] leftPossibleTypes = GetPossibleTypes(left);
                            MetaType[] rightPossibleTypes = GetPossibleTypes(right);
 
                            bool someMatch = false;
                            for (int i = 0; i < leftPossibleTypes.Length; ++i) {
                                for (int j = 0; j < rightPossibleTypes.Length; ++j) {
                                    if (leftPossibleTypes[i] == rightPossibleTypes[j]) {
                                        someMatch = true;
                                        break;
                                    }
                                }
                            }
 
                            // Is a match possible?
                            if (!someMatch) {
                                // No match is possible
                                return this.VisitExpression(sql.ValueFromObject(bo.NodeType == SqlNodeType.NE, false, bo.SourceExpression));
                            }
 
                            // Is the match known statically?
                            if (leftPossibleTypes.Length == 1 && rightPossibleTypes.Length == 1) {
                                // Yes, the match is statically known.
                                return this.VisitExpression(sql.ValueFromObject(
                                    (bo.NodeType == SqlNodeType.EQ) == (leftPossibleTypes[0] == rightPossibleTypes[0]),
                                    false,
                                    bo.SourceExpression));
                            }
 
                            // If both sides are discriminated types, then create a comparison of discriminators instead;
                            SqlDiscriminatedType leftDt = bo.Left as SqlDiscriminatedType;
                            SqlDiscriminatedType rightDt = bo.Right as SqlDiscriminatedType;
                            if (leftDt != null && rightDt != null) {
                                return this.VisitExpression(sql.Binary(bo.NodeType, leftDt.Discriminator, rightDt.Discriminator));
                            }
                        }
 
                        // can only compare sql scalars
                        if (TypeSystem.IsSequenceType(bo.Left.ClrType)) {
                            throw Error.ComparisonNotSupportedForType(bo.Left.ClrType);
                        }
                        if (TypeSystem.IsSequenceType(bo.Right.ClrType)) {
                            throw Error.ComparisonNotSupportedForType(bo.Right.ClrType);
                        }
                        break;
                }
                return bo;
            }
 
            /// <summary>
            /// Given an expression, return the set of dynamic types that could be returned.
            /// </summary>
            private MetaType[] GetPossibleTypes(SqlExpression typeExpression) {
                if (!typeof(Type).IsAssignableFrom(typeExpression.ClrType)) {
                    return new MetaType[0];
                }
                if (typeExpression.NodeType == SqlNodeType.DiscriminatedType) {
                    SqlDiscriminatedType dt = (SqlDiscriminatedType)typeExpression;
                    List<MetaType> concreteTypes = new List<MetaType>();
                    foreach (MetaType mt in dt.TargetType.InheritanceTypes) {
                        if (!mt.Type.IsAbstract) {
                            concreteTypes.Add(mt);
                        }
                    }
                    return concreteTypes.ToArray();
                }
                else if (typeExpression.NodeType == SqlNodeType.Value) {
                    SqlValue val = (SqlValue)typeExpression;
                    MetaType mt = this.model.GetMetaType((Type)val.Value);
                    return new MetaType[] { mt };
                } else if (typeExpression.NodeType == SqlNodeType.SearchedCase) {
                    SqlSearchedCase sc = (SqlSearchedCase)typeExpression;
                    HashSet<MetaType> types = new HashSet<MetaType>();
                    foreach (var when in sc.Whens) {
                        types.UnionWith(GetPossibleTypes(when.Value));
                    }
                    return types.ToArray();
                }
                throw Error.UnexpectedNode(typeExpression.NodeType);
            }
 
            /// <summary>
            /// Evaluate the object and extract its discriminator.
            /// </summary>
            internal override SqlExpression VisitDiscriminatorOf(SqlDiscriminatorOf dof) {
                SqlExpression obj = this.FetchExpression(dof.Object); // FetchExpression removes Link.
                // It's valid to unwrap optional and outer-join values here because type case already handles
                // NULL values correctly.
                while (obj.NodeType == SqlNodeType.OptionalValue
                    || obj.NodeType == SqlNodeType.OuterJoinedValue) {
                    if (obj.NodeType == SqlNodeType.OptionalValue) {
                        obj = ((SqlOptionalValue)obj).Value;
                    }
                    else {
                        obj = ((SqlUnary)obj).Operand;
                    }
                }
                if (obj.NodeType == SqlNodeType.TypeCase) {
                    SqlTypeCase tc = (SqlTypeCase)obj;
                    // Rewrite a case of discriminators. We can't just reduce to 
                    // discriminator (yet) because the ELSE clause needs to be considered.
                    // Later in the conversion there is an optimization that will turn the CASE
                    // into a simple combination of ANDs and ORs.
                    // Also, cannot reduce to IsNull(Discriminator,DefaultDiscriminator) because
                    // other unexpected values besides NULL need to be handled.
                    List<SqlExpression> matches = new List<SqlExpression>();
                    List<SqlExpression> values = new List<SqlExpression>();
                    MetaType defaultType = tc.RowType.InheritanceDefault;
                    object discriminator = defaultType.InheritanceCode;
                    foreach (SqlTypeCaseWhen when in tc.Whens) {
                        matches.Add(when.Match);
                        if (when.Match == null) {
                            SqlExpression @default = sql.Value(discriminator.GetType(), tc.Whens[0].Match.SqlType, defaultType.InheritanceCode, true, tc.SourceExpression);
                            values.Add(@default);
                        }
                        else {
                            // Must duplicate so that columnizer doesn't nominate the match as a value.
                            values.Add(sql.Value(discriminator.GetType(), when.Match.SqlType, ((SqlValue)when.Match).Value, true, tc.SourceExpression));
                        }
                    }
                    return sql.Case(tc.Discriminator.ClrType, tc.Discriminator, matches, values, tc.SourceExpression);
                } else {
                    var mt = this.model.GetMetaType(obj.ClrType).InheritanceRoot;
                    if (mt.HasInheritance) {
                        return this.VisitExpression(sql.Member(dof.Object, mt.Discriminator.Member));
                    }
                }
                return sql.TypedLiteralNull(dof.ClrType, dof.SourceExpression);
            }
 
            internal override SqlExpression VisitSearchedCase(SqlSearchedCase c) {
                if ((c.ClrType == typeof(bool) || c.ClrType == typeof(bool?)) &&
                    c.Whens.Count == 1 && c.Else != null) {
                    SqlValue litElse = c.Else as SqlValue;
                    SqlValue litWhen = c.Whens[0].Value as SqlValue;
 
                    if (litElse != null && litElse.Value != null && !(bool)litElse.Value) {
                        return this.VisitExpression(sql.Binary(SqlNodeType.And, c.Whens[0].Match, c.Whens[0].Value));
                    }
                    else if (litWhen != null && litWhen.Value != null && (bool)litWhen.Value) {
                        return this.VisitExpression(sql.Binary(SqlNodeType.Or, c.Whens[0].Match, c.Else));
                    }
                }
                return base.VisitSearchedCase(c);
            }
 
            [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification="Unknown reason.")]
            private bool IsConstNull(SqlExpression sqlExpr) {
                SqlValue sqlValue = sqlExpr as SqlValue;
                if (sqlValue == null) {
                    return false;
                }
                // literal nulls are encoded as IsClientSpecified=false
                return sqlValue.Value == null && !sqlValue.IsClientSpecified;
            }
 
            /// <summary>
            /// Apply the 'TREAT' operator into the given target. The goal is for instances of non-assignable types 
            /// to be nulled out.
            /// </summary>
            private SqlExpression ApplyTreat(SqlExpression target, Type type) {
                switch (target.NodeType) {
                    case SqlNodeType.OptionalValue:
                        SqlOptionalValue optValue = (SqlOptionalValue)target;
                        return ApplyTreat(optValue.Value, type);
                    case SqlNodeType.OuterJoinedValue:
                        SqlUnary unary = (SqlUnary)target;
                        return ApplyTreat(unary.Operand, type);
                    case SqlNodeType.New:
                        var n = (SqlNew)target;
                        // Are we constructing a concrete instance of a type we know can't be assigned
                        // to 'type'? If so, make it null.
                        if (!type.IsAssignableFrom(n.ClrType)) { 
                            return sql.TypedLiteralNull(type, target.SourceExpression);
                        }
                        return target;
                    case SqlNodeType.TypeCase:
                        SqlTypeCase tc = (SqlTypeCase)target;
                        // Null out type case options that are impossible now.
                        int reducedToNull = 0;
                        foreach (SqlTypeCaseWhen when in tc.Whens) {
                            when.TypeBinding = (SqlExpression)ApplyTreat(when.TypeBinding, type);
                            if (this.IsConstNull(when.TypeBinding)) {
                                ++reducedToNull;
                            }
                        }
                        // If every case reduced to NULL then reduce the whole clause entirely to NULL.
                        if (reducedToNull == tc.Whens.Count) {
                            // This is not an optimization. We need to do this because the type-case may be the l-value of an assign.
                            tc.Whens[0].TypeBinding.SetClrType(type);
                            return tc.Whens[0].TypeBinding; // <-- Points to a SqlValue null.
                        }
                        tc.SetClrType(type);
                        return target;
                    default:
                        SqlExpression expr = target as SqlExpression;
                        if (expr != null) {
                            if (!type.IsAssignableFrom(expr.ClrType) && !expr.ClrType.IsAssignableFrom(type)) {
                                return sql.TypedLiteralNull(type, target.SourceExpression);
                            } 
                        }
                        else {
                            System.Diagnostics.Debug.Assert(false, "Don't know how to apply 'as' to " + target.NodeType);
                        }
                        return target;
                }
            }
 
            internal override SqlExpression VisitTreat(SqlUnary a) {
                return VisitUnaryOperator(a);
            }
 
            [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")]
            [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")]
            internal override SqlExpression VisitUnaryOperator(SqlUnary uo) {
                uo.Operand = this.VisitExpression(uo.Operand);
                // ------------------------------------------------------------
                // PHASE 1: If possible, evaluate without fetching the operand.
                // This is preferred because fetching LINKs causes them to not
                // be deferred.
                // ------------------------------------------------------------
                if (uo.NodeType == SqlNodeType.IsNull || uo.NodeType == SqlNodeType.IsNotNull) {
                    SqlExpression translated = this.translator.TranslateLinkIsNull(uo);
                    if (translated != uo) {
                        return this.VisitExpression(translated);
                    }
                    if (uo.Operand.NodeType==SqlNodeType.OuterJoinedValue) {
                        SqlUnary ojv = uo.Operand as SqlUnary;
                        if (ojv.Operand.NodeType == SqlNodeType.OptionalValue) {
                            SqlOptionalValue ov = (SqlOptionalValue)ojv.Operand;
                            return this.VisitUnaryOperator(
                                new SqlUnary(uo.NodeType, uo.ClrType, uo.SqlType,
                                    new SqlUnary(SqlNodeType.OuterJoinedValue, ov.ClrType, ov.SqlType, ov.HasValue, ov.SourceExpression)
                                , uo.SourceExpression)
                            );
                        }
                        else if (ojv.Operand.NodeType == SqlNodeType.TypeCase) {
                            SqlTypeCase tc = (SqlTypeCase)ojv.Operand;
                            return new SqlUnary(uo.NodeType, uo.ClrType, uo.SqlType,
                                       new SqlUnary(SqlNodeType.OuterJoinedValue, tc.Discriminator.ClrType, tc.Discriminator.SqlType, tc.Discriminator, tc.SourceExpression),
                                       uo.SourceExpression
                                       );
                        }
                    }
                }
 
                // Fetch the expression.
                uo.Operand = this.ConvertToFetchedExpression(uo.Operand);
 
                // ------------------------------------------------------------
                // PHASE 2: Evaluate operator on fetched expression.
                // ------------------------------------------------------------
                if ((uo.NodeType == SqlNodeType.Not || uo.NodeType == SqlNodeType.Not2V) && uo.Operand.NodeType == SqlNodeType.Value) {
                    SqlValue val = (SqlValue)uo.Operand;
                    return sql.Value(typeof(bool), val.SqlType, !(bool)val.Value, val.IsClientSpecified, val.SourceExpression);
                }
                else if (uo.NodeType == SqlNodeType.Not2V) {
                    if (SqlExpressionNullability.CanBeNull(uo.Operand) != false) {
                        SqlSearchedCase c = new SqlSearchedCase(
                            typeof(int),
                            new [] { new SqlWhen(uo.Operand, sql.ValueFromObject(1, false, uo.SourceExpression)) },
                            sql.ValueFromObject(0, false, uo.SourceExpression),
                            uo.SourceExpression
                            );
                        return sql.Binary(SqlNodeType.EQ, c, sql.ValueFromObject(0, false, uo.SourceExpression));
                    }
                    else {
                        return sql.Unary(SqlNodeType.Not, uo.Operand);
                    }
                }
                // push converts of client-expressions inside the client-expression (to be evaluated client side) 
                else if (uo.NodeType == SqlNodeType.Convert && uo.Operand.NodeType == SqlNodeType.Value) {
                    SqlValue val = (SqlValue)uo.Operand;
                    return sql.Value(uo.ClrType, uo.SqlType, DBConvert.ChangeType(val.Value, uo.ClrType), val.IsClientSpecified, val.SourceExpression);
                }
                else if (uo.NodeType == SqlNodeType.IsNull || uo.NodeType == SqlNodeType.IsNotNull) {
                    bool? canBeNull = SqlExpressionNullability.CanBeNull(uo.Operand);
                    if (canBeNull == false) {
                        return sql.ValueFromObject(uo.NodeType == SqlNodeType.IsNotNull, false, uo.SourceExpression);
                    }
                    SqlExpression exp = uo.Operand;
                    switch (exp.NodeType) {
                        case SqlNodeType.Element:
                            exp = sql.SubSelect(SqlNodeType.Exists, ((SqlSubSelect)exp).Select);
                            if (uo.NodeType == SqlNodeType.IsNull) {
                                exp = sql.Unary(SqlNodeType.Not, exp, exp.SourceExpression);
                            }
                            return exp;
                        case SqlNodeType.ClientQuery: {
                                SqlClientQuery cq = (SqlClientQuery)exp;
                                if (cq.Query.NodeType == SqlNodeType.Element) {
                                    exp = sql.SubSelect(SqlNodeType.Exists, cq.Query.Select);
                                    if (uo.NodeType == SqlNodeType.IsNull) {
                                        exp = sql.Unary(SqlNodeType.Not, exp, exp.SourceExpression);
                                    }
                                    return exp;
                                }
                                return sql.ValueFromObject(uo.NodeType == SqlNodeType.IsNotNull, false, uo.SourceExpression);
                            }
                        case SqlNodeType.OptionalValue:
                            uo.Operand = ((SqlOptionalValue)exp).HasValue;
                            return uo;
 
                        case SqlNodeType.ClientCase: {
                                // Distribute unary into simple case.
                                SqlClientCase sc = (SqlClientCase)uo.Operand;
                                List<SqlExpression> matches = new List<SqlExpression>();
                                List<SqlExpression> values = new List<SqlExpression>();
                                foreach (SqlClientWhen when in sc.Whens) {
                                    matches.Add(when.Match);
                                    values.Add(VisitUnaryOperator(sql.Unary(uo.NodeType, when.Value, when.Value.SourceExpression)));
                                }
                                return sql.Case(sc.ClrType, sc.Expression, matches, values, sc.SourceExpression);
                            }
                        case SqlNodeType.TypeCase: {
                                // Distribute unary into type case. In the process, convert to simple case.
                                SqlTypeCase tc = (SqlTypeCase)uo.Operand;
                                List<SqlExpression> newMatches = new List<SqlExpression>();
                                List<SqlExpression> newValues = new List<SqlExpression>();
                                foreach (SqlTypeCaseWhen when in tc.Whens) {
                                    SqlUnary un = new SqlUnary(uo.NodeType, uo.ClrType, uo.SqlType, when.TypeBinding, when.TypeBinding.SourceExpression);
                                    SqlExpression expr = VisitUnaryOperator(un);
                                    if (expr is SqlNew) {
                                        throw Error.DidNotExpectTypeBinding();
                                    }
                                    newMatches.Add(when.Match);
                                    newValues.Add(expr);
                                }
                                return sql.Case(uo.ClrType, tc.Discriminator, newMatches, newValues, tc.SourceExpression);
                            }
                        case SqlNodeType.Value: {
                                SqlValue val = (SqlValue)uo.Operand;
                                return sql.Value(typeof(bool), this.typeProvider.From(typeof(int)), (val.Value == null) == (uo.NodeType == SqlNodeType.IsNull), val.IsClientSpecified, uo.SourceExpression);
                            }
                    }
                }
                else if (uo.NodeType == SqlNodeType.Treat) {
                    return ApplyTreat(VisitExpression(uo.Operand), uo.ClrType);
                }
 
                return uo;
            }
 
            internal override SqlExpression VisitNew(SqlNew sox) {
                for (int i = 0, n = sox.Args.Count; i < n; i++) {
                    if (inGroupBy) {
                        // we don't want to fetch expressions for group by,
                        // since we want links to remain links so SqlFlattener
                        // can deal with them properly
                        sox.Args[i] = this.VisitExpression(sox.Args[i]);
                    }
                    else {
                        sox.Args[i] = this.FetchExpression(sox.Args[i]);
                    }
                }
                for (int i = 0, n = sox.Members.Count; i < n; i++) {
                    SqlMemberAssign ma = sox.Members[i];
                    MetaDataMember mm = sox.MetaType.GetDataMember(ma.Member);
                    MetaType otherType = mm.DeclaringType.InheritanceRoot;
                    if (mm.IsAssociation && ma.Expression != null && ma.Expression.NodeType != SqlNodeType.Link
                        && this.shape != null && this.shape.IsPreloaded(mm.Member) && mm.LoadMethod == null
                        && this.alreadyIncluded != null && !this.alreadyIncluded.Contains(otherType)) {
                        // The expression is already fetched, add it to the alreadyIncluded set.
                        this.alreadyIncluded.Add(otherType);
                        ma.Expression = this.VisitExpression(ma.Expression);
                        this.alreadyIncluded.Remove(otherType);
                    }
                    else if (mm.IsAssociation || mm.IsDeferred) {
                        ma.Expression = this.VisitExpression(ma.Expression);
                    }
                    else {
                        ma.Expression = this.FetchExpression(ma.Expression);
                    }
                }
                return sox;
            }
 
            internal override SqlNode VisitMember(SqlMember m) {
                return this.AccessMember(m, this.FetchExpression(m.Expression));
            }
 
            [SuppressMessage("Microsoft.Performance", "CA1809:AvoidExcessiveLocals", Justification="These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")]
            [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")]
            [SuppressMessage("Microsoft.Maintainability", "CA1505:AvoidUnmaintainableCode", Justification = "These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")]
            [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")]
            private SqlNode AccessMember(SqlMember m, SqlExpression expo) {
                SqlExpression exp = expo;
 
                switch (exp.NodeType) {
                    case SqlNodeType.ClientCase: {
                            // Distribute into each case.
                            SqlClientCase sc = (SqlClientCase)exp;
                            Type newClrType = null;
                            List<SqlExpression> matches = new List<SqlExpression>();
                            List<SqlExpression> values = new List<SqlExpression>();
                            foreach (SqlClientWhen when in sc.Whens) {
                                SqlExpression newValue = (SqlExpression)AccessMember(m, when.Value);
                                if (newClrType == null) {
                                    newClrType = newValue.ClrType;
                                }
                                else if (newClrType != newValue.ClrType) {
                                    throw Error.ExpectedClrTypesToAgree(newClrType, newValue.ClrType);
                                }
                                matches.Add(when.Match);
                                values.Add(newValue);
                            }
 
                            SqlExpression result = sql.Case(newClrType, sc.Expression, matches, values, sc.SourceExpression);
                            return result;
                        }
                    case SqlNodeType.SimpleCase: {
                            // Distribute into each case.
                            SqlSimpleCase sc = (SqlSimpleCase)exp;
                            Type newClrType = null;
                            List<SqlExpression> newMatches = new List<SqlExpression>();
                            List<SqlExpression> newValues = new List<SqlExpression>();
                            foreach (SqlWhen when in sc.Whens) {
                                SqlExpression newValue = (SqlExpression)AccessMember(m, when.Value);
                                if (newClrType == null) {
                                    newClrType = newValue.ClrType;
                                }
                                else if (newClrType != newValue.ClrType) {
                                  throw Error.ExpectedClrTypesToAgree(newClrType, newValue.ClrType);
                                }
                                newMatches.Add(when.Match);
                                newValues.Add(newValue);
                            }
                            SqlExpression result = sql.Case(newClrType, sc.Expression, newMatches, newValues, sc.SourceExpression);
                            return result;
                        }
                    case SqlNodeType.SearchedCase: {
                            // Distribute into each case.
                            SqlSearchedCase sc = (SqlSearchedCase)exp;
                            List<SqlWhen> whens = new List<SqlWhen>(sc.Whens.Count);
                            foreach (SqlWhen when in sc.Whens) {
                                SqlExpression value = (SqlExpression)AccessMember(m, when.Value);
                                whens.Add(new SqlWhen(when.Match, value));
                            }
                            SqlExpression @else = (SqlExpression)AccessMember(m, sc.Else);
                            return sql.SearchedCase(whens.ToArray(), @else, sc.SourceExpression);
                        }
                    case SqlNodeType.TypeCase: {
                            // We don't allow derived types to map members to different database fields.
                            // Therefore, just pick the best SqlNew to call AccessMember on.
                            SqlTypeCase tc = (SqlTypeCase)exp;
 
                            // Find the best type binding for this member.
                            SqlNew tb = tc.Whens[0].TypeBinding as SqlNew;
                            foreach (SqlTypeCaseWhen when in tc.Whens) {
                                if (when.TypeBinding.NodeType == SqlNodeType.New) {
                                    SqlNew sn = (SqlNew)when.TypeBinding;
                                    if (m.Member.DeclaringType.IsAssignableFrom(sn.ClrType)) {
                                        tb = sn;
                                        break;
                                    }
                                }
                            }
                            return AccessMember(m, tb);
                        }
                    case SqlNodeType.AliasRef: {
                            // convert alias.Member => column
                            SqlAliasRef aref = (SqlAliasRef)exp;
                            // if its a table, find the matching column
                            SqlTable tab = aref.Alias.Node as SqlTable;
                            if (tab != null) {
                                MetaDataMember mm = GetRequiredInheritanceDataMember(tab.RowType, m.Member);
                                System.Diagnostics.Debug.Assert(mm != null);
                                string name = mm.MappedName;
                                SqlColumn c = tab.Find(name);
                                if (c == null) {
                                    ProviderType sqlType = sql.Default(mm);
                                    c = new SqlColumn(m.ClrType, sqlType, name, mm, null, m.SourceExpression);
                                    c.Alias = aref.Alias;
                                    tab.Columns.Add(c);
                                }
                                return new SqlColumnRef(c);
                            }
                            // if it is a table valued function, find the matching result column                                
                            SqlTableValuedFunctionCall fc = aref.Alias.Node as SqlTableValuedFunctionCall;
                            if (fc != null) {
                                MetaDataMember mm = GetRequiredInheritanceDataMember(fc.RowType, m.Member);
                                System.Diagnostics.Debug.Assert(mm != null);
                                string name = mm.MappedName;
                                SqlColumn c = fc.Find(name);
                                if (c == null) {
                                    ProviderType sqlType = sql.Default(mm);
                                    c = new SqlColumn(m.ClrType, sqlType, name, mm, null, m.SourceExpression);
                                    c.Alias = aref.Alias;
                                    fc.Columns.Add(c);
                                }
                                return new SqlColumnRef(c);
                            }
                            break;
                        }
                    case SqlNodeType.OptionalValue:
                        // convert option(exp).Member => exp.Member
                        return this.AccessMember(m, ((SqlOptionalValue)exp).Value);
 
                    case SqlNodeType.OuterJoinedValue: {
                            SqlNode n = this.AccessMember(m, ((SqlUnary)exp).Operand);
                            SqlExpression e = n as SqlExpression;
                            if (e != null) return sql.Unary(SqlNodeType.OuterJoinedValue, e);
                            return n;
                        }
 
                    case SqlNodeType.Lift:
                        return this.AccessMember(m, ((SqlLift)exp).Expression);
 
                    case SqlNodeType.UserRow: {
                            // convert UserRow.Member => UserColumn
                            SqlUserRow row = (SqlUserRow)exp;
                            SqlUserQuery suq = row.Query;
                            MetaDataMember mm = GetRequiredInheritanceDataMember(row.RowType, m.Member);
                            System.Diagnostics.Debug.Assert(mm != null);
                            string name = mm.MappedName;
                            SqlUserColumn c = suq.Find(name);
                            if (c == null) {
                                ProviderType sqlType = sql.Default(mm);
                                c = new SqlUserColumn(m.ClrType, sqlType, suq, name, mm.IsPrimaryKey, m.SourceExpression);
                                suq.Columns.Add(c);
                            }
                            return c;
                        }
                    case SqlNodeType.New: {
                            // convert (new {Member = expr}).Member => expr
                            SqlNew sn = (SqlNew)exp;
                            SqlExpression e = sn.Find(m.Member);
                            if (e != null) {
                                return e;
                            }
                            MetaDataMember mm = sn.MetaType.PersistentDataMembers.FirstOrDefault(p => p.Member == m.Member);
                            if (!sn.SqlType.CanBeColumn && mm != null) {
                                throw Error.MemberNotPartOfProjection(m.Member.DeclaringType, m.Member.Name);
                            }
                            break;
                        }
                    case SqlNodeType.Element:
                    case SqlNodeType.ScalarSubSelect: {
                            // convert Scalar/Element(select exp).Member => Scalar/Element(select exp.Member) / select exp.Member
                            SqlSubSelect sub = (SqlSubSelect)exp;
                            SqlAlias alias = new SqlAlias(sub.Select);
                            SqlAliasRef aref = new SqlAliasRef(alias);
 
                            SqlSelect saveSelect = this.currentSelect;
                            try {
                                SqlSelect newSelect = new SqlSelect(aref, alias, sub.SourceExpression);
                                this.currentSelect = newSelect;
                                SqlNode result = this.Visit(sql.Member(aref, m.Member));
 
                                SqlExpression rexp = result as SqlExpression;
                                if (rexp != null) {
 
                                    // If the expression is still a Member after being visited, but it cannot be a column, then it cannot be collapsed
                                    // into the SubSelect because we need to keep track of the fact that this member has to be accessed on the client.
                                    // This must be done after the expression has been Visited above, because otherwise we don't have
                                    // enough context to know if the member can be a column or not.
                                    if (rexp.NodeType == SqlNodeType.Member && !SqlColumnizer.CanBeColumn(rexp)) {
                                        // If the original member expression is an Element, optimize it by converting to an OuterApply if possible.
                                        // We have to do this here because we are creating a new member expression based on it, and there are no
                                        // subsequent visitors that will do this optimization.
                                        if (this.canUseOuterApply && exp.NodeType == SqlNodeType.Element && this.currentSelect != null) {
                                            // Reset the currentSelect since we are not going to use the previous SqlSelect that was created
                                            this.currentSelect = saveSelect;                                            
                                            this.currentSelect.From = sql.MakeJoin(SqlJoinType.OuterApply, this.currentSelect.From, alias, null, sub.SourceExpression);
                                            exp = this.VisitExpression(aref);
                                        }                                        
                                        return sql.Member(exp, m.Member);
                                    }
 
                                    // Since we are going to make a SubSelect out of this member expression, we need to make
                                    // sure it gets columnized before it gets to the PostBindDotNetConverter, otherwise only the
                                    // entire SubSelect will be columnized as a whole. Subsequent columnization does not know how to handle
                                    // any function calls that may be produced by the PostBindDotNetConverter, but we know how to handle it here.
                                    newSelect.Selection = rexp;
                                    newSelect.Selection = this.columnizer.ColumnizeSelection(newSelect.Selection);
                                    newSelect.Selection = this.ConvertLinks(newSelect.Selection);
                                    SqlNodeType subType = (rexp is SqlTypeCase || !rexp.SqlType.CanBeColumn) ? SqlNodeType.Element : SqlNodeType.ScalarSubSelect;
                                    SqlSubSelect subSel = sql.SubSelect(subType, newSelect);
                                    return this.FoldSubquery(subSel);
                                }
 
                                SqlSelect rselect = result as SqlSelect;
                                if (rselect != null) {
                                    SqlAlias ralias = new SqlAlias(rselect);
                                    SqlAliasRef rref = new SqlAliasRef(ralias);
                                    newSelect.Selection = this.ConvertLinks(this.VisitExpression(rref));
                                    newSelect.From = new SqlJoin(SqlJoinType.CrossApply, alias, ralias, null, m.SourceExpression);
                                    return newSelect;
                                }
                                throw Error.UnexpectedNode(result.NodeType);
                            }
                            finally {
                                this.currentSelect = saveSelect;
                            }
                        }
                    case SqlNodeType.Value: {
                            SqlValue val = (SqlValue)exp;
                            if (val.Value == null) {
                                return sql.Value(m.ClrType, m.SqlType, null, val.IsClientSpecified, m.SourceExpression);
                            }
                            else if (m.Member is PropertyInfo) {
                                PropertyInfo p = (PropertyInfo)m.Member;
                                return sql.Value(m.ClrType, m.SqlType, p.GetValue(val.Value, null), val.IsClientSpecified, m.SourceExpression);
                            }
                            else {
                                FieldInfo f = (FieldInfo)m.Member;
                                return sql.Value(m.ClrType, m.SqlType, f.GetValue(val.Value), val.IsClientSpecified, m.SourceExpression);
                            }
                        }
                    case SqlNodeType.Grouping: {
                            SqlGrouping g = ((SqlGrouping)exp);
                            if (m.Member.Name == "Key") {
                                return g.Key;
                            }
                            break;
                        }
                    case SqlNodeType.ClientParameter: {
                            SqlClientParameter cp = (SqlClientParameter)exp;
                            // create new accessor including this member access
                            LambdaExpression accessor =
                                Expression.Lambda(
                                    typeof(Func<,>).MakeGenericType(typeof(object[]), m.ClrType),
                                    Expression.MakeMemberAccess(cp.Accessor.Body, m.Member),
                                    cp.Accessor.Parameters
                                    );
                            return new SqlClientParameter(m.ClrType, m.SqlType, accessor, cp.SourceExpression);
                        }
                    default:
                        break;  
                }
                if (m.Expression == exp) {
                    return m;
                }
                else {
                    return sql.Member(exp, m.Member);
                }
            }
 
            private SqlExpression FoldSubquery(SqlSubSelect ss) {
                // convert ELEMENT(SELECT MULTISET(SELECT xxx FROM t1 WHERE p1) FROM t2 WHERE p2)
                // into MULTISET(SELECT xxx FROM t2 CA (SELECT xxx FROM t1 WHERE p1) WHERE p2))
                while (true) {
                    if (ss.NodeType == SqlNodeType.Element && ss.Select.Selection.NodeType == SqlNodeType.Multiset) {
                        SqlSubSelect msub = (SqlSubSelect)ss.Select.Selection;
                        SqlAlias alias = new SqlAlias(msub.Select);
                        SqlAliasRef aref = new SqlAliasRef(alias);
                        SqlSelect sel = ss.Select;
                        sel.Selection = this.ConvertLinks(this.VisitExpression(aref));
                        sel.From = new SqlJoin(SqlJoinType.CrossApply, sel.From, alias, null, ss.SourceExpression);
                        SqlSubSelect newss = sql.SubSelect(SqlNodeType.Multiset, sel, ss.ClrType);
                        ss = newss;
                    }
                    else if (ss.NodeType == SqlNodeType.Element && ss.Select.Selection.NodeType == SqlNodeType.Element) {
                        SqlSubSelect msub = (SqlSubSelect)ss.Select.Selection;
                        SqlAlias alias = new SqlAlias(msub.Select);
                        SqlAliasRef aref = new SqlAliasRef(alias);
                        SqlSelect sel = ss.Select;
                        sel.Selection = this.ConvertLinks(this.VisitExpression(aref));
                        sel.From = new SqlJoin(SqlJoinType.CrossApply, sel.From, alias, null, ss.SourceExpression);
                        SqlSubSelect newss = sql.SubSelect(SqlNodeType.Element, sel);
                        ss = newss;
                    }
                    else {
                        break;
                    }
                }
                return ss;
            }
 
            /// <summary>
            /// Get the MetaDataMember from the given table. Look in the inheritance hierarchy.
            /// The member is expected to be there and an exception will be thrown if it isn't.
            /// </summary>
            /// <param name="type">The hierarchy type that should have the member.</param>
            /// <param name="mi">The member to retrieve.</param>
            /// <returns>The MetaDataMember for the type.</returns>
            private static MetaDataMember GetRequiredInheritanceDataMember(MetaType type, MemberInfo mi) {
                System.Diagnostics.Debug.Assert(type != null);
                System.Diagnostics.Debug.Assert(mi != null);
                MetaType root = type.GetInheritanceType(mi.DeclaringType);
                if (root == null) {
                    throw Error.UnmappedDataMember(mi, mi.DeclaringType, type);
                }
                return root.GetDataMember(mi);
            }
 
            internal override SqlStatement VisitAssign(SqlAssign sa) {
                sa.LValue = this.FetchExpression(sa.LValue);
                sa.RValue = this.FetchExpression(sa.RValue);
                return sa;
            }
 
            internal SqlExpression ExpandExpression(SqlExpression expression) {
                SqlExpression expanded = this.expander.Expand(expression);
                if (expanded != expression) {
                    expanded = this.VisitExpression(expanded);
                }
                return expanded;
            }
 
            internal override SqlExpression VisitAliasRef(SqlAliasRef aref) {
                return this.ExpandExpression(aref);
            }
 
            internal override SqlAlias VisitAlias(SqlAlias a) {
                SqlAlias saveAlias = this.currentAlias;
                if (a.Node.NodeType == SqlNodeType.Table) {
                    this.outerAliasMap[a] = this.currentAlias;
                }
                this.currentAlias = a;
                try {
                    a.Node = this.ConvertToFetchedSequence(this.Visit(a.Node));
                    return a;
                }
                finally {
                    this.currentAlias = saveAlias;
                }
            }
 
            internal override SqlNode VisitLink(SqlLink link) {
                link = (SqlLink)base.VisitLink(link);
 
                // prefetch all 'LoadWith' links
                if (!this.disableInclude && this.shape != null && this.alreadyIncluded != null) {
                    MetaDataMember mdm = link.Member;
                    MemberInfo mi = mdm.Member;
                    if (this.shape.IsPreloaded(mi) && mdm.LoadMethod == null) {
                        // Is the other side of the relation in the list already?
                        MetaType otherType = mdm.DeclaringType.InheritanceRoot;
                        if (!this.alreadyIncluded.Contains(otherType)) {
                            this.alreadyIncluded.Add(otherType);
                            SqlNode fetched = this.ConvertToFetchedExpression(link);
                            this.alreadyIncluded.Remove(otherType);
                            return fetched;
                        }
                    }
                }
 
                if (this.inGroupBy && link.Expansion != null) {
                    return this.VisitLinkExpansion(link);
                }
 
                return link;
            }
 
            internal override SqlExpression VisitSharedExpressionRef(SqlSharedExpressionRef sref) {
                // always make a copy
                return (SqlExpression) SqlDuplicator.Copy(sref.SharedExpression.Expression);
            }
 
            internal override SqlExpression VisitSharedExpression(SqlSharedExpression shared) {
                shared.Expression = this.VisitExpression(shared.Expression);
                // shared expressions in group-by/select must be only column refs
                if (shared.Expression.NodeType == SqlNodeType.ColumnRef) {
                    return shared.Expression;
                }
                else {
                    // not simple? better push it down (make a sub-select that projects the relevant bits
                    shared.Expression = this.PushDownExpression(shared.Expression);
                    return shared.Expression;
                }
            }
       
            internal override SqlExpression VisitSimpleExpression(SqlSimpleExpression simple) {
                simple.Expression = this.VisitExpression(simple.Expression);
                if (SimpleExpression.IsSimple(simple.Expression)) {
                    return simple.Expression;
                }
                SqlExpression result = this.PushDownExpression(simple.Expression);
                // simple expressions must be scalar (such that they can be formed into a single column declaration)
                System.Diagnostics.Debug.Assert(result is SqlColumnRef);
                return result;
            }
 
            // add a new sub query that projects the given expression
            private SqlExpression PushDownExpression(SqlExpression expr) {
                // make sure this expression was columnized like a selection
                if (expr.NodeType == SqlNodeType.Value && expr.SqlType.CanBeColumn) {
                    expr = new SqlColumn(expr.ClrType, expr.SqlType, null, null, expr, expr.SourceExpression);
                }
                else {
                    expr = this.columnizer.ColumnizeSelection(expr);
                }
 
                SqlSelect simple = new SqlSelect(expr, this.currentSelect.From, expr.SourceExpression);
                this.currentSelect.From = new SqlAlias(simple);
 
                // make a copy of the expression for the current scope
                return this.ExpandExpression(expr);
            }
 
            internal override SqlSource VisitJoin(SqlJoin join) {
                if (join.JoinType == SqlJoinType.CrossApply ||
                    join.JoinType == SqlJoinType.OuterApply) {
                    join.Left = this.VisitSource(join.Left);
 
                    SqlSelect saveSelect = this.currentSelect;
                    try {
                        this.currentSelect = this.GetSourceSelect(join.Left);
                        join.Right = this.VisitSource(join.Right);
 
                        this.currentSelect = null;
                        join.Condition = this.VisitExpression(join.Condition);
 
                        return join;
                    }
                    finally {
                        this.currentSelect = saveSelect;
                    }
                }
                else {
                    return base.VisitJoin(join);
                }
            }
 
            [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification="Unknown reason.")]
            private SqlSelect GetSourceSelect(SqlSource source) {
                SqlAlias alias = source as SqlAlias;
                if (alias == null) { 
                    return null; 
                }
                return alias.Node as SqlSelect;
            }
 
            internal override SqlSelect VisitSelect(SqlSelect select) {
                LinkOptimizationScope saveScope = this.linkMap;
                SqlSelect saveSelect = this.currentSelect;
                bool saveInGroupBy = inGroupBy;
                inGroupBy = false;
 
                try {
                    // don't preserve any link optimizations across a group or distinct boundary
                    bool linkOptimize = true;
                    if (this.binder.optimizeLinkExpansions &&
                        (select.GroupBy.Count > 0 || this.aggregateChecker.HasAggregates(select) || select.IsDistinct)) {
                        linkOptimize = false;
                        this.linkMap = new LinkOptimizationScope(this.linkMap);
                    }
                    select.From = this.VisitSource(select.From);
                    this.currentSelect = select;
 
                    select.Where = this.VisitExpression(select.Where);
 
                    this.inGroupBy = true;
                    for (int i = 0, n = select.GroupBy.Count; i < n; i++) {
                        select.GroupBy[i] = this.VisitExpression(select.GroupBy[i]);
                    }
                    this.inGroupBy = false;
 
                    select.Having = this.VisitExpression(select.Having);
                    for (int i = 0, n = select.OrderBy.Count; i < n; i++) {
                        select.OrderBy[i].Expression = this.VisitExpression(select.OrderBy[i].Expression);
                    }
                    select.Top = this.VisitExpression(select.Top);
                    select.Row = (SqlRow)this.Visit(select.Row);
 
                    select.Selection = this.VisitExpression(select.Selection);
                    select.Selection = this.columnizer.ColumnizeSelection(select.Selection);
                    if (linkOptimize) {
                        select.Selection = ConvertLinks(select.Selection);
                    }
 
                    // optimize out where clause for WHERE TRUE
                    if (select.Where != null && select.Where.NodeType == SqlNodeType.Value && (bool)((SqlValue)select.Where).Value) {
                        select.Where = null;
                    }
                }
                finally {
                    this.currentSelect = saveSelect;
                    this.linkMap = saveScope;
                    this.inGroupBy = saveInGroupBy;
                }
 
                return select;
            }
 
            internal override SqlExpression VisitSubSelect(SqlSubSelect ss) {
                // don't preserve any link optimizations across sub-queries
                LinkOptimizationScope saveScope = this.linkMap;
                SqlSelect saveSelect = this.currentSelect;
                try {
                    this.linkMap = new LinkOptimizationScope(this.linkMap);
                    this.currentSelect = null;
                    return base.VisitSubSelect(ss);
                }
                finally {
                    this.linkMap = saveScope;
                    this.currentSelect = saveSelect;
                }
            }
 
            /// <summary>
            /// Convert links. Need to recurse because there may be a client case with cases that are links.
            /// </summary>
            private SqlExpression ConvertLinks(SqlExpression node) {
                if (node == null) {
                    return null;
                }
                switch (node.NodeType) {
                    case SqlNodeType.Column: {
                            SqlColumn col = (SqlColumn)node;
                            if (col.Expression != null) {
                                col.Expression = this.ConvertLinks(col.Expression);
                            }
                            return node;
                        }
                    case SqlNodeType.OuterJoinedValue: {
                        SqlExpression o = ((SqlUnary)node).Operand;
                        SqlExpression e = this.ConvertLinks(o);
                        if (e == o) {
                            return node;
                        }
                        if (e.NodeType != SqlNodeType.OuterJoinedValue) {
                            return sql.Unary(SqlNodeType.OuterJoinedValue, e);
                        }
                        return e;
                    }
                    case SqlNodeType.Link:
                        return this.ConvertToFetchedExpression((SqlLink)node);
                    case SqlNodeType.ClientCase: {
                            SqlClientCase sc = (SqlClientCase)node;
                            foreach (SqlClientWhen when in sc.Whens) {
                                SqlExpression converted = ConvertLinks(when.Value);
                                when.Value = converted;
                                if (!sc.ClrType.IsAssignableFrom(when.Value.ClrType)) {
                                    throw Error.DidNotExpectTypeChange(when.Value.ClrType, sc.ClrType);
                                }
 
                            }
                            return node;
                        }
                }
                return node;
            }
 
            internal SqlExpression ConvertToExpression(SqlNode node) {
                if (node == null) {
                    return null;
                }
                SqlExpression x = node as SqlExpression;
                if (x != null) {
                    return x;
                }
                SqlSelect select = node as SqlSelect;
                if (select != null) {
                    SqlSubSelect ms = sql.SubSelect(SqlNodeType.Multiset, select);
                    return ms;
                }
                throw Error.UnexpectedNode(node.NodeType);
            }
 
            [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Microsoft: Cast is dependent on node type and casts do not happen unecessarily in a single code path.")]
            [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")]
            [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "These issues are related to our use of if-then and case statements for node types, which adds to the complexity count however when reviewed they are easy to navigate and understand.")]
            internal SqlExpression ConvertToFetchedExpression(SqlNode node) {
                if (node == null) {
                    return null;
                }
                switch (node.NodeType) {
                    case SqlNodeType.OuterJoinedValue: {
                            SqlExpression o = ((SqlUnary)node).Operand;
                            SqlExpression e = this.ConvertLinks(o);
                            if (e == o) {
                                return (SqlExpression)node;
                            }
                            return e;
                        }
                    case SqlNodeType.ClientCase: {
                            // Need to recurse in case the object case has links.
                            SqlClientCase cc = (SqlClientCase)node;
                            List<SqlNode> fetchedValues = new List<SqlNode>();
                            bool allExprs = true;
                            foreach (SqlClientWhen when in cc.Whens) {
                                SqlNode fetchedValue = ConvertToFetchedExpression(when.Value);
                                allExprs = allExprs && (fetchedValue is SqlExpression);
                                fetchedValues.Add(fetchedValue);
                            }
 
                            if (allExprs) {
                                // All WHEN values are simple expressions (no sequences). 
                                List<SqlExpression> matches = new List<SqlExpression>();
                                List<SqlExpression> values = new List<SqlExpression>();
                                for (int i = 0, c = fetchedValues.Count; i < c; ++i) {
                                    SqlExpression fetchedValue = (SqlExpression)fetchedValues[i];
                                    if (!cc.ClrType.IsAssignableFrom(fetchedValue.ClrType)) {
                                        throw Error.DidNotExpectTypeChange(cc.ClrType, fetchedValue.ClrType);
                                    }
                                    matches.Add(cc.Whens[i].Match);
                                    values.Add(fetchedValue);
                                }
                                node = sql.Case(cc.ClrType, cc.Expression, matches, values, cc.SourceExpression);
                            }
                            else {
                                node = SimulateCaseOfSequences(cc, fetchedValues);
                            }
                            break;
                        }
                    case SqlNodeType.TypeCase: {
                            SqlTypeCase tc = (SqlTypeCase)node;
                            List<SqlNode> fetchedValues = new List<SqlNode>();
                            foreach (SqlTypeCaseWhen when in tc.Whens) {
                                SqlNode fetchedValue = ConvertToFetchedExpression(when.TypeBinding);
                                fetchedValues.Add(fetchedValue);
                            }
 
                            for (int i = 0, c = fetchedValues.Count; i < c; ++i) {
                                SqlExpression fetchedValue = (SqlExpression)fetchedValues[i];
                                tc.Whens[i].TypeBinding = fetchedValue;
                            }
                            break;
                        }
                    case SqlNodeType.SearchedCase: {
                            SqlSearchedCase sc = (SqlSearchedCase)node;
                            foreach (SqlWhen when in sc.Whens) {
                                when.Match = this.ConvertToFetchedExpression(when.Match);
                                when.Value = this.ConvertToFetchedExpression(when.Value);
                            }
                            sc.Else = this.ConvertToFetchedExpression(sc.Else);
                            break;
                        }
                    case SqlNodeType.Link: {
                            SqlLink link = (SqlLink)node;
 
                            if (link.Expansion != null) {
                                return this.VisitLinkExpansion(link);
                            }
 
                            SqlExpression cached;
                            if (this.linkMap.TryGetValue(link.Id, out cached)) {
                                return this.VisitExpression(cached);
                            }
 
                            // translate link into expanded form
                            node = this.translator.TranslateLink(link, true);
 
                            // New nodes may have been produced because of Subquery.
                            // Prebind again for method-call and static treat handling.
                            node = binder.Prebind(node);
 
                            // Make it an expression.
                            node = this.ConvertToExpression(node);
 
                            // bind the translation
                            node = this.Visit(node);
 
                            // Check for element node, rewrite as sql apply.
                            if (this.currentSelect != null 
                                && node != null 
                                && node.NodeType == SqlNodeType.Element 
                                && link.Member.IsAssociation
                                && this.binder.OptimizeLinkExpansions
                                ) {
                                // if link in a non-nullable foreign key association then inner-join is okay to use (since it must always exist)
                                // otherwise use left-outer-join 
                                SqlJoinType joinType = (link.Member.Association.IsForeignKey && !link.Member.Association.IsNullable)
                                    ? SqlJoinType.Inner : SqlJoinType.LeftOuter;
                                SqlSubSelect ss = (SqlSubSelect)node;
                                SqlExpression where = ss.Select.Where;
                                ss.Select.Where = null;
                                // form cross apply 
                                SqlAlias sa = new SqlAlias(ss.Select);
                                if (joinType == SqlJoinType.Inner && this.IsOuterDependent(this.currentSelect.From, sa, where))
                                {
                                    joinType = SqlJoinType.LeftOuter;
                                }
                                this.currentSelect.From = sql.MakeJoin(joinType, this.currentSelect.From, sa, where, ss.SourceExpression);
                                SqlExpression result = new SqlAliasRef(sa);
                                this.linkMap.Add(link.Id, result);
                                return this.VisitExpression(result);
                            }
                        }
                        break;
                }
                return (SqlExpression)node;
            }
 
            // insert new join in an appropriate location within an existing join tree
            private bool IsOuterDependent(SqlSource location, SqlAlias alias, SqlExpression where)
            {
                HashSet<SqlAlias> consumed = SqlGatherConsumedAliases.Gather(where);
                consumed.ExceptWith(SqlGatherProducedAliases.Gather(alias));
                HashSet<SqlAlias> produced;
                if (this.IsOuterDependent(false, location, consumed, out produced))
                    return true;
                return false;
            }
 
            // insert new join closest to the aliases it depends on
            private bool IsOuterDependent(bool isOuterDependent, SqlSource location, HashSet<SqlAlias> consumed, out HashSet<SqlAlias> produced)
            {
                if (location.NodeType == SqlNodeType.Join)
                {
 
                    // walk down join tree looking for best location for join
                    SqlJoin join = (SqlJoin)location;
                    if (this.IsOuterDependent(isOuterDependent, join.Left, consumed, out produced))
                        return true;
 
                    HashSet<SqlAlias> rightProduced;
                    bool rightIsOuterDependent = join.JoinType == SqlJoinType.LeftOuter || join.JoinType == SqlJoinType.OuterApply;
                    if (this.IsOuterDependent(rightIsOuterDependent, join.Right, consumed, out rightProduced))
                        return true;
                    produced.UnionWith(rightProduced);
                }
                else 
                {
                    SqlAlias a = location as SqlAlias;
                    if (a != null)
                    {
                        SqlSelect s = a.Node as SqlSelect;
                        if (s != null && !isOuterDependent && s.From != null)
                        {
                            if (this.IsOuterDependent(false, s.From, consumed, out produced))
                                return true;
                        }
                    }
                    produced = SqlGatherProducedAliases.Gather(location);
                }
                // look to see if this subtree fully satisfies join condition
                if (consumed.IsSubsetOf(produced))
                {
                    return isOuterDependent;
                }
                return false;
            }
 
            /// <summary>
            /// The purpose of this function is to look in 'node' for delay-fetched structures (eg Links)
            /// and to make them into fetched structures that will be evaluated directly in the query.
            /// </summary>
            internal SqlNode ConvertToFetchedSequence(SqlNode node) {
                if (node == null) {
                    return node;
                }
 
                while (node.NodeType == SqlNodeType.OuterJoinedValue) {
                    node = ((SqlUnary)node).Operand;
                }
 
                SqlExpression expr = node as SqlExpression;
                if (expr == null) {
                    return node;
                }
 
                if (!TypeSystem.IsSequenceType(expr.ClrType)) {
                    throw Error.SequenceOperatorsNotSupportedForType(expr.ClrType);
                }
 
                if (expr.NodeType == SqlNodeType.Value) {
                    throw Error.QueryOnLocalCollectionNotSupported();
                }
 
                if (expr.NodeType == SqlNodeType.Link) {
                    SqlLink link = (SqlLink)expr;
 
                    if (link.Expansion != null) {
                        return this.VisitLinkExpansion(link);
                    }
 
                    // translate link into expanded form
                    node = this.translator.TranslateLink(link, false);
 
                    // New nodes may have been produced because of Subquery.
                    // Prebind again for method-call and static treat handling.
                    node = binder.Prebind(node);
 
                    // bind the translation
                    node = this.Visit(node);
                }
                else if (expr.NodeType == SqlNodeType.Grouping) {
                    node = ((SqlGrouping)expr).Group;
                }
                else if (expr.NodeType == SqlNodeType.ClientCase) {
                  /* 
                     * Client case needs to be handled here because it may be a client-case
                     * of delay-fetch structures such as links (or other client cases of links):
                     * 
                     * CASE [Disc]
                     *  WHEN 'X' THEN A
                     *  WHEN 'Y' THEN B
                     * END
                     * 
                     * Abstractly, this would be rewritten as 
                     * 
                     * CASE [Disc]
                     *  WHEN 'X' THEN ConvertToFetchedSequence(A)
                     *  WHEN 'Y' THEN ConvertToFetchedSequence(B)
                     * END
                     * 
                     * The hitch is that the result of ConvertToFetchedSequence() is likely 
                     * to be a SELECT which is not legal in a CASE. Instead, we need to rewrite as
                     * 
                     * SELECT [ProjectionX] WHERE [Disc]='X'
                     * UNION ALL
                     * SELECT [ProjectionY] WHERE [Disc]='Y'
                     * 
                     * In other words, a Union where only one SELECT will have a WHERE clase
                     * that can produce a non-empty set for each instance of [Disc].
                     */
                    SqlClientCase sc = (SqlClientCase)expr;
                    List<SqlNode> newValues = new List<SqlNode>();
                    bool rewrite = false;
                    bool allSame = true;
                    foreach (SqlClientWhen when in sc.Whens) {
                        SqlNode newValue = ConvertToFetchedSequence(when.Value);
                        rewrite = rewrite || (newValue != when.Value);
                        newValues.Add(newValue);
                        allSame = allSame && SqlComparer.AreEqual(when.Value, sc.Whens[0].Value);
                    }
 
                    if (rewrite) {
                        if (allSame) {
                            // If all branches are the same then just take one.
                            node = newValues[0];
                        }
                        else {
                            node = this.SimulateCaseOfSequences(sc, newValues);
                        }
                    }
                }
 
                SqlSubSelect ss = node as SqlSubSelect;
                if (ss != null) {
                    node = ss.Select;
                }
 
                return node;
            }
 
            private SqlExpression VisitLinkExpansion(SqlLink link) {
                SqlAliasRef aref = link.Expansion as SqlAliasRef;
                if (aref != null && aref.Alias.Node.NodeType == SqlNodeType.Table) {
                    SqlAlias outerAlias;
                    if (this.outerAliasMap.TryGetValue(aref.Alias, out outerAlias)) {
                        return this.VisitAliasRef(new SqlAliasRef(outerAlias));
                    }
                    // should not happen
                    System.Diagnostics.Debug.Assert(false);
                }
                return this.VisitExpression(link.Expansion);
            }
 
            /// <summary>
            /// Given a ClientCase and a list of sequence (one for each case), construct a structure
            /// that is equivalent to a CASE of SELECTs. To accomplish this we use UNION ALL and attach
            /// a WHERE clause which will pick the SELECT that matches the discriminator in the Client Case.
            /// </summary>
            private SqlSelect SimulateCaseOfSequences(SqlClientCase clientCase, List<SqlNode> sequences) {
                /*
                   * There are two situations we may be in:
                   * (1) There is exactly one case alternative. 
                   *     Here, no where clause is needed.
                   * (2) There is more than case alternative.
                   *     Here, each WHERE clause needs to be ANDed with [Disc]=D where D
                   *     is the literal discriminanator value.
                   */
                if (sequences.Count == 1) {
                    return (SqlSelect)sequences[0];
                }
                else {
                    SqlNode union = null;
                    SqlSelect sel = null;
                    int elseIndex = clientCase.Whens.Count - 1;
                    int elseCount = clientCase.Whens[elseIndex].Match == null ? 1 : 0;
                    SqlExpression elseFilter = null;
                    for (int i = 0; i < sequences.Count - elseCount; ++i) {
                        sel = (SqlSelect)sequences[i];
                        SqlExpression discriminatorPredicate = sql.Binary(SqlNodeType.EQ, clientCase.Expression, clientCase.Whens[i].Match);
                        sel.Where = sql.AndAccumulate(sel.Where, discriminatorPredicate);
                        elseFilter = sql.AndAccumulate(elseFilter, sql.Binary(SqlNodeType.NE, clientCase.Expression, clientCase.Whens[i].Match));
 
                        if (union == null) {
                            union = sel;
                        }
                        else {
                            union = new SqlUnion(sel, union, true /* Union All */);
                        }
                    }
                    // Handle 'else' if present.
                    if (elseCount == 1) {
                        sel = (SqlSelect)sequences[elseIndex];
                        sel.Where = sql.AndAccumulate(sel.Where, elseFilter);
 
                        if (union == null) {
                            union = sel;
                        }
                        else {
                            union = new SqlUnion(sel, union, true /* Union All */);
                        }
 
                    }
                    SqlAlias alias = new SqlAlias(union);
                    SqlAliasRef aref = new SqlAliasRef(alias);
                    return new SqlSelect(aref, alias, union.SourceExpression);
                }
            }
        }
    }
}