phoenixdb 2.1.0
phoenixdb: ^2.1.0 copied to clipboard
ACID-compliant embedded database for Dart and Flutter, written in Rust with dart:ffi. B+Tree storage, MVCC transactions, a write-ahead log, HNSW vector search and optional SQL.
Changelog #
All notable changes to this project are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
2.1.0 - 2026-08-10 #
Embedded vector search: approximate k-NN over f32 embeddings, so a
local-first app can do semantic retrieval with no server and no second
database.
Breaking #
- Native ABI raised from 2 to 3. The change is additive (every v2 entry point keeps its signature) but the Dart loader enforces an exact match, so a v2 native library and this package will not load together. Both are rebuilt and shipped in this release; anyone building the library themselves must rebuild it.
Added #
- HNSW vector index.
PhoenixVectorDB(sync) andAsyncPhoenixVectorDB(worker isolate) exposeinsert,search,get,remove,save,flush,compactandstats.- Metrics: cosine, Euclidean (L2) and dot product, selected at index creation and fixed thereafter.
VectorQuerycarries the query vector,k, and an optionalefSearchbeam width;VectorMatchcarries the id, the metric distance, and a "higher is better" score.- Vectors up to 65 536 dimensions; ids up to 128 bytes;
kup to 4096.
- SIMD distance kernels. Runtime-dispatched AVX2+FMA on x86_64, NEON on
AArch64, and a portable auto-vectorised fallback everywhere else.
PhoenixVectorDB.kernelreports which one this CPU selected. - Durable, memory-mapped vector storage. A fixed-stride append-only file
with a per-record CRC32, read zero-copy through the existing
mmaplayer. The HNSW graph is snapshotted separately withbincode, written atomically via a temporary file and a rename. - Crash and corruption recovery. A torn tail record is ignored, a corrupt or stale graph snapshot is rebuilt from the vectors (which are the source of truth), and reopening an index with the wrong dimensionality or metric is refused rather than silently reinterpreted.
- Nine new C entry points (
phoenix_vector_*) plusphoenix_free_string_arrayandphoenix_has_vector, all with the same null-check, length-check andcatch_unwindguarantees as the existing surface. .github/workflows/build_native.yml: a SIMD-aware build matrix covering Linux, macOS, Windows, Android, iOS, and cross-compilation-only targets.
Notes #
+avx2is deliberately not passed inRUSTFLAGS. This package ships prebuilt binaries, and a crate-wide AVX2 build wouldSIGILLon pre-2013 x86_64 CPUs — on a user's machine, not in CI. AVX2 is instead enabled per-function and selected byis_x86_feature_detected!.+neonis passed on AArch64, where NEON is part of the base architecture and therefore always safe.- No new dependencies.
hnsw_rswould have pulled in roughly 100 transitive crates (anndists,mmap-rs,rayon,env_logger,jiff), several of which do not cross-compile cleanly to every mobile target, so the graph is implemented directly inrust/src/vector/hnsw.rs. - Indexes with fewer than 512 live vectors are searched exhaustively, so small collections return exact rather than approximate results.
2.0.0 - 2026-08-09 #
Multi-modal storage: a hybrid LSM layer, a SQL front end, encryption at rest, RBAC, audit logging, and metrics — all behind feature flags so the embedded core stays small.
Breaking #
- Native ABI raised from 1 to 2. The change is additive (every v1 entry point keeps its signature) but the Dart loader enforces an exact match, so a v1 native library and this package will not load together. Both are rebuilt and shipped in this release; anyone building the library themselves must rebuild it.
Added #
LSM storage layer (lsm) — library only, see Known limitations
- MemTable, SSTable (block-based, with a Bloom filter per table), leveled compaction, and a compaction scheduler prioritised by write amplification.
- A crash-safe, CRC-framed durable manifest recording which SSTables are
live at which level. A torn tail from a power loss is truncated rather than
treated as corruption; the layout, level placement, tombstones, and
checkpoint sequence number all survive a restart. Unreferenced
.sstfiles are reclaimed on open; a referenced table that fails its checksum is a hard error rather than silent data loss. - The manifest log is snapshotted once it grows past a threshold, so startup replay does not slow down without bound.
SQL front end (sql, opt-in)
- Hand-written lexer, recursive-descent parser, and executor supporting
CREATE TABLE,DROP TABLE,INSERT,SELECT(projection,WHERE,AND/OR,ORDER BY,LIMIT),UPDATE, andDELETE, plusIF NOT EXISTS/IF EXISTS. - SQL semantics where they matter:
NULLis never equal to anything under an ordinary comparison,ORDER BYis applied beforeLIMIT, integers and floats compare numerically, and mismatched types yield no rows rather than an arbitrary ordering. - Mutations are transactional — a multi-row
INSERTeither lands completely or not at all. - Reachable from Dart as
db.query(...)(synchronous) andAsyncPhoenixDB.query(...)(on the worker isolate, so a slow query cannot block a Flutter frame). Results arrive as a typedSqlResultwithscalar,firstOrNull,cell(), andasMapshelpers.
Security (encryption, opt-in) — library only, see Known limitations
- Transparent AES-256-GCM encryption at rest. Pages are encrypted before checksumming and decrypted after verification, so a tampered or swapped page is rejected rather than decrypted into garbage.
- Role-based access control with constant-time credential comparison.
- An append-only audit log kept separate from the WAL, resistant to log injection.
Observability (metrics, opt-in) — library only, see Known limitations
- Latency histograms with p50/p99/p999, covering WAL fsync, compaction throughput, and cache hit/miss ratios.
- Structured tracing spans with trace ids.
Dart API
PhoenixPrefs, ashared_preferences-style typed facade (getString,setInt,getBool, …) over the key/value store.phoenix_has_sql()reports whether the loaded library includes the SQL layer, so an app can degrade gracefully on a lean embedded build.
Fixed #
-
The package could not be used as an ordinary dependency. Every native library search path was relative to the consumer's working directory, but the binaries ship inside the installed package (in the pub cache, or at a
path:dependency's location). A plaindart pub getfollowed bydart runfailed with "Could not load phoenixdb.dll". The loader now resolves its own package root first — viaIsolate.resolvePackageUriSync, falling back to reading.dart_tool/package_config.json, which is what theflutter testrunner needs. -
Windows lookups missed MSVC builds. Only
x86_64-pc-windows-gnuwas searched, so the MSVC library that CI ships would not be found. Both triples are now searched, andwindows_arm64was added. -
Flutter and script builds produced an unloadable library. The Linux and Windows CMake files, the Apple build script,
build.sh, andbuild.ps1all rancargo buildwithout--features sql, yielding an ABI v1 library that the v2 loader rejects. All build paths now enable the feature. -
Platform manifests still declared 0.1.0. The iOS and macOS podspecs and
android/build.gradlewere never bumped, so CocoaPods and Gradle advertised a version that no longer matched the package. -
dart pub getfailed for plain Dart consumers.pubspec.yamldeclared aflutter:constraint underenvironment:, which makes the whole package require the Flutter SDK:Because phoenixdb requires the Flutter SDK, version solving failed.Nothing under
lib/importspackage:flutter— the only package imports areffiandphoenixdb— so the constraint was never warranted. Flutter support comes from theflutter: plugin:section, which plain Dart ignores. The constraint is removed and CI now guards against its return. -
A missing Rust toolchain failed opaquely. A desktop Flutter build without
cargoon PATH stopped atError 1from the custom build command, with no indication that Rust was the cause. The Linux and Windows CMake files now fail configuration with an explicit message pointing at rustup.
Changed #
- Feature flags (
encryption,json,sql,metrics,async-runtime,full) keep the default build lean for Flutter. The default build adds no heavy dependencies. serde_jsonis now optional, behind thejsonfeature.
Notes on dependencies #
Three crates named in the original design could not be used, because they require a C toolchain that is unavailable on the Flutter/Android cross-compilation path. Substitutions with the same guarantees were used instead:
| Planned | Shipped | Reason |
|---|---|---|
ring |
aes-gcm |
Pure Rust, same AES-256-GCM construction |
sqlparser-rs |
hand-written parser | Its stacker dependency needs a C compiler |
| OpenTelemetry OTLP | internal tracing | tonic pulls in cc-based dependencies |
Known limitations #
- The
lsm,security::encryption,security::rbac,security::auditandobservabilitymodules are libraries, not yet engine behaviour. They are implemented and tested, butDatabasestill writes through the B+Tree only, the pager does not encrypt, no permission check runs at the FFI boundary, and the engine emits no metrics. Key and value length validation fromsecurityis enforced on every FFI call. SELECTscans the visible key space per query rather than using a prefix-bounded iterator: appropriate for embedded workloads, O(database) on large tables.- The SQL layer has no planner, joins, subqueries, or aggregate functions.
- Raft replication, gossip anti-entropy, full-text search, and the REPL are not implemented.
- Web is not supported: the engine is native code reached over
dart:ffi.
0.1.0 - 2026-08-09 #
Initial release.
Added #
Storage engine (Rust)
- B+Tree index with configurable fill factors (minimum 50%, maximum 100%).
- Fixed 4096-byte slotted pages with a 32-byte header carrying
page_id,is_leaf,num_keys,parent_idand a CRC32 checksum. - CRC32 written before every page write and verified on every read; corruption is reported as an error and never returned as data.
- Structural validation of the slot directory in addition to the checksum, so a page cannot direct an accessor outside its own buffer.
- Overflow-page chains for values larger than 1 KiB, with cycle guards.
- Free-list recycling of released pages.
- Zero-copy reads through
mmap(Unix) andCreateFileMappingW(Windows), paired with positional writes andfsync/sync_allfor durability. - LRU page cache in front of the mapping.
Transactions
- MVCC snapshot isolation: many concurrent readers, one writer serialised by
parking_lot::RwLock. - Write-ahead log with
Begin,Insert,Delete,CommitandRollbackrecords, each framed with its own CRC32. fsyncon commit, so a transaction is durable whencommitreturns.- Crash recovery that replays only committed transactions and tolerates a torn log tail.
- Write-write conflict detection with a dedicated status code for retry.
- Checkpointing that merges versions into the tree, flushes, then truncates the log.
FFI layer
- Pure C ABI (
extern "C") covering open, close, insert, get, delete, begin/commit/rollback, checkpoint, flush, verify, count and free. phoenixdb.hgenerated automatically bycbindgenduring the build.- Pointer non-nullness and length limits (key 1 MiB, value 10 MiB) validated
before any dereference, returning
-2. - Constant-time handle-tag verification, poisoned on close, so use-after-free and double close are rejected rather than followed.
- Panics contained with
catch_unwindand mapped to-7; nothing unwinds into Dart.
Dart package
- Type-safe API using
Uint8Listfor keys and values. NativeFinalizerattached to the native handle for automatic cleanup.AsyncPhoenixDB, an isolate-backed client that keeps blocking disk I/O off the UI thread.- Automatic native-library discovery with an ABI-version check on load.
Packaging
- Installable with
dart pub add phoenixdbandflutter pub add phoenixdb. - Declared as a Flutter FFI plugin (
ffiPlugin: true) for Android, iOS, macOS, Linux and Windows, so no method-channel registrant code is generated. - Android: prebuilt
.soforarm64-v8a,armeabi-v7a,x86_64andx86shipped inandroid/src/main/jniLibs/, packaged into the host APK/AAB with no NDK required by the consumer (minSdk 21). - iOS and macOS: CocoaPods podspecs that compile a static archive during the
Xcode build via
rust/build-apple.sh; the archive is-force_loaded so thephoenix_*symbols survive dead-stripping. - Linux and Windows: CMake integration that runs
cargo buildand bundles the resulting shared library with the app. - Platform-aware library loading: bare-name
dlopenon Android,DynamicLibrary.process()on iOS (statically linked), and a per-target-triple filesystem search on desktop.
Tooling
build.shandbuild.ps1with cross-compilation support viaCC/AR.libfuzzerharnesses for the B+Tree, page parsing and the FFI surface.- 86 Rust tests and 33 Dart tests;
dart analyze --fatal-infosclean. - Scores 160/160 on pub.dev's package analysis (
pana).
License #
Released under the BSD 3-Clause License.