From 135b7d71c16075da6ec5e9e94615adbddd9e933f Mon Sep 17 00:00:00 2001 From: John Bolliger Date: Thu, 25 Jun 2026 13:30:59 -0700 Subject: [PATCH 1/2] Expose PG-compatible status/transaction_status/async_exec on JDBC connection ActiveRecord's native PostgreSQL reconnect/retry machinery (e.g. #retryable_query_error? checks transaction_status against PQTRANS_INERROR) and the Rails test-suite helper #remote_disconnect call PG::Connection methods on the raw connection, which under JRuby is a PostgreSQLJdbcConnection - not a PG::Connection. They raised NoMethodError / 'uninitialized constant PG'. Implement #status, #transaction_status (read from the driver's protocol-level BaseConnection#getTransactionState) and #async_exec on the JDBC connection, and define the libpq ::PG::PQTRANS_* / ::PG::CONNECTION_* constants AR references (only when the real pg gem is absent). Co-Authored-By: Claude Opus 4.8 (1M context) --- lib/arjdbc/postgresql/adapter.rb | 1 + lib/arjdbc/postgresql/pg_compat.rb | 25 ++++++++ .../PostgreSQLRubyJdbcConnection.java | 62 +++++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 lib/arjdbc/postgresql/pg_compat.rb diff --git a/lib/arjdbc/postgresql/adapter.rb b/lib/arjdbc/postgresql/adapter.rb index df059b063..6f8df4bd7 100644 --- a/lib/arjdbc/postgresql/adapter.rb +++ b/lib/arjdbc/postgresql/adapter.rb @@ -22,6 +22,7 @@ require 'arjdbc/postgresql/base/array_decoder' require 'arjdbc/postgresql/base/array_encoder' require 'arjdbc/postgresql/name' +require 'arjdbc/postgresql/pg_compat' require 'arjdbc/postgresql/database_statements' require 'arjdbc/postgresql/schema_statements' require "arjdbc/postgresql/adapter_hash_config" diff --git a/lib/arjdbc/postgresql/pg_compat.rb b/lib/arjdbc/postgresql/pg_compat.rb new file mode 100644 index 000000000..a8bcd5883 --- /dev/null +++ b/lib/arjdbc/postgresql/pg_compat.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +# Minimal subset of the libpq constants that ActiveRecord's native PostgreSQL +# code paths reference as ::PG::* (e.g. #retryable_query_error? checks +# transaction_status against PQTRANS_INERROR, and the Rails test-suite helper +# +remote_disconnect+ uses CONNECTION_BAD / PQTRANS_INTRANS). +# +# The matching #status / #transaction_status / #async_exec methods are defined +# on the JDBC connection (PostgreSQLRubyJdbcConnection) in the Java extension. +# +# Only defined when the real pg gem is absent (i.e. under JRuby). +unless defined?(PG) + module PG + # PQtransactionStatus + PQTRANS_IDLE = 0 # connection idle, no transaction open + PQTRANS_ACTIVE = 1 # command in progress + PQTRANS_INTRANS = 2 # idle, within a transaction block + PQTRANS_INERROR = 3 # idle, within a failed transaction block + PQTRANS_UNKNOWN = 4 # connection is bad / state cannot be determined + + # PQstatus (subset that AR uses) + CONNECTION_OK = 0 + CONNECTION_BAD = 1 + end +end diff --git a/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java b/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java index ded69a537..193846517 100644 --- a/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +++ b/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java @@ -214,6 +214,68 @@ public IRubyObject database_product(final ThreadContext context) { }); } + // libpq PQtransactionStatus codes (see PG::PQTRANS_* / pg_compat.rb). + private static final int PQTRANS_IDLE = 0; + private static final int PQTRANS_INTRANS = 2; + private static final int PQTRANS_INERROR = 3; + private static final int PQTRANS_UNKNOWN = 4; + // libpq PQstatus codes (see PG::CONNECTION_* / pg_compat.rb). + private static final int CONNECTION_OK = 0; + private static final int CONNECTION_BAD = 1; + + /** + * Mirrors PG::Connection#transaction_status. ActiveRecord's + * #retryable_query_error? distinguishes PQTRANS_INERROR from other states, + * so we read the JDBC driver's protocol-level transaction state (exposed + * only on the internal BaseConnection; PGConnection has no equivalent). + */ + @JRubyMethod(name = "transaction_status") + public IRubyObject transaction_status(final ThreadContext context) { + // getTransactionState() is a local protocol-state read (no server + // round-trip), so use the plain accessor rather than withConnection's + // retry/reconnect machinery; a missing/closed connection or any error + // falls through to PQTRANS_UNKNOWN (matching libpq). + final Connection connection = getConnection(false); + try { + if (connection != null) { + final org.postgresql.core.BaseConnection base = + connection.unwrap(org.postgresql.core.BaseConnection.class); + switch (base.getTransactionState()) { + case IDLE: return context.runtime.newFixnum(PQTRANS_IDLE); + case OPEN: return context.runtime.newFixnum(PQTRANS_INTRANS); + case FAILED: return context.runtime.newFixnum(PQTRANS_INERROR); + } + } + } + catch (SQLException e) { /* fall through to unknown */ } + return context.runtime.newFixnum(PQTRANS_UNKNOWN); + } + + /** + * Mirrors PG::Connection#status (CONNECTION_OK / CONNECTION_BAD). + * A closed connection reports CONNECTION_BAD so AR / test helpers reconnect. + */ + @JRubyMethod(name = "status") + public IRubyObject status(final ThreadContext context) { + final Connection connection = getConnection(false); + try { + if (connection != null && !connection.isClosed()) { + return context.runtime.newFixnum(CONNECTION_OK); + } + } + catch (SQLException e) { /* treat as bad below */ } + return context.runtime.newFixnum(CONNECTION_BAD); + } + + /** + * Mirrors PG::Connection#async_exec; under JDBC the statement + * runs synchronously. Used by Rails' +remote_disconnect+ test helper. + */ + @JRubyMethod(name = "async_exec", required = 1) + public IRubyObject async_exec(final ThreadContext context, final IRubyObject sql) { + return execute(context, sql); + } + @JRubyMethod public IRubyObject exec_params(ThreadContext context, IRubyObject sql, IRubyObject binds) { return execute_prepared_query(context, sql, binds, null); From 3577de65ad4e6c8fb6f9f2cc6e972c3a4f6d9561 Mon Sep 17 00:00:00 2001 From: John Bolliger Date: Thu, 25 Jun 2026 13:31:00 -0700 Subject: [PATCH 2/2] CI: bump PostgreSQL service image 11 -> 14 PostgreSQL 11 is EOL and lacks pg_current_xact_id() (added in PG 13), which AR 7.2's test suite uses - causing a cascade of 'function does not exist' / 'current transaction is aborted' errors in the Postgres jobs. Move to a supported release. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/ruby.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index a74557bd5..5213690d1 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -84,7 +84,7 @@ jobs: services: postgres: - image: postgres:11 + image: postgres:14 env: POSTGRES_PASSWORD: postgres POSTGRES_HOST_AUTH_METHOD: trust @@ -211,7 +211,7 @@ jobs: services: postgres: - image: postgres:11 + image: postgres:14 env: POSTGRES_PASSWORD: postgres POSTGRES_HOST_AUTH_METHOD: trust