fix(sql): honor Statement.setMaxRows in the Avatica JDBC server - #19941
Open
kdelay wants to merge 1 commit into
Open
fix(sql): honor Statement.setMaxRows in the Avatica JDBC server#19941kdelay wants to merge 1 commit into
kdelay wants to merge 1 commit into
Conversation
DruidMeta.prepareAndExecute receives the row limit a JDBC client sets through Statement.setMaxRows(), but DruidJdbcStatement.execute() dropped it and built the result set with Long.MAX_VALUE, so the limit never took effect. Pass the received value through instead. Avatica maps "no limit" to a negative value before this point, and DruidJdbcResultSet already reads a negative limit as unlimited, so unlimited statements are unaffected. The prepared-statement path already passed its limit through. ResultFetcher marked a frame complete only when the yielder was exhausted. With a limit lower than the number of rows the query produces, the last in-limit frame reported done = false and every later fetch returned an empty, not-done frame, so the client never reached EOF. Mark the frame complete when the limit is reached as well. Prepared statements are still not covered: Avatica does not transmit PreparedStatement.getMaxRows() to the server (CALCITE-719), so the value never arrives at Druid.
FrankChen021
reviewed
Aug 9, 2026
FrankChen021
left a comment
Member
There was a problem hiding this comment.
I have reviewed the code for correctness, edge cases, concurrency, and integration risks; no issues found.
Reviewed 3 of 3 changed files.
Validation: git diff --check passed; tests and builds were not run.
This is an automated review by Codex GPT-5.6-Luna(max)
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.
Fixes #19918.
Description
A JDBC client that calls
Statement.setMaxRows(n)still gets every row the query produces. Avatica sends the value toDruidMeta.prepareAndExecuteasmaxRowCount, butDruidJdbcStatement.execute()discarded it and constructed the result set withLong.MAX_VALUE.Passed the received row limit through
DruidJdbcStatement.execute()now handsmaxRowCounttoDruidJdbcResultSetinstead ofLong.MAX_VALUE.DruidJdbcPreparedStatementalready did this, so this makes the two statement types consistent.Unlimited statements are unaffected.
AvaticaStatement.executeInternalconverts the JDBC "no limit" value to-1before the request leaves the client (maxRowCount <= 0 ? -1 : maxRowCount), andDruidJdbcResultSet.execute()already reads a negative limit asInteger.MAX_VALUE. So the only values that reach the fetcher as a real cap are the ones the user asked for.Marked a frame complete when the row limit is reached
DruidJdbcResultSet.ResultFetcher.call()setMeta.Frame.donefromyielder.isDone()alone. When the limit is lower than the number of rows the query produces, the yielder is not exhausted at the limit, so the last in-limit frame reporteddone = false. The next fetch then computesbatchLimit = min(limit - offset, batchSize) = 0, returns an empty frame that is again not done, and the client keeps fetching forever. The frame is now also complete onceoffset + rowCountreaches the limit.Not covered: prepared statements
The reporter's example uses a
PreparedStatement, and that path still does not honor the limit. Avatica callsMeta.prepare(..., -1)when the statement is created and passes only the fetch size at execution time, soPreparedStatement.getMaxRows()never reaches the server. That is CALCITE-719, still open upstream. Adding an explicitLIMITto the SQL remains the workaround there. This PR fixes what Druid can fix on its own.Release note
The Avatica JDBC server now honors
Statement.setMaxRows(). Previously the setting was ignored and the full result was returned.PreparedStatement.setMaxRows()is still not honored because the value is not transmitted to the server (CALCITE-719).Key changed/added classes in this PR
DruidJdbcStatementDruidJdbcResultSetThis PR has:
Two tests were added to
DruidStatementTest:testMaxRowCountDirect(limit reached inside the first frame) andtestMaxRowCountSplitOverTwoFramesDirect(limit reached in a later frame, which is the case that used to hang).Local run on this branch, JDK 26 on macOS:
DruidStatementTest16/16 andDruidAvaticaHandlerTest45/45 pass.AvaticaModuleTestand one case each inDruidAvaticaJsonHandlerTestandDruidAvaticaProtobufHandlerTesterror withFailed to mock class org.apache.druid.server.DruidNode; those reproduce identically on an unmodifiedmastercheckout here, so they are a local JDK/EasyMock issue rather than a result of this change.mvn checkstyle:check -pl sqlreports 0 violations.