Pure virtual function called (SIGABRT) on COMMIT of a txn doing DROP INDEX + CREATE INDEX on a reopened file database (Python, macOS arm64)
- 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
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