Python-sqlparse解析SQL工具库一文详解(二)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: Python-sqlparse解析SQL工具库一文详解(二)

前言


文接上篇内容:


Python-sqlparse解析SQL工具库一文详解


写此sqlparse库的目的还是寻找在python编程内可行的SQL血缘解析,JAVA去解析Hive的源码实践的话我还是打算放到后期来做,先把Python能够实现的先实现完。上篇系列讲述的基于antrl解析说是用python其实还是太牵强了,无非就是使用PyJnius调用JAVA的类方法来实现,没有多大的意义来牵扯到Python编程。主要是HiveSQL的底层就是JAVA代码,怎么改写还是绕不开JAVA的。不过上篇系列我有提到过sqlparse,其实这个库用来解析血缘的话也不是不可以,但是能够实现的功能是有限的,目前我实验还行,一些复杂超过千行的数据分析SQL没有测试过。做一些简单的血缘解析的话还是没有应该太大问题,后续我会在此基础之上开发尝试。


一、基类-Statement


此类作为基类存在有绝对的意义。parse函数解析转换的类型也是该类,众多处理方法也是根据此类来编写,那么此类必定承载着SQL分析的基础。

class Statement(TokenList):
    """Represents a SQL statement."""
    def get_type(self):
        """Returns the type of a statement.
        The returned value is a string holding an upper-cased reprint of
        the first DML or DDL keyword. If the first token in this group
        isn't a DML or DDL keyword "UNKNOWN" is returned.
        Whitespaces and comments at the beginning of the statement
        are ignored.
        """
        first_token = self.token_first(skip_cm=True)
        if first_token is None:
            # An "empty" statement that either has not tokens at all
            # or only whitespace tokens.
            return 'UNKNOWN'
        elif first_token.ttype in (T.Keyword.DML, T.Keyword.DDL):
            return first_token.normalized
        elif first_token.ttype == T.Keyword.CTE:
            # The WITH keyword should be followed by either an Identifier or
            # an IdentifierList containing the CTE definitions;  the actual
            # DML keyword (e.g. SELECT, INSERT) will follow next.
            fidx = self.token_index(first_token)
            tidx, token = self.token_next(fidx, skip_ws=True)
            if isinstance(token, (Identifier, IdentifierList)):
                _, dml_keyword = self.token_next(tidx, skip_ws=True)
                if dml_keyword is not None \
                        and dml_keyword.ttype == T.Keyword.DML:
                    return dml_keyword.normalized
        # Hmm, probably invalid syntax, so return unknown.
        return 'UNKNOWN'

此类只有一个方法就是返回一个获取此条SQL的DML类型,也就是SQL的功能类型:

query = 'CREATE TABLE AS Select a, col_2 as b from Table_A;select * from foo'
for each in sqlparse.parse(query):
    print(each.get_type())

3d49c1f47c8043d99c6f6f36b0205776.png


里面的判断逻辑也是根据 Keyword.DML和Keyword.DDL来判断的。根据第一次获取到的token来判断。有了get_type那么我们要实现的SQL解析的第一步已经有了,首先就可以确定这个SQL的功能与用户的读写查改权限匹配了。先不急我们还需要知道如何解析成一颗树。


二、基类-TokenList


这个类就相当的大了,也正是我们了解解析成AST抽象解析树的关键所在了。源码就不贴上去可太多了,主要找一些能够改写使用到的方法即可。


该类继承Token,而Statement就是继承的此类,也就是Statement最终继承的此两者全部方法。

query = 'CREATE TABLE AS Select a, col_2 as b from Table_A;select * from foo'
stmt=sqlparse.parse(query)
stmt_1=stmt[0].tokens
#for each_token in stmt_1:
    #print(each_token)
sqlparse.sql.TokenList(stmt_1)._get_repr_name()
stmt[0]._get_repr_name()

1. _get_repr_name()方法


将输出自身数据结构:


def _get_repr_name(self):
    return type(self).__name__


2._pprint_tree()方法


这里有关树的解析在这个打印_pprint_tree函数上面:

def _pprint_tree(self, max_depth=None, depth=0, f=None, _pre=''):
        """Pretty-print the object tree."""
        token_count = len(self.tokens)
        for idx, token in enumerate(self.tokens):
            cls = token._get_repr_name()
            value = token._get_repr_value()
            last = idx == (token_count - 1)
            pre = '`- ' if last else '|- '
            q = '"' if value.startswith("'") and value.endswith("'") else "'"
            print("{_pre}{pre}{idx} {cls} {q}{value}{q}"
                  .format(**locals()), file=f)
            if token.is_group and (max_depth is None or depth < max_depth):
                parent_pre = '   ' if last else '|  '
                token._pprint_tree(max_depth, depth + 1, f, _pre + parent_pre)

第一次看到这个函数我就认为使用sqlparse解析SQL血缘是可以做成功的:


2246b3d43f6d414aa58b0116b80c9ce2.png

从打印的函数循迹看是否能够得到血缘关系。这点是可以做到的,先遍历最底层的结构,再依次输出,此时这里我已经有了一个明确的实现思路,但是这里先不开展,我们还是先将此类看明白再下定论。先通读这个方法:


和我之前写的树递归函数差不多,这里要注意到一点就是空格会影响树的输出,所以传入sql之前还是得做去除空格的操作,最好还是化成一句没有空格和缩进的语句。当然也可以通过改写去除Whitespace这一标识符。


通过解析树的输出我们发现到IdentifierList 此类就开始往下层调了,这取决于这段代码:


 if token.is_group and (max_depth is None or depth < max_depth):
                parent_pre = '   ' if last else '|  '
                token._pprint_tree(max_depth, depth + 1, f, _pre + parent_pre)


也就是说is_group为True就会开始下一层的遍历,而token的初始is_group则为False,也就是解析为TokenList的时候才为True。此Tokenlist就很明显是与IdentifierList 这个类有关了。下个小节我们再细细研究IdentifierList 基类,先让我们再看看TokenList的其他功能函数。


3.get_token_at_offset(self, offset)


该方法将返回一个位置偏移上的token。

offsert_token=stmt[0].get_token_at_offset(1)
offsert_token

185a7967885b4ad885c08efaa2527093.png


4.flatten(self)


和token的方法几乎差不多,但是生产的没有分类的tokens。对所有子tokens递归时调用此方法


5._token_matching(self, funcs, start=0, end=None, reverse=False)


该函数就是将token与funcs功能函数进行匹配,内部调用。


6.token_first(self, skip_ws=True, skip_cm=False)


这个是一个比较重要的方法,返回第一个子token。也就是可以返回这条sql的功能类型。


stmt[0].token_first()

03cd1202a1b2489299919b089f204ed2.png

其他方法很多都是主类方法的工具函数,主要是现在抓到了重点先搞清楚。Identifier这个类


三、Identifier类


这个类继承了两个父类NameAliasMixin和TokenList,前者为主要为实现get_real_name和get_alias的方法,后者也是我们摸清楚了的TokenList方法。


Identifier类主要代表标识符。标识符可能有别名或类型转换。其中有四个主要方法:


1.get_array_indices()


返回索引token列表的迭代器:

print(stmt_1[13].get_array_indices())


2.get_ordering()


将order或None作为大写字符串返回。

print(Identifier.get_ordering(stmt[0]))

214cb9e74b374aae8c63bf8ad99282b1.png

我写的sql没有order故为None。


3.get_typecast()


以字符串形式返回此对象的typecast或None。


4.is_wildcard()


如果此标识符包含通配符,则返回True。


以上就是Identifier的四大类,也就是说这个类以后如果我们想要识别什么新的功能函数可以继承此类。


先大体研究这么多,写出点相关的代码再继续深入研究,下篇文章我会将一些初步构思和代码加入其中完成简单的SQL语法解析。

目录
相关文章
|
7天前
|
SQL 存储 数据挖掘
使用Python和PDFPlumber进行简历筛选:以SQL技能为例
本文介绍了一种使用Python和`pdfplumber`库自动筛选简历的方法,特别是针对包含“SQL”技能的简历。通过环境准备、代码解析等步骤,实现从指定文件夹中筛选出含有“SQL”关键词的简历,并将其移动到新的文件夹中,提高招聘效率。
25 8
使用Python和PDFPlumber进行简历筛选:以SQL技能为例
|
9天前
|
数据可视化 编译器 Python
Manim:数学可视化的强大工具 | python小知识
Manim(Manim Community Edition)是由3Blue1Brown的Grant Sanderson开发的数学动画引擎,专为数学和科学可视化设计。它结合了Python的灵活性与LaTeX的精确性,支持多领域的内容展示,能生成清晰、精确的数学动画,广泛应用于教育视频制作。安装简单,入门容易,适合教育工作者和编程爱好者使用。
61 7
|
17天前
|
数据采集 JavaScript API
网页解析库:BeautifulSoup与Cheerio的选择
网页解析库:BeautifulSoup与Cheerio的选择
|
24天前
|
JavaScript 前端开发 开发者
探索 DrissionPage: 强大的Python网页自动化工具
DrissionPage 是一个基于 Python 的网页自动化工具,结合了浏览器自动化的便利性和 requests 库的高效率。它提供三种页面对象:ChromiumPage、WebPage 和 SessionPage,分别适用于不同的使用场景,帮助开发者高效完成网页自动化任务。
104 4
|
26天前
|
SQL IDE 数据库连接
IntelliJ IDEA处理大文件SQL:性能优势解析
在数据库开发和管理工作中,执行大型SQL文件是一个常见的任务。传统的数据库管理工具如Navicat在处理大型SQL文件时可能会遇到性能瓶颈。而IntelliJ IDEA,作为一个强大的集成开发环境,提供了一些高级功能,使其在执行大文件SQL时表现出色。本文将探讨IntelliJ IDEA在处理大文件SQL时的性能优势,并与Navicat进行比较。
29 4
|
26天前
|
开发者 Python
探索Python中的列表推导式:简洁而强大的工具
【10月更文挑战第41天】 在编程的世界中,效率与简洁是永恒的追求。本文将深入探讨Python编程语言中一个独特且强大的特性——列表推导式(List Comprehension)。我们将通过实际代码示例,展示如何利用这一工具简化代码、提升性能,并解决常见编程问题。无论你是初学者还是资深开发者,掌握列表推导式都将使你的Python之旅更加顺畅。
|
20天前
|
SQL Java 数据库连接
canal-starter 监听解析 storeValue 不一样,同样的sql 一个在mybatis执行 一个在数据库操作,导致解析不出正确对象
canal-starter 监听解析 storeValue 不一样,同样的sql 一个在mybatis执行 一个在数据库操作,导致解析不出正确对象
|
21天前
|
存储 Go PHP
Go语言中的加解密利器:go-crypto库全解析
在软件开发中,数据安全和隐私保护至关重要。`go-crypto` 是一个专为 Golang 设计的加密解密工具库,支持 AES 和 RSA 等加密算法,帮助开发者轻松实现数据的加密和解密,保障数据传输和存储的安全性。本文将详细介绍 `go-crypto` 的安装、特性及应用实例。
53 0
|
1月前
|
SQL 监控 安全
员工上网行为监控软件:SQL 在数据查询监控中的应用解析
在数字化办公环境中,员工上网行为监控软件对企业网络安全和管理至关重要。通过 SQL 查询和分析数据库中的数据,企业可以精准了解员工的上网行为,包括基础查询、复杂条件查询、数据统计与分析等,从而提高网络管理和安全防护的效率。
28 0
|
27天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
65 2