Добавьте файлы проекта.
This commit is contained in:
151
SQLLinter/Infrastructure/Rules/NonSargableRule.cs
Normal file
151
SQLLinter/Infrastructure/Rules/NonSargableRule.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
using Microsoft.SqlServer.TransactSql.ScriptDom;
|
||||
using SQLLinter.Common;
|
||||
using SQLLinter.Infrastructure.Rules.Common;
|
||||
|
||||
namespace SQLLinter.Infrastructure.Rules;
|
||||
|
||||
public class NonSargableRule : BaseRuleVisitor, IRule
|
||||
{
|
||||
private readonly List<TSqlFragment> errorsReported = new();
|
||||
|
||||
public override string Text => "Выполнение функций с предложениями фильтра или предикатами соединения может вызвать проблемы с производительностью.";
|
||||
|
||||
public override void Visit(JoinTableReference node)
|
||||
{
|
||||
var predicateExpressionVisitor = new PredicateVisitor();
|
||||
node.AcceptChildren(predicateExpressionVisitor);
|
||||
var multiClauseQuery = predicateExpressionVisitor.PredicatesFound;
|
||||
|
||||
var joinVisitor = new JoinQueryVisitor(VisitorCallback, multiClauseQuery);
|
||||
node.AcceptChildren(joinVisitor);
|
||||
}
|
||||
|
||||
public override void Visit(WhereClause node)
|
||||
{
|
||||
var predicateExpressionVisitor = new PredicateVisitor();
|
||||
node.Accept(predicateExpressionVisitor);
|
||||
var multiClauseQuery = predicateExpressionVisitor.PredicatesFound;
|
||||
|
||||
var childVisitor = new FunctionVisitor(VisitorCallback, multiClauseQuery);
|
||||
node.Accept(childVisitor);
|
||||
}
|
||||
|
||||
private void VisitorCallback(TSqlFragment childNode)
|
||||
{
|
||||
if (errorsReported.Contains(childNode))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var dynamicSqlColumnAdjustment = GetDynamicSqlColumnOffset(childNode);
|
||||
|
||||
errorsReported.Add(childNode);
|
||||
AddViolation(Name, Text, GetLineNumber(childNode), ColumnNumberCalculator.GetNodeColumnPosition(childNode) + dynamicSqlColumnAdjustment);
|
||||
}
|
||||
|
||||
private class JoinQueryVisitor : TSqlFragmentVisitor
|
||||
{
|
||||
private readonly Action<TSqlFragment> childCallback;
|
||||
private readonly bool isMultiClauseQuery;
|
||||
|
||||
public JoinQueryVisitor(Action<TSqlFragment> childCallback, bool multiClauseQuery)
|
||||
{
|
||||
this.childCallback = childCallback;
|
||||
isMultiClauseQuery = multiClauseQuery;
|
||||
}
|
||||
|
||||
public override void Visit(BooleanComparisonExpression node)
|
||||
{
|
||||
var childVisitor = new FunctionVisitor(childCallback, isMultiClauseQuery);
|
||||
node.Accept(childVisitor);
|
||||
}
|
||||
}
|
||||
|
||||
private class PredicateVisitor : TSqlFragmentVisitor
|
||||
{
|
||||
public bool PredicatesFound { get; private set; }
|
||||
|
||||
public override void Visit(BooleanBinaryExpression node)
|
||||
{
|
||||
PredicatesFound = true;
|
||||
}
|
||||
}
|
||||
|
||||
private class FunctionVisitor : TSqlFragmentVisitor
|
||||
{
|
||||
private readonly bool isMultiClause;
|
||||
private readonly Action<TSqlFragment> childCallback;
|
||||
private bool hasColumnReferenceParameter;
|
||||
|
||||
public FunctionVisitor(Action<TSqlFragment> errorCallback, bool isMultiClause)
|
||||
{
|
||||
childCallback = errorCallback;
|
||||
this.isMultiClause = isMultiClause;
|
||||
}
|
||||
|
||||
public override void Visit(FunctionCall node)
|
||||
{
|
||||
switch (node.FunctionName.Value.ToUpper())
|
||||
{
|
||||
// разрешить предикаты isnull при наличии других фильтров
|
||||
case "ISNULL" when isMultiClause:
|
||||
return;
|
||||
case "DATEADD":
|
||||
case "DATEDIFF":
|
||||
case "DATEDIFF_BIG":
|
||||
case "DATENAME":
|
||||
case "DATEPART":
|
||||
case "DATETRUNC":
|
||||
case "DATE_BUCKET":
|
||||
hasColumnReferenceParameter = true;
|
||||
break;
|
||||
}
|
||||
|
||||
FindColumnReferences(node);
|
||||
}
|
||||
|
||||
public override void Visit(LeftFunctionCall node)
|
||||
{
|
||||
FindColumnReferences(node);
|
||||
}
|
||||
|
||||
public override void Visit(RightFunctionCall node)
|
||||
{
|
||||
FindColumnReferences(node);
|
||||
}
|
||||
|
||||
public override void Visit(ConvertCall node)
|
||||
{
|
||||
FindColumnReferences(node);
|
||||
}
|
||||
|
||||
public override void Visit(CastCall node)
|
||||
{
|
||||
FindColumnReferences(node);
|
||||
}
|
||||
|
||||
private void FindColumnReferences(TSqlFragment node)
|
||||
{
|
||||
var columnReferenceVisitor = new ColumnReferenceVisitor();
|
||||
node.AcceptChildren(columnReferenceVisitor);
|
||||
|
||||
if (columnReferenceVisitor.ColumnReferenceFound && (!hasColumnReferenceParameter || columnReferenceVisitor.ColumnReferenceCount > 1))
|
||||
{
|
||||
childCallback(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ColumnReferenceVisitor : TSqlFragmentVisitor
|
||||
{
|
||||
public bool ColumnReferenceFound { get; private set; }
|
||||
|
||||
public int ColumnReferenceCount { get; private set; }
|
||||
|
||||
public override void Visit(ColumnReferenceExpression node)
|
||||
{
|
||||
ColumnReferenceCount++;
|
||||
ColumnReferenceFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user