diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityVersionMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityVersionMapping.java index 868d96f090ce..5dfd845780c5 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityVersionMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityVersionMapping.java @@ -17,6 +17,10 @@ public interface EntityVersionMapping extends BasicValuedModelPart { String VERSION_ROLE_NAME = "{version}"; + static boolean matchesRoleName(String name) { + return VERSION_ROLE_NAME.equals( name ); + } + /** * The attribute marked as the version */ diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AbstractIdentifiableType.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AbstractIdentifiableType.java index a956fd635d67..3e03274013c3 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AbstractIdentifiableType.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AbstractIdentifiableType.java @@ -261,7 +261,7 @@ public SingularPersistentAttribute getVersion(Class javaTyp } @Override - public SingularPersistentAttribute findVersionAttribute() { + public SqmSingularPersistentAttribute findVersionAttribute() { if ( versionAttribute != null ) { return versionAttribute; } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EntityTypeImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EntityTypeImpl.java index b7d8b00c8961..0fa1d2c01da2 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EntityTypeImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EntityTypeImpl.java @@ -17,6 +17,7 @@ import org.hibernate.metamodel.mapping.DiscriminatorType; import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; import org.hibernate.metamodel.mapping.EntityIdentifierMapping; +import org.hibernate.metamodel.mapping.EntityVersionMapping; import org.hibernate.metamodel.model.domain.IdentifiableDomainType; import org.hibernate.metamodel.model.domain.JpaMetamodel; import org.hibernate.metamodel.model.domain.PersistentAttribute; @@ -27,7 +28,6 @@ import org.hibernate.query.sqm.tree.domain.SqmManagedDomainType; import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.domain.SqmPersistentAttribute; -import org.hibernate.query.sqm.tree.domain.SqmSingularPersistentAttribute; import org.hibernate.query.sqm.tree.domain.SqmEntityDomainType; import org.hibernate.type.descriptor.java.JavaType; @@ -156,6 +156,9 @@ public SqmPathSource findSubPathSource(String name) { else if ( EntityIdentifierMapping.matchesRoleName( name ) ) { return hasSingleIdAttribute() ? findIdAttribute() : getIdentifierDescriptor(); } + else if ( EntityVersionMapping.matchesRoleName( name ) ) { + return hasVersionAttribute() ? findVersionAttribute() : null; + } else if ( EntityDiscriminatorMapping.matchesRoleName( name ) ) { return discriminatorPathSource; } @@ -164,11 +167,6 @@ else if ( EntityDiscriminatorMapping.matchesRoleName( name ) ) { } } - @Override - public SqmSingularPersistentAttribute findIdAttribute() { - return super.findIdAttribute(); - } - @Override public SqmPathSource getIdentifierDescriptor() { return super.getIdentifierDescriptor(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/HibernateCriteriaBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/HibernateCriteriaBuilder.java index 380e0a664269..62c8ab28306b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/HibernateCriteriaBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/HibernateCriteriaBuilder.java @@ -352,7 +352,11 @@ default JpaSubQuery except(Subquery query1, Subquery... q // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Paths - JpaExpression fk(Path

path); + JpaExpression id(Path path); + + JpaExpression version(Path path); + + JpaExpression fk(Path path); @Override JpaPath treat(Path path, Class type); diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaFrom.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaFrom.java index 43936d18d681..77e7fc827316 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaFrom.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/JpaFrom.java @@ -324,4 +324,7 @@ default JpaJoin join(JpaCteCriteria cte, org.hibernate.query.common @Override JpaTreatedFrom treatAs(EntityDomainType treatJavaType); + + @Incubating + JpaExpression id(); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/spi/HibernateCriteriaBuilderDelegate.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/spi/HibernateCriteriaBuilderDelegate.java index 5729b1e22cf9..06de2f468fca 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/spi/HibernateCriteriaBuilderDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/spi/HibernateCriteriaBuilderDelegate.java @@ -335,7 +335,7 @@ public JpaExpression extract(TemporalField fiel } @Override - public JpaExpression fk(Path

path) { + public JpaExpression fk(Path path) { return criteriaBuilder.fk( path ); } @@ -799,6 +799,16 @@ public JpaFunction currentInstant() { return criteriaBuilder.currentInstant(); } + @Override + public JpaExpression id(Path path) { + return criteriaBuilder.id( path ); + } + + @Override + public JpaExpression version(Path path) { + return criteriaBuilder.version( path ); + } + @Override public JpaFunction function(String name, Class type, Expression... args) { return criteriaBuilder.function( name, type, args ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java index 1ed8bd64c9aa..980153632c87 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java @@ -3472,7 +3472,8 @@ public SqmPath visitEntityIdReference(HqlParser.EntityIdReferenceContext ctx) return sqmPath.get( identifierDescriptor.getPathName(), true ); } else if ( sqmPath instanceof SqmAnyValuedSimplePath ) { - return sqmPath.resolvePathPart( AnyKeyPart.KEY_NAME, true, processingStateStack.getCurrent().getCreationState() ); + return sqmPath.resolvePathPart( AnyKeyPart.KEY_NAME, true, + processingStateStack.getCurrent().getCreationState() ); } else { throw new FunctionArgumentException( "Argument '" + sqmPath.getNavigablePath() diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/NodeBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/NodeBuilder.java index c14fecb51365..a1b1d9cd8121 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/NodeBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/NodeBuilder.java @@ -976,7 +976,13 @@ SqmJsonValueExpression jsonValue( SqmPredicate wrap(Expression... expressions); @Override - SqmExpression fk(Path

path); + SqmExpression fk(Path path); + + @Override + SqmExpression id(Path path); + + @Override + SqmExpression version(Path path); @Override SqmPath treat(Path path, Class type); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java index c1ad16c8977d..a613f3c4eb60 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java @@ -43,6 +43,8 @@ import org.hibernate.internal.util.ReflectHelper; import org.hibernate.internal.util.StringHelper; import org.hibernate.jpa.spi.JpaCompliance; +import org.hibernate.metamodel.mapping.EntityIdentifierMapping; +import org.hibernate.metamodel.mapping.EntityVersionMapping; import org.hibernate.metamodel.model.domain.DomainType; import org.hibernate.metamodel.model.domain.JpaMetamodel; import org.hibernate.metamodel.model.domain.PersistentAttribute; @@ -602,8 +604,8 @@ public T unwrap(Class clazz) { } @Override - public SqmExpression fk(Path

path) { - final SqmPath

sqmPath = (SqmPath

) path; + public SqmPath fk(Path path) { + final SqmPath sqmPath = (SqmPath) path; final SqmPathSource toOneReference = sqmPath.getReferencedPathSource(); final boolean validToOneRef = toOneReference.getBindableType() == Bindable.BindableType.SINGULAR_ATTRIBUTE @@ -617,7 +619,6 @@ public SqmExpression fk(Path

path) { ) ); } - return new SqmFkExpression<>( sqmPath ); } @@ -2016,6 +2017,16 @@ public JpaExpression localTime() { ); } + @Override + public SqmPath id(Path path) { + return ((SqmPath) path).get( EntityIdentifierMapping.ID_ROLE_NAME ); + } + + @Override + public SqmPath version(Path path) { + return ((SqmPath) path).get( EntityVersionMapping.VERSION_ROLE_NAME ); + } + @Override public SqmFunction function(String name, Class type, Expression[] args) { final BasicType resultType = getTypeConfiguration().standardBasicTypeForJavaType( type ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmFrom.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmFrom.java index 5cedaf440b36..e48e96d100a0 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmFrom.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmFrom.java @@ -25,6 +25,7 @@ import org.hibernate.query.criteria.JpaCrossJoin; import org.hibernate.query.criteria.JpaCteCriteria; import org.hibernate.query.criteria.JpaDerivedJoin; +import org.hibernate.query.criteria.JpaExpression; import org.hibernate.query.criteria.JpaFunctionJoin; import org.hibernate.query.criteria.JpaPath; import org.hibernate.query.criteria.JpaSelection; @@ -949,4 +950,9 @@ public JpaSelection alias(String name) { } return super.alias( name ); } + + @Override + public JpaExpression id() { + return nodeBuilder().id( this ); + } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/CriteriaIdVersionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/CriteriaIdVersionTest.java new file mode 100644 index 000000000000..97ec709470e6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/CriteriaIdVersionTest.java @@ -0,0 +1,80 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.query.criteria; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Version; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SessionFactory +@DomainModel(annotatedClasses = CriteriaIdVersionTest.Thing.class) +class CriteriaIdVersionTest { + @Test + void test(SessionFactoryScope scope) { + scope.getSessionFactory().getSchemaManager().truncate(); + scope.inTransaction( session -> { + session.persist( new Thing() ); + } ); + scope.inSession( session -> { + var cb = session.getCriteriaBuilder(); + var cq = cb.createQuery( Long.class ); + var root = cq.from( Thing.class ); + cq.select( cb.id(root).as( Long.class ) ); + assertEquals( 2L, session.createSelectionQuery( cq ).getSingleResult() ); + } ); + scope.inSession( session -> { + var cb = session.getCriteriaBuilder(); + var cq = cb.createQuery( Long.class ); + var root = cq.from( Thing.class ); + cq.select( cb.version(root).as( Long.class ) ); + assertEquals( 3L, session.createSelectionQuery( cq ).getSingleResult() ); + } ); + scope.inSession( session -> { + var cb = session.getCriteriaBuilder(); + var cq = cb.createQuery( Long.class ); + var root = cq.from( Thing.class ); + cq.select( root.id().asLong() ); + assertEquals( 2L, session.createSelectionQuery( cq ).getSingleResult() ); + } ); + } + + @Test + void testPath(SessionFactoryScope scope) { + scope.getSessionFactory().getSchemaManager().truncate(); + scope.inTransaction( session -> { + Thing thing = new Thing(); + Thing otherThing = new Thing(); + otherThing.id = 5; + thing.other = otherThing; + session.persist( thing ); + } ); + scope.inSession( session -> { + var cb = session.getCriteriaBuilder(); + var cq = cb.createQuery( Long.class ); + var root = cq.from( Thing.class ); + cq.select( cb.id( root.get("other") ).as( Long.class ) ); + cq.where( root.get("other").isNotNull() ); + assertEquals( 5L, session.createSelectionQuery( cq ).getSingleResult() ); + } ); + } + + @Entity + static class Thing { + @Id + long id = 2; + @Version + long version = 3; + @ManyToOne(cascade = CascadeType.PERSIST) + Thing other; + } +}