e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
299 lines
11 KiB
C#
299 lines
11 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Data.Linq;
|
|
|
|
namespace System.Data.Linq.SqlClient {
|
|
|
|
// Resolves references to columns/expressions defined in other scopes
|
|
internal class SqlResolver {
|
|
Visitor visitor;
|
|
|
|
internal SqlResolver() {
|
|
this.visitor = new Visitor();
|
|
}
|
|
|
|
internal SqlNode Resolve(SqlNode node) {
|
|
return this.visitor.Visit(node);
|
|
}
|
|
|
|
private static string GetColumnName(SqlColumn c) {
|
|
#if DEBUG
|
|
return c.Text;
|
|
#else
|
|
return c.Name;
|
|
#endif
|
|
}
|
|
|
|
class Visitor : SqlScopedVisitor {
|
|
SqlBubbler bubbler;
|
|
|
|
internal Visitor() {
|
|
this.bubbler = new SqlBubbler();
|
|
}
|
|
|
|
internal override SqlExpression VisitColumnRef(SqlColumnRef cref) {
|
|
SqlColumnRef result = this.BubbleUp(cref);
|
|
if (result == null) {
|
|
throw Error.ColumnReferencedIsNotInScope(GetColumnName(cref.Column));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private SqlColumnRef BubbleUp(SqlColumnRef cref) {
|
|
for (Scope s = this.CurrentScope; s != null; s = s.ContainingScope) {
|
|
if (s.Source != null) {
|
|
SqlColumn found = this.bubbler.BubbleUp(cref.Column, s.Source);
|
|
if (found != null) {
|
|
if (found != cref.Column)
|
|
return new SqlColumnRef(found);
|
|
return cref;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
internal class SqlScopedVisitor : SqlVisitor {
|
|
internal Scope CurrentScope;
|
|
|
|
internal class Scope {
|
|
SqlNode source;
|
|
Scope containing;
|
|
internal Scope(SqlNode source, Scope containing) {
|
|
this.source = source;
|
|
this.containing = containing;
|
|
}
|
|
internal SqlNode Source {
|
|
get { return this.source; }
|
|
}
|
|
internal Scope ContainingScope {
|
|
get { return this.containing; }
|
|
}
|
|
}
|
|
|
|
internal SqlScopedVisitor() {
|
|
this.CurrentScope = new Scope(null, null);
|
|
}
|
|
|
|
internal override SqlExpression VisitSubSelect(SqlSubSelect ss) {
|
|
Scope save = this.CurrentScope;
|
|
this.CurrentScope = new Scope(null, this.CurrentScope);
|
|
base.VisitSubSelect(ss);
|
|
this.CurrentScope = save;
|
|
return ss;
|
|
}
|
|
|
|
internal override SqlSelect VisitSelect(SqlSelect select) {
|
|
select.From = (SqlSource)this.Visit(select.From);
|
|
|
|
Scope save = this.CurrentScope;
|
|
this.CurrentScope = new Scope(select.From, this.CurrentScope.ContainingScope);
|
|
|
|
select.Where = this.VisitExpression(select.Where);
|
|
for (int i = 0, n = select.GroupBy.Count; i < n; i++) {
|
|
select.GroupBy[i] = this.VisitExpression(select.GroupBy[i]);
|
|
}
|
|
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);
|
|
|
|
// selection must be able to see its own projection
|
|
this.CurrentScope = new Scope(select, this.CurrentScope.ContainingScope);
|
|
select.Selection = this.VisitExpression(select.Selection);
|
|
|
|
this.CurrentScope = save;
|
|
return select;
|
|
}
|
|
|
|
internal override SqlStatement VisitInsert(SqlInsert sin) {
|
|
Scope save = this.CurrentScope;
|
|
this.CurrentScope = new Scope(sin, this.CurrentScope.ContainingScope);
|
|
base.VisitInsert(sin);
|
|
this.CurrentScope = save;
|
|
return sin;
|
|
}
|
|
|
|
internal override SqlStatement VisitUpdate(SqlUpdate sup) {
|
|
Scope save = this.CurrentScope;
|
|
this.CurrentScope = new Scope(sup.Select, this.CurrentScope.ContainingScope);
|
|
base.VisitUpdate(sup);
|
|
this.CurrentScope = save;
|
|
return sup;
|
|
}
|
|
|
|
internal override SqlStatement VisitDelete(SqlDelete sd) {
|
|
Scope save = this.CurrentScope;
|
|
this.CurrentScope = new Scope(sd, this.CurrentScope.ContainingScope);
|
|
base.VisitDelete(sd);
|
|
this.CurrentScope = save;
|
|
return sd;
|
|
}
|
|
|
|
internal override SqlSource VisitJoin(SqlJoin join) {
|
|
Scope save = this.CurrentScope;
|
|
switch (join.JoinType) {
|
|
case SqlJoinType.CrossApply:
|
|
case SqlJoinType.OuterApply: {
|
|
this.Visit(join.Left);
|
|
Scope tmp = new Scope(join.Left, this.CurrentScope.ContainingScope);
|
|
this.CurrentScope = new Scope(null, tmp);
|
|
this.Visit(join.Right);
|
|
Scope tmp2 = new Scope(join.Right, tmp);
|
|
this.CurrentScope = new Scope(null, tmp2);
|
|
this.Visit(join.Condition);
|
|
break;
|
|
}
|
|
default: {
|
|
this.Visit(join.Left);
|
|
this.Visit(join.Right);
|
|
this.CurrentScope = new Scope(null, new Scope(join.Right, new Scope(join.Left, this.CurrentScope.ContainingScope)));
|
|
this.Visit(join.Condition);
|
|
break;
|
|
}
|
|
}
|
|
this.CurrentScope = save;
|
|
return join;
|
|
}
|
|
}
|
|
|
|
// finds location of expression definition and re-projects that value all the
|
|
// way to the outermost projection
|
|
internal class SqlBubbler : SqlVisitor {
|
|
SqlColumn match;
|
|
SqlColumn found;
|
|
|
|
internal SqlBubbler() {
|
|
}
|
|
|
|
internal SqlColumn BubbleUp(SqlColumn col, SqlNode source) {
|
|
this.match = this.GetOriginatingColumn(col);
|
|
this.found = null;
|
|
this.Visit(source);
|
|
return this.found;
|
|
}
|
|
|
|
internal SqlColumn GetOriginatingColumn(SqlColumn col) {
|
|
SqlColumnRef cref = col.Expression as SqlColumnRef;
|
|
if (cref != null) {
|
|
return this.GetOriginatingColumn(cref.Column);
|
|
}
|
|
return col;
|
|
}
|
|
|
|
internal override SqlRow VisitRow(SqlRow row) {
|
|
foreach (SqlColumn c in row.Columns) {
|
|
if (this.RefersToColumn(c, this.match)) {
|
|
if (this.found != null) {
|
|
throw Error.ColumnIsDefinedInMultiplePlaces(GetColumnName(this.match));
|
|
}
|
|
this.found = c;
|
|
break;
|
|
}
|
|
}
|
|
return row;
|
|
}
|
|
|
|
internal override SqlTable VisitTable(SqlTable tab) {
|
|
foreach (SqlColumn c in tab.Columns) {
|
|
if (c == this.match) {
|
|
if (this.found != null)
|
|
throw Error.ColumnIsDefinedInMultiplePlaces(GetColumnName(this.match));
|
|
this.found = c;
|
|
break;
|
|
}
|
|
}
|
|
return tab;
|
|
}
|
|
|
|
internal override SqlSource VisitJoin(SqlJoin join) {
|
|
switch (join.JoinType) {
|
|
case SqlJoinType.CrossApply:
|
|
case SqlJoinType.OuterApply: {
|
|
this.Visit(join.Left);
|
|
if (this.found == null) {
|
|
this.Visit(join.Right);
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
this.Visit(join.Left);
|
|
this.Visit(join.Right);
|
|
break;
|
|
}
|
|
}
|
|
return join;
|
|
}
|
|
|
|
internal override SqlExpression VisitTableValuedFunctionCall(SqlTableValuedFunctionCall fc) {
|
|
foreach (SqlColumn c in fc.Columns) {
|
|
if (c == this.match) {
|
|
if (this.found != null)
|
|
throw Error.ColumnIsDefinedInMultiplePlaces(GetColumnName(this.match));
|
|
this.found = c;
|
|
break;
|
|
}
|
|
}
|
|
return fc;
|
|
}
|
|
|
|
private void ForceLocal(SqlRow row, string name) {
|
|
bool isLocal = false;
|
|
// check to see if it already exists locally
|
|
foreach (SqlColumn c in row.Columns) {
|
|
if (this.RefersToColumn(c, this.found)) {
|
|
this.found = c;
|
|
isLocal = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!isLocal) {
|
|
// need to put this in the local projection list to bubble it up
|
|
SqlColumn c = new SqlColumn(found.ClrType, found.SqlType, name, this.found.MetaMember, new SqlColumnRef(this.found), row.SourceExpression);
|
|
row.Columns.Add(c);
|
|
this.found = c;
|
|
}
|
|
}
|
|
|
|
private bool IsFoundInGroup(SqlSelect select) {
|
|
// does the column happen to be listed in the group-by clause?
|
|
foreach (SqlExpression exp in select.GroupBy) {
|
|
if (this.RefersToColumn(exp, this.found) || this.RefersToColumn(exp, this.match)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
internal override SqlSelect VisitSelect(SqlSelect select) {
|
|
// look in this projection
|
|
this.Visit(select.Row);
|
|
|
|
if (this.found == null) {
|
|
// look in upstream projections
|
|
this.Visit(select.From);
|
|
|
|
// bubble it up
|
|
if (this.found != null) {
|
|
if (select.IsDistinct && !match.IsConstantColumn) {
|
|
throw Error.ColumnIsNotAccessibleThroughDistinct(GetColumnName(this.match));
|
|
}
|
|
if (select.GroupBy.Count == 0 || this.IsFoundInGroup(select)) {
|
|
this.ForceLocal(select.Row, this.found.Name);
|
|
}
|
|
else {
|
|
// found it, but its hidden behind the group-by
|
|
throw Error.ColumnIsNotAccessibleThroughGroupBy(GetColumnName(this.match));
|
|
}
|
|
}
|
|
}
|
|
|
|
return select;
|
|
}
|
|
}
|
|
}
|
|
}
|