Обсуждение: BUG #19350: Short circuit optimization missed when running sql scriptes in JDBC

Поиск
Список
Период
Сортировка

BUG #19350: Short circuit optimization missed when running sql scriptes in JDBC

От
PG Bug reporting form
Дата:
The following bug has been logged on the website:

Bug reference:      19350
Logged by:          Chi Zhang
Email address:      798604270@qq.com
PostgreSQL version: 17.6
Operating system:   ubuntu 24.04 with docker
Description:

Hi,

In the following test case, if I run it in JDBC, the prepared statement will
trigger a divide by zero error. But if run it in command line, there will
not be the same error.

```
SET plan_cache_mode = force_generic_plan;
CREATE TABLE t0(c0 int4range);
INSERT INTO t0(c0) VALUES('[-1920846305,-1018839689)'::int4range);
PREPARE prepare_query (int8, int8) AS SELECT
((((((upper(t0.c0))))/($1)))*(($2::int8))) FROM ONLY t0;
EXECUTE prepare_query(0, NULL);   -- trigger error in JDBC
SELECT ((((((upper(t0.c0))))/(0::int8)))*((NULL::int8))) FROM ONLY t0;   --
always no error
```

This is the Java workload:
```
import java.io.*;
import java.sql.*;
import java.util.Properties;

public class PostgresExecutor {

    public static void main(String[] args) {
        if (args.length != 1) {
            System.err.println("usage: java PostgresExecutor <sql file
path>");
            System.exit(1);
        }

        String filePath = args[0];
        String url = "jdbc:postgresql://localhost:5433/test";
        String user = "sqlancer";
        String password = "sqlancer";

        try (Connection conn = DriverManager.getConnection(url, user,
password);
             BufferedReader reader = new BufferedReader(new
FileReader(filePath))) {

            System.out.println("Successfully connected to Postgres
database.");

            String line;
            try (Statement stmt = conn.createStatement()) {
                while ((line = reader.readLine()) != null) {
                    line = line.trim();
                    if (line.isEmpty() || line.startsWith("--")) {
                        continue;
                    }

                    try {
                        System.out.println("Executing SQL: " + line);
                        boolean hasResult = stmt.execute(line);
                        if (hasResult) {
                            try (ResultSet rs = stmt.getResultSet()) {
                                ResultSetMetaData meta = rs.getMetaData();
                                int columnCount = meta.getColumnCount();

                                while (rs.next()) {
                                    for (int i = 1; i <= columnCount; i++) {
                                        System.out.print(rs.getString(i));
                                        if (i < columnCount)
System.out.print("\t");
                                    }
                                    System.out.println();
                                }
                            }
                        } else {
                            int updateCount = stmt.getUpdateCount();
                            System.out.println("Execute successfully: " +
updateCount + " rows affected");
                        }
                    } catch (SQLException e) {
                        System.err.println("Execute error: " + line);
                        e.printStackTrace();
                    }
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

I can reproduce this with postgresql-42.7.8.jar
```
java -cp
/root/.m2/repository/org/postgresql/postgresql/42.7.8/postgresql-42.7.8.jar
PostgresExecutor.java test.sql


Re: BUG #19350: Short circuit optimization missed when running sql scriptes in JDBC

От
"David G. Johnston"
Дата:
On Wednesday, December 10, 2025, PG Bug reporting form <noreply@postgresql.org> wrote:
The following bug has been logged on the website:

Bug reference:      19350
Logged by:          Chi Zhang
Email address:      798604270@qq.com
PostgreSQL version: 17.6
Operating system:   ubuntu 24.04 with docker
Description:       

Hi,

In the following test case, if I run it in JDBC, the prepared statement will
trigger a divide by zero error. But if run it in command line, there will
not be the same error.


This isn’t a bug in PostgreSQL, it’s a bug in the query you’ve written.  The expectation of short-circuiting is flawed - the non-failure outcome is not something than can be relied upon.

David J.

Re: BUG #19350: Short circuit optimization missed when running sqlscriptes in JDBC

От
"ZhangChi"
Дата:
Hi David J.

Thanks for your reply. I wonder why this query triggers an error under JDBC but not in the CLI.

Original

From: David G. Johnston <david.g.johnston@gmail.com>
Date: 2025-12-10 21:33
To: 798604270@qq.com <798604270@qq.com>, pgsql-bugs@lists.postgresql.org <pgsql-bugs@lists.postgresql.org>
Subject: Re: BUG #19350: Short circuit optimization missed when running sqlscriptes in JDBC

On Wednesday, December 10, 2025, PG Bug reporting form <noreply@postgresql.org> wrote:
The following bug has been logged on the website:

Bug reference:      19350
Logged by:          Chi Zhang
Email address:      798604270@qq.com
PostgreSQL version: 17.6
Operating system:   ubuntu 24.04 with docker
Description:       

Hi,

In the following test case, if I run it in JDBC, the prepared statement will
trigger a divide by zero error. But if run it in command line, there will
not be the same error.


This isn’t a bug in PostgreSQL, it’s a bug in the query you’ve written.  The expectation of short-circuiting is flawed - the non-failure outcome is not something than can be relied upon.

David J.


Re: BUG #19350: Short circuit optimization missed when running sql scriptes in JDBC

От
"David G. Johnston"
Дата:


On Wednesday, December 10, 2025, ZhangChi <798604270@qq.com> wrote:
Hi David J.

Thanks for your reply. I wonder why this query triggers an error under JDBC but not in the CLI.

Because the planner can simplify the expression if it sees a constant null value but cannot when everything is injected via parameter.

David J. 

Re: BUG #19350: Short circuit optimization missed when running sqlscriptes in JDBC

От
"ZhangChi"
Дата:
Hi David J.

Yes! You are right. I mean, when I run this test case with the wrapper I provide, which executes each SQL statement with JDBC (but without the prepare API of JDBC), then the test case will trigger the error. But if I run it in CLI, for example `psql -U root -W root -p 5433 -h 127.0.0.1 < test.sql`, then there is no error. I do not modify the test caes. But it has different behaviours. I wonder the reason for this discrepancy.


Original

From: David G. Johnston <david.g.johnston@gmail.com>
Date: 2025-12-10 21:46
To: ZhangChi <798604270@qq.com>
Cc: pgsql-bugs@lists.postgresql.org <pgsql-bugs@lists.postgresql.org>
Subject: Re: BUG #19350: Short circuit optimization missed when running sqlscriptes in JDBC



On Wednesday, December 10, 2025, ZhangChi <798604270@qq.com> wrote:
Hi David J.

Thanks for your reply. I wonder why this query triggers an error under JDBC but not in the CLI.

Because the planner can simplify the expression if it sees a constant null value but cannot when everything is injected via parameter.

David J. 

"=?utf-8?B?WmhhbmdDaGk=?=" <798604270@qq.com> writes:
> Yes! You are right. I mean, when I run this test case with the wrapper I provide, which executes each SQL statement
withJDBC (but without the prepare API of JDBC), then the test case will trigger the error. But if I run it in CLI, for
example`psql -U root -W root -p 5433 -h 127.0.0.1 < test.sql`, then there is no error. I do not modify the test caes.
Butit has different behaviours. I wonder the reason for this discrepancy. 

It's not the same query, or at least not the same plan.

regression=# PREPARE prepare_query (int8, int8) AS SELECT
regression-# ((((((upper(t0.c0))))/($1)))*(($2::int8))) FROM ONLY t0;
PREPARE
regression=# EXECUTE prepare_query(0, NULL);
 ?column?
----------

(1 row)

regression=# explain verbose EXECUTE prepare_query(0, NULL);
                         QUERY PLAN
-------------------------------------------------------------
 Seq Scan on public.t0  (cost=0.00..23.60 rows=1360 width=8)
   Output: NULL::bigint
(2 rows)

regression=# SET plan_cache_mode = force_generic_plan;
SET

regression=# EXECUTE prepare_query(0, NULL);
ERROR:  division by zero
regression=# explain verbose EXECUTE prepare_query(0, NULL);
                         QUERY PLAN
-------------------------------------------------------------
 Seq Scan on public.t0  (cost=0.00..33.80 rows=1360 width=8)
   Output: ((upper(c0) / $1) * $2)
(2 rows)

In the first case (with plan_cache_mode = auto), the EXECUTE
will generate a plan in which the given parameter values are
inserted into the query and then the result is constant-folded
before execution.  With a generic plan, that doesn't happen,
so we reach the division and fail.

Yeah, this isn't super consistent, but we're unlikely to
change it.  The only thing we could do to make it consistent
is to mostly-disable constant folding, which would be a
performance disaster.  What you should do if you want closer
consistency of prepared statements and direct execution
is to set plan_cache_mode = custom.

            regards, tom lane



Re: BUG #19350: Short circuit optimization missed when running sqlscriptes in JDBC

От
Greg Sabino Mullane
Дата:
I wonder why this query triggers an error under JDBC but not in the CLI.

Fails as expected from the command line:

prepare foo1 as select 99 / $1::int * $2;
PREPARE
execute foo1(0, null);
ERROR:  division by zero

prepare foo2 as select 99 / ($1::int * $2);
PREPARE
execute foo2(0, null);
 ?column?
----------
        ☃
(1 row)

prepare foo3 as select (99 / $1::int) * $2;
PREPARE
execute foo3(0, null);
ERROR:  division by zero

If you want the "null combined with anything = null" to always happen before the "divide by zero is wrong" make your query specific like in foo2 above.


Cheers,
Greg

--
Enterprise Postgres Software Products & Tech Support

Re: BUG #19350: Short circuit optimization missed when runningsqlscriptes in JDBC

От
"ZhangChi"
Дата:
Hi Greg,

Thanks for your reply. I can understand what you describe.

But my question is that in the CLI, the test case (like foo2) I showed can return the results. But when I run the same test case in JDBC, the test case triggers an error. I wonder why there is an inconsistency.

```
SET plan_cache_mode = force_generic_plan;
CREATE TABLE t0(c0 int4range);
INSERT INTO t0(c0) VALUES('[-1920846305,-1018839689)'::int4range);
PREPARE prepare_query (int8, int8) AS SELECT
((((((upper(t0.c0))))/($1)))*(($2::int8))) FROM ONLY t0;
EXECUTE prepare_query(0, NULL); -- trigger error in JDBC but no error in CLI
```

Original

From: Greg Sabino Mullane <htamfids@gmail.com>
Date: 2025-12-16 23:17
To: ZhangChi <798604270@qq.com>
Cc: David G. Johnston <david.g.johnston@gmail.com>, pgsql-bugs@lists.postgresql.org <pgsql-bugs@lists.postgresql.org>
Subject: Re: BUG #19350: Short circuit optimization missed when runningsqlscriptes in JDBC

I wonder why this query triggers an error under JDBC but not in the CLI.

Fails as expected from the command line:

prepare foo1 as select 99 / $1::int * $2;
PREPARE
execute foo1(0, null);
ERROR:  division by zero

prepare foo2 as select 99 / ($1::int * $2);
PREPARE
execute foo2(0, null);
 ?column?
----------
        ☃
(1 row)

prepare foo3 as select (99 / $1::int) * $2;
PREPARE
execute foo3(0, null);
ERROR:  division by zero

If you want the "null combined with anything = null" to always happen before the "divide by zero is wrong" make your query specific like in foo2 above.


Cheers,
Greg

--
Enterprise Postgres Software Products & Tech Support


Re: BUG #19350: Short circuit optimization missed when runningsqlscriptes in JDBC

От
Dave Cramer
Дата:



On Tue, 16 Dec 2025 at 20:40, ZhangChi <798604270@qq.com> wrote:
Hi Greg,

Thanks for your reply. I can understand what you describe.

But my question is that in the CLI, the test case (like foo2) I showed can return the results. But when I run the same test case in JDBC, the test case triggers an error. I wonder why there is an inconsistency.

```
SET plan_cache_mode = force_generic_plan;
CREATE TABLE t0(c0 int4range);
INSERT INTO t0(c0) VALUES('[-1920846305,-1018839689)'::int4range);
PREPARE prepare_query (int8, int8) AS SELECT
((((((upper(t0.c0))))/($1)))*(($2::int8))) FROM ONLY t0;
EXECUTE prepare_query(0, NULL); -- trigger error in JDBC but no error in CLI
```


Possibly because JDBC does everything using the V3 protocol whereas psql does not. psql uses simple query.

Dave Cramer
www.postgres.rocks

Re: BUG #19350: Short circuit optimization missed when runningsqlscriptes in JDBC

От
Greg Sabino Mullane
Дата:
Possibly because JDBC does everything using the V3 protocol whereas psql does not. psql uses simple query.

Maybe JDBC is doing something else? I cannot duplicate the error when doing it via the protocol (DBD::Pg) or via psql. Even forcing non-server-side prepares (i.e. making the driver replace the vars itself) fails to fail.

Re: BUG #19350: Short circuit optimization missed when runningsqlscriptes in JDBC

От
Dave Cramer
Дата:



On Wed, 17 Dec 2025 at 09:51, Greg Sabino Mullane <htamfids@gmail.com> wrote:
Possibly because JDBC does everything using the V3 protocol whereas psql does not. psql uses simple query.

Maybe JDBC is doing something else? I cannot duplicate the error when doing it via the protocol (DBD::Pg) or via psql. Even forcing non-server-side prepares (i.e. making the driver replace the vars itself) fails to fail.

Logs from the server. I can replicate it.

2025-12-17 10:31:07.860 EST [27985] LOG:  duration: 5.875 ms  statement: SET application_name = 'PostgreSQL JDBC Driver'
2025-12-17 10:31:07.880 EST [27985] LOG:  duration: 0.945 ms  parse <unnamed>: SET plan_cache_mode = force_generic_plan
2025-12-17 10:31:07.880 EST [27985] LOG:  duration: 0.057 ms  bind <unnamed>: SET plan_cache_mode = force_generic_plan
2025-12-17 10:31:07.880 EST [27985] LOG:  duration: 0.019 ms  execute <unnamed>: SET plan_cache_mode = force_generic_plan
2025-12-17 10:31:07.885 EST [27985] LOG:  duration: 3.442 ms  parse <unnamed>: CREATE TABLE t0(c0 int4range)
2025-12-17 10:31:07.885 EST [27985] LOG:  duration: 0.023 ms  bind <unnamed>: CREATE TABLE t0(c0 int4range)
2025-12-17 10:31:07.939 EST [27985] LOG:  duration: 54.701 ms  execute <unnamed>: CREATE TABLE t0(c0 int4range)
2025-12-17 10:31:07.944 EST [27985] LOG:  duration: 2.667 ms  parse <unnamed>: INSERT INTO t0(c0) VALUES('[-1920846305,-1018839689)'::int4range)
2025-12-17 10:31:07.945 EST [27985] LOG:  duration: 0.695 ms  bind <unnamed>: INSERT INTO t0(c0) VALUES('[-1920846305,-1018839689)'::int4range)
2025-12-17 10:31:07.946 EST [27985] LOG:  duration: 1.593 ms  execute <unnamed>: INSERT INTO t0(c0) VALUES('[-1920846305,-1018839689)'::int4range)
2025-12-17 10:31:07.947 EST [27985] LOG:  duration: 0.311 ms  parse <unnamed>: PREPARE prepare_query (int8, int8) AS SELECT ((((((upper(t0.c0))))/($1)))*(($2::int8))) FROM ONLY t0
2025-12-17 10:31:07.947 EST [27985] LOG:  duration: 0.006 ms  bind <unnamed>: PREPARE prepare_query (int8, int8) AS SELECT ((((((upper(t0.c0))))/($1)))*(($2::int8))) FROM ONLY t0
2025-12-17 10:31:07.952 EST [27985] LOG:  duration: 4.653 ms  execute <unnamed>: PREPARE prepare_query (int8, int8) AS SELECT ((((((upper(t0.c0))))/($1)))*(($2::int8))) FROM ONLY t0
2025-12-17 10:31:07.953 EST [27985] LOG:  duration: 0.020 ms  parse <unnamed>: EXECUTE prepare_query(0, NULL)
2025-12-17 10:31:07.953 EST [27985] LOG:  duration: 0.005 ms  bind <unnamed>: EXECUTE prepare_query(0, NULL)
2025-12-17 10:31:07.954 EST [27985] ERROR:  division by zero 

Dave Cramer
www.postgres.rocks

Re: BUG #19350: Short circuit optimization missed when runningsqlscriptes in JDBC

От
Dave Cramer
Дата:

Dave Cramer
www.postgres.rocks


On Wed, 17 Dec 2025 at 10:33, Dave Cramer <davecramer@postgres.rocks> wrote:



On Wed, 17 Dec 2025 at 09:51, Greg Sabino Mullane <htamfids@gmail.com> wrote:
Possibly because JDBC does everything using the V3 protocol whereas psql does not. psql uses simple query.

Maybe JDBC is doing something else? I cannot duplicate the error when doing it via the protocol (DBD::Pg) or via psql. Even forcing non-server-side prepares (i.e. making the driver replace the vars itself) fails to fail.

Logs from the server. I can replicate it.

2025-12-17 10:31:07.860 EST [27985] LOG:  duration: 5.875 ms  statement: SET application_name = 'PostgreSQL JDBC Driver'
2025-12-17 10:31:07.880 EST [27985] LOG:  duration: 0.945 ms  parse <unnamed>: SET plan_cache_mode = force_generic_plan
2025-12-17 10:31:07.880 EST [27985] LOG:  duration: 0.057 ms  bind <unnamed>: SET plan_cache_mode = force_generic_plan
2025-12-17 10:31:07.880 EST [27985] LOG:  duration: 0.019 ms  execute <unnamed>: SET plan_cache_mode = force_generic_plan
2025-12-17 10:31:07.885 EST [27985] LOG:  duration: 3.442 ms  parse <unnamed>: CREATE TABLE t0(c0 int4range)
2025-12-17 10:31:07.885 EST [27985] LOG:  duration: 0.023 ms  bind <unnamed>: CREATE TABLE t0(c0 int4range)
2025-12-17 10:31:07.939 EST [27985] LOG:  duration: 54.701 ms  execute <unnamed>: CREATE TABLE t0(c0 int4range)
2025-12-17 10:31:07.944 EST [27985] LOG:  duration: 2.667 ms  parse <unnamed>: INSERT INTO t0(c0) VALUES('[-1920846305,-1018839689)'::int4range)
2025-12-17 10:31:07.945 EST [27985] LOG:  duration: 0.695 ms  bind <unnamed>: INSERT INTO t0(c0) VALUES('[-1920846305,-1018839689)'::int4range)
2025-12-17 10:31:07.946 EST [27985] LOG:  duration: 1.593 ms  execute <unnamed>: INSERT INTO t0(c0) VALUES('[-1920846305,-1018839689)'::int4range)
2025-12-17 10:31:07.947 EST [27985] LOG:  duration: 0.311 ms  parse <unnamed>: PREPARE prepare_query (int8, int8) AS SELECT ((((((upper(t0.c0))))/($1)))*(($2::int8))) FROM ONLY t0
2025-12-17 10:31:07.947 EST [27985] LOG:  duration: 0.006 ms  bind <unnamed>: PREPARE prepare_query (int8, int8) AS SELECT ((((((upper(t0.c0))))/($1)))*(($2::int8))) FROM ONLY t0
2025-12-17 10:31:07.952 EST [27985] LOG:  duration: 4.653 ms  execute <unnamed>: PREPARE prepare_query (int8, int8) AS SELECT ((((((upper(t0.c0))))/($1)))*(($2::int8))) FROM ONLY t0
2025-12-17 10:31:07.953 EST [27985] LOG:  duration: 0.020 ms  parse <unnamed>: EXECUTE prepare_query(0, NULL)
2025-12-17 10:31:07.953 EST [27985] LOG:  duration: 0.005 ms  bind <unnamed>: EXECUTE prepare_query(0, NULL)
2025-12-17 10:31:07.954 EST [27985] ERROR:  division by zero 


I also get the division by zero error with psql

bin/psql -h localhost -U test -f ~/projects/jdbc/testjdbc/test.sql
SET
CREATE TABLE
INSERT 0 1
PREPARE
psql:/Users/davecra/projects/jdbc/testjdbc/test.sql:5: ERROR:  division by zero
 ?column?
----------

(1 row) 
Dave Cramer
www.postgres.rocks

Re: BUG #19350: Short circuit optimization missed when runningsqlscriptes in JDBC

От
Tom Lane
Дата:
Dave Cramer <davecramer@postgres.rocks> writes:
> On Tue, 16 Dec 2025 at 20:40, ZhangChi <798604270@qq.com> wrote:
>> But my question is that in the CLI, the test case (like foo2) I showed can
>> return the results. But when I run the same test case in JDBC, the test
>> case triggers an error. I wonder why there is an inconsistency.

> Possibly because JDBC does everything using the V3 protocol whereas psql
> does not. psql uses simple query.

The error will occur if we try to build a "custom plan" for the
prepared query.  I suspect a discrepancy in the plan_cache_mode
settings between JDBC and the user's psql setup.

            regards, tom lane