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

98 lines
3.2 KiB
C#

using Microsoft.SqlServer.TransactSql.ScriptDom;
using SQLLinter.Common;
using SQLLinter.Common.Helpers;
using SQLLinter.Infrastructure.Rules.Common;
namespace SQLLinter.Infrastructure.Rules;
public class MultiTableAliasRule : BaseRuleVisitor, IRule
{
private HashSet<string> cteNames = new HashSet<string>();
public override string Text => "Найдена таблица без псевдонимов при объединении нескольких таблиц: {0}";
public override void Visit(TSqlStatement node)
{
var childCommonTableExpressionVisitor = new ChildCommonTableExpressionVisitor();
node.AcceptChildren(childCommonTableExpressionVisitor);
cteNames = childCommonTableExpressionVisitor.CommonTableExpressionIdentifiers;
}
public override void Visit(TableReference node)
{
void ChildCallback(TSqlFragment childNode)
{
var dynamicSqlAdjustment = GetDynamicSqlColumnOffset(childNode);
var tabsOnLine = ColumnNumberCalculator.CountTabsBeforeToken(childNode.StartLine, childNode.LastTokenIndex, childNode.ScriptTokenStream);
var column = ColumnNumberCalculator.GetColumnNumberBeforeToken(tabsOnLine, childNode.ScriptTokenStream[childNode.FirstTokenIndex]);
string tableName = "";
if (childNode is NamedTableReference namedTable)
{
tableName = SQLHelpers.ObjectGetFullName(namedTable.SchemaObject);
}
AddViolation(Name, GetText(tableName), GetLineNumber(childNode), column + dynamicSqlAdjustment);
}
var childTableJoinVisitor = new ChildTableJoinVisitor();
node.AcceptChildren(childTableJoinVisitor);
if (!childTableJoinVisitor.TableJoined)
{
return;
}
var childTableAliasVisitor = new ChildTableAliasVisitor(ChildCallback, cteNames);
node.AcceptChildren(childTableAliasVisitor);
}
public class ChildCommonTableExpressionVisitor : TSqlFragmentVisitor
{
public HashSet<string> CommonTableExpressionIdentifiers { get; } = new HashSet<string>();
public override void Visit(CommonTableExpression node)
{
CommonTableExpressionIdentifiers.Add(node.ExpressionName.Value);
}
}
public class ChildTableJoinVisitor : TSqlFragmentVisitor
{
public bool TableJoined { get; private set; }
public override void Visit(JoinTableReference node)
{
TableJoined = true;
}
}
public class ChildTableAliasVisitor : TSqlFragmentVisitor
{
private readonly Action<TSqlFragment> childCallback;
public ChildTableAliasVisitor(Action<TSqlFragment> errorCallback, HashSet<string> cteNames)
{
CteNames = cteNames;
childCallback = errorCallback;
}
public HashSet<string> CteNames { get; }
public override void Visit(NamedTableReference node)
{
if (CteNames.Contains(node.SchemaObject.BaseIdentifier.Value))
{
return;
}
if (node.Alias == null)
{
childCallback(node);
}
}
}
}