From be7ac2ff21264707a9671e5b2dd0dfe5e0ec0b73 Mon Sep 17 00:00:00 2001 From: Gavin Date: Sun, 28 May 2023 00:59:18 +0200 Subject: [PATCH] minor improvements to new HQL guide --- .../asciidoc/querylanguage/Expressions.adoc | 65 ++++++++++--------- .../src/main/asciidoc/querylanguage/From.adoc | 2 +- .../asciidoc/querylanguage/Relational.adoc | 61 +++++++++++++---- .../querylanguage/extras/select_item_bnf.txt | 5 +- 4 files changed, 85 insertions(+), 48 deletions(-) diff --git a/documentation/src/main/asciidoc/querylanguage/Expressions.adoc b/documentation/src/main/asciidoc/querylanguage/Expressions.adoc index 38cab92680..77de0a7d34 100644 --- a/documentation/src/main/asciidoc/querylanguage/Expressions.adoc +++ b/documentation/src/main/asciidoc/querylanguage/Expressions.adoc @@ -957,7 +957,7 @@ where total > 100.0 ---- [[between-predicate]] -==== `between` predicate +==== The `between` predicate The ternary `between` operator, and its negation, `not between`, determine if a value falls within a range. @@ -988,6 +988,32 @@ The following operators make it easier to deal with null values. from Author where nomDePlume is not null ---- +[[collection-operators]] +==== Collection predicates + +The following operators apply to collection-valued attributes and to-many associations. + +[cols="15,15,20,~"] +|=== +| Operator | Negation | Type | Semantics + +| `is empty` | `is not empty` | Unary postfix | `true` if the collection or association on the left has no elements +| `member of` | `not member of` | Binary | `true` if the value on the left is a member of the collection or association on the right +|=== + +[[empty-collection-predicate-example]] +[source, hql] +---- +from Author where books is empty +---- + +[[member-of-collection-predicate-example]] +[source, hql] +---- +from Author as author, Book as book +where author member of book.authors +---- + [[like-predicate]] ==== String pattern matching @@ -1019,7 +1045,7 @@ The optional `escape` character allows a pattern to include a literal `_` or `%` As you can guess, `not like` and `not ilike` are the enemies of `like` and `ilike`, and evaluate to the exact opposite boolean values. [[in-predicate]] -==== `in` predicate +==== The `in` predicate The `in` predicates evaluates to true if the value to its left is in ... well, whatever it finds to its right. @@ -1063,6 +1089,11 @@ where type(payment) in (CreditCardPayment, WireTransferPayment) from Author as author where author.person.name in (select name from OldAuthorData) ---- +[source, hql] +---- +from Book as book +where :edition in elements(book.editions) +---- This example doesn't work on every database: @@ -1118,7 +1149,7 @@ from Publisher pub where :title = some(select title from pub.books) ---- [[exists-predicate]] -==== `exists` predicate +==== The `exists` predicate The unary prefix `exists` operator evaluates to true if the thing to its right is nonempty. @@ -1143,34 +1174,6 @@ where exists ( ) ---- - -[[collection-operators]] -==== Collection predicates - -The following operators apply to collection-valued attributes and to-many associations. - -[cols="15,15,20,~"] -|=== -| Operator | Negation | Type | Semantics - -| `is empty` | `is not empty` | Unary postfix | `true` if the collection or association on the left has no elements -| `member of` | `not member of` | Binary | `true` if the value on the left is a member of the collection or association on the right -|=== - -[[empty-collection-predicate-example]] -[source, hql] ----- -from Author where books is empty ----- - -[[member-of-collection-predicate-example]] -[source, hql] ----- -from Author as author, Book as book -where author member of book.authors ----- - - [[logical-operators]] ==== Logical operators diff --git a/documentation/src/main/asciidoc/querylanguage/From.adoc b/documentation/src/main/asciidoc/querylanguage/From.adoc index 128eac0767..84aa560c33 100644 --- a/documentation/src/main/asciidoc/querylanguage/From.adoc +++ b/documentation/src/main/asciidoc/querylanguage/From.adoc @@ -452,7 +452,7 @@ The example above is equivalent to: ---- select book from Book as book - join book.publisher pub + join book.publisher as pub where pub.name like :pubName ---- diff --git a/documentation/src/main/asciidoc/querylanguage/Relational.adoc b/documentation/src/main/asciidoc/querylanguage/Relational.adoc index b6274f9229..fc0018e10d 100644 --- a/documentation/src/main/asciidoc/querylanguage/Relational.adoc +++ b/documentation/src/main/asciidoc/querylanguage/Relational.adoc @@ -101,6 +101,7 @@ So let's restrict our result set to data from our own more civilized times: [[group-by-having-example]] [source, hql] +[%unbreakable] ---- select book.isbn, year(order.dateTime) as year, @@ -120,13 +121,21 @@ The `having` restriction is applied after grouping and aggregation has already b === Projection The `select` list identifies which objects and values to return as the query results. +This operation is called _projection_. -NOTE: This operation is called _projection_. +[source,antlrv4] +---- +selectClause + : "SELECT" "DISTINCT"? selection (","" selection)* +---- Any of the expression types discussed in <> may occur in the projection list, unless otherwise noted. -TIP: If a query has no explicit `select` list, the projection is inferred from the entities and joins occurring in the `from` clause, together with the result type specified by the call to `createQuery()`. -It's better to specify the projection explicitly, except in the simplest cases. +[TIP] +==== +If a query has no explicit `select` list, then, as we saw <>, the projection is inferred from the entities and joins occurring in the `from` clause, together with the result type specified by the call to `createQuery()`. +But it's better to specify the projection explicitly, except in the simplest cases. +==== There might be multiple items in a projection list, in which case each query result is a tuple, and this poses a problem: Java doesn't have a good way to represent tuples. @@ -134,13 +143,19 @@ Java doesn't have a good way to represent tuples. If there's just one projected item in the `select` list, then, no sweat, that's the type of each query result. There's no need to bother with trying to represent a "tuple of length 1". -But if there are multiple expressions in the select list then: +[source, java] +[%unbreakable] +---- +List results = + entityManager.createQuery("select title from Book", String.class) + .getResultList(); +---- -- by default, each query result is packaged as an array of type `Object[]`, or -- if explicitly requested by passing the class `Tuple` to `createQuery()`, the query result is packaged as an instance of `javax.persistence.Tuple`. +But if there are multiple expressions in the select list then, by default, each query result is packaged as an array of type `Object[]`. [[select-clause-projection-example]] [source, java] +[%unbreakable] ---- List results = entityManager.createQuery("select title, left(book.text, 200) from Book", @@ -151,7 +166,11 @@ for (var result : results) { String preamble = (String) result[1]; } ---- + +Or, if explicitly requested by passing the class `Tuple` to `createQuery()`, the query result is packaged as an instance of `javax.persistence.Tuple`. + [source, java] +[%unbreakable] ---- List results = entityManager.createQuery("select title as title, left(book.text, 200) as preamble from Book", @@ -164,10 +183,9 @@ for (Tuple tuple : tuples) { ---- The names of the `Tuple` elements are determined by the aliases given to the projected items in the select list. -If no aliases are specified, the elements may be accessed by their position in the list (positions are numbered from 0). +If no aliases are specified, the elements may be accessed by their position in the list, where the first item is assigned the position zero. -Unfortunately, neither `Object[]` nor `Tuple` lets us access an individual item in a result tuple of an HQL query without explicitly specifying the type of the item. -(Using a typecast in the case of `Object[]`, or by passing the class object to `get()` in the case of `Tuple`.) +Unfortunately, neither `Object[]` nor `Tuple` lets us access an individual item in a result tuple of an HQL query without explicitly specifying the type of the item, either using a typecast in the case of `Object[]`, or by passing the class object to `get()` in the case of `Tuple`. But there's another option, as we're about to see. Simplifying slightly, the BNF for a projected item is: @@ -175,13 +193,20 @@ Simplifying slightly, the BNF for a projected item is: [[select-item-bnf]] [source, antlrv4] ---- -include::{extrasdir}/select_item_bnf.txt[] +selection + : (expression | instantiation) alias? + +instantiation + : "NEW" instantiationTarget "(" selection ("," selection)* ")" + +alias + : "AS"? identifier ---- -where `instantiatiationArgs` is essentially a nested projection list. +Where the list of ``selection``s in an `instantiation` is essentially a nested projection list. So there's a special expression type that's only legal in the select clause: the `instantiation` rule in the BNF above. -Let's see what it does. +// Let's see what it does. [[select-new]] ==== Instantiation @@ -190,6 +215,7 @@ The `select new` construct packages the query results into a user-written Java c [[select-clause-dynamic-instantiation-example]] [source, java] +[%unbreakable] ---- record BookSummary(String title, String summary) {} @@ -216,6 +242,7 @@ Alternatively, using the syntax `select new map`, the query may specify that eac [[select-clause-dynamic-map-instantiation-example]] [source, java] +[%unbreakable] ---- List results = entityManager.createQuery("select new map(title as title, left(book.text, 200) as summary) from Book", @@ -224,12 +251,13 @@ List results = ---- The keys of the map are determined by the aliases given to the projected items in the select list. -If no aliases are specified, the key of an item is its position in the list (positions are numbered from 0). +If no aliases are specified, the key of an item is its position in the list, where the first item is assigned the position zero. Or, using the syntax `select new list`, the query may specify that each result should be packaged as a list: [[select-clause-dynamic-list-instantiation-example]] [source, java] +[%unbreakable] ---- List results = entityManager.createQuery("select new list(title as title, left(book.text, 200) as summary) from Book", @@ -242,7 +270,7 @@ List results = This is an older syntax, that predates JPQL. In hindsight, it's hard to see what advantage `List` offers compared to `Object[]`. We mention it here only for completeness. -On the other hand, `Map` is a perfectly fine alternative `Tuple`, but of course isn't portable to other implementations of JPA. +On the other hand, `Map` is a perfectly fine alternative `Tuple`, but of course it isn't portable to other implementations of JPA. ==== [[distinct]] @@ -295,18 +323,21 @@ The standard aggregate functions defined in both ANSI SQL and JPQL are these one [[aggregate-functions-example]] [source, hql] +[%unbreakable] ---- select count(distinct item.book) from Item as item where year(item.order.dateTime) = :year ---- [source, hql] +[%unbreakable] ---- select sum(item.quantity) as totalSales from Item as item where item.book.isbn = :isbn ---- [source, hql] +[%unbreakable] ---- select year(item.order.dateTime) as year, @@ -316,6 +347,7 @@ where item.book.isbn = :isbn group by year(item.order.dateTime) ---- [source, hql] +[%unbreakable] ---- select month(item.order.dateTime) as month, @@ -385,6 +417,7 @@ All aggregate functions support the inclusion of a _filter clause_, a sort of mi [[aggregate-functions-filter-example]] [source, hql] +[%unbreakable] ---- select year(item.order.dateTime) as year, diff --git a/documentation/src/main/asciidoc/querylanguage/extras/select_item_bnf.txt b/documentation/src/main/asciidoc/querylanguage/extras/select_item_bnf.txt index cc784601b7..fad7561f50 100644 --- a/documentation/src/main/asciidoc/querylanguage/extras/select_item_bnf.txt +++ b/documentation/src/main/asciidoc/querylanguage/extras/select_item_bnf.txt @@ -1,7 +1,8 @@ -(expression | instantiation) alias? +selection + : (expression | instantiation) alias? instantiation - : "NEW" instantiationTarget "(" instantiationArguments ")" + : "NEW" instantiationTarget "(" selection ("," selection)* ")" alias : "AS"? IDENTIFIER \ No newline at end of file