sqlparse问题:WHERE子句将自定义关键词FACET归入自身TokenList
自定义SQL关键词FACET在sqlparse中的解析异常问题
我在用sqlparse给自定义方言添加关键词FACET时,发现一个奇怪的现象:
- 当
FACET紧跟在WHERE子句后面时,会被归入WHERE的TokenList里,在顶层TokenList中看不到它; - 但如果在WHERE和FACET之间加入GROUP BY这类语句,
FACET就会出现在解析后的TokenList顶层。
我是不是操作错了?找不到修正的方法。
复现代码
import re import sqlparse from sqlparse import keywords from sqlparse.lexer import Lexer from pprint import pprint # 获取默认的lexer单例对象进行配置 lex = Lexer.get_default_instance() # 清空默认配置,之后需要重新加载正则和关键词字典才能让lexer正常工作 lex.clear() my_regex = (r"FACET\b", sqlparse.tokens.Keyword) # 插入自定义正则到默认SQL_REGEX列表中 sql_regexes = keywords.SQL_REGEX[:38] + [my_regex] + keywords.SQL_REGEX[38:] lex.set_SQL_REGEX(sql_regexes) # pprint([(str(sql_regex[0]), sql_regex[1]) for sql_regex in sql_regexes]) pprint(keywords.KEYWORDS) # 添加默认关键词字典 lex.add_keywords(keywords.KEYWORDS_COMMON) lex.add_keywords(keywords.KEYWORDS_ORACLE) lex.add_keywords(keywords.KEYWORDS_PLPGSQL) lex.add_keywords(keywords.KEYWORDS_HQL) lex.add_keywords(keywords.KEYWORDS_MSACCESS) lex.add_keywords(keywords.KEYWORDS) # 添加自定义关键词字典 lex.add_keywords({'FACET': sqlparse.tokens.Keyword}) # 测试语句1:FACET紧跟WHERE querystring = "SELECT x, latest(y) FROM (SELECT x, y, z FROM table) WHERE x = 5 FACET x.name" # 使用单例lexer解析SQL parsed = sqlparse.parse(querystring) pprint(parsed[0].tokens) # 输出结果: # # [<DML 'SELECT' at 0x104A934C0>, # <Whitespace ' ' at 0x104A93820>, # <IdentifierList 'x, lat...' at 0x104D1A450>, # <Whitespace ' ' at 0x104CEEF80>, # <Keyword 'FROM' at 0x104CEEFE0>, # <Whitespace ' ' at 0x104CEF040>, # <Parenthesis '(SELEC...' at 0x104D19DD0>, # <Whitespace ' ' at 0x104CEF640>, # <Where 'WHERE ...' at 0x104D19ED0>] # 测试语句2:WHERE和FACET之间加入GROUP BY # querystring = "SELECT x, latest(y) FROM (SELECT x, y, z FROM table) WHERE x = 5 GROUP BY y FACET x.name" # # 输出结果: # # [<DML 'SELECT' at 0x1002674C0>, # <Whitespace ' ' at 0x100267820>, # <IdentifierList 'x, lat...' at 0x1004EE3D0>, # <Whitespace ' ' at 0x1004C2F80>, # <Keyword 'FROM' at 0x1004C2FE0>, # <Whitespace ' ' at 0x1004C3040>, # <Parenthesis '(SELEC...' at 0x1004EDD50>, # <Whitespace ' ' at 0x1004C3640>, # <Where 'WHERE ...' at 0x1004EDE50>, # <Keyword 'GROUP ...' at 0x1004C39A0>, # <Whitespace ' ' at 0x1004C3A00>, # <Identifier 'y' at 0x1004EE2D0>, # <Whitespace ' ' at 0x1004C3AC0>, # <Keyword 'FACET' at 0x1004C3B20>, # <Whitespace ' ' at 0x1004C3B80>, # <Identifier 'x.name' at 0x1004EDED0>]
问题原因
这不是你的操作错误,而是sqlparse的解析逻辑导致的:
- sqlparse的WHERE子句解析器会持续读取后续Token,直到遇到它识别的顶层查询子句关键词(比如GROUP BY、ORDER BY等)才会终止解析;
- 你只把
FACET注册成了普通的Keyword,没有被sqlparse识别为顶层子句关键词,所以当它紧跟WHERE时,会被当成WHERE子句的一部分; - 当中间有GROUP BY时,GROUP BY是已被识别的顶层关键词,会终止WHERE的解析,后续的FACET就会被当作顶层Token处理。
解决方案
方案一:修改sqlparse源码(彻底解决)
要让sqlparse原生支持FACET作为顶层子句,需要修改其内部的语法规则:
- 在
sqlparse/sql.py中,找到Select类的解析逻辑,添加FACET到顶层子句的识别列表; - 或者创建自定义的方言类,继承自默认方言,重写子句识别规则。
不过这种方式需要修改sqlparse源码,维护成本较高。
方案二:解析后手动修正Token(快速 workaround)
如果不想修改源码,可以在解析完成后,手动遍历TokenList,把WHERE子句中的FACET提取到顶层:
def extract_facet_from_where(parsed_stmt): new_tokens = [] token_index = 0 while token_index < len(parsed_stmt.tokens): token = parsed_stmt.tokens[token_index] if isinstance(token, sqlparse.sql.Where): where_inner = [] found_facet = False # 遍历WHERE内部的Token,寻找FACET for inner_idx, inner_token in enumerate(token.tokens): if (isinstance(inner_token, sqlparse.tokens.Keyword) and inner_token.value.upper() == 'FACET'): # 把FACET之前的内容保留在WHERE中 token.tokens = where_inner new_tokens.append(token) # 把FACET及其后续内容移到顶层 new_tokens.extend(parsed_stmt.tokens[token_index + 1:]) found_facet = True break where_inner.append(inner_token) if found_facet: break else: new_tokens.append(token) token_index += 1 else: new_tokens.append(token) token_index += 1 parsed_stmt.tokens = new_tokens # 使用示例 querystring = "SELECT x, latest(y) FROM (SELECT x, y, z FROM table) WHERE x = 5 FACET x.name" parsed = sqlparse.parse(querystring)[0] extract_facet_from_where(parsed) pprint(parsed.tokens)
运行这段代码后,FACET就会出现在顶层TokenList中,和GROUP BY后的情况一致。
内容的提问来源于stack exchange,提问作者schemanic
相关产品推荐
相关产品推荐

