Skip to content

Reuse the JDBC statement of a prepared statement in PgProtocolHandler - #767

Open
trsb-standards wants to merge 1 commit into
Convex-Dev:developfrom
trsb-standards:fix-pgwire-prepared-statement-reuse
Open

trsb-standards wants to merge 1 commit into
Convex-Dev:developfrom
trsb-standards:fix-pgwire-prepared-statement-reuse

Conversation

@trsb-standards

Copy link
Copy Markdown
Contributor

Execute prepares a new JDBC statement for every Bind+Execute and closes it right after, so a statement the client prepared once is parsed, validated and planned by Calcite again on every execution. The extended query protocol exists to avoid that, and the PostgreSQL JDBC driver relies on it once a statement passes its prepare threshold.

This keeps the JDBC statement for each statement from Parse, made on its first Execute. Later executions clear and bind the parameters and run the same statement again. It is closed when:

  • its statement is closed (Close 'S');
  • a Parse replaces the statement under the same name, which also covers the unnamed statement;
  • the connection ends.

The cache is keyed by statement identity, so a statement parsed again under the same name never reuses the old one.

This was offered as item 4 after #754, which it needed: before #754, a numeric parameter from the JDBC driver failed outright.

Measured with the PostgreSQL JDBC driver: a SELECT by key over a 100-row table, one prepared statement executed 1000 times (prepareThreshold=1), third round:

  • before: about 8.4 ms per execution;
  • after: about 0.26 ms per execution.

Tests (in PgServerTest):

  • testPreparedStatementPreparedOnce: 12 executions of one named statement prepare one JDBC statement on the server's connection. A connection proxy counts prepareStatement calls.
  • testStatementParsedAgainUnderSameName: with prepareThreshold=0 the driver sends every execution as the unnamed statement. Two different queries alternate on it, each returns its own rows, and each Parse prepares anew.

./mvnw -B test -pl convex-db -am -Dtest='convex.db.**': 192 tests, all pass.

One behavioural note: a statement that stays prepared keeps the plan it was prepared with. For a table that changes shape while a client holds a statement on it, this is the same as with a JDBC PreparedStatement used directly.

🤖 Generated with Claude Code

Execute prepared a new JDBC statement for every Bind+Execute and closed it
again afterwards, so a statement the client prepared once was parsed,
validated and planned by Calcite again on every execution.

Keep the JDBC statement for each statement from Parse, made on its first
Execute: later executions clear and bind the parameters and run it again.
It is closed when its statement is closed (Close), replaced by a Parse
under the same name, or when the connection ends. Keyed by identity, so a
statement parsed again under the same name never reuses the old one.

A SELECT by key over the PostgreSQL JDBC driver, 1000 executions of one
prepared statement: about 8.4 ms per execution before, 0.26 ms after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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.

1 participant