e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
186 lines
7.5 KiB
C#
186 lines
7.5 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
}
|