Run a Kotlin result flow's query when it is collected, and read windows as one flow of rows - #590
Merged
Merged
Conversation
…windows as one flow of rows A resultFlow opened its result stream as the flow was built, so building a second flow, or issuing any statement, inside a transaction before the first was read to its end was refused by the stream guard, which is how two flows passed as the arguments of one call failed. The flow is now cold, as its documentation states: the statement runs when collection starts, closes when collection completes, fails or is cancelled, and runs again on each collection. windows(size).rows() reads the windows as one Flow<R> with the connection free at every row, the one-token switch for a resultFlow loop the guard refuses; the guard names it beside the Java form windows(size).flatMap(Slice::stream). The batched stream reads selectById(Stream) and selectByRef(Stream) document that no query runs until the stream is consumed. The skills and batch-streaming page carry both, and a design-notes page records the reasoning behind the two shapes.
…ds on the transaction's connection
…s carry what is theirs
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
A Kotlin
resultFlowopened its result stream as the flow was built (resultStream.consumeAsFlow()), while its documentation said the flow was cold. Since 1.14.1 the stream guard refuses a statement on a connection whose stream still has rows, so building a second flow inside a transaction before the first was read to its end was refused, as was any other statement in between. Two repository flows passed as the arguments of one call, the natural way to hand reads to a pure function, failed on the second argument on every database.Cold flows.
resultFlowon the query builder, andresultFlow,getResultFlowandgetRefFlowon a template query, go through onestreamFlow { }helper: the statement runs when collection starts, rows are read as they are emitted, the statement closes when collection completes, fails or is cancelled, and each collection runs the query again. A flow that is built and not yet collected holds nothing, so several may be built up front and collected in turn, and a flow that is never collected leaves the connection free.windowsis left as it was: it already fetches nothing until collected, and wrapping it would move its argument checks from call time to collect time. The JavaStreamstays eager, as its Javadoc states and as JDBC libraries do; the caller closes it.windows(size).rows(). AFlow<Window<R>>extension that emits the windows' rows as one flow. Every window's statement has closed before its rows are emitted, so the connection is free at every row, memory stays bounded by the window size and the rows arrive in key order. It is the one-token switch for aresultFlowloop the guard refuses; the guard's message names it, with the Java formwindows(size).flatMap(Slice::stream). Where a loop writes, the per-window form still batches better, and the docs say so.Javadoc.
selectById(Stream)andselectByRef(Stream)on the repositories claimed to run their query when called; they run each batch's query as the stream reaches it, and none when it is never consumed. Twelve sites across core and java21 now say that.Docs and skills.
batch-streaming.mdstates the cold-flow consequences and both flat forms. A newdocs/streaming-design.mdunder Internals records the reasoning: the driver matrix that makes the constraint a protocol fact, what other frameworks do, the options considered and rejected, why two explicit shapes with one rule everywhere, and why the flow is cold while the stream is not. The Kotlin and Java query and repository skills andstorm-rulescarry the cold flow and the flat forms.Related Issue
None filed; found through a production failure in a downstream application (a reach-and-frequency computation handing
studyUsers::findByStudyandstudyExposures::findByStudyto one function insidetransaction { }).Type of Change
Checklist
Module(s) Affected
storm-kotlin (flows,
rows()), storm-core (guard message, Javadoc), storm-java21 (Javadoc), docs, website skills and sidebar.Breaking Changes
None in the API. Behaviour a caller may notice, recorded in the changelog's upgrade note: a query failure surfaces from
collectrather than from the line that built the flow; a flow collected twice runs its query twice; a flow built inside a transaction but collected outside it reads outside the transaction, on a connection of its own.Verification
FlowTest: two flows built inside a transaction and collected one after the other; a flow built and never collected leaves the connection free; a flow collected twice;rows()yields the ids in key order across windows; a count and a remove at every row ofwindows(5).rows()inside a transaction. InQueryDefaultsTest: the template query's three flows open no stream until collected, one per collection, and close it. The first three fail on the previous code with the guard's message or "can be collected only once".storm-kotlinsuite (1,700 before therows()tests, 1,702 after) green;StreamGuardIntegrationTest, java21RepositoryTestandSpringTransactionBridgeTestgreen after the message change.generate-llms-full.shdrift check passes with the new page in the sidebar and the DOCS list.