=Start=
缘由:
当前需要用 Python 或 Java 对 SQL语句 进行解析,以提取出其中的 表名、列名 等信息,方便后续进行精准的安全问题分析。
如果仅仅只是提取表名的话还比较简单(直接用字符串切分、匹配等处理就可以搞定),但如果还要提取列名(且SQL比较复杂)的话,就不那么容易搞定了,这时就需要对SQL进行语法分析以进行准确提取。问了一下别人,说 ANTLR4 可以做,于是在网上搜了一些资料,在此实践整理一下,方便以后参考。
正文:
参考解答:
这里先介绍一下如何通过字符串切分和正则的方式来提取SQL中的表名(列名提取比较复杂,不太好实现,后续找到好的方法了再补充进来)。
方法一(字符串切分):
def tables_in_query(sql):
replace_list = ['\n', '(', ')', '*', '=', '`']
for i in replace_list:
sql = sql.replace(i, ' ')
sql = sql.split()
res = set()
for i in range(1, len(sql)):
if sql[i-1].lower() in ['from', 'join'] and sql[i].lower() != 'select':
res.add(sql[i])
# print(res) # ['table1', 'table2', 'table3', 'table4', 'table5']
return list(res)
方法二(正则提取):
import re
def tables_in_query2(sql_str):
# remove the /* */ comments (先移除块注释)
q = re.sub(r"/\*[^*]*\*+(?:[^*/][^*]*\*+)*/", "", sql_str)
# remove whole line -- and # comments (再移除整行注释)
lines = [line for line in q.splitlines() if not re.match("^\s*(--|#)", line)]
# remove trailing -- and # comments (再移除行尾注释)
q = " ".join([re.split("--|#", line)[0] for line in lines])
# split on blanks, parens and semicolons (用「空白符、括号、分号」作为分隔符进行切分)
tokens = re.split(r"[\s)(;`]+", q)
# scan the tokens. if we see a FROM or JOIN, we set the get_next
# flag, and grab the next one (unless it's SELECT).
# 扫描经过上述分词的列表,当看到 FROM/JOIN 关键字时,获取下一个非「空/select」的作为表名
result = set()
get_next = False
for tok in tokens:
if get_next:
if tok.lower() not in ["", "select"]:
result.add(tok)
get_next = False
get_next = tok.lower() in ["from", "join"]
return result
其实正则的这个方法考虑的挺完整的(比如在移除注释那里),但是上面的字符串版本会更简单而且效率更高(在线上MySQL操作中一般不会有注释),所以可以按需选择。下面提供一个测试效果用的SQL:
sql = """SELECT a.time_updated_server/1000,
content,
nick,
name
FROM table1 a
JOIN table2 b ON a.sender_id = b.user_id
JOIN table3 c ON a.channel_id = c.channel_id
JOIN table4 d ON c.store_id = d.store_id
WHERE sender_id NOT IN
(SELECT user_id
FROM table5
WHERE store_id IN ('agent_store:1', 'ask:1'))
AND to_timestamp(a.time_updated_server/1000)::date >= '2014-05-01'
GROUP BY 1,2,3,4
HAVING sum(1) > 500
ORDER BY 1 ASC
"""
提取列名的方式其实照着表名的方法改一下应该也OK,但这里暂时就不放出来了,有需要的可以按需进行修改。
参考链接:
- Parsing Table Names From SQL
https://grisha.org/blog/2016/11/14/table-names-from-sql/
- How to extract tables names in a SQL script?https://stackoverflow.com/questions/49773059/how-to-extract-tables-names-in-a-sql-script
=END=
《 “如何用Python从SQL中提取出涉及到的表名、列名?” 》 有 4 条评论
如何用Golang提取SQL中的表名和列名?
https://stackoverflow.com/questions/54178905/get-table-name-from-sql-query
https://github.com/xwb1989/sqlparser
https://godoc.org/github.com/xwb1989/sqlparser
`
当前测试的效果感觉一般,对一些SQL的提取不成功,还需要进一步的理解和更多的测试。
`
TiDB SQL Parser
https://github.com/pingcap/parser
https://godoc.org/github.com/pingcap/parser
TiDB 源码阅读系列文章(五)TiDB SQL Parser 的实现
https://zhuanlan.zhihu.com/p/34770765
https://pingcap.com/blog-cn/tidb-source-code-reading-5/
[…] 如何用Python从SQL中提取出涉及到的表名、列名?https://ixyzero.com/blog/archives/4404.html […]
[…] 如何用Python从SQL中提取出涉及到的表名、列名?https://ixyzero.com/blog/archives/4404.html […]