using Microsoft.SqlServer.TransactSql.ScriptDom; using SQLLinter.Common; using SQLLinter.Common.Helpers; namespace SQLLinter.Infrastructure.Rules; public class SchemaQualifyRule : BaseRuleVisitor, IRule { private readonly List tableAliases = new() { "INSERTED", "UPDATED", "DELETED" }; public override string Text => "Имя объекта без схемы: {0}"; public override void Visit(TSqlStatement node) { var childAliasVisitor = new ChildAliasVisitor(); node.AcceptChildren(childAliasVisitor); tableAliases.AddRange(childAliasVisitor.TableAliases); } public override void Visit(NamedTableReference node) => VisitTableName(node.SchemaObject, true); public override void Visit(CreateTableStatement node) => VisitTableName(node.SchemaObjectName, false); public override void Visit(AlterTableStatement node) => VisitTableName(node.SchemaObjectName, false); public override void Visit(TruncateTableStatement node) => VisitTableName(node.TableName, false); public override void Visit(DropTableStatement node) { foreach (var schemaObjectName in node.Objects) { VisitTableName(schemaObjectName, false); } } private void VisitTableName(SchemaObjectName node, bool canHaveTableAliases) { if (node.SchemaIdentifier != null) { return; } // не проверять схему во временных таблицах if (node.BaseIdentifier.Value.Contains('#')) { return; } // не проверять схему псевдонимов таблиц if (canHaveTableAliases && tableAliases.Exists(x => x.Equals(node.BaseIdentifier.Value, StringComparison.OrdinalIgnoreCase))) { return; } AddViolation(node, SQLHelpers.ObjectGetFullName(node)); } public class ChildAliasVisitor : TSqlFragmentVisitor { public List TableAliases { get; } = new(); public override void Visit(TableReferenceWithAlias node) { if (node.Alias != null) { TableAliases.Add(node.Alias.Value); } } public override void Visit(CommonTableExpression node) { TableAliases.Add(node.ExpressionName.Value); } } }