python 用于提取和替换查询参数的正则表达式 - cx_oracle 要求

python regular expression to extract and replace query parameters - cx_oracle requirement

python 模块 cx_Oracle 要求在 where 语句中传递字符串时对查询进行参数化。我正在从一个文件中读取查询,这些查询看起来就像是 IDE 像 sql 开发人员那样执行它们。

示例查询

select name, count(1) from employees where status = 'Active' and role= 'Manager' group by name order by 1;

我想编写一些函数,将此查询作为输入,然后将参数作为元组输出:

query = 'select name, count(1) from employees where status = :1 and role= :2 group by name order by 1;'
parms = ('Active','Manager')

这样我就可以在一个简单的函数中传递这两个来执行查询:

cursor_object.execute(query,parms)

不幸的是,我在正则表达式方面非常糟糕,而且我已经尝试了几个小时都无济于事。

给你:

import re

sql = """select name, count(1) from employees where status = 'Active' and role= 'Manager' group by name order by 1;"""

rx = re.compile(r"""\w+\s*=\s*'([^']+)'""")
params = rx.findall(sql)
print(params)
# ['Active', 'Manager']

主要部分是

\w+\s*=\s*'([^']+)'

分解,这表示:

\w+\s*    # 1+ word characters, 0+ whitespace characters
=\s*      # =, 0+ whitespace characters
'([^']+)' # '(...)' -> group 1

参见 a demo on regex101.com


要同时拥有查询和参数,您可以编写一个小函数:

import re

sql = """select name, count(1) from employees where status = 'Active' and role= 'Manager' group by name order by 1;"""

rx = re.compile(r"""(\w+\s*=\s*)'([^']+)'""")

def replacer(match):
    replacer.params.append(match.group(2))
    return '{}:{}'.format(match.group(1), len(replacer.params))

replacer.params = list()
query = rx.sub(replacer, sql)
params = replacer.params

print(query)
print(params)
# select name, count(1) from employees where status = :1 and role= :2 group by name order by 1;
# ['Active', 'Manager']

如评论中所述,您需要为要分析的每个查询重置参数列表。

一个快速而肮脏的解决方案是编写一个匹配引用字符串的正则表达式。你可以这样开始:

import re
import textwrap

query = textwrap.dedent("""\
select name, count(1)
from employees
where status = 'Active' and role= 'Manager'
group by name order by 1;""")

sub_var = re.compile(r"'[^']+'").sub

print(sub_var("VAR", query))
# select name, count(1)
# from employees
# where status = VAR and role= VAR
# group by name order by 1;

但是,在这里您需要替换为一个值,该值会在每次匹配时自行递增。

为此,您需要一个函数。请记住 re.sub 可以将可调用对象作为替代字符串。可调用对象必须将 MatchObject 作为参数并 return 替换。

在这里,我更喜欢使用可调用的 class:

class CountVar(object):
    def __init__(self):
        self.count = 0

    def __call__(self, mo):
        self.count += 1
        return ":{0}".format(self.count)


print(sub_var(CountVar(), query))
# select name, count(1)
# from employees
# where status = :1 and role= :2
# group by name order by 1;

在这里!

Jan 的回答的唯一问题是它没有生成您想要的带有“:1”、“:2”等的字符串。

像下面这样的东西应该可以工作:

import re
i=1
pattern = r"(?<==\s*)'\w+'"
params = []
while True:
    match = re.find( pattern, cmd )
    if match is None: break
    params.append(match.group())
    cmd = re.sub( pattern, ":" + str(i), 1 )
    i += 1

在该模式中,(?<=) 称为正后视,并确保参数(在本例中为 =\s*,后跟任意数量的空格的等号)出现在匹配,但不包含在匹配项中(因此它不会包含在 params 中或在替换中被替换)。