You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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作为顶层子句,需要修改其内部的语法规则:

  1. 在sqlparse/sql.py中,找到Select类的解析逻辑,添加FACET到顶层子句的识别列表;
  2. 或者创建自定义的方言类,继承自默认方言,重写子句识别规则。

不过这种方式需要修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 12:03:09