Same release as v0.3.0, renumbered so the published wheel matches the tag:
PyPI already held an mpedb 0.3.0, uploaded on 2026-08-12 from the abi3-py310
work, while every other crate was still at 0.2.9. pip install mpedb==0.3.0
therefore predates everything below. 0.3.1 is a number no artifact has used.
The shim reads and writes real sqlite files
mpedb-sqlitefmt gained a writer for b-trees that span pages, with indexes
that stock sqlite's integrity_check accepts. The C-API shim can open a real
.db, keep writes in a .mpedb sidecar, and PRAGMA wal_checkpoint the whole
database back out as a sqlite file.
NOT NULL survives both directions. That is not tidiness: mpedb's planner
reads declared nullability, and a row with a NULL in an indexed column has no
index entry at all — so a composite index whose trailing column is nullable
cannot serve a range over the leading one. Losing the flag on import turned an
indexed lookup into a full scan on a 945 234-row table.
PHP, with nothing installed
No PECL, no extension to compile, no rebuild of PHP. The distro's own
pdo_sqlite and sqlite3 are dynamically linked against libsqlite3.so.0,
so substituting the shim under that soname is the whole procedure:
mkdir shim && ln -s $PWD/libmpedb_sqlite3.so shim/libsqlite3.so.0
LD_LIBRARY_PATH=$PWD/shim php app.phpLD_PRELOAD does not work here: PHP loads extensions with dlopen, and
their own DT_NEEDED wins over the preload scope, so the shim loads, binds
nothing, and the program quietly keeps using stock sqlite.
Four missing symbols were added (sqlite3_sql, sqlite3_column_table_name,
sqlite3_mprintf, sqlite3_snprintf). The printf pair cannot be written in
Rust — defining a C-variadic function is still unstable — and a symbol defined
only in a C object binds local in a cdylib, invisible to any consumer. So
Rust owns the names and C owns the bodies, joined by naked tail-branch thunks
that leave every argument register untouched.
A CI job now loads both PHP extensions against the shim, compares its printf
against the runner's real sqlite format for format, and checks that stock
sqlite accepts the checkpointed file.
Islands in the planner
A predicate can bound a column without ever naming a bound.
(lat-A)*(lat-A) + (lon-B)*(lon-B) < R*R is a circle, and a circle is a box in
every axis — but the planner saw f(col) > 0, classified it unknown, and took
a full scan over a tree that could have answered it. planner::interval
computes the box by backward interval arithmetic. Nothing in it knows
Pythagoras, circles or geography.
IndexState (schema v21)
A background build must publish an index before filling it, so concurrent
writers maintain it from that moment. But an index the planner can see is one
it will use, and a half-filled index is not a slower answer — it is a wrong
one. Building participates in maintenance and is invisible to access
selection; a crash leaves it resumable, where a Ready index missing rows
would be unrecoverable.
Fixed before release
This branch had never run CI. The first run found four defects, two of them
answer-changing:
- an island bound encoded as a float against integer keys —
keycodegives an
Intthe sign-flipped i64 image and aFloatthe IEEE total-order image,
and neither orders against the other, soUPDATE t SET b = a WHERE a + 3 < -15silently skipped the row wherea = -47; - a checkpoint that wrote mpedb's synthesized
rowidas a real column, giving
every row a NULL in a column the schema declared; - the printf thunks assembling only on x86-64 (
macos-latestis ARM); - an EXPLAIN label naming the wrong column for a composite range.
mpedb-pg is versioned with everything else again — it is its own workspace
and was left at 0.2.9 when v0.2.9 was cut.