File: SqlClient\Query\SqlExpressionNullability.cs
Project: ndp\fx\src\DLinq\Dlinq\System.Data.Linq.csproj (System.Data.Linq)
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Reflection;
using System.Text;
using System.Linq;
using System.Linq.Expressions;
using System.Data.Linq;
using System.Data.Linq.Provider;
using System.Diagnostics.CodeAnalysis;
 
namespace System.Data.Linq.SqlClient {
 
    internal static class SqlExpressionNullability {
 
        /// <summary>
        /// Determines whether the given expression may return a null result.
        /// </summary>
        /// <param name="expr">The expression to check.</param>
        /// <returns>null means that it couldn't be determined</returns>
        [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 static bool? CanBeNull(SqlExpression expr) {
            switch (expr.NodeType) {
                case SqlNodeType.ExprSet:
                    SqlExprSet exprSet = (SqlExprSet)expr;
                    return CanBeNull(exprSet.Expressions);
 
                case SqlNodeType.SimpleCase:                  
                    SqlSimpleCase sc = (SqlSimpleCase)expr;
                    return CanBeNull(sc.Whens.Select(w => w.Value));
 
                case SqlNodeType.Column:
                    SqlColumn col = (SqlColumn)expr;
                    if (col.MetaMember != null) {
                        return col.MetaMember.CanBeNull;
                    }
                    else if (col.Expression != null) {
                        return CanBeNull(col.Expression);
                    }
                    return null;  // Don't know.
 
                case SqlNodeType.ColumnRef:
                    SqlColumnRef cref = (SqlColumnRef)expr;
                    return CanBeNull(cref.Column);
 
                case SqlNodeType.Value:
                    return ((SqlValue)expr).Value == null;
 
                case SqlNodeType.New:
                case SqlNodeType.Multiset:
                case SqlNodeType.Grouping:
                case SqlNodeType.DiscriminatedType:
                case SqlNodeType.IsNotNull: // IsNull\IsNotNull always return true or false and can never return NULL.
                case SqlNodeType.IsNull:
                case SqlNodeType.Exists:
                    return false;
 
                case SqlNodeType.Add:
                case SqlNodeType.Sub:
                case SqlNodeType.Mul:
                case SqlNodeType.Div:
                case SqlNodeType.Mod:
                case SqlNodeType.BitAnd:
                case SqlNodeType.BitOr:
                case SqlNodeType.BitXor:
                case SqlNodeType.Concat: {
                    SqlBinary bop = (SqlBinary)expr;
                    bool? left = CanBeNull(bop.Left);
                    bool? right = CanBeNull(bop.Right);
                    return (left != false) || (right != false);
                }
 
                case SqlNodeType.Negate:
                case SqlNodeType.BitNot: {
                    SqlUnary uop = (SqlUnary)expr;
                    return CanBeNull(uop.Operand);
                }
 
                case SqlNodeType.Lift: {
                    SqlLift lift = (SqlLift)expr;
                    return CanBeNull(lift.Expression);
                }
 
                case SqlNodeType.OuterJoinedValue:
                    return true;
 
                default: 
                    return null; // Don't know.
            }
        }
 
        /// <summary>
        /// Used to determine nullability for a collection of expressions.
        ///   * If at least one of the expressions is nullable, the collection is nullable.
        ///   * If no expressions are nullable, but at least one is 'don't know', the collection is 'don't know'.
        ///   * Otherwise all expressions are non-nullable and the nullability is false.
        /// </summary>
        private static bool? CanBeNull(IEnumerable<SqlExpression> exprs) {
            bool hasAtleastOneUnknown = false;
            foreach(SqlExpression e in exprs) {
                bool? nullability = CanBeNull(e);
 
                // Even one expression that could return null means the
                // collection can return null.
                if (nullability == true) 
                    return true;
 
                // If there is one or more 'unknown' and no definitely nullable
                // results then the collection nullability is 'unknown'.
                if (nullability == null)
                    hasAtleastOneUnknown = true;
            }
            if (hasAtleastOneUnknown)
                return null;
 
            return false;
        }
    }
}