update documentation to explain implicit collection joins

This commit is contained in:
Gavin King 2022-02-09 21:30:05 +01:00
parent 437da23961
commit 4d024fde8b
2 changed files with 112 additions and 47 deletions

View File

@ -953,48 +953,20 @@ The syntax is `format(datetime as pattern)`, and the pattern must be written in
For a full list of `format()` pattern elements, see the Javadoc for https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/dialect/Dialect.html#appendDatetimeFormat[`Dialect#appendDatetimeFormat`].
[[hql-collection-qualification]]
==== Collection elements, map keys, and list indexes
[[hql-list-functions]]
===== `element()` and `index()`
The following functions may be applied to a collection-valued path expression to obtain a reference to a list index or map key.
A reference to an element or index of <<hql-collection-valued-associations,joined list>>.
|===
| Function | Applies to | Interpretation | Notes
[[hql-map-functions]]
===== `key()`, `value()`, and `entry()`
| `value()` or `element()` | Any collection | The collection element or map entry value
| Always optional, and useful only to explicitly indicate intent.
| `index()` | Any `List` with an index column | The index of the element in the list
| For backward compatibility, it's also an alternative to ``key()``, when applied to a map.
| `key()` | Any `Map` | The key of the entry in the list | If the key is of entity type, it may be further navigated.
| `entry()` | Any `Map` | The map entry, that is, the `Map.Entry` of key and value.
| Only legal as a terminal path, and only allowed in the `select` clause.
|===
A reference to a key, value, or entry of a <<hql-collection-valued-associations,joined map>>.
We've intentionally left two functions off this list, so we can come back to them <<hql-elements-indices,later>>.
[[hql-collection-subquery]]
===== `elements()`, and `indices()`
NOTE: Of these, only `index()` is defined by the JPQL specification.
[[hql-collection-qualification-example]]
//.Qualified collection references example
====
[source, JAVA, indent=0]
----
include::{modeldir}/Phone.java[tags=hql-collection-qualification-example, indent=0]
include::{sourcedir}/HQLTest.java[tags=hql-collection-qualification-example, indent=0]
----
====
An element of an indexed collection (an array, list, or map) may even be identified using the index operator:
[[hql-collection-index-operator-example]]
//.Index operator examples
====
[source, JAVA, indent=0]
----
include::{sourcedir}/HQLTest.java[tags=hql-collection-index-operator-example]
----
====
Later, in <<hql-elements-indices>>, and in <<hql-aggregate-functions-collections>>, we will learn about these special functions for quantifying over the elements or indices of a particular collection.
[[hql-more-functions]]
==== More HQL functions
@ -1194,7 +1166,7 @@ As you can guess, `not like` and `not ilike` are the enemies of `like` and `ilik
[[hql-elements-indices]]
==== Elements and indices
There's two special HQL functions that we didn't mention <<hql-collection-qualification,earlier>>, since they're only useful in conjunction with the predicate operators we're about to meet.
There's two special HQL functions that we mentioned <<hql-collection-subquery,earlier>>, without giving much of an explanation, since they're only useful in conjunction with the predicate operators we're about to meet.
These functions are only allowed in the `where` clause, and result in a subquery in the generated SQL.
Indeed, you can think of them as just a shortcut way to write a subquery.
@ -1673,9 +1645,13 @@ include::{sourcedir}/HQLTest.java[tags=hql-implicit-join-alias-example]
====
[[hql-collection-valued-associations]]
==== Collection member references
==== Joining collections and many-valued associations
References to collection-valued associations actually refer to the _elements_ of that collection.
When a join involves a collection or many-valued association, the declared identification variable refers to the _elements_ of the collection, that is:
- to the elements of a `Set`,
- to the elements of a `List`, not to their indices in the list, or
- to the values of a `Map`, not to their keys.
[[hql-collection-valued-associations-example]]
//.Collection references example
@ -1686,9 +1662,61 @@ include::{sourcedir}/HQLTest.java[tags=hql-collection-valued-associations]
----
====
In the example, the identification variable `ph` actually refers to the object model type `Phone`, which is the type of the elements of the `Person#phones` association.
In this example, the identification variable `ph` is of type `Phone`, the element type of the list `Person#phones`.
But if we need to refer to the index of a `Phone` in the list, we need some extra syntax.
You might recall that we mentioned <<hql-list-functions>> and <<hql-map-functions>> a bit earlier.
These functions may be applied to the identification variable declared in a collection join or many-valued association join.
|===
| Function | Applies to | Interpretation | Notes
| `value()` or `element()` | Any collection | The collection element or map entry value
| Often optional.
| `index()` | Any `List` with an index column | The index of the element in the list
| For backward compatibility, it's also an alternative to ``key()``, when applied to a map.
| `key()` | Any `Map` | The key of the entry in the list | If the key is of entity type, it may be further navigated.
| `entry()` | Any `Map` | The map entry, that is, the `Map.Entry` of key and value.
| Only legal as a terminal path, and only allowed in the `select` clause.
|===
In particular, `index()` and `key()` obtain a reference to a list index or map key.
[[hql-collection-qualification-example]]
//.Qualified collection references example
====
[source, JAVA, indent=0]
----
include::{modeldir}/Phone.java[tags=hql-collection-qualification-example, indent=0]
include::{sourcedir}/HQLTest.java[tags=hql-collection-qualification-example, indent=0]
----
====
[[hql-implicit-collection-join]]
==== Implicit joins involving collections
The functions `element()`, `index()`, `key()`, and `value()` may even be applied to a path expression to express an implicit join.
[[hql-collection-implicit-join-example]]
====
[source, JAVA, indent=0]
----
include::{sourcedir}/HQLTest.java[tags=hql-collection-implicit-join-example, indent=0]
----
====
An element of an indexed collection (an array, list, or map) may even be identified using the index operator:
[[hql-collection-index-operator-example]]
//.Index operator examples
====
[source, JAVA, indent=0]
----
include::{sourcedir}/HQLTest.java[tags=hql-collection-index-operator-example]
----
====
But there _is_ a way to refer to the keys or indexes of a collection, as we've already seen in <<hql-collection-qualification>>.
[[hql-select-clause]]
=== Projection: `select`

View File

@ -570,6 +570,8 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
//tag::hql-collection-qualification-example[]
// select all the calls (the map value) for a given Phone
// note that here we don't need to use value() or element()
// since it is implicit
List<Call> calls = entityManager.createQuery(
"select ch " +
"from Phone ph " +
@ -590,7 +592,7 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
Long id = 1L;
//tag::hql-collection-qualification-example[]
// same as above
// same as above, but with value() explicit
List<Call> calls = entityManager.createQuery(
"select value(ch) " +
"from Phone ph " +
@ -611,6 +613,7 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
//tag::hql-collection-qualification-example[]
// select all the Call timestamps (the map key) for a given Phone
// note that here we *do* need to explicitly specify key()
List<LocalDateTime> timestamps = entityManager.createQuery(
"select key(ch) " +
"from Phone ph " +
@ -626,7 +629,6 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
@Test
public void test_hql_collection_qualification_associations_4() {
try {
doInJPA(this::entityManagerFactory, entityManager -> {
Long id = 1L;
@ -643,9 +645,6 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
//end::hql-collection-qualification-example[]
});
} catch(Exception e) {
//@see https://hibernate.atlassian.net/browse/HHH-10491
}
}
@Test
@ -674,6 +673,44 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase {
});
}
@Test
public void test_hql_collection_implicit_join_1() {
doInJPA(this::entityManagerFactory, entityManager -> {
Long id = 1L;
//tag::hql-collection-implicit-join-example[]
// implicit join to a map value()
List<Call> calls = entityManager.createQuery(
"select value(ph.callHistory) " +
"from Phone ph " +
"where ph.id = :id ",
Call.class)
.setParameter("id", id)
.getResultList();
//end::hql-collection-implicit-join-example[]
assertEquals(2, calls.size());
});
}
@Test
public void test_hql_collection_implicit_join_2() {
doInJPA(this::entityManagerFactory, entityManager -> {
Long id = 1L;
//tag::hql-collection-implicit-join-example[]
// implicit join to a map key()
List<LocalDateTime> timestamps = entityManager.createQuery(
"select key(ph.callHistory) " +
"from Phone ph " +
"where ph.id = :id ",
LocalDateTime.class)
.setParameter("id", id)
.getResultList();
//end::hql-collection-implicit-join-example[]
assertEquals(2, timestamps.size());
});
}
@Test
public void test_projection_example() {
doInJPA(this::entityManagerFactory, entityManager -> {