Fork me on GitHub

利用逆波兰表达式,二叉树对sql语句解析

做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); //遍历二叉树

posted @ 2013-09-29 23:29  europelee  阅读(1384)  评论(0编辑  收藏  举报