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


    目录

    前言

    一、sqlparse简介

    二、功能代码解析

    1.初始方法

    1.parse

     2.parsestream

    3.format

    4.split

    2.基类-Token

    1.flatten()

     2.match(ttype, values, regex=False)

    点关注,防走丢,如有纰漏之处,请留言指教,非常感谢


    前言

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


    一、sqlparse简介

    首先先给官网地址:python-sqlparse。有足够好编码能力可以直接上github上面看源码,解读更细:github.sqlparse

    sqlparse是用于Python的非验证SQL解析器。它支持解析、拆分和格式化SQL语句。既然有解析功能那么我们就能做初步的血缘解析功能。这个库的函数解析没有像Pandas和numpy写的那么详细,毕竟是人家个人的开源库,功能写的已经很不错了,能够省去我们很多递归剥离AST树的时间。官网上关于该库使用操作很简单,很多比较好的功能函数也没有使用到,我希望可以尽力将此库开发为通用SQL血缘解析的基础工具库。如果该功能开发完我会将此项目开源。

    我通过细读源码来了解此库的大体功能。

    二、功能代码解析

    1.初始方法

    看初始化代码方法有四种:parse,parsestream,format,split这四种

    1.parse

    1. def parse(sql, encoding=None):
    2. """Parse sql and return a list of statements.
    3. :param sql: A string containing one or more SQL statements.
    4. :param encoding: The encoding of the statement (optional).
    5. :returns: A tuple of :class:`~sqlparse.sql.Statement` instances.
    6. """
    7. return tuple(parsestream(sql, encoding))

    传入一个SQL语句,返回一个 sqlparse.sql.Statement的元组,我们可以递归方式获得输出。

    1. query = 'Select a, col_2 as b from Table_A;'
    2. for each in sqlparse.parse(query):
    3. print(each)

    其元组根据;符号来进行切分存储:

    1. query = 'Select a, col_2 as b from Table_A;select * from foo'
    2. for each in sqlparse.parse(query):
    3. print(each)

     

     2.parsestream

    可以看到第一个方法是调用了parsestream来完成流式解析的,那么这个方法也就是循环读取sql语句来完成转换statment的:

    1. def parsestream(stream, encoding=None):
    2. """Parses sql statements from file-like object.
    3. :param stream: A file-like object.
    4. :param encoding: The encoding of the stream contents (optional).
    5. :returns: A generator of :class:`~sqlparse.sql.Statement` instances.
    6. """
    7. stack = engine.FilterStack()
    8. stack.enable_grouping()
    9. return stack.run(stream, encoding)

    这里的引擎是可以替换的。

    sqlparse.parsestream(query)

    它将返回一个sqlparse.sql.Statement实例的发生器。来看看这个run方法:

    1. def run(self, sql, encoding=None):
    2. stream = lexer.tokenize(sql, encoding)
    3. # Process token stream
    4. for filter_ in self.preprocess:
    5. stream = filter_.process(stream)
    6. stream = StatementSplitter().process(stream)
    7. # Output: Stream processed Statements
    8. for stmt in stream:
    9. if self._grouping:
    10. stmt = grouping.group(stmt)
    11. for filter_ in self.stmtprocess:
    12. filter_.process(stmt)
    13. for filter_ in self.postprocess:
    14. stmt = filter_.process(stmt)
    15. yield stmt

     该方法就是生产一个statment,这个类应该就是这个库的基类了,多半围绕这个数据结构来处理。

    3.format

    该方法就是将sql语句标准化:

    1. query = 'Select a, col_2 as b from Table_A;select * from foo'
    2. print(sqlparse.format(query, reindent=True, keyword_case='upper'))

     

    format()函数接受关键字参数:

    •     keyword_case 关键词upper、lowersql的保留字大小写
    •     identifier_case 标识符的upper、lower大小写
    •     strip_comments=Ture删除注释
    •     reindent=Ture美化sq缩进语句发生改变

    4.split

    该方法用于分割sql语句:

    sqlparse.split(query)

    这里补充一下calss类sqlparse.sql.Statement是可以直接通过str转换为字符串的。

    结果返回一个分割后的list。至此初始方法就写完了,下面我将详解一下基类,这将决定是我们是否能灵活运用此库。

    2.基类-Token

    我们来看看Token的初始方法属性:

    1. def __init__(self, ttype, value):
    2. value = str(value)
    3. self.value = value
    4. self.ttype = ttype
    5. self.parent = None
    6. self.is_group = False
    7. self.is_keyword = ttype in T.Keyword
    8. self.is_whitespace = self.ttype in T.Whitespace
    9. self.normalized = value.upper() if self.is_keyword else value

     这个Token类也就是语法解析器的重点数据流了:

     此类需要生成Tokens使用,这牵扯到另一个方法tokens.py

    此方法也就是将statment类转换为Token流:

    1. parsed = sqlparse.parse(query)
    2. stmt = parsed[0]
    3. stmt.tokens

     

     其中我们需要解析的每个Token的标识码也就是第一个ttype属性,解析之后:

    1. for each_token in sql_tokens:
    2. print(each_token.ttype,each_token.value)

     

     我们拿一个Token来研究就能逐渐解析到其他token。我们建立一个列表将其主要属性ttype和value收集起来:

    1. type(list_ttype[0])
    2. type(list_value[0])

     第一个属性为sqlparse.tokens._TokenType第二个value直接就是str了。上tokens看_TokenType:

    1. # Special token types
    2. Text = Token.Text
    3. Whitespace = Text.Whitespace
    4. Newline = Whitespace.Newline
    5. Error = Token.Error
    6. # Text that doesn't belong to this lexer (e.g. HTML in PHP)
    7. Other = Token.Other
    8. # Common token types for source code
    9. Keyword = Token.Keyword
    10. Name = Token.Name
    11. Literal = Token.Literal
    12. String = Literal.String
    13. Number = Literal.Number
    14. Punctuation = Token.Punctuation
    15. Operator = Token.Operator
    16. Comparison = Operator.Comparison
    17. Wildcard = Token.Wildcard
    18. Comment = Token.Comment
    19. Assignment = Token.Assignment
    20. # Generic types for non-source code
    21. Generic = Token.Generic
    22. Command = Generic.Command
    23. # String and some others are not direct children of Token.
    24. # alias them:
    25. Token.Token = Token
    26. Token.String = String
    27. Token.Number = Number
    28. # SQL specific tokens
    29. DML = Keyword.DML
    30. DDL = Keyword.DDL
    31. CTE = Keyword.CTE

     可以发现这就是Token的识别解析类型码,通过该码就可以访问获得解析出的关键字了。

    关于此基类又有五种主要的方法:

    1.flatten()

    用于解析子组

    1. for each_token in sql_tokens:
    2. #list_ttype.append(each_token.ttype),list_value.append(each_token.value)
    3. print(each_token.flatten())

     2.match(ttype, values, regex=False)

    检查标记是否与给定参数匹配。

    1. list_ttype=[]
    2. list_value=[]
    3. for each_token in sql_tokens:
    4. #list_ttype.append(each_token.ttype),list_value.append(each_token.value)
    5. print(each_token.match(each_token.ttype,each_token.ttype))

     

     or运算为None匹配为True输出。

    ttype是一种token类型。如果此标记与给定的标记类型不匹配。values是此标记的可能值列表。这些values一起进行OR运算,因此如果只有一个值与True匹配,则返回。除关键字标记外,比较区分大小写。为了方便起见,可以传入单个字符串。如果regex为True(默认值为False),则给定值将被视为正则表达式。

    另外还有三种方法has_ancestor(other),is_child_of(other),within(group_cls)这都有调用功能函数相关,可以先不用了解。

    由此Token传入流单体已经差不多分析完,但是AST树该如何生成这是个问题,还有关于树的递归问题和层级问题,我们继续根据基类来慢慢摸清。这篇文章已经足够多内容了,先打住。下一篇再细讲。

    点关注,防走丢,如有纰漏之处,请留言指教,非常感谢

    以上就是本期全部内容。我是fanstuck ,有问题大家随时留言讨论 ,我们下期见


  • 相关阅读:
    windows下 解决PHP-CGI 进程崩溃502
    【回归预测】基于DBO-BP(蜣螂优化算法优化BP神经网络)的回归预测 多输入单输出【Matlab代码#68】
    YOLO系列 --- YOLOV7算法(四):YOLO V7算法网络结构解析
    echarts的双向柱形图的legend图例不显示的原因是,sereies里的数据,没有写name属性,必须要写,图例才会出现
    SpringBoot 博客网站
    电压放大器如何选择型号规格(电压放大器选型标准)
    NET7下用WebSocket做简易聊天室
    《爆肝整理》保姆级系列教程-玩转Charles抓包神器教程(15)-Charles如何配置反向代理
    谈一谈 Android Application 的理解
    获取dubbo源码编译并导入idea以及启动入门项目dubbo-demo
  • 原文地址:https://blog.csdn.net/master_hunter/article/details/127270029