jOOQ 无法在 PostgreSQL 模式下为 H2 数据库正确翻译 SQL

Posted

技术标签:

【中文标题】jOOQ 无法在 PostgreSQL 模式下为 H2 数据库正确翻译 SQL【英文标题】:jOOQ does not translate SQL properly for H2 database in PostgreSQL mode 【发布时间】:2020-04-28 06:05:45 【问题描述】:

问题在于 Postgres ON CONFLICT 语法。

版本(maven 依赖项):

postgresql:42.2.9 jooq:3.12.3 h2数据库:1.4.200
// mocking connection
final Connection connection = DriverManager.getConnection("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;Mode=PostgreSQL", "sa", "");
final Settings settings = new Settings().withRenderNameStyle(RenderNameStyle.AS_IS);
Mockito.doReturn(DSL.using(connection, SQLDialect.POSTGRES, settings)).when(mockDbConn).getDSLContext();

// java code for upsert:
dc.insertInto(MY_TABLE)
    .columns(MY_TABLE.TOKEN, MY_TABLE.NAME, MY_TABLE.EMAIL)
    .values(token, name, email)
    .onDuplicateKeyUpdate()
    .set(MY_TABLE.EMAIL, email)
    .execute();

得到以下错误日志(似乎问题可能是因为[*](第 4 行↓)。我不明白它出现的原因以及如何删除它):

-- Syntax error in SQL statement:
INSERT INTO PUBLIC.MY_TABLE (TOKEN, NAME, EMAIL)
VALUES (?, ?, ?)
ON CONFLICT ([*]TOKEN, NAME) -- line 4
DO UPDATE SET EMAIL = EXCLUDED.EMAIL;

-- expected "DO";
-- SQL statement:
insert into public.my_table (token, name, email)
values (?, ?, ?)
on conflict (token, name)
do update set email = excluded.email;

-- [42001-200]

如果我将方言从SQLDialect.POSTGRES 切换到SQLDialect.H2,会发生以下情况:

-- Column "EXCLUDED.EMAIL" not found; SQL statement:
merge into public.my_table using (select 1 one)
on (public.my_table.token = cast(? as varchar) and public.my_table.name = cast(? as varchar))
when matched then update set public.my_table.email = excluded.email
when not matched then insert (token, name, email)
values (cast(? as varchar), cast(? as varchar), cast(? as varchar))

-- [42122-200]

【问题讨论】:

[*] 只是 H2 附加到查询的标记,以指示 SQL 中包含第一个解析错误的位置。 jOOQ中的H2应该使用H2方言,H2的兼容模式提供的兼容性非常有限,H2不是其他数据库的模拟器。但是 jOOQ 为 H2 生成的查询也是无效的; EXCLUDED 虚拟表来自 PostgreSQL 的 ON CONFLICT 子句,它不能在标准的 MERGE 命令中使用。 【参考方案1】:

您在 jOOQ API 使用中混合了 3 种方言:

    SQLDialect.mysql 方言,这是产生onDuplicateKeyUpdate() 语法的方言。这可以在各种方言上进行模拟,但通常最好使用本机语法(SQL 标准 MERGE 如果可用,或 ON CONFLICT 在 PostgreSQL 中)。 SQLDialect.POSTGRES 方言,这是您的生产目标方言 SQLDialect.H2 方言,用于进行集成测试

考虑到您可能只将 PostgreSQL 定位为生产数据库产品,这非常复杂。我强烈建议您使用testcontainers 进行集成测试,这将允许您从等式中删除 H2。此外,一旦您确定 PostgreSQL 仅作为目标方言,您可以避免使用 onDuplicateKeyUpdate() 语法,并使用 jOOQ 的原生 onConflict() 语法支持以获得更可预测的结果。

如果您继续混合使用上述 3 种方言,您通常会遇到一些限制,即 jOOQ 或 H2 无法模拟您正在使用的语法。如果您确实必须在生产中支持这 3 种方言,这只是一种可接受的情况。

【讨论】:

以上是关于jOOQ 无法在 PostgreSQL 模式下为 H2 数据库正确翻译 SQL的主要内容,如果未能解决你的问题,请参考以下文章

如何在 jOOQ 中转换“to_json()”PostgreSQL 函数?

使用 jOOQ 在 PostgreSQL 中进行 UPSERT

PostgreSQL 函数 + 在 jOOQ 中强制转换

如何在 PostgreSql 上使用 Jooq 级联截断?

JOOQ快速上手(基于springboot 和 postgresql)

在 PostgreSQL 数据库中插入带有 jOOQ 的 SQL 枚举