随着数据库技术的不断发展,SQL脚本变得越来越庞大和复杂。为了更好地管理和分析这些脚本,需要一些工具来帮助理解脚本中的对象和逻辑。本文将介绍一种基于正则表达式和文本处理技巧的SQL脚本分析工具的实现方法,以及如何使用该工具进行数据库对象的搜索和依赖分析。
为了实现SQL脚本的分析,需要获取整个数据库对象的单文件SQL脚本,然后使用正则表达式将SQL脚本分割成文本块,最后在这些文本块中搜索特定的文本字符串。
1. 获取所有SQL对象脚本
每个关系数据库管理系统(RDBMS)都提供了生成SQL删除/创建脚本的选项。首先,需要获取所有数据库对象的单个SQL脚本。或者,可以使用示例脚本,如northwind.sql。
2. 使用正则表达式获取所有DDL语句
使用以下正则表达式:
\b(create|alter)\s+(unique)*\s*(nonclustered)*\s*(procedure|proc|table|trigger|view|function|index)\b\s([^=<>\s\'\(]+).([^=<>\s\'\(]+)[*\s\b\t]*
FastColoredTextBox提供了强大的正则表达式处理工具,因此使用:
var result = range.GetRangesByLines(regexStr, RegexOptions.IgnoreCase);
首先,构建了一些有用的正则表达式,并将它们收集在一个静态类中:
public static class RegexValues
{
public static string SqlCmdObjects = @"[\$\(([^=<>\[\]\s\']+)\)]\.\[[^=<>\s\']+\]";
public static string SqlCmdObjectsShort = @"\$\(([^=<>\[\]\s\']+)\)";
public static string DdlObjects = @"\b(create)\s+(procedure|proc|table|trigger|view|function)\b\s\[\$\(([^<>\[\]\s\']+)\)\].\[[^=<>\s\']+\]";
public static string DdlObjectsPrepared = @"\b(create)\s+(procedure|proc|table|trigger|view|function)\b\s\[([^<>\[\]\s\']+)\].\[[^=<>\s\']+\]";
public static string DdlObjects_ = @"\b(create)\s+(procedure|proc|table|trigger|view|function)\b\s\$\(([^<>\[\]\s\']+)\).\[[^=<>\s\']+]";
public static string DdlObjectsPrepared_ = @"\b(create)\s+(procedure|proc|table|trigger|view|function)\b\s([^<>\s\']+).([^=<>\s\'\(]+)[*\s\b\t]*";
public static string DdlObjectsPreparedProcViewTrigger = @"\b(create)\s+(procedure|proc|trigger|view|function)\b\s([^=<>\s\']+).([^=<>\s\'\(]+)[*\s\b\t]*";
public static string DdlObjectsPreparedWithIndex = @"\b(create)\s+(unique)*\s*(nonclustered)*\s*(procedure|proc|table|trigger|view|function|index)\b\s([^=<>\s\']+).([^=<>\s\'\(]+)[*\s\b\t]*";
public static string DdlIndexAll = @"\b(create|alter)\s+(procedure|proc|table|trigger|view|function)\b\s\[([^<>\[\]\s\']+)\].\[[^=<>\s\']+\]";
public static string Variables = @"\@([^=<>\s\'\)\(\,]+)";
public static string SqlCmdVariables = @"\:SETVAR\s+([a-zA-Z_]+)\s+([a-zA-Z_]+)";
}
SqlCmdObjects - 匹配 [$(some_var)].[obj_name] 或 [$(some_var)].[obj_schema].[obj_name]。
SqlCmdObjectsShort - 匹配 $(var)。
DdlObjects - 与SqlCmdObjects相同,但包含CREATE PROC/TABLE/VIEW/FUNC/TRIGGER语句。
DdlObjectsPrepared - 与DdlObjects相同,但移除了$(sqlcmd变量)限制。
DdlObjects_ - 与DdlObjects相同,但移除了包含[]限制。
DdlObjectsPrepared_ - 与DdlObjectsPrepared相同,但移除了包含[]限制。
DdlObjectsPreparedProcViewTrigger - 仅包含proc, views, triggers和functions的DDL语句,即仅代码对象。
DdlObjectsPreparedWithIndex - 与DdlObjectsPrepared_相同,但扩展了index语句。
DdlIndexAll - 与DdlObjectsPrepared_相同,但扩展了alter语句。
Variables - 正则表达式,用于在脚本中查找所有@变量。
SqlCmdVariables - 查找SQL CMD变量(如:SETVAR var1 val1)。
该软件的核心代码如下:
var range = Syncronized(() => fastColoredTextBox1.Selection.Clone());
range.Normalize();
range.Start = new Place(0, 0);
range.End = Syncronized(() => new Place(tb.GetLineLength(tb.LinesCount - 1), tb.LinesCount - 1));
只需将SQL脚本加载到FastColoredTextBox中(感谢其作者!),然后对其所有内容应用一些正则表达式。作为输出,得到了RESULT变量,其中包含找到的范围列表。Range是一个强大的类(再次感谢FastColoredTextBox作者)。Range包含SQL脚本中找到的片段的行号和列号。只需将范围列表存储在虚拟列表框中(常见技术),并在SelectedIndexChanged上执行以下操作:
fastColoredTextBox1.Selection = range;
fastColoredTextBox1.DoSelectionVisible();
通过这两行代码,可以立即导航到找到的代码片段(即DDL语句)。然后构建另一个范围列表,但不是使用正则表达式字符串,而是放置要找到的对象名称。
最后一步是在第一个范围列表中找到第二个范围列表的出现的。这是在Form1.cs的FindUsage(string regex)方法中完成的。
以上就是全部内容,其余的工作只是常见的.NET编码,将所有内容整合在一起。
以上逻辑的简要说明如下:
使用正则表达式找到所有的create/alter语句,并记住这些行号。使用正则表达式找到所有文本(对象,例如表名)的出现,并记住行号。现在将这两个数据集结合起来,找到文本行在create/alter语句行之间的位置。
结果,得到了一个简单的GUI设计器,可以:
打开一个SQL脚本或整个文件夹(脚本将合并到临时文件中,并作为单个脚本打开)
搜索所有DDL语句(CREATE/ALTER)- 列表将在左侧窗格中构建,支持完整的导航。
选择一些文本(例如,某个SQL对象名称),右键单击并选择“查找用法”- 列表将在右侧窗格中构建-这些是包含选定文本的SQL对象。
另一个好处是,不仅可以在SQL对象中搜索SQL对象的出现,还可以搜索几乎所有的代码片段、文本、注释等。
由于这种分析SQL依赖关系的方法基于元数据信息,因此应该始终记住,正在连接两个数据集的行/。因此,有一些限制,或者更好的说法是“特性”:
假设有一个存储过程:
SQL
create
proc
test
as
declare
@somevar
int
create
table
#tmp(
colum_we_search
nvarchar(
255
),
somevar
int
)
如果应用正则表达式匹配"CREATE TABLE"语句,会匹配到临时表#tmp,以及CREATE PROC。然后,如果尝试搜索"COLUMN_WE_SEARCH"-它将在临时表#tmp中找到,而不是在过程test中。
这可以通过更精确的初始正则表达式来解决。对于这种情况,编写了DdlObjectsPreparedProcViewTrigger正则表达式语句(见上文)。
想添加MSAGL支持以可视化依赖关系。