Summary
In a JPQL bulk UPDATE, a fully-qualified enum literal used as the new value of a SET item is silently dropped from the generated SQL when the target state field is referenced without an identification variable (alias). The resulting SQL is malformed (SET <COL> = WHERE ...) and fails at the database.
The same query works correctly if the entity is given an alias and the field is qualified with it. Non-enum literals (e.g. strings) work fine even without an alias. So the failure is specific to the combination unqualified SET target field + fully-qualified enum literal.
This was originally reported against GlassFish / Jakarta Data, where alias-less queries (JDQL style) are idiomatic and are forwarded verbatim to EntityManager.createQuery(...): eclipse-ee4j/glassfish#26015
Environment
- EclipseLink
5.0.0 (build 5.0.0.v202603230926)
- JDK 21
- Database: H2 2.3.230 (DB-independent — failure is at JPQL→SQL generation, before execution)
Entity
package com.example.domain;
import jakarta.persistence.*;
@Entity
public class Todo {
@Id
Long id;
String title;
@Enumerated(EnumType.STRING)
Status status;
// getters/setters omitted
}
public enum Status { PENDING, COMPLETED }
Reproduction (results)
Running each statement via em.createQuery(jpql).setParameter("id", 1L).executeUpdate():
| JPQL |
Result |
update Todo set status=com.example.domain.Status.PENDING where id=:id |
❌ FAIL |
update Todo t set t.status=com.example.domain.Status.PENDING where t.id=:id |
✅ OK (status set to PENDING) |
update Todo set title='done' where id=:id |
✅ OK |
Expected
The first statement should resolve com.example.domain.Status.PENDING to the enum literal and update the row, identical to the aliased form.
Actual
EclipseLink generates SQL with an empty new value:
UPDATE TODO SET STATUS = WHERE (ID = ?)
which the database rejects, e.g. (H2):
org.h2.jdbc.JdbcSQLSyntaxErrorException: Syntax error in SQL statement
"UPDATE TODO SET STATUS = [*]WHERE (ID = ?)"
The enum literal is correctly resolved and emitted only when the target field is qualified by an identification variable (case 2 above).
Minimal standalone reproduction
persistence.xml (RESOURCE_LOCAL, H2 in-memory):
<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_2.xsd"
version="3.2">
<persistence-unit name="pu" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<class>com.example.domain.Todo</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<property name="jakarta.persistence.jdbc.driver" value="org.h2.Driver"/>
<property name="jakarta.persistence.jdbc.url" value="jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"/>
<property name="jakarta.persistence.jdbc.user" value="sa"/>
<property name="jakarta.persistence.schema-generation.database.action" value="drop-and-create"/>
</properties>
</persistence-unit>
</persistence>
Driver/runner:
import jakarta.persistence.*;
public class Repro {
public static void main(String[] a) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("pu");
// seed one row with id=1 first (persist a Todo), then:
try {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
em.createQuery("update Todo set status=com.example.domain.Status.PENDING where id=:id")
.setParameter("id", 1L)
.executeUpdate(); // throws: SET STATUS = WHERE ...
em.getTransaction().commit();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Notes / impact
- Reproduced layer is purely EclipseLink's Hermes JPQL parser (
UpdateQueryVisitor / ExpressionBuilderVisitor$PathResolver enum-literal resolution); no other framework involved.
- This blocks Jakarta Data
@Query repository methods that set an enum via a literal, because JDQL omits the alias by convention.
- Workarounds: qualify the field with an alias, or bind the value as a parameter instead of an inline literal.
Summary
In a JPQL bulk
UPDATE, a fully-qualified enum literal used as the new value of aSETitem is silently dropped from the generated SQL when the target state field is referenced without an identification variable (alias). The resulting SQL is malformed (SET <COL> = WHERE ...) and fails at the database.The same query works correctly if the entity is given an alias and the field is qualified with it. Non-enum literals (e.g. strings) work fine even without an alias. So the failure is specific to the combination unqualified SET target field + fully-qualified enum literal.
This was originally reported against GlassFish / Jakarta Data, where alias-less queries (JDQL style) are idiomatic and are forwarded verbatim to
EntityManager.createQuery(...): eclipse-ee4j/glassfish#26015Environment
5.0.0(build5.0.0.v202603230926)Entity
Reproduction (results)
Running each statement via
em.createQuery(jpql).setParameter("id", 1L).executeUpdate():update Todo set status=com.example.domain.Status.PENDING where id=:idupdate Todo t set t.status=com.example.domain.Status.PENDING where t.id=:idstatusset toPENDING)update Todo set title='done' where id=:idExpected
The first statement should resolve
com.example.domain.Status.PENDINGto the enum literal and update the row, identical to the aliased form.Actual
EclipseLink generates SQL with an empty new value:
which the database rejects, e.g. (H2):
The enum literal is correctly resolved and emitted only when the target field is qualified by an identification variable (case 2 above).
Minimal standalone reproduction
persistence.xml(RESOURCE_LOCAL, H2 in-memory):Driver/runner:
Notes / impact
UpdateQueryVisitor/ExpressionBuilderVisitor$PathResolverenum-literal resolution); no other framework involved.@Queryrepository methods that set an enum via a literal, because JDQL omits the alias by convention.