diff --git a/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java b/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java index 7b7383c7c..76498f676 100644 --- a/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +++ b/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java @@ -173,6 +173,21 @@ protected final IRubyObject beginTransaction(final ThreadContext context, final // NOTE: only reversed order - just to ~ match how Rails does it : /* if ( connection.getAutoCommit() ) */ connection.setAutoCommit(false); if ( isolation != null ) setTransactionIsolation(context, connection, isolation); + // pgjdbc's setAutoCommit(false) only flips a client-side flag; the + // wire-level BEGIN is deferred until the next statement is sent + // (QueryExecutorImpl#sendQueryPreamble). Rails 7.1+ lazy transactions + // assume begin_db_transaction opens a server-side transaction once + // materialized, so flush the deferred BEGIN now with a no-op statement + // - pgjdbc prepends BEGIN to it in the same round-trip, matching the + // cost of pg's exec("BEGIN"). + Statement statement = null; + try { + statement = connection.createStatement(); + statement.execute("SELECT 1"); + } + finally { + close(statement); + } return context.nil; } diff --git a/test/db/postgresql/transaction_test.rb b/test/db/postgresql/transaction_test.rb index 74e087d14..52c34e3dc 100644 --- a/test/db/postgresql/transaction_test.rb +++ b/test/db/postgresql/transaction_test.rb @@ -8,6 +8,20 @@ def test_supports_savepoints assert_true ActiveRecord::Base.connection.supports_savepoints? end + # pgjdbc defers the wire-level BEGIN until the next statement is sent, but + # AR 7.1+ lazy transactions expect begin_db_transaction to actually open a + # server-side transaction (e.g. materialize_transactions), so it must flush + # the deferred BEGIN right away. + def test_begin_db_transaction_opens_transaction_on_the_server + connection = ActiveRecord::Base.connection + assert_equal 'IDLE', connection.jdbc_connection(true).transaction_state.to_s + + connection.begin_db_transaction + assert_equal 'OPEN', connection.jdbc_connection(true).transaction_state.to_s + ensure + connection.rollback_db_transaction + end + # @override def test_releasing_named_savepoints omit 'savepoins not supported' unless @supports_savepoints