Subject: [pgjdbc/pgjdbc] issue #3993: fix Enable cursor-based bug, when start transaction by 'START TRANSAC'

From: "qinarmy-zoro (@qinarmy-zoro)" <[email-masked]>
Date: Mon, 23 Mar 2026 10:19:27 +0000

Please read https://stackoverflow.com/help/minimal-reproducible-example 

**Describe the issue**
A clear and concise description of what the issue is.

**Driver Version?** 
42.7.10

**Java Version?**
all
**OS Version?**
all 
**PostgreSQL Version?**
18.3
**To Reproduce**
Steps to reproduce the behaviour:
I reproduce , see https://github.com/PillArmy/army/blob/master/army-example/src/test/java/io/army/temp/PostgreBugReproduce.java

**Expected behaviour**
A clear and concise description of what you expected to happen.
And what actually happens
// org.postgresql.jdbc.PgStatement.executeInternal()
   // no ->  flags |= QueryExecutor.QUERY_FORWARD_CURSOR;

**Logs**
If possible PostgreSQL logs surrounding the occurrence of the issue
Additionally logs from the driver can be obtained adding

Using the following template code make sure the bug can be replicated in the driver alone.
```
package io.army.temp;

import com.alibaba.druid.pool.DruidDataSource;
import org.testng.annotations.Test;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;

public class PostgreBugReproduce {

    @Test
    public void reproduce() throws Exception {
        try (DruidDataSource db = createDataSource()) {
            try (Connection conn = db.getConnection()) {
                try (Statement stmt = conn.createStatement()) {
                    stmt.executeUpdate(ddl());
                    stmt.executeUpdate("START TRANSACTION READ ONLY");
                    System.out.printf("autoCommit is %s after start transaction%n", conn.getAutoCommit());

                    stmt.setFetchSize(1);
                    // org.postgresql.jdbc.PgStatement.executeInternal()
                    // no ->  flags |= QueryExecutor.QUERY_FORWARD_CURSOR;
                    try (ResultSet rs = stmt.executeQuery("SELECT u.id,u.name FROM u_user as u ORDER BY u.id")) {
                        while (rs.next()) {
                            System.out.printf("id:%s ; name : %s%n", rs.getLong(1), rs.getString(2));
                        }
                    }
                }

            }
        }

    }

    private static String ddl() {
        return """
                create table IF NOT EXISTS u_user
                (
                    id                 BIGSERIAL       not null primary key,
                    name varchar(15)  not null 
                );
                
                """;
    }

    private static DruidDataSource createDataSource() {

        final String url;
        url = "jdbc:postgresql://localhost:5432/postgres";

        final Properties properties = new Properties();
        properties.put("user", "army_w");
        properties.put("password", "army123");

        final DruidDataSource ds;
        ds = new DruidDataSource();

        ds.setUrl(url);
        ds.setDriverClassName(org.postgresql.Driver.class.getName());
        ds.setConnectProperties(properties);

        ds.setInitialSize(10);
        ds.setMaxActive(200);
        ds.setMaxWait(27L * 1000L);
        ds.setValidationQuery("SELECT 1 ");

        ds.setTestOnBorrow(Boolean.FALSE);
        ds.setTestWhileIdle(Boolean.TRUE);
        ds.setTestOnReturn(Boolean.TRUE);
        ds.setTimeBetweenEvictionRunsMillis(5L * 1000L);

        ds.setRemoveAbandoned(Boolean.FALSE);
        ds.setMinEvictableIdleTimeMillis(30000L);
        return ds;
    }

}

```

---

From: "qinarmy-zoro (@qinarmy-zoro)" <[email-masked]>
Date: Mon, 23 Mar 2026 10:19:46 +0000

more see https://github.com/pgjdbc/pgjdbc/pull/3980

---

From: "davecramer (@davecramer)" <[email-masked]>
Date: Mon, 23 Mar 2026 10:22:06 +0000

Does this problem exist if you use setAutoCommit(false) and setReadonly(true)

---

From: "qinarmy-zoro (@qinarmy-zoro)" <[email-masked]>
Date: Mon, 23 Mar 2026 10:26:35 +0000

> Does this problem exist if you use setAutoCommit(false) and setReadonly(true)

not exists, but you should not restrict how developers use it, as long as their usage is legal, reasonable, and supported by the database.  Do you agree with me?

---

From: "davecramer (@davecramer)" <[email-masked]>
Date: Mon, 23 Mar 2026 10:35:35 +0000

I suppose not, but if you aren't going to use the API, why would you expect the rest of the API to work ?

---

From: "qinarmy-zoro (@qinarmy-zoro)" <[email-masked]>
Date: Mon, 23 Mar 2026 10:48:39 +0000

> I suppose not, but if you aren't going to use the API, why would you expect the rest of the API to work ?

Why can't I use the START TRANSACTION command while using a cursor? Does PostgreSQL prohibit me from using it?

Is it unreasonable to add a single line of code just to check if the current connection is inside a database transaction block？

---

From: "qinarmy-zoro (@qinarmy-zoro)" <[email-masked]>
Date: Mon, 23 Mar 2026 10:54:20 +0000

> Is it unreasonable to add a single line of code just to check if the current connection is inside a database transaction block？

It is incorrect to judge the transactional state of a connection using only getAutoCommit(), as it is unreliable. The accurate approach is to verify if connection.getQueryExecutor().getTransactionState() == TransactionState.OPEN

---

From: "davecramer (@davecramer)" <[email-masked]>
Date: Mon, 23 Mar 2026 11:17:16 +0000

No, it's fine. Just add tests for it before and after any operations on the connection