jsqlparser(五):修改语法定义(JSqlParserCC.jjt)实现UPSERT支持Phoenix语法ON DUPLICATE KEY IGNORE

本文介绍如何修改jsqlparser以支持Apache Phoenix的UPSERT语句中ON DUPLICATE KEY IGNORE语法,并提供了解决方案及代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在用jsqlparser4.5解析SQL时遇到了一个问题,

如下是apache phoenixUPSERT语句

UPSERT INTO TEST (ID, COUNTER) VALUES (123, 0) ON DUPLICATE KEY IGNORE

ON DUPLICATE KEY IGNORE即为当主键重复时忽略,这与MySQL的IGNORE语法不同:

INSERT IGNORE INTO TEST (ID, COUNTER) VALUES (123, 0) 

jsqlparser目前的最新版本支持UPSERT语法,也支持ON DUPLICATE KEY UPDATE,但不支持ON DUPLICATE KEY IGNORE

所以jsqlparser解析SQL遇到ON DUPLICATE KEY IGNORE语法时解析器会报错 expect "UPDATE"

JSqlParserCC.jjt

为了解决这个问题我翻了jsqlparser的源码,找到了jsqlparser的语法定义文件(JSqlParserCC.jjt),是基于JavaCC实现的。

JSqlParserCC.jjt文件的位置在:jsqlparser/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt

打开它在1635行就能找到UPSERT语句分析ON DUPLICATE KEY UPDATE语法的位置,
为了看懂语法定义文件(JSqlParserCC.jjt),我又恶补了一下JavaCC的语法,基本上能看懂了。
原来的代码是

    [ <K_ON> <K_DUPLICATE> <K_KEY> <K_UPDATE>
        { useDuplicate = true; }
      tableColumn=Column() "=" exp=SimpleExpression()
        {
            duplicateUpdateColumns = new ArrayList<Column>();
            duplicateUpdateExpressionList = new ArrayList<Expression>();
            duplicateUpdateColumns.add(tableColumn);
            duplicateUpdateExpressionList.add(exp);
        }
      ("," tableColumn=Column() "=" exp=SimpleExpression()
        { duplicateUpdateColumns.add(tableColumn);
        duplicateUpdateExpressionList.add(exp); } )*]

修改为如下:

    [<K_ON> <K_DUPLICATE> <K_KEY> 
    	(
    		<K_IGNORE>
    			{ modifierIgnore = true; }
    		|
    		<K_UPDATE>
	        { useDuplicate = true; }
		      tableColumn=Column() "=" exp=SimpleExpression()
		        {
		            duplicateUpdateColumns = new ArrayList<Column>();
		            duplicateUpdateExpressionList = new ArrayList<Expression>();
		            duplicateUpdateColumns.add(tableColumn);
		            duplicateUpdateExpressionList.add(exp);
		        }
		      ("," tableColumn=Column() "=" exp=SimpleExpression()
		        { duplicateUpdateColumns.add(tableColumn);
		        duplicateUpdateExpressionList.add(exp); } )*
			)
    ]

即允许 ON DUPLICATE后为IGNOREUPDATE,为IGNORE时将modifierIgnore 设置为true

modifierIgnore为新增加的变量定义,后续会用到。

如下图为主要修改的前后对比
在这里插入图片描述

Upsert.java

不仅要修改语法定义文件,同时还要修改src/main/java/net/sf/jsqlparser/statement/upsert/Upsert.java,相应增加modifierIgnore字段,
完整代码参见我的码云仓库:
https://gitee.com/l0km/JSqlParser.git

【不是master分支,是gyd分支,gyd分支,gyd分支,重要的事情说三遍】

下载代码

git clone -b gyd https://gitee.com/l0km/JSqlParser.git

maven编译

cd JSqlParser
$ mvn clean install -DskipTests=true

单元测试

jsqlparser项目有比较完备的单元测试,上述修改完毕执行UPSERT语句的单元测试UpsertTest.java验证修改是否有效。
先执行原有的ON DUPLICATE KEY UPDATE语法测试,看看是否有影响

$ mvn -Dtest=UpsertTest#testUpsertDuplicate test

我在src/test/java/net/sf/jsqlparser/statement/upsert/UpsertTest.java增加了新的单元测试方法testUpsertIgnore 以验证ON DUPLICATE KEY IGNORE语法有效性。

    @Test
    public void testUpsertIgnore() throws JSQLParserException {
        String statement = "UPSERT INTO TEST (ID, COUNTER) VALUES (123, 0) ON DUPLICATE KEY IGNORE";
        Upsert upsert = (Upsert) parserManager.parse(new StringReader(statement));
        assertEquals("TEST", upsert.getTable().getName());
        assertEquals(2, upsert.getColumns().size());
        assertTrue(upsert.isUseValues());
        assertEquals("ID", upsert.getColumns().get(0).getColumnName());
        assertEquals("COUNTER", upsert.getColumns().get(1).getColumnName());
        assertEquals(2, ((ExpressionList) upsert.getItemsList()).getExpressions().size());
        assertEquals(123, ((LongValue) upsert.getItemsList(ExpressionList.class).getExpressions().get(0)).getValue());
        assertEquals(0, ((LongValue) upsert.getItemsList(ExpressionList.class).getExpressions().get(1)).getValue());
        assertTrue(upsert.isModifierIgnore());
        assertEquals(statement, "" + upsert);
    }

执行新增的ON DUPLICATE KEY IGNORE语法测试

$ mvn -Dtest=UpsertTest#testUpsertIgnore test

Pull Request

此问题修复已经向jsqlparser提交了Pull Request
参见 https://github.com/JSQLParser/JSqlParser/pull/1689

如果批准,就可以在下一个官方版本中更新了。

参考资料

《UPSERT》
《JavaCC》

jsqlparser系列文章

《jsqlparser(一):基于抽象语法树(AST)遍历SQL语句的语法元素》
《jsqlparser(二):实现基于SQL语法分析的SQL注入攻击检查》
《jsqlparser(三):基于语法分析实现SQL中的CAST函数替换》
《jsqlparser(四):实现MySQL 函数DATE_FORMAT到Phoenix函数TO_CHAR的替换》
《jsqlparser(五):修改语法定义(JSqlParserCC.jjt)实现UPSERT支持Phoenix语法ON DUPLICATE KEY IGNORE》

### JPA 和 MyBatis 启动时遇到 `JSQLParserException` 或 `ParseException` 的原因分析与解决方案 当使用 JPA 或 MyBatis 开发应用程序时,如果在启动阶段遇到了类似于 `JSQLParserException` 或者 `ParseException` 的异常提示 “invalid SQL query”,这通常是由于以下几个方面的问题所引起的: #### 1. **SQL 查询语法错误** 如果查询语句本身存在语法问题(例如拼写错误、关键字误用等),那么解析器将无法正确理解该查询。这种情况下,应该重新审查所有的自定义 SQL 脚本文件或注解中的查询语句。 #### 示例代码 (MyBatis XML 映射) ```xml <select id="getUserById" parameterType="int" resultType="User"> SELECT user_name, created_date FROM users WHERE id = #{userId} AND active_status='Y' </select> ``` 确认以上 SQL 片段中表名(`users`)、列名(`user_name`, `created_date`, `active_status`)以及条件逻辑均符合数据库的实际设计[^2]。 #### 2. **动态 SQL 构建缺陷** 对于复杂场景下构建的动态 SQL (比如通过字符串拼接方式生成最终执行版本的情况),一旦其中某部分缺失或者多余都会造成整个请求失效。因此务必谨慎对待每一段可选子句的设计实现。 下面是一个简单的例子展示了如何正确运用 MyBatis 提供的功能来减少人为失误的可能性: ```xml <select id="searchUsersByCriteria" parameterType="SearchCriteria" resultMap="UserResultMap"> SELECT * FROM users u <where> <if test="criteria.name != null">AND LOWER(u.user_name) LIKE CONCAT('%',LOWER(#{criteria.name}),'%')</if> <if test="criteria.minAge != null && criteria.maxAge != null"> AND u.age BETWEEN #{criteria.minAge} AND #{criteria.maxAge} </if> </where> </select> ``` 利用 `<where>` 标签可以帮助我们更方便地管理多个过滤条件之间的关系,从而降低出错几率[^3]。 #### 3. **第三方库冲突** 当项目依赖了不同版本甚至相互矛盾的一些组件包时,也有可能引发类似的兼容性难题。特别是像 Hibernate/JPA 这样的框架内部集成了自己的 DDL/DML 处理机制之后再额外引入其他专门用于优化性能表现但未经充分测试过的插件就很容易出现问题。 推荐做法是从 POM 文件里彻底清理掉不必要的重复声明项,并严格按照官方文档推荐的方式调整相关设置参数值范围等等细节之处。 #### Maven Dependency Management Example: ```xml <!-- Ensure consistent versions across all dependencies --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.26</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency> </dependencies> </dependencyManagement> ``` #### 4. **驱动程序加载失败** 即使提供了正确的 JDBC URL ,但如果指定的数据源驱动类未能成功注册至 JVM 类路径当中的话同样会造成连接建立环节报错。所以要特别留意各个厂商发布的最新版客户端 jar 包下载地址链接是否已经更新到了最新的稳定发行状态之下才行哦! Oracle 数据库示例配置如下所示: ```properties spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver spring.datasource.url=jdbc:oracle:thin:@//localhost:1521/orclpdb1 spring.datasource.username=scott spring.datasource.password=tiger # Additional properties to fine-tune connection pool behavior spring.datasource.hikari.maximum-pool-size=10 spring.datasource.hikari.minimum-idle=5 spring.datasource.hikari.idle-timeout=300000 spring.datasource.hikari.connection-test-query=SELECT 1 FROM DUAL ``` 注意这里设置了 `connection-test-query` 属性用来验证每次获取新实例之前其可用性状况良好与否[^1]。 --- ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

10km

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值