【python】使用Antlr4实现识别sql中的表或视图名
前言
先上成果预览图吧
作为一个数据库sql开发者,肯定有很多人和我一样,想要有一个工具,能传入任意sql,解析出sql中的所有表。
我之前有一篇文章【AIO】将任意查询sql转换成带远程数据库DBLINK的sql 中就提到了,使用纯文本硬解析会存在很多不确定因素,比如oracle新版本就添加了新的sql语法,有些场景太难处理,而解析器则只需要配置好规则,并且标准化规则的语法,那么扩展性就很强了。
antlr4 https://github.com/antlr/antlr4
Antlr这个老早就有了,如今已经比较成熟,像代码高亮插件prism就是用的这个。
但是,我找了好久,竟然在网上没找到一份有关“识别sql中的表名”的现成完整代码或者程序!解析其他语言的倒是有,但最后解析出来一个树就啥也不提了。
另外也找了很多问答,都是说Antlr4再加grammars-v4就可以解析sql了,但是就是不给代码。
无奈之下,我只能抱着github啃作者的文档,这作者只给了个使用Antlr4解析器最简单的例子,没给个实际开发语言解析的例子,我就只能靠着自己的理解加上看源码来看到底是怎么回事了。
首先,翻了很多文章,了解了Antlr4本身是个java程序,定义为解析器生成程序,而不是解析器本身。
可以编写符合规范的解析文档,文件名后缀为"*.g4",然后使用antlr4对这个g4文件生成其对应的解析器程序,在生成之前,可以指定好要生成哪种开发语言的能用的解析器,比如你想在python用,就传个python的参数进去,它就会生成对应的py文件;想生成java的,它就给你生成java文件。Antlr4支持的目标开发语言(不是指要解析的代码)目前多达9种
Cpp
CSharp
Dart
Go
Java
JavaScript
Python2
Python3
Swift
而理论上,Antlr4能解析的语法是无穷的,因为它本身并不带解析规则,解析规则完全依靠g4文件。
所以为了配合Antlr4,这个作者开了个项目,让大家都来写各种语言的g4文件
grammars-v4
https://github.com/antlr/grammars-v4
好家伙,打开这个项目一看,目前(2021-10-08)有多达240种语言的g4文件!
管他三七二十一,先把这两个项目的代码都下载下来。
然后对着Antlr4的文档,开干
https://github.com/antlr/antlr4/blob/master/doc/getting-started.md
操作步骤(windows)
第1步,安装java jdk
1.7版本以上,已安装请略过,前面说过了,Antlr4是java编写的解析器程序的生成程序,要运行java就得安装java,官方地址
https://www.oracle.com/java/technologies/downloads
第2步,下载Antlr4
地址 https://www.antlr.org/download/
找最新版的,文件名是这个格式的 antlr-4.9.2-complete.jar
建一个项目文件夹,我这里命名为 d:\sql_table,
把下载好的antlr-4.9.2-complete.jar文件,放入sql_table文件夹
第3步,设置环境变量
在系统环境变量中,添加 CLASSPATH 指向 d:\sql_table\antlr-4.9.2-complete.jar
第4步,下载并安装Antlr4的runtime
下载Antlr4的整个库
https://github.com/antlr/antlr4
打开压缩包,解压 \runtime\python3 文件夹至任意目录,
然后打开cmd,进入这个解压后的文件夹,
在cmd中运行
python setup.py install
第5步,获得plsql的语法文件
下载grammars-v4的整个库
https://github.com/antlr/grammars-v4
下载后,解压压缩包中以下几个文件,放到我们的项目目录d:\sql_table中去
- sql\plsql\PlSqlLexer.g4
- sql\plsql\PlSqlParser.g4
- sql\plsql\Python3\PlSqlLexerBase.py
- sql\plsql\Python3\PlSqlParserBase.py
第6步,生成解析器程序
打开cmd,定位到我们的项目目录 d:\sql_table
在cmd窗口中依次运行以下三条命令
doskey antlr4vpy3=java org.antlr.v4.Tool -Dlanguage=Python3 -no-listener -visitor $*antlr4vpy3 PlSqlLexer.g4antlr4vpy3 PlSqlParser.g4
运行后,目录内会生成以下几个文件
PlSqlLexer.py
PlSqlParserVisitor.py
PlSqlParser.py
PlSqlLexer.tokens
PlSqlLexer.interp
PlSqlParser.interp
PlSqlParser.tokens
至此,原材料才算准备齐全,接下来开始使用这个东西
第7步,测试
在刚刚解压的Antlr4\runtime\python3\bin目录下,有个pygrun文件,是用python语言编写的,把这个文件复制到我们的项目目录 d:\sql_table ,
然后打开cmd窗口,定位这里,在cmd’中执行,注意区分大小写
python pygrun -t PlSql sql_script
然后再输入一个sql,注意必须都大写, 比如
SELECT A FROM B
再 ctrl+z,回车
稍等一会儿,程序就会返回
有一说一,这个性能真的有点差。
这里提几点,
- pygrun 后面的参数中 -t表示生成树显示出来,不加就只解析语法是否正确。
- 必填的两个参数为 语法名称 和 开始规则
- 语法名称即为PlSqlLexer.py这个文件中, Lexer.py之前的几个字符,如果不匹配就会说找不到
- 传入SQL必须大写,作者说是希望规范化,但是实际上大小写混用真的很难识别
这个开始规则传什么,我研究了不少时间,它来源于PlSqlParser.g4文件中,一开始我想的是我要找from后面的表,就传了个from_clause,结果没识别出来,然后我又换成query_block,才识别出来,中间还被sql的大小写坑了,但是我换了个例子,用WITH开头的sql,又识别不出来了。仔细看这个PlSqlParser.g4文件的内容,是分一段一段的,其中有一段这个
subquery_basic_elements: query_block| '(' subquery ')';subquery_operation_part: (UNION ALL? | INTERSECT | MINUS) subquery_basic_elements;query_block: SELECT (DISTINCT | UNIQUE | ALL)? selected_listinto_clause? from_clause where_clause? hierarchical_query_clause? group_by_clause? model_clause? order_by_clause? fetch_clause?;
query_block作为一个开始规则,同时出现在了subquery_basic_elements下面,是不是代表这个是包含关系?
所以我一路找,找到了最顶层的,就是
sql_script: ((unit_statement | sql_plus_command) SEMICOLON?)* EOF;
把sql_script带进去,没错了,只是速度会比之前用query_block更慢,但这样兼容性更高
另外,这个pygrun有更新版本,支持界面显示树,加上 -g 参数即可,下载在这里
https://github.com/jszheng/py3antlr4book/tree/master/bin
效果如下
有了这个例子,我们就要看怎么把我们要的东西挑出来了
第8步,编写获取表名的程序
根据这个界面分析,拿着原始sql,一个个位置数,看哪些关键字用得上。
最后经过多种sql的验证,找出了Tableview_name这一层对应的start和stop中,包含有这个表名的开始位置和结束位置。
因为表名 可能由 “用户名.表名@DBLINK 别名” 这一长串组成,这个解析器会把这些词分成很多截,我需要的是个完整的,但不包含别名的部分,只有Tableview_name中的几个数字才能凑出准确的位置
即start中 第一个 逗号和第一个冒号之间的数字为开始位置,
stop中 ,第一个冒号和第一个等于号之间的数字加1为结束位置
分析到这里,
程序来了
import os
import re
from antlr4 import *
#file_name=r'test.sql'
tb_list=[]def get_table_position( node):if isinstance(node, ParserRuleContext):class_name = str(type(node))if str(class_name)=="<class 'PlSqlParser.PlSqlParser.Tableview_nameContext'>":tb_list.append([re.split(r'[,:]',str(node.start))[1],re.split(r'[:=]',str(node.stop))[1]])if hasattr(node, 'children'):for child in getattr(node, 'children'):get_table_position(child)return tb_listdef get_table(l_sql):lexerName = grammar + 'Lexer'parserName = grammar + 'Parser'module_lexer = __import__(lexerName, globals(), locals(), lexerName)class_lexer = getattr(module_lexer, lexerName)module_parser = __import__(parserName, globals(), locals(), parserName)class_parser = getattr(module_parser, parserName)#input_stream = FileStream(file_name)input_stream = InputStream(l_sql.upper())lexer = class_lexer(input_stream)token_stream = CommonTokenStream(lexer)token_stream.fill()parser = class_parser(token_stream)parser.buildParseTrees = Truefunc_start_rule = getattr(parser, start_rule)parser_ret = func_start_rule()tb_p=get_table_position(parser_ret)return tb_pgrammar='PlSql'
start_rule='sql_script'
l_sql='''select COLA C1 FROM U.TAB1
X,TAB2 WHERE X.COL2=4'''
tb_p=get_table(l_sql)
print('该SQL中含有的表如下')
for i in tb_p:print(str(l_sql)[int(i[0]):int(i[1])+1])
直接运行,就得到了本文最开始的那个效果。
有两行注释掉了,可以切换传入文件名
完整程序包
已完成新建文件夹,最近天气不好,有点累,休息一下,以后再弄
我打算把整个过程需要用到的文件,包括安装命令,封一个包出来,然后程序也封装成一个函数,输入文件或者字符串返回结果,另外加上那个自动转换成dblink的功能,再让这个程序兼容在oracle中使用,就完美了。
另外java版本的制作方式也极其类似,只多了一步,在生成解析器程序后,要用javac命令编译生成的解析器程序,我本地已测试成功,大佬们应该比我会玩,就不多说了
2021-10-15 更新
项目已上传github
https://github.com/Dark-Athena/list_table_sql-py
2021-10-16 更新
已支持在oracle数据库中调用
https://github.com/Dark-Athena/pl4py
- 本文作者: DarkAthena
- 本文链接: https://www.darkathena.top/archives/antlr4-sql-tablename
- 版权声明: 本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处!
【python】使用Antlr4实现识别sql中的表或视图名相关推荐
- 02-SQL语句给表起别名SQL中的表连接
什么情况下需要给表起别名? 1.表名比较长 2.当需要在多个表中进行查询并把查询内容同时输出的时候 3.当需要进行表连接的时候(其实和2一个意思,一般情况下多个表进行连接主要目的就是为了从多个表中查询 ...
- 30-数据字典中的表或视围1
数据库组件 数据字典中的表或视围 说 明 数据库 V$DATAFILE 记录系统的运行情况 表空间 DBA_TABLESPACES 记录系统表空间的基本信息 DBA_FREE_SPACE 记录系统表空 ...
- SQL中给表起别名的两个必须知道的知识点
给列其别名: SELECT RTRIM(vend_name) + '(' + RTRIM(vend_contry) + ')' AS vend_title FROM Vendors ORDER BY ...
- SQL中,表之间的左联和右联是什么意思
首先来看张图片: 定义: 左联:首先取出A表中所有数据,然后再加上A.B表通过关联字段key查询到的数据 右联:首先取出B表中所有数据,然后再加上A.B表通过关联字段key查询到的数据 内联:查询A. ...
- python识图 web_轻松简单搭建一个python的OCR服务器 识别截图中内容并提取
Why? OCR(也称为光学字符识别)已经成为Python的常用工具.随着开源库Tesseract和Ocrad的出现,越来越多的程序员使用OCR编写自己程序.OCR的一个小例子,例如使用OCR直接从截 ...
- SQL中的表 与关系数据库
SQL Server 中几个重要的表 1 主系统表 Sysobjexts 出现在每个数据库中,每个数据库对象含有一行记录 2 系统表 Syscolumns 出现在master数据库和侮个用户自定义的数 ...
- 交叉表的概念及sql中交叉表的使用
交叉表是一种常用的分类汇总表格.使用交叉表查询,显示源于表中某个字段的汇总值,并将它们分组, 其中一组列在数据表的左侧,另一组列在数据表的上部. 行和列的交叉处可以对数据进行多种汇总计算,如:求和.平 ...
- mysql重复数据只取一条数据_My sql 中删除表中重复记录?只保留一条
匿名用户 1级 2018-08-20 回答 在几千条记录里,存在着些相同的记录,如何能用SQL语句,删除掉重复的呢 1.查找表中多余的重复记录,重复记录是根据单个字段(peopleId)来判断 sel ...
- SQL中多表联查概念
多表连接查询 当查询的字段来自多个表 分类:内连接:等值连接.非等值连接.自连接 外连接:左外连接.右外连接.全外连接 交叉连接 不同的 SQL JOIN 在展示具体语句结构之前,我们先列出您可以使用 ...
最新文章
- php 中间代码,PHP内核中用户函数、内部函数和中间代码的转换
- GIT的 .gitignore 配置
- 了解git的命令行使用
- android camera fragment,Android Camera 模块分析(三)
- creo管道设计教程_Creo7.0设计探索在管道设计的应用
- javascript 的线程问题
- Eclipse运行异常:Could not find the main class. Program will exit.
- Java 序列化漏洞多到修不完
- 认知维度与API的可用性评估
- php 上传微信媒体,关于微信公众号API多媒体上传
- ASP.NET 超市管理系统
- Unity 生成随机房间、洞穴(2D、3D地图)总结
- 81.【SpringMVC】
- vue elementui checkbox第一次点击选不中的问题
- windows注册表_改进Windows的20个最佳注册表技巧
- ubuntu top命令详解
- 计算机算法对程序设计的作用,【程序设计论文】高中算法与程序设计教学意义及有效性(共3796字)...
- mysql 的基本表的应用_Mysql的基本应用笔记1
- 脉冲函数、阶跃函数和斜坡函数
- CCF CSP 序列查询新解
热门文章
- 窥探Windows UUP 正向差分更新机制的高效性
- 畅想软件显示无法连接服务器,投屏神器怎么连接不上 连接电视方法详解
- 在谈声子谱计算的运动方程方法和动力学矩阵方法
- 怎么控制LED灯的开关呢?最简单的方法是给它加一个开关
- WebSocket(含表情包)-聊天室
- 两个世纪最近一次:一颗小行星近距离飞掠地球
- 暗光图像增强—opencv(C++)
- powerdesigner导出数据库word文件
- *现在感觉librealsense和realsense-ros的安装挺简单的(普通X86平台)(现在发现都有两种安装方式,下载源码编译或者二进制安装)
- 【Python-利用动态二维码传输文件(四)】使用pyautogui库录屏(连续截图),然后利用OpenCV逐张读取截图,识别当中的二维码信息,并把信息重组成原文件