duckdb / duckdb/duckdb

Pure virtual function called (SIGABRT) on COMMIT of a txn doing DROP INDEX + CREATE INDEX on a reopened file database (Python, macOS arm64)

Open
#23,929 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
C++
Stars
41.2k
Forks
3.8k
Avg merge
2d 8h
Merged PRs (30d)
491

Description

### What happens?

The Python process aborts with `libc++abi: Pure virtual function called!` (SIGABRT) when a `DROP INDEX` and a `CREATE INDEX` for the same (or a rebuilt) index run inside **one explicit transaction** on a **file-backed database that was closed and reopened** since the index was created.

Minimization notes (each variant tested in isolation):

| variant | result |
|---|---|
| `DROP INDEX` + `CREATE INDEX` in one explicit txn, reopened file DB | **SIGABRT** |
| same, but on the connection that originally created the index (no reopen) | OK |
| same, but in-memory database | OK |
| `DROP INDEX` alone in a txn (reopened) | OK |
| `CREATE INDEX` alone in a txn (reopened) | OK |
| same sequence with `ALTER TABLE … DROP NOT NULL` between drop and create | **SIGABRT** (this is how we originally hit it — the ALTER is not required) |
| same crashing sequence, but the txn also contains a net non-zero row change (e.g. one real UPDATE) | OK |

So the trigger appears to be: a transaction with no net row changes that drops a catalog-loaded index and creates an index on the same table, committed against a reopened file database.

Autocommit (no explicit `BEGIN`/`COMMIT`) is a working workaround.

### To Reproduce

```python
import duckdb

path = "/tmp/repro.duckdb"
c = duckdb.connect(path)
c.execute("create table t(x int)")
c.execute("create index i1 on t(x)")
c.close()

c = duckdb.connect(path) # reopen is required to trigger it
c.execute("begin")
c.execute("drop index i1")
c.execute("create index i1 on t(x)")
c.execute("commit") # <- libc++abi: Pure virtual function called! / SIGABRT
```

Output:

```
libc++abi: Pure virtual function called!
Abort trap: 6
```

macOS crash report shows the faulting (main) thread inside the extension module ending in:

```
__pthread_kill / pthread_kill / abort
libc++abi.dylib abort_message
libc++abi.dylib __cxa_pure_virtual
_duckdb.cpython-313-darwin.so (many frames)
Python cfunction_vectorcall_FASTCALL_KEYWORDS
```

### OS:

macOS 15.7 (Sequoia), Apple Silicon (arm64)

### DuckDB Version:

Reproduced on 1.4.4, 1.5.2 and 1.5.4 (all identical behavior)

### DuckDB Client:

Python (PyPI `duckdb` wheels), Python 3.13.3

### Hardware:

Apple Silicon Mac

### Full Name:

(submitted via GitHub account)

### Affiliation:

Independent

### What is the latest build you tested with? If possible, we recommend testing with the latest nightly build.

I have tested with a stable release (1.5.4, latest stable at the time of filing)

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

Yes

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

- [x] Yes, I have

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

- [x] Yes, I have

Contributor guide

Open the contributing guide

Research direction

Start with the Python reproduction in the issue and trace the explicit COMMIT path for a reopened file-backed database after DROP INDEX and CREATE INDEX. Compare the reopened-file case with the in-memory and original-connection variants, then verify that the transaction commits without SIGABRT while the other listed behaviors remain unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp, python
Domain
databases
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.