如何修改ANTLR SQL语法以支持对象访问时忽略保留关键字?
修改ANTLR语法以支持带空格的限定标识符(含保留关键字)
背景说明
在SQL里,保留关键字一般不能直接当列名用,比如写SELECT select FROM tbl会触发语法错误。要想用关键字当列名,通常得给它加引号,比如SELECT select FROM tbl(具体引号语法依方言而定)。
但有个例外:当标识符是限定对象(比如带表名/函数前缀)时,不用加引号就能直接用关键字,比如:
SELECT tbl.select FROM tbl SELECT function().select
现在你的ANTLR语法能正常解析普通的SELECT语句(比如SELECT col1, xxx, xxx;),但没法处理SELECT tbl.select, tbl . select;这种带空格的限定标识符,下面是具体的修改方案。
方言适配说明
不同SQL方言对.前后空格的要求不一样:
- Postgres、BigQuery:允许
.前后有空格 - MySQL:不允许
.前后有空格 - SQLite、SQLServer:不支持“限定后直接用关键字”的特性,末尾标识符仍需加引号
修改步骤
1. 更新词法分析器(DBLexer.g4)
首先要把.单独定义为TOKEN,这样才能支持前后带空格的场景:
lexer grammar DBLexer; options { caseInsensitive=true; } COMMA : ','; // 参数列表、选择列表分隔符 SEMI : ';'; // 语句终止符 SELECT : 'SELECT'; // SELECT关键字 DOT : '.'; // 限定标识符的分隔点 IDENTIFIER : [A-Z_] [A-Z_0-9]* ; WHITESPACE : [ \t\r\n] -> skip ;
2. 更新解析器(DBParser.g4)
核心是修改expr规则,支持嵌套的限定结构,并且允许限定部分的末尾是保留关键字。这里有两种简洁的实现方式:
方式一:直接允许关键字作为限定后的标识符
把关键字加入到可解析的表达式单元中,这样select这类关键字在限定结构里会被正常识别:
parser grammar DBParser; options { tokenVocab = DBLexer;} root : selectStatement? (SEMI selectStatement)* SEMI EOF? ; selectStatement : SELECT selectItem (COMMA selectItem)* COMMA? ; selectItem : expr (AS? IDENTIFIER)? ; // 支持嵌套限定,比如 tbl.select、schema.tbl.select、tbl . select 等 expr : primaryExpr (DOT primaryExpr)* ; // 允许标识符或关键字作为表达式单元 primaryExpr : IDENTIFIER | SELECT // 这里可以继续添加其他需要支持的关键字,比如 FROM、WHERE 等 ;
方式二:用模式让关键字仅在特定语境生效
如果不想逐个列关键字,可以修改词法分析器,让SELECT这类字符串只有在语句开头(比如作为SELECT语句的起始关键字)才被识别为关键字,其他场景下都作为普通标识符:
lexer grammar DBLexer; options { caseInsensitive=true; } // 默认模式 COMMA : ','; SEMI : ';'; DOT : '.'; // 只有当SELECT出现在语句起始位置时,才识别为关键字,否则作为IDENTIFIER SELECT : 'SELECT' -> pushMode(DEFAULT_MODE); IDENTIFIER : [A-Z_] [A-Z_0-9]* ; WHITESPACE : [ \t\r\n] -> skip ;
对应的解析器expr规则可以简化为:
expr : IDENTIFIER (DOT IDENTIFIER)* ;
3. 方言差异化处理
- 对于允许
.前后空格的方言(Postgres、BigQuery):因为词法分析器已经自动跳过空格,修改后的语法天然支持这种场景。 - 对于不允许空格的方言(MySQL):可以在语义分析阶段添加检查,当
DOT前后存在空格时抛出错误。 - 对于不支持该特性的方言(SQLite、SQLServer):在语义分析阶段,若限定结构的最后一个标识符是关键字,要求用户添加引号。
测试验证
修改后的语法可以正常解析以下语句:
SELECT tbl.select, tbl . select; SELECT schema.tbl.select, func().select;
而SELECT col1, select;这类非限定的关键字列名依然会报错,符合SQL的原生规则。
内容的提问来源于stack exchange,提问作者samuelbrody1249
相关产品推荐
相关产品推荐

