Skip to content

Commit c8392df

Browse files
committed
experiment with a SimpleProjectionSpecification
1 parent f4ff683 commit c8392df

File tree

4 files changed

+239
-1
lines changed

4 files changed

+239
-1
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.query.specification;
6+
7+
import jakarta.persistence.EntityManager;
8+
import jakarta.persistence.TypedQueryReference;
9+
import jakarta.persistence.criteria.CriteriaBuilder;
10+
import jakarta.persistence.criteria.CriteriaQuery;
11+
import jakarta.persistence.metamodel.SingularAttribute;
12+
import org.hibernate.Incubating;
13+
import org.hibernate.Session;
14+
import org.hibernate.StatelessSession;
15+
16+
import org.hibernate.query.SelectionQuery;
17+
import org.hibernate.query.restriction.Path;
18+
import org.hibernate.query.specification.internal.SimpleProjectionSpecificationImpl;
19+
20+
/**
21+
* Allows a {@link SelectionSpecification} to be augmented with the specification of
22+
* a single projected {@linkplain SingularAttribute attribute} or {@linkplain Path path}.
23+
* <pre>
24+
* var specification =
25+
* SelectionSpecification.create(Book.class)
26+
* .restrict(Restriction.contains(Book_.title, "hibernate", false))
27+
* .sort(Order.desc(Book_.title));
28+
* var projection = SimpleProjectionSpecification.create(specification, Book_.isbn);
29+
* var isbns = projection.createQuery(session).getResultList();
30+
* </pre>
31+
* <p>
32+
* Use of a {@link Path} allows joining to associated entities.
33+
* <pre>
34+
* var specification =
35+
* SelectionSpecification.create(Book.class)
36+
* .restrict(Restriction.contains(Book_.title, "hibernate", false))
37+
* .sort(Order.desc(Book_.title));
38+
* var projection =
39+
* SimpleProjectionSpecification.create(specification,
40+
* Path.from(Book.class)
41+
* .to(Book_.publisher)
42+
* .to(Publisher_.name));
43+
* var publisherNames = projection.createQuery(session).getResultList();
44+
* </pre>
45+
*
46+
* @param <T> The result type of the {@link SelectionSpecification}
47+
* @param <X> The type of the projected path or attribute
48+
*
49+
* @since 7.2
50+
*
51+
* @author Gavin King
52+
*/
53+
@Incubating
54+
public interface SimpleProjectionSpecification<T,X> extends QuerySpecification<T> {
55+
/**
56+
* Create a new {@code ProjectionSpecification} which augments the given
57+
* {@link SelectionSpecification}.
58+
*/
59+
static <T,X> SimpleProjectionSpecification<T,X> create(
60+
SelectionSpecification<T> selectionSpecification,
61+
Path<T,X> projectedPath) {
62+
return new SimpleProjectionSpecificationImpl<>( selectionSpecification, projectedPath );
63+
}
64+
65+
/**
66+
* Create a new {@code ProjectionSpecification} which augments the given
67+
* {@link SelectionSpecification}.
68+
*/
69+
static <T,X> SimpleProjectionSpecification<T,X> create(
70+
SelectionSpecification<T> selectionSpecification,
71+
SingularAttribute<T,X> projectedAttribute) {
72+
return new SimpleProjectionSpecificationImpl<>( selectionSpecification, projectedAttribute );
73+
}
74+
75+
@Override
76+
SelectionQuery<X> createQuery(Session session);
77+
78+
@Override
79+
SelectionQuery<X> createQuery(StatelessSession session);
80+
81+
@Override
82+
SelectionQuery<X> createQuery(EntityManager entityManager);
83+
84+
@Override
85+
CriteriaQuery<X> buildCriteria(CriteriaBuilder builder);
86+
87+
@Override
88+
TypedQueryReference<X> reference();
89+
90+
@Override
91+
SimpleProjectionSpecification<T,X> validate(CriteriaBuilder builder);
92+
}

hibernate-core/src/main/java/org/hibernate/query/specification/internal/ProjectionSpecificationImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public String getName() {
116116
}
117117

118118
@Override
119-
public Class<? extends Object[]> getResultType() {
119+
public Class<Object[]> getResultType() {
120120
return Object[].class;
121121
}
122122

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.query.specification.internal;
6+
7+
import jakarta.persistence.EntityManager;
8+
import jakarta.persistence.TypedQueryReference;
9+
import jakarta.persistence.criteria.CriteriaBuilder;
10+
import jakarta.persistence.criteria.CriteriaQuery;
11+
import jakarta.persistence.metamodel.SingularAttribute;
12+
import org.hibernate.Session;
13+
import org.hibernate.SharedSessionContract;
14+
import org.hibernate.StatelessSession;
15+
import org.hibernate.query.SelectionQuery;
16+
import org.hibernate.query.restriction.Path;
17+
import org.hibernate.query.restriction.Restriction;
18+
import org.hibernate.query.specification.QuerySpecification;
19+
import org.hibernate.query.specification.SelectionSpecification;
20+
import org.hibernate.query.specification.SimpleProjectionSpecification;
21+
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
22+
23+
import java.util.Collections;
24+
import java.util.Map;
25+
26+
/**
27+
* @author Gavin King
28+
*/
29+
public class SimpleProjectionSpecificationImpl<T,X> implements SimpleProjectionSpecification<T,X>, TypedQueryReference<X> {
30+
31+
private final SelectionSpecification<T> selectionSpecification;
32+
private final Path<T, X> path;
33+
private final SingularAttribute<T, X> attribute;
34+
35+
public SimpleProjectionSpecificationImpl(SelectionSpecification<T> specification, Path<T, X> path) {
36+
this.selectionSpecification = specification;
37+
this.path = path;
38+
this.attribute = null;
39+
}
40+
41+
public SimpleProjectionSpecificationImpl(SelectionSpecification<T> specification, SingularAttribute<T, X> attribute) {
42+
this.selectionSpecification = specification;
43+
this.attribute = attribute;
44+
this.path = null;
45+
}
46+
47+
@Override
48+
public QuerySpecification<T> restrict(Restriction<? super T> restriction) {
49+
throw new UnsupportedOperationException( "This is not supported yet!" );
50+
}
51+
52+
@Override
53+
public SelectionQuery<X> createQuery(Session session) {
54+
return session.createSelectionQuery( buildCriteria( session.getCriteriaBuilder() ) );
55+
}
56+
57+
@Override
58+
public SelectionQuery<X> createQuery(StatelessSession session) {
59+
return session.createSelectionQuery( buildCriteria( session.getCriteriaBuilder() ) );
60+
}
61+
62+
@Override
63+
public SelectionQuery<X> createQuery(EntityManager entityManager) {
64+
return entityManager.unwrap( SharedSessionContract.class )
65+
.createQuery( buildCriteria( entityManager.getCriteriaBuilder() ) );
66+
}
67+
68+
@Override
69+
public CriteriaQuery<X> buildCriteria(CriteriaBuilder builder) {
70+
var impl = (SelectionSpecificationImpl<T>) selectionSpecification;
71+
// TODO: handle HQL, existing criteria
72+
final var tupleQuery =
73+
(SqmSelectStatement<X>)
74+
builder.createQuery( getResultType() );
75+
final var root = tupleQuery.from( impl.getResultType() );
76+
// This cast is completely bogus
77+
final var castStatement = (SqmSelectStatement<T>) tupleQuery;
78+
impl.getSpecifications().forEach( spec -> spec.accept( castStatement, root ) );
79+
if ( path != null ) {
80+
tupleQuery.select( path.path( root ) );
81+
}
82+
else if ( attribute != null ) {
83+
tupleQuery.select( root.get( attribute ) );
84+
}
85+
return tupleQuery;
86+
}
87+
88+
@Override
89+
public SimpleProjectionSpecification<T,X> validate(CriteriaBuilder builder) {
90+
selectionSpecification.validate( builder );
91+
// TODO: validate projection
92+
return this;
93+
}
94+
95+
@Override
96+
public TypedQueryReference<X> reference() {
97+
return this;
98+
}
99+
100+
@Override
101+
public String getName() {
102+
return null;
103+
}
104+
105+
@Override
106+
public Class<X> getResultType() {
107+
if ( path != null ) {
108+
return path.getType();
109+
}
110+
else if ( attribute != null ) {
111+
return attribute.getJavaType();
112+
}
113+
else {
114+
throw new IllegalStateException( "No path or attribute" );
115+
}
116+
}
117+
118+
@Override
119+
public Map<String, Object> getHints() {
120+
return Collections.emptyMap();
121+
}
122+
}

hibernate-core/src/test/java/org/hibernate/orm/test/query/dynamic/ProjectionSpecificationTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.hibernate.query.restriction.Path;
88
import org.hibernate.query.specification.ProjectionSpecification;
99
import org.hibernate.query.specification.SelectionSpecification;
10+
import org.hibernate.query.specification.SimpleProjectionSpecification;
1011
import org.hibernate.testing.orm.junit.DomainModel;
1112
import org.hibernate.testing.orm.junit.SessionFactoryScope;
1213
import org.junit.jupiter.api.BeforeAll;
@@ -47,4 +48,27 @@ void testProjection(SessionFactoryScope factoryScope) {
4748
assertNull( otherId.in( tuple ) );
4849
});
4950
}
51+
52+
@Test
53+
void testSimpleProjection(SessionFactoryScope factoryScope) {
54+
factoryScope.inTransaction( (session) -> {
55+
var spec = SelectionSpecification.create( BasicEntity.class );
56+
var projection = SimpleProjectionSpecification.create( spec, BasicEntity_.name );
57+
var name = projection.createQuery( session ).getSingleResult();
58+
assertEquals( "Gavin", name );
59+
});
60+
}
61+
62+
@Test
63+
void testSimpleProjectionPath(SessionFactoryScope factoryScope) {
64+
factoryScope.inTransaction( (session) -> {
65+
var spec = SelectionSpecification.create( BasicEntity.class );
66+
var projection = SimpleProjectionSpecification.create( spec,
67+
Path.from( BasicEntity.class)
68+
.to( BasicEntity_.other )
69+
.to( OtherEntity_.id ) );
70+
var id = projection.createQuery( session ).getSingleResult();
71+
assertNull( id );
72+
});
73+
}
5074
}

0 commit comments

Comments
 (0)