Skip to content

fix: 修复 UPDATE 的 SET 子句含函数调用时生成非法 SQL - #10

Merged
xlorne merged 2 commits into
mainfrom
dev
Sep 22, 2026
Merged

xlorne merged 2 commits into
mainfrom
dev

Conversation

@xlorne

@xlorne xlorne commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #9

问题

UPDATE 的 SET 子句含函数调用时,dbstream 会生成非法 SQL 并抛给调用方。

UpdateSQLParser.getColumnValues() 取到 SET 子句后直接 split(","),假设逗号只用于分隔列。但 replace(col, ?, ?) / concat(a, b) / substr(s, 1, 2) 这类函数调用的参数之间也有逗号,于是被当成列分隔符,解析出 "?"、"?)" 等伪列名。

脏列名经 latestSQL() 拼进内部查询,产出:

SELECT short_name_tair,?,?),ID FROM BIZ_PBM_ORGANIZATION WHERE ID IN (...) AND 1=1

达梦 DM8 上报 SQLState 42000 / -2007,第 1 行, 第 26 列[)]附近出现错误。

为什么难排查

内部 SQL 不经过业务侧的 SQL 日志,调用方(Hibernate)看到的是自己那条合法 UPDATE 的失败:

JDBC exception executing SQL [update biz_pbm_organization set short_name_tair=replace(...) where ...]
[第 1 行, 第 26 列[)]附近出现错误: 语法分析出错]

一条合法 SQL 顶着另一条非法 SQL 的报错。

修复

  • SQLUtils.splitTopLevel() — 抽出按「括号深度 + 字符串状态」切分的原语,仅在顶层切分。复用 parseInsertSQLValues() 中已有的同类逻辑,不引入 jsqlparser,保持零运行时依赖
  • UpdateSQLParser.getColumnValues() — 改用 splitTopLevel,并跳过无 列名=表达式 结构的片段
  • UpdateDBEventParser.latestSQL() — 主键值为空时不再拼出 IN ()(全部为空退化为 1=0,避免退化成全表扫描);字符串主键转义单引号;null 主键跳过
  • 前镜像为空时不再进入后镜像查询
  • 错误归因 — 前镜像/后镜像查询失败时把内部 SQL 原文带进 SQLException(Update/Delete 两侧)

验证

修复后渲染出的内部查询,与 issue §4 的「黄金对照」第 3 条逐字一致:

-- 修复前(DM8 42000/-2007)
SELECT short_name_tair,?,?),ID FROM BIZ_PBM_ORGANIZATION WHERE ID IN (784812337803333) AND 1=1
-- 修复后
SELECT short_name_tair,ID FROM BIZ_PBM_ORGANIZATION WHERE ID IN (784812337803333)

其他边界:前镜像为空 → WHERE 1=0;字符串主键含引号 → IN ('o''brien')。

./mvnw clean test — 59 个测试全部通过。UpdateSQLParserTest 由 5 例增至 14 例,新增覆盖 issue 原始 SQL、多函数调用、字面量内逗号、无等号片段、别名 + substr。

行为变化说明

SQLUtils.splitTopLevel 原样保留字符串里的 ''(原 parseInsertSQLValues 会折叠成 ',丢失转义信息)。该变化已通过集成测试确认无影响。

🤖 Generated with Claude Code

xlorne and others added 2 commits September 22, 2026 16:04
SET 子句原按 split(",") 切分,假设逗号只用于分隔列,导致 replace(col,?,?)、
concat(a,b)、substr(s,1,2) 等函数调用的参数逗号被当作列分隔符,解析出 "?"、"?)"
等伪列名。脏列名经 latestSQL() 拼进前镜像/后镜像查询,产出非法 SQL:

  SELECT short_name_tair,?,?),ID FROM BIZ_PBM_ORGANIZATION WHERE ID IN (...) AND 1=1

该 SQL 在达梦 DM8 上报 42000/-2007,且 dbstream 内部 SQL 不经过业务侧 SQL 日志,
调用方看到的却是自己那条合法 UPDATE 的失败,错误归因误导、排查成本高。

修复:
- 抽出 SQLUtils.splitTopLevel(),按括号深度 + 字符串状态切分,仅在顶层切分;
  复用 parseInsertSQLValues() 已有的同类逻辑,避免第二份实现,不引入新依赖
- UpdateSQLParser.getColumnValues() 改用 splitTopLevel,且跳过无 "列名=表达式"
  结构的片段
- UpdateDBEventParser.latestSQL() 不再拼出 "IN ()"(主键值为空时跳过该条件,
  全部为空时退化为 1=0),字符串主键转义单引号,null 主键跳过
- 前镜像为空时不再进入后镜像查询
- 前镜像/后镜像查询失败时把内部 SQL 原文带进 SQLException,避免归因到业务 SQL
  (Update/DeleteDBEventParser)

测试:UpdateSQLParserTest 由 5 例增至 14 例,覆盖 issue 原始 SQL、多函数调用、
字面量内逗号、无等号片段、别名 + substr。全量 59 个测试通过。

Fixes #9

Co-Authored-By: Claude Code <[email protected]>
pom.xml 版本号 1.0.19 → 1.0.20,包含 issue #9 的修复。

Co-Authored-By: Claude Code <[email protected]>
@xlorne
xlorne merged commit 5cc9f76 into main Sep 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

这个sql执行时出现了错误

1 participant