File: SqlClient\Query\SqlColumnizer.cs
Project: ndp\fx\src\DLinq\Dlinq\System.Data.Linq.csproj (System.Data.Linq)
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Data.Linq.Provider;
using System.Linq;
using System.Data.Linq.SqlClient;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics;
 
namespace System.Data.Linq.SqlClient {
 
    // partions select expressions and common subexpressions into scalar and non-scalar pieces by 
    // wrapping scalar pieces floating column nodes.
    internal class SqlColumnizer {
        ColumnNominator nominator;
        ColumnDeclarer declarer;
 
        internal SqlColumnizer() {
            this.nominator = new ColumnNominator();
            this.declarer = new ColumnDeclarer();
        }
 
        internal SqlExpression ColumnizeSelection(SqlExpression selection) {
            return this.declarer.Declare(selection, this.nominator.Nominate(selection));
        }
 
        internal static bool CanBeColumn(SqlExpression expression) {
            return ColumnNominator.CanBeColumn(expression);
        }
 
        class ColumnDeclarer : SqlVisitor {
            HashSet<SqlExpression> candidates;
 
            internal ColumnDeclarer() {
            }
 
            internal SqlExpression Declare(SqlExpression expression, HashSet<SqlExpression> candidates) {
                this.candidates = candidates;
                return (SqlExpression)this.Visit(expression);
            }
 
            internal override SqlNode Visit(SqlNode node) {
                SqlExpression expr = node as SqlExpression;
                if (expr != null) {
                    if (this.candidates.Contains(expr)) {
                        if (expr.NodeType == SqlNodeType.Column ||
                            expr.NodeType == SqlNodeType.ColumnRef) {
                            return expr;
                        }
                        else {
                            return new SqlColumn(expr.ClrType, expr.SqlType, null, null, expr, expr.SourceExpression);
                        }
                    }
                }
                return base.Visit(node);
            }
        }
 
        class ColumnNominator : SqlVisitor {
            bool isBlocked;
            HashSet<SqlExpression> candidates;
 
            internal HashSet<SqlExpression> Nominate(SqlExpression expression) {
                this.candidates = new HashSet<SqlExpression>();
                this.isBlocked = false;
                this.Visit(expression);
                return this.candidates;
            }
 
            internal override SqlNode Visit(SqlNode node) {
                SqlExpression expression = node as SqlExpression;
                if (expression != null) {
                    bool saveIsBlocked = this.isBlocked;
                    this.isBlocked = false;
                    if (CanRecurseColumnize(expression)) {
                        base.Visit(expression);
                    }
                    if (!this.isBlocked) {
                        if (CanBeColumn(expression)) {
                            this.candidates.Add(expression);
                        }
                        else {
                            this.isBlocked = true;
                        }
                    }
                    this.isBlocked |= saveIsBlocked;
                }
                return node;
            }
 
            internal override SqlExpression VisitSimpleCase(SqlSimpleCase c) {
                c.Expression = this.VisitExpression(c.Expression);
                for (int i = 0, n = c.Whens.Count; i < n; i++) {
                    // Don't walk down the match side. This can't be a column.
                    c.Whens[i].Value = this.VisitExpression(c.Whens[i].Value);
                }
                return c;
            }
 
            internal override SqlExpression VisitTypeCase(SqlTypeCase tc) {
                tc.Discriminator = this.VisitExpression(tc.Discriminator);
                for (int i = 0, n = tc.Whens.Count; i < n; i++) {
                    // Don't walk down the match side. This can't be a column.
                    tc.Whens[i].TypeBinding = this.VisitExpression(tc.Whens[i].TypeBinding);
                }
                return tc;
            }
 
            internal override SqlExpression VisitClientCase(SqlClientCase c) {
                c.Expression = this.VisitExpression(c.Expression);
                for (int i = 0, n = c.Whens.Count; i < n; i++) {
                    // Don't walk down the match side. This can't be a column.
                    c.Whens[i].Value = this.VisitExpression(c.Whens[i].Value);
                }
                return c;
            }
 
            private static bool CanRecurseColumnize(SqlExpression expr) {
                switch (expr.NodeType) {
                    case SqlNodeType.AliasRef:
                    case SqlNodeType.ColumnRef:
                    case SqlNodeType.Column:
                    case SqlNodeType.Multiset:
                    case SqlNodeType.Element:
                    case SqlNodeType.ScalarSubSelect:
                    case SqlNodeType.Exists:
                    case SqlNodeType.ClientQuery:
                    case SqlNodeType.SharedExpressionRef:
                    case SqlNodeType.Link:
                    case SqlNodeType.Nop:
                    case SqlNodeType.Value:
                    case SqlNodeType.Select:
                        return false;
                    default:
                        return true;
                }
            }
 
            [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 static bool IsClientOnly(SqlExpression expr) {
                switch (expr.NodeType) {
                    case SqlNodeType.ClientCase:
                    case SqlNodeType.TypeCase:
                    case SqlNodeType.ClientArray:
                    case SqlNodeType.Grouping:
                    case SqlNodeType.DiscriminatedType:
                    case SqlNodeType.SharedExpression:
                    case SqlNodeType.SimpleExpression:
                    case SqlNodeType.AliasRef:
                    case SqlNodeType.Multiset:
                    case SqlNodeType.Element:
                    case SqlNodeType.ClientQuery:
                    case SqlNodeType.SharedExpressionRef:
                    case SqlNodeType.Link:
                    case SqlNodeType.Nop:
                        return true;
                    case SqlNodeType.OuterJoinedValue:
                        return IsClientOnly(((SqlUnary)expr).Operand);
                    default:
                        return false;
                }
            }
 
            internal static bool CanBeColumn(SqlExpression expression) {
                if (!IsClientOnly(expression)
                    && expression.NodeType != SqlNodeType.Column                            
                    && expression.SqlType.CanBeColumn) {
 
                    switch (expression.NodeType) {
                        case SqlNodeType.MethodCall:
                        case SqlNodeType.Member:
                        case SqlNodeType.New:
                            return PostBindDotNetConverter.CanConvert(expression);
                        default:
                            return true;
                    }
                }
                return false;
            }
        }
    }
}