DacFX:忽略 ColumnReferenceExpression 访问者中的非列
DacFX: Ignore non Columns in a ColumnReferenceExpression visitor
当使用访问者扫描 TSqlFragment 中的 ColumnReferenceExpression 时,有些东西会被选为列,但实际上并非如此。例如在这个 sql:
select Age, FirstName, LastName, DATEADD(year, Age, getdate())
from table1
inner join table2 on 1 = 1
year 参数与 Age、FirstName 和 LastName 一起作为列引用。似乎没有任何方法可以将年份与其他列区分开来。
我问的原因是我们编写了一个 SqlCodeAnalysisRule 来检查两个部分名称,当 select 中有多个 table 并且它正在拾取这些非列时以及。这是规则的分析,以防有人对如何排除它们有任何想法:
public override IList<SqlRuleProblem> Analyze(SqlRuleExecutionContext ruleExecutionContext)
{
var problems = new List<SqlRuleProblem>();
var sqlObj = ruleExecutionContext.ModelElement;
if (sqlObj == null) { return problems; }
var fragment = ruleExecutionContext.ScriptFragment;
var selectStatementVisitor = new SelectStatementVisitor();
fragment.Accept(selectStatementVisitor);
if (selectStatementVisitor.Statements.Count == 0) { return problems; }
foreach (var select in selectStatementVisitor.Statements)
{
var fromClause = (select.QueryExpression as QuerySpecification)?.FromClause;
if (fromClause == null) { continue; }
//check to ensure we have more than one table
var namedTableVisitor = new NamedTableReferenceVisitor();
fromClause.Accept(namedTableVisitor);
if(namedTableVisitor.Statements.Count <= 1) { continue; }
var columnReferences = new ColumnReferenceExpressionVisitor();
select.Accept(columnReferences);
//TODO: This will erroneously pickup things which appear to be column references as well
// such as the 'dd' in DATEADD(dd, rev.ReviewReferenceDaysStart, @StartDate)
var offenders = columnReferences.Statements
.Where(c => (c as ColumnReferenceExpression).MultiPartIdentifier?.Identifiers.Count == 1)
.Select(n => (n as ColumnReferenceExpression).MultiPartIdentifier.Identifiers[0]);
problems.AddRange(offenders.Select(cr => new SqlRuleProblem(string.Format(Message, cr.Value), sqlObj, cr)));
}
return problems;
}
我们所有的访问者都遵循几乎相同的模式。以下是引用访问者的列,作为提到的其他列的示例:
internal class ColumnReferenceExpressionVisitor : TSqlFragmentVisitor, IVisitor<ColumnReferenceExpression>
{
public IList<ColumnReferenceExpression> Statements { get; } = new List<ColumnReferenceExpression>();
public override void ExplicitVisit(ColumnReferenceExpression node)
{
Statements.Add(node);
}
}
public interface IVisitor<T> where T : TSqlFragment
{
IList<T> Statements { get; }
}
我已经将找到的每个列的所有属性与非列进行了比较,没有什么不同可以用来排除我看到的它们。
dd 是一个列引用,因为您正在获取树中的所有 ColumnReferences - 脚本 dom 不知道 dateadd 是什么,它只知道它是一个函数,所以 dd 可以非常做一个专栏吧。
如果是我,我会得到 select 语句,得到 Select 元素并遍历它们(例如,如果你有相关的子查询,通过使用 Select 访问者你仍然应该得到所有这些)并且在这种情况下忽略任何不是 ColumnReference 的东西。
手动执行(而不是 linq),这样会更清楚一些:
foreach (var select in selectStatementVisitor.Statements)
{
var columnReferences = new ColumnReferenceExpressionVisitor();
select.Accept(columnReferences);
foreach (var item in (select.QueryExpression as QuerySpecification).SelectElements)
{
//other code here if you want ...
if (item is SelectScalarExpression)
{
var expression = item as SelectScalarExpression;
if (expression.Expression is ColumnReferenceExpression)
{
var column = expression.Expression as ColumnReferenceExpression;
Console.WriteLine(column); // <-- this is only ColumnReferenceExpression's
}
}
}
}
当使用访问者扫描 TSqlFragment 中的 ColumnReferenceExpression 时,有些东西会被选为列,但实际上并非如此。例如在这个 sql:
select Age, FirstName, LastName, DATEADD(year, Age, getdate())
from table1
inner join table2 on 1 = 1
year 参数与 Age、FirstName 和 LastName 一起作为列引用。似乎没有任何方法可以将年份与其他列区分开来。
我问的原因是我们编写了一个 SqlCodeAnalysisRule 来检查两个部分名称,当 select 中有多个 table 并且它正在拾取这些非列时以及。这是规则的分析,以防有人对如何排除它们有任何想法:
public override IList<SqlRuleProblem> Analyze(SqlRuleExecutionContext ruleExecutionContext)
{
var problems = new List<SqlRuleProblem>();
var sqlObj = ruleExecutionContext.ModelElement;
if (sqlObj == null) { return problems; }
var fragment = ruleExecutionContext.ScriptFragment;
var selectStatementVisitor = new SelectStatementVisitor();
fragment.Accept(selectStatementVisitor);
if (selectStatementVisitor.Statements.Count == 0) { return problems; }
foreach (var select in selectStatementVisitor.Statements)
{
var fromClause = (select.QueryExpression as QuerySpecification)?.FromClause;
if (fromClause == null) { continue; }
//check to ensure we have more than one table
var namedTableVisitor = new NamedTableReferenceVisitor();
fromClause.Accept(namedTableVisitor);
if(namedTableVisitor.Statements.Count <= 1) { continue; }
var columnReferences = new ColumnReferenceExpressionVisitor();
select.Accept(columnReferences);
//TODO: This will erroneously pickup things which appear to be column references as well
// such as the 'dd' in DATEADD(dd, rev.ReviewReferenceDaysStart, @StartDate)
var offenders = columnReferences.Statements
.Where(c => (c as ColumnReferenceExpression).MultiPartIdentifier?.Identifiers.Count == 1)
.Select(n => (n as ColumnReferenceExpression).MultiPartIdentifier.Identifiers[0]);
problems.AddRange(offenders.Select(cr => new SqlRuleProblem(string.Format(Message, cr.Value), sqlObj, cr)));
}
return problems;
}
我们所有的访问者都遵循几乎相同的模式。以下是引用访问者的列,作为提到的其他列的示例:
internal class ColumnReferenceExpressionVisitor : TSqlFragmentVisitor, IVisitor<ColumnReferenceExpression>
{
public IList<ColumnReferenceExpression> Statements { get; } = new List<ColumnReferenceExpression>();
public override void ExplicitVisit(ColumnReferenceExpression node)
{
Statements.Add(node);
}
}
public interface IVisitor<T> where T : TSqlFragment
{
IList<T> Statements { get; }
}
我已经将找到的每个列的所有属性与非列进行了比较,没有什么不同可以用来排除我看到的它们。
dd 是一个列引用,因为您正在获取树中的所有 ColumnReferences - 脚本 dom 不知道 dateadd 是什么,它只知道它是一个函数,所以 dd 可以非常做一个专栏吧。
如果是我,我会得到 select 语句,得到 Select 元素并遍历它们(例如,如果你有相关的子查询,通过使用 Select 访问者你仍然应该得到所有这些)并且在这种情况下忽略任何不是 ColumnReference 的东西。
手动执行(而不是 linq),这样会更清楚一些:
foreach (var select in selectStatementVisitor.Statements)
{
var columnReferences = new ColumnReferenceExpressionVisitor();
select.Accept(columnReferences);
foreach (var item in (select.QueryExpression as QuerySpecification).SelectElements)
{
//other code here if you want ...
if (item is SelectScalarExpression)
{
var expression = item as SelectScalarExpression;
if (expression.Expression is ColumnReferenceExpression)
{
var column = expression.Expression as ColumnReferenceExpression;
Console.WriteLine(column); // <-- this is only ColumnReferenceExpression's
}
}
}
}