做sql的的where子句解析,是出于实际业务很多场景是sql查询,通过sql解析 并穷举各种分支反向生成 测试数据。
其实有开源的flex和bison来做,但是感觉太重,而且时间成本问题,所以自己来写了个轻量解析方法。
eg: 有一个where子句如下
((handoutstatus = 2 and horrcause = 28 and eventid = 9 ) or (eventid = 8 and handinstatus = 2 and horrcause = 28)) and protocolid = 111
A)基于上述语句,生成其逆波兰表达式
首先对运算符分类为(且优先级从高到低):
| &
| =, !=,in 。。。
| and
| or
V
运算符包括: 操作符(=, in 等) 和 连接符(and, or)
步骤:
(其中A:操作数栈, B:运算堆栈)
1. 扫描上面sql语句字符串, 利用空格符获取每一个子串(如 ”((handoutstatus” ).
2. 对每个子串按照各种情况处理:(function: Infix2Postfix)
各种情况:(function: In2PostHelper)
(1) 字段名(子串入A栈)
(2) 字段的取值 (子串入A栈, 取值可以是固定数值或范围如(1,3, 12) )
(3) 运算符:(包括and, or)
具体步骤:
(a) 若运算符堆栈栈顶的运算符为括号,则直接存入运算符堆栈B。
(b) 若比运算符堆栈栈顶的运算符优先级高或相等,则直接存入运算符堆栈B。
(c) 若比运算符堆栈栈顶的运算符优先级低,则输出栈顶运算符到操作数堆栈B,并将当前运算符压入运算符堆栈(循环此步骤直至不满足条件)。
(4) 为左括号"(",则直接存入运算符堆栈
(5) 为右括号")",则输出运算符堆栈中的运算符到操作数堆栈,直到遇到左括号为止
(6) 当以上各种情况都不是时,考虑要进行分解子串:(注:本步骤仅进行一次),将得到的子子串输到算法步骤2.(1)-(5)步骤进行处理(function: DecStringHelperCplx)。
考虑子串分解情况:
(a) case: “and/or(....”,分解得到三个子子串(eg: and, (, 字段名 )输入到2.(1)-(5)步骤进行处理.
(b) case: in(3,..)))...,子串为包含”in(”: 分解为eg: in, (1,2,12),以及可能含有右括号,得到这些子子串输入到2.(1)-(5)步骤进行处理.
(c) 含有操作符情况,分析这个子串可能含有一个或两个操作符,如(a&b=0, 含两个:&, =; 或a=0), 含一个=), 以操作符为界限,将左子串,右子串再分解到最小单元(按d步骤来分解)(最小单元:字段名,值,运算符,括号)
(d) Case: (4,...)))...; (a, ((a, (((...a; 23...)))...(function: DecStringDeepHelper)
(7) 当表达式读取完成后运算符堆栈中尚有运算符时,则依序取出运算符到操作数
堆栈,直到运算符堆栈为空
B) 最终对A栈内容(即逆波兰表达式)进行创建二叉树:(function: CreateLinkList)
Eg: Handoutstatus,2,=,horrcause,28,=,and,eventide,9,=,and,eventid,8,=,handinstatus,2,=,and,horrcause,28,=,and,or,protocolid,111,=,and
由栈底开始依次取元素建立二叉树, 直至全部处理完
操作数栈C, 结点栈D
(1) 如果扫描的项目是操作数(即字段名,值),则将其压入操作数堆栈C,并扫描下
一个项目。
(2) 如果扫描的项目是一个&运算符,则对操作数栈的顶上两个操作数执行合并操作, 并入操作数栈C。(eg: A&B<>0)
(3) 如果扫描的项目是一个二元操作符(=, <>等等),则对操作数栈的顶上两个操作数执行该运算,建立值结点存储,入结点栈D。(eg:Node(protocolid, = , 12), 或Node(1222&umpl, <>, 0),注意对于&符特殊处理,当填充字段值时,要检查是否有&进行特殊处理)
(4) 若为连接符and, or, 则为它建立一结点,并对结点栈D的顶上两个结点出栈,存储为其左,右子,并将此结点入结点栈D。
(5) 重复步骤1-5,最后结点堆栈中剩下的最后一个结点指针即指向了二叉树
图示:
C)广度优先遍历二叉树,得到一种sql分支(function: OutputFields)
两个队列A:存字段置正,B:字段置反
(1) 将根结点入队列A
(2) 当队列A或B不为空时:
(a) 从队列B头pop一个结点:
If 结点域名非and, or
将各种运算符置反转到=, 将最终值结点存到refMsgConVec
If 结点域名为and 或or
让左,右子结点都入B
(b) 从队列A头pop一个结点:
If 结点域名非and, or
// this->ProcessValueNode(pTmpCNode->pCondition, refMsgConVec);
将各种运算符取正转到=, 将最终值结点存到refMsgConVec
If结点域名为and
将左, 右子结点均入队列A,
If 结点域名为or
随机让左,右子结点分别入A,B
Application:
Infix2Postfix(); //转为逆波兰表达式
CreateLinkList(); //创建二叉树
OutputFields(refMsgConVec); //遍历二叉树