c# 怎样能写个sql的解析器

本示例主要是讲明sql解析的原理 。
前言阅读本文需要有一定正则表达式基础 正则表达式基础教程,和编译原理的基础 。有使用过VUE的伙伴可能知道vue是自定了模版解析编译器的,vue用的是标准的AST语法树统计,如果对语法树不了了解的请查看 什么是AST抽像语法树
本示例介绍的是参考编译原理 词法分析->语法分析->构建AST语法树->解析成目标sql 的流程来实现
示例sqlserver 的一条查询语句
selecta.UniqueCode,a.BarCode,a.CategoryId from GD_UniqueCodeInfo as a假如我们要将以上代码进行格式化成以下方式
select[a].[UniqueCode],[a].[BarCode],[a].[CategoryId] from [GD_UniqueCodeInfo] as [a]分析首先我们来分析一下这个语句有什么特点 。

  1. 找关键词
    这个sql语法有三个关键词如select ,from,as
  2. 找结构
    有字段信息a.UniqueCode,a.BarCode,a.CategoryId,有表名信息GD_UniqueCodeInfo 还有 被重命名的表信息a 这些信息可能符合命名规范可能有些不符合,那么在解析时都要进行检测出来
  3. 标识符
    在生成的目标sql语句中有[] 这个的作用主要是万一字段名出现与关键词有相同的字段名称能进行正常识别
开始首先我们先创建两个c#解析正则表达式的方法
这个方法就是可以将正则表达式中的匹配数据提出来返回一个字典数据
public static Dictionary<string, string> RegexGrp(string regex,string text){Regex _regex = new Regex(regex, RegexOptions.IgnoreCase | RegexOptions.Multiline);Dictionary<string, string> _dic = new Dictionary<string, string>();Match _match = _regex.Match(text);while (_match.Success){foreach (string name in _regex.GetGroupNames()){if(!_dic.ContainsKey(name))_dic.Add(name, _match.Groups[_regex.GroupNumberFromName(name)].Value);}_match = _match.NextMatch();}return _dic;}检测正则表达工是否正确匹配
public static bool RegexMatch(string regex, string text){Regex _regex = new Regex(regex, RegexOptions.IgnoreCase | RegexOptions.Multiline);Match _match = _regex.Match(text);return _match.Success;}第一步 先检测这个sql语句是否是一个查询语句正则代码:^s*(?<cmd>select)s+(?<field>[wsS]+(?=bfromb))(?:bfromb)(?<from>(?:[s]+)(?<flag>[#]{1,2}|[@]{1})?(?<tab>[w]+)s*[swS]*)
那么我们来验证下
通过把要解析的SQL语句放入测试工具中运行
c# 怎样能写个sql的解析器

文章插图
 
【c# 怎样能写个sql的解析器】在右下方的区域通过正则匹配已经把该语句结构已经拆解出来了
cmd:select
field:a.UniqueCode,a.BarCode,a.CategoryId
tab:GD_UniqueCodeInfo
一下就把SQL语句结构化出来了,有匹配结果说明是一个正常的sql语句
第二步 通过代码获取结构信息string sql="selecta.UniqueCode,a.BarCode,a.CategoryId from GD_UniqueCodeInfo as a";Dictionary<string, string> dic =RegexGrp(@"^s*(?<cmd>select)s+(?<field>[wsS]+(?=bfromb))(?:bfromb)(?<from>(?:[s]+)(?<flag>[#]{1,2}|[@]{1})?(?<tab>[w]+)s*[swS]*)",sql);if(dic.ConstainsKey("cmd")){// 说明匹配成功Console.Write(dic["cmd"]);}拆解select 后要把select 替换为空剩余的sql 为a.UniqueCode,a.BarCode,a.CategoryId from GD_UniqueCodeInfo as a
第三步 拆解字段正则表达式:^s*(?<field>[wsS]*?(?=bfromb))
两通过测试工具测试一下
c# 怎样能写个sql的解析器

文章插图
 
那么可以通过代码获取出来
string sql="a.UniqueCode,a.BarCode,a.CategoryId from GD_UniqueCodeInfo as a";Dictionary<string, string> dic =RegexGrp(@"^s*(?<field>[wsS]*?(?=bfromb))",sql);if (dic.ContainsKey("field")){//说明匹配成功}字段是有多个的 还要单独拆解成一个一个的字段,拆解字段的这个就不详细描述了,可以继续用正则表达式也可以用Split(',') 进行分拆

var _field=dic["field"];var fields=_field.Split(',')拆解完字段后 剩余的sql:from GD_UniqueCodeInfo as a
拆解from正则表达式:^s*(?:bfromb)(?<from>(?:[s]+)(?<table>(?:[s]*)(?<flag>[#]{1,2}|[@]{1})?(?<tab>[w]+))s*(?:basbs*(?<asname>[w]+))?s*)
通过该正则表达式可以拆解出 通过 as 重命名的表
下面通过正则表达式工具测试一下
c# 怎样能写个sql的解析器

文章插图
 
那么通过以下代码来获取


推荐阅读