Skip to content
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

feat: fetch size for RefCursor #2976

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

davecramer
Copy link
Member

This replaces the original PR #925 originally written by @rhavermans It was easier to pull in the changes from his PR instead of rebasing the original. This is entirely his code.

We now support setting the fetch size on a RefCursor.

All Submissions:

  • Have you followed the guidelines in our Contributing document?
  • Have you checked to ensure there aren't other open Pull Requests for the same update/change?

New Feature Submissions:

  1. Does your submission pass tests?
  2. Does ./gradlew autostyleCheck checkstyleAll pass ?
  3. Have you added your new test classes to an existing test suite in alphabetical order?

Changes to Existing Features:

  • Does this break existing behaviour? If so please explain.
  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your core changes, as applicable?
  • Have you successfully run tests with your changes locally?

Comment on lines +2185 to +2188
// ignore request to set fetchSize to 0 if it is currently > 0
if (fetchSize > 0 && rows == 0) {
return;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please clarify why ignore the new fetch size?
I guess users might want to adjust fetchSize as they fetch, so it might be unexpected to ignore the new values.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you set the fetchSize to 0 here we will stop fetching more data.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should treat fetchSize like a hint rather than an "exact value to use for fetching". Then 0 would not cause "0 rows to fetch"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The initial value for fetchSize is 0, so ignoring setFetchSize(0) sounds arbitrary

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fetch size of zero is specifically called out as special If the fetch size specified is zero, the JDBC driver ignores the value and is free to make its own best guess as to what the fetch size should be.

pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java Outdated Show resolved Hide resolved
Comment on lines +2253 to +2269
if (refCursorName == null || refCursorName.isEmpty()) {
castNonNull(resultCursor, "resultCursor");
connection.getQueryExecutor()
.fetch(resultCursor, new CursorResultHandler(), fetchRows, adaptiveFetch);
// .fetch(...) could update this.cursor, and cursor==null means
// there are no more rows to fetch

// .fetch(...) could update this.cursor, and cursor==null means
// there are no more rows to fetch
closeRefCursor();
} else {
StringBuilder sb = new StringBuilder("FETCH FORWARD ");
sb.append(fetchRows);
sb.append(" IN ");
castNonNull(refCursorName, "refCursorName");
Utils.escapeIdentifier(sb, refCursorName);
final Query cursorForward = connection.getQueryExecutor().createSimpleQuery(sb.toString());
// pass maxRows=0 since fetchSize has already been corrected to account for maxRows
connection.getQueryExecutor().execute(cursorForward, null, new CursorResultHandler(), 0, fetchSize, QueryExecutor.QUERY_ONESHOT | QueryExecutor.QUERY_SUPPRESS_BEGIN
| QueryExecutor.QUERY_EXECUTE_AS_SIMPLE );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we can reuse the existing "extended protocol" fetch to fetch the data rather than build "fetch forward" commands dynamically

This replaces the original PR pgjdbc#925 originally written by @rhavermans
It was easier to pull in the changes from his PR instead of rebasing the original.
This is entirely his code.

We now support setting the fetch size on a RefCursor.
@davecramer
Copy link
Member Author

@vlsi I want to include this in the release. Thoughts ?

Comment on lines +2253 to +2263
if (refCursorName == null || refCursorName.isEmpty()) {
castNonNull(resultCursor, "resultCursor");
connection.getQueryExecutor()
.fetch(resultCursor, new CursorResultHandler(), fetchRows, adaptiveFetch);
// .fetch(...) could update this.cursor, and cursor==null means
// there are no more rows to fetch

// .fetch(...) could update this.cursor, and cursor==null means
// there are no more rows to fetch
closeRefCursor();
} else {
StringBuilder sb = new StringBuilder("FETCH FORWARD ");
sb.append(fetchRows);
sb.append(" IN ");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am afraid this change does not fix the issue.

It uses fetchRows from the base ResultSet rather than fetchRows of the ResultSet that is associated with refcursor in question.

In other words:

Imagine we call {? = call test_blob(?)}.
CallableStatement.execute would gather call results, and it would execute getObject at

Then it would delegate to PgResultSet.getObject -> internalGetObject -> getRefCursor, and it would effectively use fetchSize from a temporary resultset associated with {? = call test_blob(?)}.

I believe we should add a test to ensure fetch forward is triggered at all.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here are the logs from running

public void testFetchSize() throws SQLException {
    checkAndRunResult(3);
  }
2023-11-16 06:53:04.080 EST [84077] LOG:  duration: 0.136 ms  execute <unnamed>/C_1: select * from testspg__getRefcursor ($1)  as result
2023-11-16 06:53:04.080 EST [84077] DETAIL:  parameters: $1 = NULL
2023-11-16 06:53:04.080 EST [84077] LOG:  duration: 0.008 ms  parse <unnamed>: FETCH FORWARD 3 IN "<unnamed portal 1>"
2023-11-16 06:53:04.080 EST [84077] LOG:  duration: 0.002 ms  bind <unnamed>: FETCH FORWARD 3 IN "<unnamed portal 1>"
2023-11-16 06:53:04.080 EST [84077] LOG:  duration: 0.039 ms  execute <unnamed>: FETCH FORWARD 3 IN "<unnamed portal 1>"
2023-11-16 06:53:04.080 EST [84077] LOG:  duration: 0.002 ms  parse <unnamed>: FETCH FORWARD 3 IN "<unnamed portal 1>"
2023-11-16 06:53:04.080 EST [84077] LOG:  duration: 0.001 ms  bind <unnamed>: FETCH FORWARD 3 IN "<unnamed portal 1>"
2023-11-16 06:53:04.080 EST [84077] LOG:  duration: 0.003 ms  execute <unnamed>: FETCH FORWARD 3 IN "<unnamed portal 1>"
2023-11-16 06:53:04.081 EST [84077] LOG:  duration: 0.002 ms  parse <unnamed>: FETCH FORWARD 3 IN "<unnamed portal 1>"
2023-11-16 06:53:04.081 EST [84077] LOG:  duration: 0.002 ms  bind <unnamed>: FETCH FORWARD 3 IN "<unnamed portal 1>"
2023-11-16 06:53:04.081 EST [84077] LOG:  duration: 0.001 ms  execute <unnamed>: FETCH FORWARD 3 IN "<unnamed portal 1>"
2023-11-16 06:53:04.081 EST [84077] LOG:  duration: 0.006 ms  parse <unnamed>: CLOSE "<unnamed portal 1>"
2023-11-16 06:53:04.081 EST [84077] LOG:  duration: 0.001 ms  bind <unnamed>: CLOSE "<unnamed portal 1>"
2023-11-16 06:53:04.081 EST [84077] LOG:  duration: 0.002 ms  execute <unnamed>: CLOSE "<unnamed portal 1>"
2023-11-16 06:53:04.081 EST [84077] LOG:  duration: 0.005 ms  parse S_2: COMMIT
2023-11-16 06:53:04.081 EST [84077] LOG:  duration: 0.002 ms  bind S_2: COMMIT
2023-11-16 06:53:04.081 EST [84077] LOG:  duration: 0.006 ms  execute S_2: COMMIT

Comment on lines +183 to +185
if (fetchSize != null) {
call.setFetchSize(fetchSize);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Frankly speaking, I would not expect call.setFetchSize to be inherited when fetching the refcursor.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why not ?

Statement.setFetchSize() docs say

Gives the JDBC driver a hint as to the number of rows that should be fetched from the database when more rows are needed for ResultSet objects generated by this Statement. If the value specified is zero, then the hint is ignored. The default value is zero.
Params:
rows – the number of rows to fetch
Throws:
SQLException – if a database access error occurs, this method is called on a closed Statement or the condition rows >= 0 is not satisfied.

How else would they be used ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ResultSet.setFetchSize would be ignored though

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, however it is allowed.

Gives the JDBC driver a hint as to the number of rows that should be fetched from the database when more rows are needed for this ResultSet object. If the fetch size specified is zero, the JDBC driver ignores the value and is free to make its own best guess as to what the fetch size should be. The default value is set by the Statement object that created the result set. The fetch size may be changed at any time.

I don't see a way to change it once we have the statement. Does that currently work with portals ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants