Skip to content

JPAInsertClause.execute() does not route to native SQL when values contain function templates / SQL fragments #1757

Description

@zio0911
  • I am willing to put in the work and submit a PR to resolve this issue.

Summary

JPAInsertClause.execute() (and the corresponding HibernateInsertClause.execute()) still serializes through JPQLSerializer and dispatches via EntityManager.createQuery(...). Any set(...) value that is a function-call template or an arbitrary SQL fragment is therefore rejected by the Hibernate JPQL parser.

The executeWithKey() / executeWithKeys() variants introduced in #1693 already route through JpaNativeInsertSerializer and handle these expressions correctly. execute() was left behind, which is misaligned with the intent articulated in PR #1656 — routing JPA INSERTs through native SQL whenever value expressions contain function templates.

Background

I authored #1656 / #1693 and missed this gap during review: the regression suite for both PRs only exercises the executeWithKey() / executeWithKeys() paths, so execute() continuing to use the JPQL path went unnoticed. The issue surfaced in a downstream consumer doing INSERTs that use a schema-qualified external SQL function (via SQLExpressions.stringFunction(...)) where no generated key is needed.

Reproducer

QMyEntity m = QMyEntity.myEntity;
LocalDateTime now = LocalDateTime.now();

queryFactory.insert(m)
    .set(m.id, "key-1")
    .set(m.createdAt, now)
    .set(m.payload, SQLExpressions.stringFunction(
        "other_db.dbo.my_function", "arg1", "arg2"))
    .execute();   // ← SemanticException
org.hibernate.query.SemanticException: ...
  at com.querydsl.jpa.impl.JPAInsertClause.execute(JPAInsertClause.java:86)

Swapping the call to .executeWithKey(m.id) (and discarding the return value) succeeds, because that path goes through JpaNativeInsertSerializer and bypasses the JPQL parser.

Expected behaviour

execute() should produce the same SQL that executeWithKey() produces when value expressions contain function templates / SQL fragments — i.e. detect those and route through JpaNativeInsertSerializer. For pure path / literal / parameter values it should keep using the JPQL path so existing JPA semantics (cascade behaviour for paths, callbacks where applicable, etc.) are preserved.

Current behaviour

JPAInsertClause.execute() unconditionally uses JPQLSerializer:

@Override
public long execute() {
  var serializer = new JPQLSerializer(templates, entityManager);
  serializer.serializeForInsert(...);
  var query = entityManager.createQuery(serializer.toString());
  ...
}

The native-routing logic introduced by #1693 / #1656 only lives in executeWithKey() / executeWithKeys(). Users who don't need the generated key currently have to call executeWithKey(...) purely to dodge the JPQL parser, which obscures intent.

Workaround

.executeWithKey(somePath);   // discard return value

Proposed direction

Inside JPAInsertClause.execute() (and HibernateInsertClause.execute()):

  1. Build effectiveColumns / effectiveValues using the same helper as executeWithKey().
  2. If any value expression is a TemplateExpression (or otherwise non-JPQL-friendly), dispatch through JpaNativeInsertSerializer + JDBC executeUpdate (no getGeneratedKeys).
  3. Otherwise fall back to the existing JPQL path.

Centralizing the detection predicate so execute / executeWithKey / executeWithKeys share one routing decision keeps behaviour consistent across the three entry points.

Test coverage: regression tests for execute() mirroring the existing JPAExecuteWithKeyTest / HibernateExecuteWithKeyTest cases (function template values, multi-part qualified function names) should land in the same PR so this path doesn't drift again.

Related

Versions

  • querydsl 7.2 (also reproduces on master)
  • Hibernate 7.x, JPA 3.2

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions