271 lines
10 KiB
C#
271 lines
10 KiB
C#
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Text;
|
||
|
using System.Data.Linq;
|
||
|
using System.Diagnostics.CodeAnalysis;
|
||
|
|
||
|
namespace System.Data.Linq.SqlClient {
|
||
|
|
||
|
internal class SqlNamer {
|
||
|
Visitor visitor;
|
||
|
|
||
|
internal SqlNamer() {
|
||
|
this.visitor = new Visitor();
|
||
|
}
|
||
|
|
||
|
internal SqlNode AssignNames(SqlNode node) {
|
||
|
return this.visitor.Visit(node);
|
||
|
}
|
||
|
|
||
|
class Visitor : SqlVisitor {
|
||
|
int aliasCount;
|
||
|
SqlAlias alias;
|
||
|
bool makeUnique;
|
||
|
bool useMappedNames;
|
||
|
string lastName;
|
||
|
|
||
|
internal Visitor() {
|
||
|
this.makeUnique = true;
|
||
|
this.useMappedNames = false;
|
||
|
}
|
||
|
|
||
|
internal string GetNextAlias() {
|
||
|
return "t" + (aliasCount++);
|
||
|
}
|
||
|
|
||
|
internal override SqlAlias VisitAlias(SqlAlias sqlAlias) {
|
||
|
SqlAlias save = this.alias;
|
||
|
this.alias = sqlAlias;
|
||
|
sqlAlias.Node = this.Visit(sqlAlias.Node);
|
||
|
sqlAlias.Name = this.GetNextAlias();
|
||
|
this.alias = save;
|
||
|
return sqlAlias;
|
||
|
}
|
||
|
|
||
|
internal override SqlExpression VisitScalarSubSelect(SqlSubSelect ss) {
|
||
|
base.VisitScalarSubSelect(ss);
|
||
|
if (ss.Select.Row.Columns.Count > 0) {
|
||
|
System.Diagnostics.Debug.Assert(ss != null && ss.Select != null && ss.Select.Row != null && ss.Select.Row.Columns.Count == 1);
|
||
|
// make sure these scalar subselects don't get redundantly named
|
||
|
ss.Select.Row.Columns[0].Name = "";
|
||
|
}
|
||
|
return ss;
|
||
|
}
|
||
|
|
||
|
internal override SqlStatement VisitInsert(SqlInsert insert) {
|
||
|
bool saveMakeUnique = this.makeUnique;
|
||
|
this.makeUnique = false;
|
||
|
bool saveUseMappedNames = this.useMappedNames;
|
||
|
this.useMappedNames = true;
|
||
|
SqlStatement stmt = base.VisitInsert(insert);
|
||
|
this.makeUnique = saveMakeUnique;
|
||
|
this.useMappedNames = saveUseMappedNames;
|
||
|
return stmt;
|
||
|
}
|
||
|
|
||
|
internal override SqlStatement VisitUpdate(SqlUpdate update) {
|
||
|
bool saveMakeUnique = this.makeUnique;
|
||
|
this.makeUnique = false;
|
||
|
bool saveUseMappedNames = this.useMappedNames;
|
||
|
this.useMappedNames = true;
|
||
|
SqlStatement stmt = base.VisitUpdate(update);
|
||
|
this.makeUnique = saveMakeUnique;
|
||
|
this.useMappedNames = saveUseMappedNames;
|
||
|
return stmt;
|
||
|
}
|
||
|
|
||
|
internal override SqlSelect VisitSelect(SqlSelect select) {
|
||
|
select = base.VisitSelect(select);
|
||
|
|
||
|
string[] names = new string[select.Row.Columns.Count];
|
||
|
for (int i = 0, n = names.Length; i < n; i++) {
|
||
|
SqlColumn c = select.Row.Columns[i];
|
||
|
string name = c.Name;
|
||
|
if (name == null) {
|
||
|
name = SqlNamer.DiscoverName(c);
|
||
|
}
|
||
|
names[i] = name;
|
||
|
c.Name = null;
|
||
|
}
|
||
|
|
||
|
var reservedNames = this.GetColumnNames(select.OrderBy);
|
||
|
|
||
|
for (int i = 0, n = select.Row.Columns.Count; i < n; i++) {
|
||
|
SqlColumn c = select.Row.Columns[i];
|
||
|
string rootName = names[i];
|
||
|
string name = rootName;
|
||
|
if (this.makeUnique) {
|
||
|
int iName = 1;
|
||
|
while (!this.IsUniqueName(select.Row.Columns, reservedNames, c, name)) {
|
||
|
iName++;
|
||
|
name = rootName + iName;
|
||
|
}
|
||
|
}
|
||
|
c.Name = name;
|
||
|
c.Ordinal = i;
|
||
|
}
|
||
|
|
||
|
return select;
|
||
|
}
|
||
|
|
||
|
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification="Unknown reason.")]
|
||
|
private bool IsUniqueName(List<SqlColumn> columns, ICollection<string> reservedNames, SqlColumn c, string name) {
|
||
|
foreach (SqlColumn sc in columns) {
|
||
|
if (sc != c && string.Compare(sc.Name, name, StringComparison.OrdinalIgnoreCase) == 0)
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (!IsSimpleColumn(c, name)) {
|
||
|
return !reservedNames.Contains(name);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// An expression is a simple reprojection if it's a column node whose expression is null, or
|
||
|
/// whose expression is a column whose name matches the name of the given name or where
|
||
|
/// where the given name is null or empty.
|
||
|
/// </summary>
|
||
|
/// <param name="c"></param>
|
||
|
/// <returns></returns>
|
||
|
private static bool IsSimpleColumn(SqlColumn c, string name) {
|
||
|
if (c.Expression != null) {
|
||
|
switch (c.Expression.NodeType) {
|
||
|
case SqlNodeType.ColumnRef:
|
||
|
var colRef = c.Expression as SqlColumnRef;
|
||
|
return String.IsNullOrEmpty(name) || string.Compare(name, colRef.Column.Name, StringComparison.OrdinalIgnoreCase) == 0;
|
||
|
default:
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
internal override SqlExpression VisitExpression(SqlExpression expr) {
|
||
|
string saveLastName = this.lastName;
|
||
|
this.lastName = null;
|
||
|
try {
|
||
|
return (SqlExpression)this.Visit(expr);
|
||
|
}
|
||
|
finally {
|
||
|
this.lastName = saveLastName;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private SqlExpression VisitNamedExpression(SqlExpression expr, string name) {
|
||
|
string saveLastName = this.lastName;
|
||
|
this.lastName = name;
|
||
|
try {
|
||
|
return (SqlExpression)this.Visit(expr);
|
||
|
}
|
||
|
finally {
|
||
|
this.lastName = saveLastName;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal override SqlExpression VisitColumnRef(SqlColumnRef cref) {
|
||
|
if (cref.Column.Name == null && this.lastName != null) {
|
||
|
cref.Column.Name = this.lastName;
|
||
|
}
|
||
|
return cref;
|
||
|
}
|
||
|
|
||
|
internal override SqlExpression VisitNew(SqlNew sox) {
|
||
|
if (sox.Constructor != null) {
|
||
|
System.Reflection.ParameterInfo[] pis = sox.Constructor.GetParameters();
|
||
|
for (int i = 0, n = sox.Args.Count; i < n; i++) {
|
||
|
sox.Args[i] = this.VisitNamedExpression(sox.Args[i], pis[i].Name);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
for (int i = 0, n = sox.Args.Count; i < n; i++) {
|
||
|
sox.Args[i] = this.VisitExpression(sox.Args[i]);
|
||
|
}
|
||
|
}
|
||
|
foreach (SqlMemberAssign ma in sox.Members) {
|
||
|
string n = ma.Member.Name;
|
||
|
if (this.useMappedNames) {
|
||
|
n = sox.MetaType.GetDataMember(ma.Member).MappedName;
|
||
|
}
|
||
|
ma.Expression = this.VisitNamedExpression(ma.Expression, n);
|
||
|
}
|
||
|
return sox;
|
||
|
}
|
||
|
|
||
|
internal override SqlExpression VisitGrouping(SqlGrouping g) {
|
||
|
g.Key = this.VisitNamedExpression(g.Key, "Key");
|
||
|
g.Group = this.VisitNamedExpression(g.Group, "Group");
|
||
|
return g;
|
||
|
}
|
||
|
|
||
|
internal override SqlExpression VisitOptionalValue(SqlOptionalValue sov) {
|
||
|
sov.HasValue = this.VisitNamedExpression(sov.HasValue, "test");
|
||
|
sov.Value = this.VisitExpression(sov.Value);
|
||
|
return sov;
|
||
|
}
|
||
|
|
||
|
internal override SqlExpression VisitMethodCall(SqlMethodCall mc) {
|
||
|
mc.Object = this.VisitExpression(mc.Object);
|
||
|
System.Reflection.ParameterInfo[] pis = mc.Method.GetParameters();
|
||
|
for (int i = 0, n = mc.Arguments.Count; i < n; i++) {
|
||
|
mc.Arguments[i] = this.VisitNamedExpression(mc.Arguments[i], pis[i].Name);
|
||
|
}
|
||
|
return mc;
|
||
|
}
|
||
|
|
||
|
|
||
|
ICollection<string> GetColumnNames(IEnumerable<SqlOrderExpression> orderList)
|
||
|
{
|
||
|
var visitor = new ColumnNameGatherer();
|
||
|
|
||
|
foreach (var expr in orderList) {
|
||
|
visitor.Visit(expr.Expression);
|
||
|
}
|
||
|
|
||
|
return visitor.Names;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal static string DiscoverName(SqlExpression e) {
|
||
|
if (e != null) {
|
||
|
switch (e.NodeType) {
|
||
|
case SqlNodeType.Column:
|
||
|
return DiscoverName(((SqlColumn)e).Expression);
|
||
|
case SqlNodeType.ColumnRef:
|
||
|
SqlColumnRef cref = (SqlColumnRef)e;
|
||
|
if (cref.Column.Name != null) return cref.Column.Name;
|
||
|
return DiscoverName(cref.Column);
|
||
|
case SqlNodeType.ExprSet:
|
||
|
SqlExprSet eset = (SqlExprSet)e;
|
||
|
return DiscoverName(eset.Expressions[0]);
|
||
|
}
|
||
|
}
|
||
|
return "value";
|
||
|
}
|
||
|
|
||
|
class ColumnNameGatherer : SqlVisitor {
|
||
|
public HashSet<string> Names { get; set; }
|
||
|
|
||
|
public ColumnNameGatherer()
|
||
|
: base() {
|
||
|
this.Names = new HashSet<string>();
|
||
|
}
|
||
|
|
||
|
internal override SqlExpression VisitColumn(SqlColumn col) {
|
||
|
if (!String.IsNullOrEmpty(col.Name)) {
|
||
|
this.Names.Add(col.Name);
|
||
|
}
|
||
|
|
||
|
return base.VisitColumn(col);
|
||
|
}
|
||
|
|
||
|
internal override SqlExpression VisitColumnRef(SqlColumnRef cref) {
|
||
|
Visit(cref.Column);
|
||
|
|
||
|
return base.VisitColumnRef(cref);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|