2 using System.Collections.Generic;
4 namespace System.Data.Linq.SqlClient {
5 using System.Data.Linq.Mapping;
6 using System.Data.Linq.Provider;
7 using System.Diagnostics.CodeAnalysis;
9 // flatten object expressions into rows
10 internal class SqlFlattener {
13 internal SqlFlattener(SqlFactory sql, SqlColumnizer columnizer) {
14 this.visitor = new Visitor(sql, columnizer);
17 internal SqlNode Flatten(SqlNode node) {
18 node = this.visitor.Visit(node);
22 class Visitor : SqlVisitor {
23 [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Microsoft: part of our standard visitor pattern")]
25 SqlColumnizer columnizer;
27 Dictionary<SqlColumn, SqlColumn> map = new Dictionary<SqlColumn,SqlColumn>();
29 [SuppressMessage("Microsoft.Performance", "CA1805:DoNotInitializeUnnecessarily", Justification="Unknown reason.")]
30 internal Visitor(SqlFactory sql, SqlColumnizer columnizer) {
32 this.columnizer = columnizer;
33 this.isTopLevel = true;
36 internal override SqlExpression VisitColumnRef(SqlColumnRef cref) {
38 if (this.map.TryGetValue(cref.Column, out mapped)) {
39 return new SqlColumnRef(mapped);
44 internal override SqlSelect VisitSelectCore(SqlSelect select) {
45 bool saveIsTopLevel = this.isTopLevel;
46 this.isTopLevel = false;
48 return base.VisitSelectCore(select);
51 this.isTopLevel = saveIsTopLevel;
55 internal override SqlSelect VisitSelect(SqlSelect select) {
56 select = base.VisitSelect(select);
58 select.Selection = this.FlattenSelection(select.Row, false, select.Selection);
60 if (select.GroupBy.Count > 0) {
61 this.FlattenGroupBy(select.GroupBy);
64 if (select.OrderBy.Count > 0) {
65 this.FlattenOrderBy(select.OrderBy);
68 if (!this.isTopLevel) {
69 select.Selection = new SqlNop(select.Selection.ClrType, select.Selection.SqlType, select.SourceExpression);
75 internal override SqlStatement VisitInsert(SqlInsert sin) {
76 base.VisitInsert(sin);
77 sin.Expression = this.FlattenSelection(sin.Row, true, sin.Expression);
81 private SqlExpression FlattenSelection(SqlRow row, bool isInput, SqlExpression selection) {
82 selection = this.columnizer.ColumnizeSelection(selection);
83 return new SelectionFlattener(row, this.map, isInput).VisitExpression(selection);
86 class SelectionFlattener : SqlVisitor {
88 Dictionary<SqlColumn, SqlColumn> map;
92 internal SelectionFlattener(SqlRow row, Dictionary<SqlColumn, SqlColumn> map, bool isInput) {
95 this.isInput = isInput;
98 internal override SqlExpression VisitNew(SqlNew sox) {
100 return base.VisitNew(sox);
103 internal override SqlExpression VisitColumn(SqlColumn col) {
104 SqlColumn c = this.FindColumn(this.row.Columns, col);
105 if (c == null && col.Expression != null && !this.isInput && (!this.isNew || (this.isNew && !col.Expression.IsConstantColumn))) {
106 c = this.FindColumnWithExpression(this.row.Columns, col.Expression);
109 this.row.Columns.Add(col);
113 // preserve expr-sets when folding expressions together
114 if (col.Expression.NodeType == SqlNodeType.ExprSet && c.Expression.NodeType != SqlNodeType.ExprSet) {
115 c.Expression = col.Expression;
119 return new SqlColumnRef(c);
122 internal override SqlExpression VisitColumnRef(SqlColumnRef cref) {
123 SqlColumn c = this.FindColumn(this.row.Columns, cref.Column);
125 return MakeFlattenedColumn(cref, null);
128 return new SqlColumnRef(c);
132 // ignore subquery in selection
133 internal override SqlExpression VisitSubSelect(SqlSubSelect ss) {
137 internal override SqlExpression VisitClientQuery(SqlClientQuery cq) {
141 private SqlColumnRef MakeFlattenedColumn(SqlExpression expr, string name) {
142 SqlColumn c = (!this.isInput) ? this.FindColumnWithExpression(this.row.Columns, expr) : null;
144 c = new SqlColumn(expr.ClrType, expr.SqlType, name, null, expr, expr.SourceExpression);
145 this.row.Columns.Add(c);
147 return new SqlColumnRef(c);
151 private SqlColumn FindColumn(IEnumerable<SqlColumn> columns, SqlColumn col) {
152 foreach (SqlColumn c in columns) {
153 if (this.RefersToColumn(c, col)) {
160 private SqlColumn FindColumnWithExpression(IEnumerable<SqlColumn> columns, SqlExpression expr) {
161 foreach (SqlColumn c in columns) {
165 if (SqlComparer.AreEqual(c.Expression, expr)) {
173 private void FlattenGroupBy(List<SqlExpression> exprs) {
174 List<SqlExpression> list = new List<SqlExpression>(exprs.Count);
175 foreach (SqlExpression gex in exprs) {
176 if (TypeSystem.IsSequenceType(gex.ClrType)) {
177 throw Error.InvalidGroupByExpressionType(gex.ClrType.Name);
179 this.FlattenGroupByExpression(list, gex);
182 exprs.AddRange(list);
185 private void FlattenGroupByExpression(List<SqlExpression> exprs, SqlExpression expr) {
186 SqlNew sn = expr as SqlNew;
188 foreach (SqlMemberAssign ma in sn.Members) {
189 this.FlattenGroupByExpression(exprs, ma.Expression);
191 foreach (SqlExpression arg in sn.Args) {
192 this.FlattenGroupByExpression(exprs, arg);
195 else if (expr.NodeType == SqlNodeType.TypeCase) {
196 SqlTypeCase tc = (SqlTypeCase)expr;
197 this.FlattenGroupByExpression(exprs, tc.Discriminator);
198 foreach (SqlTypeCaseWhen when in tc.Whens) {
199 this.FlattenGroupByExpression(exprs, when.TypeBinding);
202 else if (expr.NodeType == SqlNodeType.Link) {
203 SqlLink link = (SqlLink)expr;
204 if (link.Expansion != null) {
205 this.FlattenGroupByExpression(exprs, link.Expansion);
208 foreach (SqlExpression key in link.KeyExpressions) {
209 this.FlattenGroupByExpression(exprs, key);
213 else if (expr.NodeType == SqlNodeType.OptionalValue) {
214 SqlOptionalValue sop = (SqlOptionalValue)expr;
215 this.FlattenGroupByExpression(exprs, sop.HasValue);
216 this.FlattenGroupByExpression(exprs, sop.Value);
218 else if (expr.NodeType == SqlNodeType.OuterJoinedValue) {
219 this.FlattenGroupByExpression(exprs, ((SqlUnary)expr).Operand);
221 else if (expr.NodeType == SqlNodeType.DiscriminatedType) {
222 SqlDiscriminatedType dt = (SqlDiscriminatedType)expr;
223 this.FlattenGroupByExpression(exprs, dt.Discriminator);
226 // this expression should have been 'pushed-down' in SqlBinder, so we
227 // should only find column-references & expr-sets unless the expression could not
228 // be columnized (in which case it was a bad group-by expression.)
229 if (expr.NodeType != SqlNodeType.ColumnRef &&
230 expr.NodeType != SqlNodeType.ExprSet) {
231 if (!expr.SqlType.CanBeColumn) {
232 throw Error.InvalidGroupByExpressionType(expr.SqlType.ToQueryString());
234 throw Error.InvalidGroupByExpression();
240 [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification="Unknown reason.")]
241 private void FlattenOrderBy(List<SqlOrderExpression> exprs) {
242 foreach (SqlOrderExpression obex in exprs) {
243 if (!obex.Expression.SqlType.IsOrderable) {
244 if (obex.Expression.SqlType.CanBeColumn) {
245 throw Error.InvalidOrderByExpression(obex.Expression.SqlType.ToQueryString());
248 throw Error.InvalidOrderByExpression(obex.Expression.ClrType.Name);