diff --git a/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc b/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc index 33e567e87b..005525319e 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc @@ -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 <>. -|=== -| 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 <>. -We've intentionally left two functions off this list, so we can come back to them <>. +[[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 <>, and in <>, 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 <>, 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 <>, 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 <> and <> 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-select-clause]] === Projection: `select` diff --git a/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java b/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java index c87de13c61..ed43e3530e 100644 --- a/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java @@ -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 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 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 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 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 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 -> {