软件开发定制定制Python-sqlparse解析SQL工具库一文详解(一)

目录


前言

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


一、sqlparse简介

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

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使用,这牵扯到另一个方法:

此方法也就是将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 ,有问题大家随时留言讨论 ,我们下期见


网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发