Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ruby.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ jobs:

services:
postgres:
image: postgres:11
image: postgres:14
env:
POSTGRES_PASSWORD: postgres
POSTGRES_HOST_AUTH_METHOD: trust
Expand Down Expand Up @@ -211,7 +211,7 @@ jobs:

services:
postgres:
image: postgres:11
image: postgres:14
env:
POSTGRES_PASSWORD: postgres
POSTGRES_HOST_AUTH_METHOD: trust
Expand Down
1 change: 1 addition & 0 deletions lib/arjdbc/postgresql/adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
25 changes: 25 additions & 0 deletions lib/arjdbc/postgresql/pg_compat.rb
Original file line number Diff line number Diff line change
@@ -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
62 changes: 62 additions & 0 deletions src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 <code>PG::Connection#transaction_status</code>. 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 <code>PG::Connection#status</code> (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 <code>PG::Connection#async_exec</code>; 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);
Expand Down
Loading