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()):
- Build
effectiveColumns / effectiveValues using the same helper as executeWithKey().
- If any value expression is a
TemplateExpression (or otherwise non-JPQL-friendly), dispatch through JpaNativeInsertSerializer + JDBC executeUpdate (no getGeneratedKeys).
- 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
Summary
JPAInsertClause.execute()(and the correspondingHibernateInsertClause.execute()) still serializes throughJPQLSerializerand dispatches viaEntityManager.createQuery(...). Anyset(...)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 throughJpaNativeInsertSerializerand 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, soexecute()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 (viaSQLExpressions.stringFunction(...)) where no generated key is needed.Reproducer
Swapping the call to
.executeWithKey(m.id)(and discarding the return value) succeeds, because that path goes throughJpaNativeInsertSerializerand bypasses the JPQL parser.Expected behaviour
execute()should produce the same SQL thatexecuteWithKey()produces when value expressions contain function templates / SQL fragments — i.e. detect those and route throughJpaNativeInsertSerializer. 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 usesJPQLSerializer:The native-routing logic introduced by #1693 / #1656 only lives in
executeWithKey()/executeWithKeys(). Users who don't need the generated key currently have to callexecuteWithKey(...)purely to dodge the JPQL parser, which obscures intent.Workaround
Proposed direction
Inside
JPAInsertClause.execute()(andHibernateInsertClause.execute()):effectiveColumns/effectiveValuesusing the same helper asexecuteWithKey().TemplateExpression(or otherwise non-JPQL-friendly), dispatch throughJpaNativeInsertSerializer+ JDBCexecuteUpdate(nogetGeneratedKeys).Centralizing the detection predicate so
execute / executeWithKey / executeWithKeysshare one routing decision keeps behaviour consistent across the three entry points.Test coverage: regression tests for
execute()mirroring the existingJPAExecuteWithKeyTest/HibernateExecuteWithKeyTestcases (function template values, multi-part qualified function names) should land in the same PR so this path doesn't drift again.Related
SQLExpressions; intent included routing INSERTs through native SQL when function templates are presentexecuteWithKey()/executeWithKeys()+ theJpaNativeInsertSerializernative pathVersions
master)