jdaugherty opened a new pull request, #16693:
URL: https://github.com/apache/grails-core/pull/16693

   ### Summary
   
   Fixes #16692.
   
   A data service method returning an `Observable` of a scalar property 
projection, such as `Observable<String> findBookAuthor(String title)`, failed 
at runtime with `GroovyCastException: Cannot cast object 'Stephen King' with 
class 'java.lang.String' to class 'java.lang.Iterable'` from the generated 
transactional body.
   
   ### Root cause
   
   The RxJava adapters in `grails-datamapping-rx` wrap every synchronous 
implementer: `SingleResultAdapter` for the single-result ones and 
`ObservableResultAdapter` for the iterable ones. For a projection implementer, 
both adapters asked the implementer whether the *projected* type is compatible 
and neither checked that the method's return type is its own reactive type:
   
   - `SingleResultAdapter` accepted `Observable<String> findBookAuthor` for 
`FindOnePropertyProjectionImplementer`, so the body found one value while 
`RxScheduleIOTransformation` typed the transactional delegate as returning 
`Iterable<String>`. That is the cast in the issue.
   - `ObservableResultAdapter` accepted `Single<BookInfo> findByTitle` for 
`FindAllByInterfaceProjectionImplementer`, so the body built a list that then 
failed to cast to `BookInfo`.
   - `ObservableResultAdapter.implement` also took the Observable's generic 
type as the domain class of a property projection, so the one spelling only it 
could implement, `Observable<String> listBookAuthor(String title)`, failed at 
compile time with `No property exists on domain class [java.lang.String]`.
   
   ### Fix
   
   Each adapter now requires its own reactive return type before delegating to 
a projection implementer, and `ObservableResultAdapter` leaves the domain class 
alone for a projection. An `Observable` of a projection therefore lists every 
projected value and completes (empty when nothing matches), and a `Single` of a 
projection finds one, in line with how `Observable<Book>` and `Single<Book>` 
already behave.
   
   ### Tests
   
   - `RxServiceImplSpec` (`grails-datamapping-core-test`, against 
`SimpleMapDatastore`): `Observable<String> findBookAuthor` emits every matching 
author and completes empty without a match; `Single<BookInfo> findByTitle` 
returns the interface projection. Both failed before the fix with the cast 
exceptions above.
   - `ObservableServiceTransformSpec`: `Observable<String> findBookAuthor` and 
`listBookAuthor` are implemented by `FindAllPropertyProjectionImplementer`, 
`Single<IFoo> findByNameLike` by `FindOneByInterfaceProjectionImplementer`.
   - `SingleResultAdapterSpec` and `ObservableResultAdapterSpec`: each adapter 
declines a projection whose return type belongs to the other adapter, and the 
Observable adapter keeps the domain class for a property projection.
   
   Run locally on JDK 21: the four specs above (67 tests, 9 of them new), 
`:grails-datamapping-rx:codeStyle`, and the `asciidoctor` task of both 
Hibernate guides. The full suites are left to CI.
   
   ### Docs
   
   The RxJava section of the data services chapter in the Hibernate 5 and 
Hibernate 7 guides gains a paragraph on projections.
   
   ### Branches
   
   `grails-datamapping-rx` is commented out of `settings.gradle` on 7.0.x, 
7.2.x and 8.0.x, so 8.1.x is the earliest branch that ships the module and this 
fix; it merges up to 9.0.x and later. The RxJava 3 port in #16619 (9.0.x) 
touches a javadoc line in `ObservableResultAdapter` and will want a merge of 
9.0.x after this lands.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to