duckdb / duckdb/duckdb-java

Native SIGSEGV in DuckDB JDBC when repeatedly executing queries with window functions and LIMIT 0

Aperta
#871 3 commenti 0 reazioni 0 assegnatari Vedi su GitHub
Lingua principale
C++
Stelle
127
Fork
80
Merge medio
13h 49m
PR unite (30g)
48

Descrizione

### What happens?

Repeatedly executing a query that contains a window function (`RANK() OVER (...)`), an `ORDER BY`, and a `LIMIT 0` causes a native `SIGSEGV` crash in DuckDB JDBC. The JVM process is aborted with exit code 134. The crash occurs in `org.duckdb.DuckDBNative.duckdb_jdbc_execute_pending`.

The crash is reproducible in **both** the `source` mode (single-table query) and the `split` mode (split query), running single-threaded with `threads=1`. Both queries use `LIMIT 0` and should return an empty result set without crashing.

### To Reproduce

### Driver

Use DuckDB JDBC:

```xml

org.duckdb
duckdb_jdbc
1.5.5.1

```

### Reproducer

Save the following as `repro/DuckDBVpCrash1279455Stable.java`:

```java
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

/**
* Stable minimized reproducer for the DuckDB JDBC native crash observed in
* hs_err_pid1279455.log from SQLancer's VP split side.
*
* Shape preserved from the crash context:
* - split-side query
* - disabled_optimizers='filter_pushdown,join_order'
* - RANK() OVER (...) over a reconstructed VP relation
* - right side pre-aggregated by row id and joined back
* - ORDER BY ... LIMIT 0
*
* Tested with org.duckdb:duckdb_jdbc:1.5.5.1 on OpenJDK 17.
*/
public final class DuckDBVpCrash1279455Stable {

private static String sourceQuery(String sourceTable) {
return """
SELECT RANK() OVER (ORDER BY vp_rowid, vp_rowid)
FROM %s
ORDER BY vp_rowid
LIMIT 0
""".formatted(sourceTable);
}

private static String splitQuery(String leftTable, String rightTable) {
return """
SELECT RANK() OVER (ORDER BY vp_rowid, vp_rowid)
FROM (
SELECT l.vp_rowid AS vp_rowid, r.c7 AS c7
FROM %s l
JOIN (SELECT vp_rowid, COUNT(*) AS vp_count FROM %s GROUP BY vp_rowid) rg
ON l.vp_rowid = rg.vp_rowid
JOIN %s r
ON r.vp_rowid = rg.vp_rowid
) vp_source
ORDER BY vp_source.vp_rowid
LIMIT 0
""".formatted(leftTable, rightTable, rightTable);
}

private DuckDBVpCrash1279455Stable() {
}

public static void main(String[] args) throws Exception {
int repetitions = args.length == 0 ? 200 : Integer.parseInt(args[0]);
String logId = args.length < 2 ? "1279455" : sanitize(args[1]);
String mode = args.length < 3 ? "split" : args[2].toLowerCase();
if (!mode.equals("source") && !mode.equals("split")) {
throw new IllegalArgumentException("mode must be 'source' or 'split'");
}
boolean sourceOnly = mode.equals("source");
Path databaseFile = Path.of("repro_duckdb" + logId + "_db_min.duckdb").toAbsolutePath();
String sourceTable = "source_" + logId;
String leftTable = "left_" + logId;
String rightTable = "right_" + logId;
Path tempDirectory = Files.createTempDirectory("duckdb-vp-crash-1279455-");
// Start from a fresh, named DuckDB database file so the developer only
// needs to compile and run this JDBC file.
Files.deleteIfExists(databaseFile);
Files.deleteIfExists(Path.of(databaseFile + ".wal"));
try (Connection connection = DriverManager.getConnection("jdbc:duckdb:" + databaseFile);
Statement statement = connection.createStatement()) {
statement.execute("SET threads=1");
statement.execute("SET temp_directory='"
+ tempDirectory.toAbsolutePath().toString().replace("'", "''") + "'");
statement.execute("""
CREATE TABLE %s AS
SELECT i AS vp_rowid,
TIMESTAMP '2000-01-01' + CAST(i AS INTEGER) * INTERVAL '1 second' AS c7
FROM range(100) t(i)
""".formatted(sourceTable));
statement.execute("CREATE TABLE " + leftTable + " AS SELECT vp_rowid FROM " + sourceTable);
statement.execute("CREATE TABLE " + rightTable + " AS SELECT vp_rowid, c7 FROM " + sourceTable);
statement.execute("SET disabled_optimizers='filter_pushdown,join_order'");
String splitQuery = splitQuery(leftTable, rightTable);
String query = sourceOnly ? sourceQuery(sourceTable) : splitQuery;

for (int i = 1; i <= repetitions; i++) {
try (ResultSet resultSet = statement.executeQuery(query)) {
if (resultSet.next()) {
throw new AssertionError("LIMIT 0 query unexpectedly returned a row");
}
}
if (i == 1 || i % 10 == 0) {
System.out.println(mode + " repetitions=" + i);
}
}
System.out.println("completed without native crash");
}
}

private static String sanitize(String value) {
String sanitized = value.replaceAll("[^A-Za-z0-9_]", "_");
return sanitized.isEmpty() ? "1279455" : sanitized;
}
}
```

### Compile

```bash
mkdir -p /tmp/duckdb-vp-repro-classes

javac \
-cp target/lib/duckdb_jdbc-1.5.5.1.jar \
-d /tmp/duckdb-vp-repro-classes \
repro/DuckDBVpCrash1279455Stable.java
```

### Run in `source` mode

```bash
timeout 45s java \
-cp '/tmp/duckdb-vp-repro-classes:target/lib/duckdb_jdbc-1.5.5.1.jar' \
DuckDBVpCrash1279455Stable 200 1279455 source
```

### Run in `split` mode

```bash
timeout 45s java \
-cp '/tmp/duckdb-vp-repro-classes:target/lib/duckdb_jdbc-1.5.5.1.jar' \
DuckDBVpCrash1279455Stable 200 1279455 split
```

Argument meanings:

```text
200 repeat the query 200 times
1279455 log / case id, used to generate database and table names
source run the source query
split run the VP split query
```

The reproducer automatically:

- creates the database file;
- creates the test tables;
- inserts 100 rows of deterministic data;
- sets `threads=1`;
- sets `SET disabled_optimizers='filter_pushdown,join_order'`;
- repeatedly executes the query.

### Actual result

Both `source` and `split` modes cause the JVM to crash inside DuckDB native code:

```text
SIGSEGV (0xb)
Problematic frame:
C [libc.so.6+...]
```

Typical Java stack:

```text
org.duckdb.DuckDBNative.duckdb_jdbc_execute_pending
org.duckdb.DuckDBPreparedStatement.execute
org.duckdb.DuckDBPreparedStatement.executeQuery
DuckDBVpCrash1279455Stable.main
```

Process exit code is typically:

```text
134
```

### Expected result

Because the queries use `LIMIT 0`, they should:

- return an empty result set normally;
- let the Java program exit cleanly;

and should **not** cause:

```text
SIGSEGV
Aborted (core dumped)
JVM native crash
```

### OS:

Ubuntu 20.04.6 LTS, x86_64

### DuckDB Version:

v1.5.5

### DuckDB Client:

Java JDBC

### Hardware:

Intel Xeon Gold 5218R, 80 cores, 1486G RAM

### Full Name:

Annie liu

### Affiliation:

ECNU

### Did you include all relevant configuration (e.g., CPU architecture, Linux distribution) to reproduce the issue?

- [x] Yes, I have

### Did you include all code required to reproduce the issue?

- [x] Yes, I have

### Did you include all relevant data sets for reproducing the issue?

Yes

Guida per i contributori

Nessuna guida per i contributori indicizzata per questo repository

Direzione di ricerca

Inizia compilando repro/DuckDBVpCrash1279455Stable.java con il jar JDBC fornito ed eseguilo sia in modalità source sia in modalità split. Conferma che le query LIMIT 0 ripetute causano un crash in duckdb_jdbc_execute_pending invece di restituire un set di risultati vuoto, quindi traccia il percorso di esecuzione JDBC/nativo per identificare il problema e verifica il completamento corretto in entrambe le modalità.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
cpp, java, sql
Ambito
database
Tipo di issue
Bug
Difficoltà
4/5
Tempo stimato
3-5 giorni
Stato di attività
Attiva
Chiarezza
Specificata chiaramente
Idoneità per principianti
48/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.