最近要封装数据库接口,查询的关键是where条件。如何让不懂SQL的用户,通过某些接口,拼接出符合条件的SQL,成为了一个问题。
不论多复杂的where,应该只包括3种类型的元素,表达式与逻辑操作符及组合元素。
表达式,即形同:colname>=val,colname=val,colname like '%val%'等等的元素
逻辑操作符,即or,and
组合元素:即逻辑操作符+表达式,或逻辑操作符+表达式+组合元素组成的元素。这是一种递归的表达,不知道是否容易理解。
可以举个简单的例子:
colname1=val1 and colname2=val2是一种组合元素,它由表达式colname1=val1,colname2=val2,以及逻辑操作符and组成。
(colname1=val1 or colnam2=val2) and colname3=val3也是一种组合元素,它由组合元素(colname1=val1 or colnam2=val2) ,逻辑操作符and,和表达式colname3=val3组成。
这样写太复杂了,简化一下。因为组合元素如果探究其本质,最终还是由表达式和逻辑操作符组成。所以,where条件的最基本元素应该只有两个,表达式和逻辑操作符。
我们将它看做一个对象,node。
class Node(object):
def __init__(self,key,val,logic,nodetype):
self.key = key
self.val = val
self.logic = logic
# 0 is logic node, 1 is normal node
self.type = nodetype
self.links = list()
def isLogicNode(self):
return self.type == 1
它具有5个属性。其中type表明它是逻辑操作符(1),还是表达式(0)。
如果是逻辑操作符,那么它的logic会有值(or,and);如果是表达式,那么key,val会被赋值(name='1'被拆分为,key="name='?'",val="1";方便我们传入的参数进行过滤,避免SQL注入之类的)
python中没有指针的概念,所以我们用过list数组,存放该节点的子节点列表。
我心目中的树是这样的:
树的描述如下:
1.叶子节点一定是表达式(因为不可能存在一个叶子节点是逻辑操作符的情况)
2.在一棵子树中,如果子树的高度>=2(那么子树的根节点一定是逻辑操作符)
那么,明显的。为了避免用户错误的建造没有子节点的逻辑节点,所以,我们拼接where的接口只对外提供两个方法:
a.创建表达式节点的方法
b.传入节点数组(节点可以是逻辑节点,也可以是表达式节点),逻辑操作符,返回逻辑节点的方法
即:逻辑节点的生成,仅能通过传入节点数组+逻辑操作符的形式生成
最后一步,就是得到树节点的根节点之后。如何还原成where语句呢?
这明显就是后序遍历了,如果对于二叉树而言,就是左子树,右子树,根(最后的根上的逻辑操作符,负责将左右子树的表达式连接起来);但是,因为我们的树是一个逻辑操作符节点,下面可能有多个表达式节点,即我们是N叉树。
后序也很简单,即:子树->根进行遍历。用递归实现即可。语言描述遍历方式如下:
方法 遍历节点方法(node):
如果节点是表达式节点:返回key,val拼接好的字符串
如果节点是逻辑操作符节点:
定义结果数组
取该节点的每一个子节点:
结果数组.append(遍历节点方法(node))
逻辑操作符 连接 结果数组
我们如何判断该递归遍历方法一定能够结束?因为根据树的描述,我们的叶子节点一定是表达式节点,所以绝对能够触发返回key,val拼接好的字符串的方法。所以递归一定能够结束。
下面是对于我的思路的实现:
#coding:utf-8
import copy
class Node(object):
def __init__(self,key,val,logic,nodetype):
self.key = key
self.val = val
self.logic = logic
# 0 is logic node, 1 is normal node
self.type = nodetype
self.links = list()
def isLogicNode(self):
return self.type == 1
class NodeTree(object):
def createNormalNode(self,key,val):
node = Node(key,val,None,0)
return node
# private method ,user can only generate normal node
def __createLogicNode(self,logic):
node = Node(None,None,logic,1)
return node
def combineWheres(self,nodes,logic):
logicnode = self.__createLogicNode(logic)
for node in nodes:
#浅拷贝,即是node对象的引用
logicnode.links.append(copy.copy(node))
return logicnode
def transSQL(self,node):
# invalid node tree
if node.isLogicNode() and len(node.links) == 0:
return -1
if not node.isLogicNode():
return node.key.replace('?',node.val)
if node.isLogicNode():
sql = list()
for item in node.links:
sql.append(self.transSQL(item))
if -1 in sql:
return -1
if len(sql) == 1:
return sql[0]
else:
logic = " "+node.logic+" "
return '('+logic.join(sql)+')'
if __name__ == '__main__':
#(name1='a' or name2='b' or name3='c') and name4 like '%u%'
tree = NodeTree()
node1 = tree.createNormalNode("name1 = '?'","a")
node2 = tree.createNormalNode("name2 = '?'","b")
node3 = tree.createNormalNode("name3 = '?'","c")
node4 = tree.createNormalNode("name4 like '?'","%m%")
orNode = tree.combineWheres([node1,node2,node3],'or')
andNode = tree.combineWheres([orNode,node4],'and')
#generate sql
print '1:',tree.transSQL(andNode)
#(name1='a' or name2='b' or name3='c') and name4 like '%u%' and (name5>='a' and name6>='b')
node5 = tree.createNormalNode("name5>='?'","a")
node6 = tree.createNormalNode("name6>='?","b")
andNode2 = tree.combineWheres([node5,node6],'and')
andNode3 = tree.combineWheres([andNode,andNode2],'and')
print '2:',tree.transSQL(andNode3)
运行结果如下:
1: ((name1 = 'a' or name2 = 'b' or name3 = 'c') and name4 like '%m%')
2: (((name1 = 'a' or name2 = 'b' or name3 = 'c') and name4 like '%m%') and (name5>='a' and name6>='b))
虽然括号与人类相比,可能会多打。但是,整个where条件的优先级是不会受到影响的。