New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Offer Result.intoMap(), intoGroups(), intoSet() functionality as Collector #9288
Comments
Let me first see if I got this straight: While some people regard jOOQ as an ORM, it is certainly foremost a query builder. And since ORMs (or FRMs) have their place, it would also make sense for jOOQ to provide more value in this area, as currently jOOQ more or less only makes it easy to map a single tuple to a single Java object. (Mapping a tuple representing a chain of one-to-one relations to a corresponding chain of connected Java objects also works quite well.) AFAICT what customers most want is a simple way to load a given set of rows / tuples into a "corresponding" collection of "connected" Java objects (e.g. an aggregate with a one-to-many relation like customer-address). And for simple use cases (e.g. only one-to-many relations (with foreign keys) and one-to-one mappings between tables and classes), jOOQ should make this dead simple, while it should also be more or less easy to achieve this goal for more complex scenarios. In jOOQ we want to implement this functionality on top of the Since the new API will somehow need to capture how the rows and columns get mapped to objects and properties (also references), I am wondering if we should also design for the use case of allowing the actual |
The paradigm I think suits most here is: jOOQ is SQL. The fact that there's a query builder is secondary, even if it does appear to be the primary feature. But I think it's better to think of jOOQ = SQL, and derive all other discussion from that paradigm.
jOOQ does add more value in this area where we do not oppose the philosophies of SQL. I personally think the "third manifesto" didn't get enough traction outside of Oracle and PostgreSQL yet, and even there, it didn't find the adoption it deserves. The ideal way to implement what I believe most customers want is to support Apart from that, additional client side convenience that works in similar fashions is definitely desireable as well, as long as it does not oppose SQL. By that, I mean we're mainly working on sets of tuples (values), not graphs of entities (objects). As long as we avoid introducing the notion of a record or object "identity", we will be able to "keep it simple". Having said so, yes, we definitely need much more convenience to produce hierarchical data structures from queries.
Yes, of course. But the difficulty will always be the mismatch between the denormalising join operator, and what customers actually wanted, the multiset operator. I don't think it is possible to re-connect denormalised tuples to what they were intended to be. I would really like to avoid any cleverness around that, because it breaks apart very easily. I think it is important to make sure everyone using jOOQ (or SQL) knows that a join is a filtered cartesian product, not a way to nest collections. However, as the popularity of
Yes, I think that we should still also offer convenience on This will limit new API features to the Java 8+ distributions, which I think is OK.
You just re-invented my But some emulations are possible as well. E.g. embeddable types (#2530), which are going to be worked on as well in jOOQ 3.13 are nothing but an emulation of a nested record. Another recent idea was this one: #9126. Another way to declare a nested record. This is not opposed to the ideas of the SQL language. In PostgreSQL and Oracle, we can create "object types" / "record types", which then allow for things like: -- Oracle
SELECT my_object_type(a, b, c) FROM t
-- PostgreSQL
SELECT (a, b, c)::my_object_type FROM t It is perfectly reasonable to 1:1 map Java DTO / POJO types onto such nested record types in SQL, and provide convenience around that, using reasonable API. The main risk here is that we'll be doing this in the After SummaryThis issue was originally about more powerful client side data collection constructs (does not affect or interact with the SQL query, but operates on the There are other issues that talk about deriving such collection algorithms from a more powerful way to express the
|
I have been thinking if it could make sense to define the projection as part of the jOOQ DSL API, using some kind of mini DSL extension. Possibly something along the lines of this: project(BOOK.ID, BOOK.TITLE)
.using(jpaProjection(Book.class))
.from(BOOK); Possibly even allow projecting nested records: project(BOOK.ID, BOOK.TITLE,
project(AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME)
.using(jpaProjection(Author.class)))
.using(jpaProjection(Book.class))
.from(BOOK)
.join(AUTHOR).on(BOOK.AUTHOR_ID.eq(AUTHOR.ID)); The |
People like jOOQ because it mimicks SQL almost perfectly (unless the Java language prevents it, e.g. with CTE or derived tables). Replacing the This is just me criticising the suggested execution here. We could definitely profit from supporting additional projection capabilities. See also the suggestion here: #9126 In other words, we're not gaining anything from avoiding the SQL keywords I'm sure we can implement this functionality entirely by enhancing the existing Besides, I really think we should not get distracted too much by what JPA is offering here. JPA has a major flaw:
The workaround is to use While I'm not excluding adding value in this area in the future, our main focus here should not be on JPA, but on the SQL view of how collections can be nested. |
Makes sense. Of course some kind of
I have seen that feature request, which is of course related. One of the proposals there was to add a
Right. Using RVEs to help map to nested Java objects sounds interesting. I have never seen this done before, but it could be a good fit.
The It however seems like my comment should instead be on #9126. |
Yes, exactly. I think that's where we might be heading. But the more cleverness we try to implement, the more brittle this will be in edge cases that depend on the actual
Yes, I already regret that API proposition...
There's native support in Oracle (with named types) and PostgreSQL. I've done this quite a few times in Oracle, myself...
All potential cleverness here will ultimately run against a wall (again), when users expect nested collections to be supported. Both of these APIs, as well as your reference to JOLO, suggest that such nested collections would be supported, just from reverse engineering some client data structure. I think that's just not going to be good enough. The same is true for #9126, btw. If we do support nested collections, we should go "all in" ans support Irrespective of this discussion...
... yes indeed. This issue here can be implemented regardless of how we start supporting nested records and collections in the jOOQ API. This issue here is only about post-processing whatever the SQL query and jOOQ's built in mapping algorithms produce. |
The scope of this issue here becomes more clear in the context of numerous improvements that have been done for jOOQ 3.15, including the most important ones:
It is now trivial to see that all of the current convenience mapping on For example: public static final <K, V, R extends Record2<K, V>> Collector<R, ?, Map<K, V>> toMap() {
return toMap(Record2::value1, Record2::value2);
}
public static final <K, R extends Record> Collector<R, ?, Map<K, R>> toMap(Function<? super R, ? extends K> keyMapper) {
return toMap(keyMapper, r -> r);
}
public static final <K, V, R extends Record> Collector<R, ?, Map<K, V>> toMap(
Function<? super R, ? extends K> keyMapper,
Function<? super R, ? extends V> valueMapper
) {
return Collectors.toMap(
keyMapper,
valueMapper,
(k1, k2) -> {
throw new InvalidResultException("Key " + k1 + " is not unique in Result");
},
LinkedHashMap::new
);
} These are just simple utilities built on top of JDK
It's not more than that. An example: record Book(int id, String title) {}
record Author(int id, String firstName, String lastName) {}
Map<Book, Author> books =
create().select(
row(
T_BOOK.ID,
T_BOOK.TITLE
).mapping(Book::new),
row(
T_BOOK.author().ID,
T_BOOK.author().FIRST_NAME,
T_BOOK.author().LAST_NAME
).mapping(Author::new)
)
.from(T_BOOK)
.orderBy(T_BOOK.ID)
.collect(Records.toMap()); All of the types and mappings are declared only once. There is no more need to re-declare columns as One additional benefit of using All of these ideas also work very well with the current working drafts of creating nested collections ( |
The naming conflicts with |
Also, when someone reads the code that has |
I'll mark this as an incompatible change (of behaviour), because if we re-use the new |
Or even better, we can be backwards compatible by creating the intermediary New users will then have the performance benefit, and power users who require an |
- In Result.intoSet() - In Result.getValues() - In ResultQuery.fetch() - In ResultQuery.fetchSet()
This includes: - [#3619] Result.intoGroups() and intoMap() do not run through RecordListener
We should have a composable, possibly
Collector
based API to collect complex results into nested DTO projections, etc. An example is this support request here:#8597
The goal (among others) would be to produce nested POJOs, such as for example:
Perhaps, some inspiration can be found in SimpleFlatMapper: https://simpleflatmapper.org/0106-getting-started-jooq.html
This issue will be split into a set of subtasks as we evaluate what steps are necessary to implement what users find most missing in this area, right now. It is also used as a host for discussions around the issue.
Tasks
Result<R>.collect(Collector<? super R, A, X>): X
<E, R extends Record1<E>> Records.intoArray(): Collector<R, ?, E[]>
<K, V, R extends Record2<K, V>> Records.intoGroups(): Collector<R, ?, Map<K, List<V>>>
<E, R extends Record1<E>> Records.intoList(): Collector<R, ?, List<E>>
<K, V, R extends Record2<K, V>> Records.intoMap(): Collector<R, ?, Map<K, V>>
<E, R extends Record1<E>> Records.intoSet(): Collector<R, ?, Set<E>>
Result
implementations to useResult::collect
ResultQuery
implementation to userResultQuery::collect
RecordType<R>
reference may not be available before actually executing the query, so constructing the mapper must be delayedResult.intoMap
andResultQuery.fetchMap()
forField<?>[]
and other array parameter types, see: Change Result.intoMap(Field[], Class) to return Map<Record, E> instead of Map<List<?>, E> #2601. We won't break compatibility yet, but handle this internallyResult.getValues(int, Class)
and related methodsTo avoid conflicts with other statically imported collectors from
java.util.stream.Collectors
, such astoList()
, ours will be prefixedinto
, e.g.intoMap()
, just like theResult.intoMap()
methods they represent / replace.Interactions with
ExecuteListener
While the
Result
refactorings are backwards compatible, refactoring existingResultQuery
implementations are not, if we skip firing theExecuteListener::resultStart
andExecuteListener::resultEnd
events. To allow for reverting to the current behaviour, let's offer a newSettings
that governs whether we create the intermediateResult
data structure: #11892, the default being not to create it if not explicitly requestedThe text was updated successfully, but these errors were encountered: