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

210 lines
6.0 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 System.Collections.Generic;
namespace SQLLinter.Infrastructure.Rules;
public class DuplicateAliasRule : BaseRuleVisitor
{
public override string Text => "Алиасы таблиц должны быть уникальными: {0}";
private readonly Dictionary<int, HashSet<string>> _activeAliases = new();
private int _dmlDepth = 0;
private bool _insideApply = false;
private void EnterDmlScope(bool inherit)
{
_dmlDepth++;
if (_dmlDepth == 1 || !inherit)
{
_activeAliases[_dmlDepth] = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
}
else
{
// дочерний скоуп видит алиасы родителя (для глобальной уникальности в рамках одного DML)
var parent = _activeAliases[_dmlDepth - 1];
_activeAliases[_dmlDepth] = new HashSet<string>(parent, StringComparer.OrdinalIgnoreCase);
}
}
private void ExitDmlScope()
{
if (_activeAliases.ContainsKey(_dmlDepth))
_activeAliases.Remove(_dmlDepth);
_dmlDepth--;
}
private void RegisterAlias(string alias, TSqlFragment node)
{
if (_dmlDepth == 0) return; // вне DML не проверяем
var set = _activeAliases[_dmlDepth];
if (set.Contains(alias))
{
AddViolation(node, "[" + alias + "]");
}
else
{
set.Add(alias);
}
}
// Корневые DML-выражения
public override void ExplicitVisit(SelectStatement node)
{
EnterDmlScope(true);
base.ExplicitVisit(node);
ExitDmlScope();
}
public override void ExplicitVisit(InsertStatement node)
{
EnterDmlScope(true);
base.ExplicitVisit(node);
ExitDmlScope();
}
public override void ExplicitVisit(UpdateStatement node)
{
EnterDmlScope(true);
base.ExplicitVisit(node);
ExitDmlScope();
}
public override void ExplicitVisit(DeleteStatement node)
{
EnterDmlScope(true);
base.ExplicitVisit(node);
ExitDmlScope();
}
public override void ExplicitVisit(MergeStatement node)
{
EnterDmlScope(true);
base.ExplicitVisit(node);
ExitDmlScope();
}
// IF: каждая ветка - отдельный скоуп
public override void ExplicitVisit(IfStatement node)
{
EnterDmlScope(false);
node.ThenStatement.Accept(this);
ExitDmlScope();
if (node.ElseStatement != null)
{
EnterDmlScope(false);
node.ElseStatement.Accept(this);
ExitDmlScope();
}
base.ExplicitVisit(node);
}
// UNION: каждая часть - отдельный скоуп
public override void ExplicitVisit(BinaryQueryExpression node)
{
EnterDmlScope(true);
node.FirstQueryExpression.Accept(this);
ExitDmlScope();
EnterDmlScope(true);
node.SecondQueryExpression.Accept(this);
ExitDmlScope();
//base.ExplicitVisit(node);
}
// CTE: регистрируем имя, тело - в дочернем скоупе
public override void ExplicitVisit(CommonTableExpression node)
{
if (node.ExpressionName?.Value is { Length: > 0 } cteAlias)
RegisterAlias(cteAlias, node);
EnterDmlScope(false);
node.QueryExpression.Accept(this);
ExitDmlScope();
base.ExplicitVisit(node);
}
// Производная таблица: регистрируем её алиас; внутренняя QueryExpression обойдётся базой
public override void ExplicitVisit(QueryDerivedTable node)
{
if (node.Alias?.Value is { Length: > 0 } a)
RegisterAlias(a, node);
// тело подзапроса в FROM - свой локальный скоуп
EnterDmlScope(_insideApply);
node.QueryExpression.Accept(this);
ExitDmlScope();
}
// CROSS APPLY
public override void ExplicitVisit(UnqualifiedJoin node)
{
if (node.UnqualifiedJoinType == UnqualifiedJoinType.CrossApply ||
node.UnqualifiedJoinType == UnqualifiedJoinType.OuterApply)
{
var prev = _insideApply;
_insideApply = true;
// обходим без нового скоупа, т.к. алиасы учитываются глобально
node.FirstTableReference.Accept(this);
node.SecondTableReference.Accept(this);
_insideApply = prev;
}
else
{
base.ExplicitVisit(node);
}
}
// Табличные источники с алиасами
public override void ExplicitVisit(NamedTableReference node)
{
if (node.Alias?.Value is { Length: > 0 } a)
RegisterAlias(a, node);
base.ExplicitVisit(node);
}
public override void ExplicitVisit(VariableTableReference node)
{
if (node.Alias?.Value is { Length: > 0 } a)
RegisterAlias(a, node);
base.ExplicitVisit(node);
}
public override void ExplicitVisit(SchemaObjectFunctionTableReference node)
{
if (node.Alias?.Value is { Length: > 0 } a)
RegisterAlias(a, node);
base.ExplicitVisit(node);
}
public override void ExplicitVisit(PivotedTableReference node)
{
if (node.Alias?.Value is { Length: > 0 } a)
RegisterAlias(a, node);
base.ExplicitVisit(node);
}
public override void ExplicitVisit(UnpivotedTableReference node)
{
if (node.Alias?.Value is { Length: > 0 } a)
RegisterAlias(a, node);
base.ExplicitVisit(node);
}
// Сброс состояния перед анализом скрипта
public override void ExplicitVisit(TSqlScript node)
{
_dmlDepth = 0;
_activeAliases.Clear();
base.ExplicitVisit(node);
}
}