好一段时间没写博客了,这次我们来一起谈谈SQL文件执行器的功能实现,在ERP软件升级时往往在客户端程序更新的同时也要对数据库进行升级,ERP程序开发人员会对数据库升级的执行代码在开发的过程中以SQL文件的形式记录下来或者保存到特定格式的文件中供软件升级时使用,有些ERP软件会附带开发数据库升级工具来方便实施人员执行软件升级操作或者ERP软件内置数据库升级功能,不管使用什么样的方式能达到软件升级的目的就是好方法,这次我们就来剥离这部分的功能来实现一个SQL文件执行器,不特定于SQL文件,只要文件里面包含有SQL语法,而且能正常得到执行,在本执行器中就能正确的执行它(本次我们主要争对SQL文件,其他格式文件只要把相关控制去掉就好了,只是得不到很好的控制比较乱,或者你有更好的方法)。介绍性引入就到此为止了,接下来我们开始进入主题,研究一下SQL执行器的原理及设计思路吧。
一、SQL文件执行器原理分析
在分析原理之前,我们先来规范一下SQL文档的书写,我们在一个SQL语句结束的时候换行来个GO关键字再换行继续书写下一个SQL语句,遇到“USE [数据库名] ”语句时希望能把此语句单独放一行。做到以上书写规范我们开始原理分析:
1.上述SQL规范居然都做到了但是SQL文件里面的内容还不是我想要的,我想重新规范一下我的SQL文档,我想理想化它,所以我需要对SQL文件进行重新洗牌,我想我应该一行一行的阅读它,并把它提取出来去掉前后的空格,我应该重组改文件的内容并且在读取每一行的同时在其末尾写入一个换行回车符,而且我还想统计出每一个SQL文件中的SQL语句的个数(循环时会用到),我用GO关键字来标记了SQL语句的个数,在阅读到一行去掉首尾空格后只剩下不区分大小写的GO时我的统计会+1(统计从1开始),哦,这还永远不够,我还没考虑到USE关键字的处理,通常会选择一个数据库来执行相应的SQL文件,USE关键字在切换着数据库,我需要在检测到USE语句时切换数据库为USE后面跟着的数据库来执行后面的代码,所以我要把USE语句单独剥离开来以待做特殊的处理,往往我们写SQL文件的时候在USE语句的前后都不带GO关键字的,我需要给它加上。有一个需要注意的地方:USE [数据库名] 后面直接跟着SQL语句的(没有回车换行),这种写法在语法上是完全正确的,但是看上去就不是很美观了,这种方式我这边就不做处理了,请遵守上述规范吧,再处理下去程序性能就严重下降啦,本程序在数据库切换时是从USE 之后的字符开始到回车换行符结束来取数据库名的,这种写法会引发SQL异常。我需要构建一个这样的方法。
2.在第一点里面我们对SQL文件进行了格式化,现在开始我希望以GO关键字作为分割点,把SQL文件里面的SQL语法进行分段,我希望一段段的得到执行并返回执行结果,此时我需要一个循环来遍历文件中的SQL语句并执行它。本工具名字叫做SQL文件执行器很显然是争对批量SQL文件的处理的,所以一个循环是永远不够的,我还得在外面再套一个循环来遍历所有的SQL文件,对每个SQL文件进行分析并遍历其中的SQL语句执行它,这样就达到我们的目的了。貌似还有一个问题未处理,比如在执行到一半的时候我不执行了需要强制停止他这如何是好呢,强制关闭程序很显然是不可取的很容易引发未知的数据库异常或者造成数据丢失这些状况都是我们不想看到的,那么有什么好办法呢?此时我启用了臭名远扬的goto语句来从深层嵌套循环中跳出循环,我让他在用户发出停止指令后在执行完当前的SQL语法段后跳出循环,从而停止接下来的SQL语法的执行,这样子保障了SQL数据的安全,在SQL文件执行的期间,程序是不允许关闭的,除非向程序发出停止指令,并成功停止的时候,才允许程序关闭。
很简单的一个程序,我就大致的做以上两点的原理分析吧,接下来我上传下我的程序界面设计图吧,大家参考下:
接下来我贴上来一些主要源代码供参考,代码可能看上去有些难懂,表述性不是很好,期待大家来改进它:
这是对SQL文件中的语法进行重新洗牌的方法
/// <summary> /// 读取文件内容(SQL关键字特殊处理主要针对GO关键字) /// </summary> /// <param name="path">文件路径</param> /// <param name="keywords">关键字</param> /// <param name="str">输出字符串</param> /// <param name="i">keywords的个数</param> public void FileReader(string path,string keywords,out int i,out string str) { bool useplusgo = false;//use后面是否跟着go bool goplususe = false;//use前面是否存在go StreamReader sr = new StreamReader(path, Encoding.GetEncoding("GB2312")); //str = sr.ReadToEnd(); string s = null; string temp = null; int x = 0; while ((temp = sr.ReadLine()) != null) { if (temp.Trim().ToUpper() == keywords.ToUpper()) { useplusgo = false;//use语句后面跟着go则关闭use判断 goplususe = true;//use前面存在go x++; s += " " + keywords + " "; } else if (temp.Trim().Length >= 4 && temp.Trim().ToUpper().Substring(0, 4).Trim() == "USE") { temp = temp.Trim().Replace("[", "").Replace("]", ""); //如果use前面不存在go则加上 if (!goplususe && s != " " && s != null) { x++; s += " " + keywords + " " + temp + " "; } else { s += temp + " "; } useplusgo = true; } else { goplususe = false;//go后面不跟use关闭判断 //如果use后面不跟go则加上go if (useplusgo) { x++; s += " " + keywords + " " + temp + " "; useplusgo = false;//关闭use判断 } else { s += temp + " "; } } } i = x; str = s; sr.Close();//关闭当前打开的文件 }
这个是处理USE语句的方法
/// <summary> /// 获取use后面的数据库名称 /// </summary> /// <param name="str">use字符串行</param> /// <returns></returns> public string UseStatementProcessing(string str) { string[] strSplit = Regex.Split(str, " ", RegexOptions.IgnoreCase); int x = strSplit.Length; string s = ""; if (/*strSplit[0].ToUpper().IndexOf("USE ", 0) >= 0*/Regex.IsMatch(strSplit[0].ToUpper(), "USE ", RegexOptions.IgnoreCase)) { s = strSplit[0].Substring(4, strSplit[0].Length - 4).Trim(); } else if (/*strSplit[1].ToUpper().IndexOf("USE ", 0) >= 0*/Regex.IsMatch(strSplit[1].ToUpper(), "USE ", RegexOptions.IgnoreCase)) { s = strSplit[1].Substring(4, strSplit[1].Length - 4).Trim(); } else { s = ""; } return s; }
这次话题就到此为止吧,这程序比较简单,大家可以写写玩,当作练练手也不错,主要在于文件的操作和字符串的处理。