Files
SQLLint/SQLLinter/Infrastructure/Rules/NonSargableRule.cs

152 lines
4.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
}
}