Roadmap
Besu is early. This page lists what is missing, in rough priority order, and what each item would actually require. The order is a judgement about impact versus cost, not a promise.
This page is the whole list. What is deliberately absent is in the comparison.
A native database backend
Tracked as #1.
Everything sqlx-specific is already behind besu_core::Driver, so the seam is ready; what is
missing is the work. A rusqlite-backed SQLite driver first (no wire protocol, but it forces
the synchronous-to-asynchronous decision: a connection thread with request channels, not
spawn_blocking), then tokio-postgres, then mysql_async. Two things to settle first,
because they change the API rather than the driver: pooling becomes Besu's problem, and TLS
becomes Besu's dependency. The issue carries the full plan.
Custom column types
Tracked as #98.
#[besu(sql_type = "besu::Decimal<10, 2>")] names a SqlType directly, so a sized VARCHAR,
an exact DECIMAL and a user-defined marker type can all be declared and all round-trip
through migrations. What is missing is one thing: an exact decimal cannot be read back.
Decimal's Rust type is a String. SQLite stores it as TEXT and that works, but PostgreSQL
and MySQL return NUMERIC, and no driver decodes that into a string. Until one does,
pick(decimal_column) needs a cast, and a decimal is only half a column type.
What it would take:
- Either a
Rust = Stringdecode path forNUMERICin the two drivers (the binary decoder already exists foru64andu128, so this is mostly formatting dscale), or a real decimal value type behind a feature. The second is better and is a dependency decision. - The same question for PostgreSQL
INTERVAL/INET/CIDR, MySQLSETand domain types, all of which currently come back asTypeDef::Customby design. - A derive attribute for a check constraint if
Table::checksshould be declarable rather than only introspectable.
wasm support
The builder half is done: besu-core and besu compile for wasm32-unknown-unknown, so a
web front end can build a query and render the exact SQL a server will run. The wasm CI job
keeps it that way and besu/tests/dry.rs pins the SQL. See
Compiling for wasm32 for what does and does not work.
Executing a query in a browser is still open, and it is not a matter of a flag. The drivers are built on sqlx, which is built on tokio, which reaches for sockets this target does not have. What it would take:
- A new driver for a browser storage API (IndexedDB, OPFS) or an HTTP bridge to a server. It
has to supply its own
Decodeimpls, because decoding a row is the driver's job, and aDialectthat spells SQL the other end understands. - A decision about blocking: browser storage is async but has no connection pool, while the
Drivertrait assumes aSendfuture and aSend + Syncdriver. On a single-threaded wasm module those bounds are vacuous rather than wrong, but a driver holding JS handles is notSendin any useful sense. - A story for the clock.
besu::loggedreadsInstant::now()once a subscriber enables it, and that traps in a browser; migrations would need a timestamp that does not.
Ship the same query builder to the client and execute it on the server in the meantime; that is the shape this half of the work was for.
Smaller items
These are not large enough for their own section, but they are real. Each is a tracking issue.
- #97 — relations. A relation object, many-to-many and nested eager loading.
- #98 — column types.
INTERVAL,INET, ranges, spatial,pgvector, and decoding an exactDECIMAL. - #99 — indexes. Composite, expression and operator-class indexes.
- #100 — check constraints. Declaring one from the derive.
- #101 — row-level security. Policies in the diff rather than in migration SQL only.
- #102 — MySQL enums. Inline enum columns.
- #103 — aliases. Two aliases of one table.
- #104 — composition. Set operations as subqueries, CTE handles beyond one table, and a dynamic handle.
- #105 — batch. Selects in a batch.
Recently closed
Kept here so the history is visible rather than deleted:
- #52, eager loading and relations.
has_many(parent_key, child_foreign_key)plusSelect::withload a has-many relation with one batched second statement, and an empty parent result sends no second statement at all. The parent and child columns are the whole descriptor, so the schema layer is unchanged. Insert-with-relations, many-to-many and nested relations stay open. - #59,
u128support.Value::UInt128/Value::Int128, aU128/I128SqlTypeand derive mapping, and encode/decode in every driver. PostgreSQL and MySQL use exact 39-digit decimals; SQLite stores the decimal digits asTEXT. - #60, the builder on wasm32.
besu-coreandbesucompile forwasm32-unknown-unknown, so SQL can be generated in a browser and executed on a server. Execution in the browser still needs a driver of its own; see wasm support. - #63, two-sided containment. Both operands of a comparison, every aggregate argument and every helper-function argument are checked now.
- #64, SQLite column changes.
pushrebuilds the table instead of refusing. - #65, observability.
besu::logged(driver)wraps any driver with tracing, timing and a slow-query warning. - #69, parameter limits.
Sqlrefuses a statement that exceeds the dialect's limit. - #70, batch insert chunking.
Insert::values_chunked. - #74, a literal
?.??is an escaped question mark, forjsonb ? 'key'. - #75, publish metadata. Every crate has a README, keywords and categories, and the
publish order is written down in
CONTRIBUTING.md. - #76, update from model.
Update::set_model. - #66, partial indexes, checks and views.
Index::predicateandTable::checksare introspected, diffed and rendered,Schema::viewsround-trips throughbesu pull, and an object the IR cannot express (an expression index, an exclusion constraint, a trigger) is skipped by introspection instead of makingpushdrop it. A check cannot be declared in Rust yet; SQLite and MySQL do not introspect one, and SQLite does not read a partial index's predicate, so those stay migration-only there. - #71, PostgreSQL composite
UNIQUE. A multi-column constraint isTable::uniques, added and dropped withALTER TABLE, instead of an index thatDROP INDEXcould not remove. - #72, schema-qualified tables. A table's identity is
(schema, name); introspection reports every non-system schema and the differ pairs tables by both. - #73,
TimeTzand enum types.TypeDef::TimeTzfor PostgreSQL'sTIMETZ, andSchema::enumsis introspected, diffed and rendered asCREATE TYPE/ALTER TYPE. ADECIMALstill cannot decode into aStringon PostgreSQL or MySQL. - #52, eager loading.
with(has_many(parent_key, child_fk))loads children in one extra, visible statement. - #56, runtime driver switching.
besu-any'sAnyDatabase, chosen from the connection string; typed queries stay per-backend, which is the honest limit. - #61, table prefixes.
Database::prefixed. The prefix lives onSql, because that is where every table name is written. - #53, streaming.
Database::stream/Transaction::streamdecode row by row. - #67, savepoints.
Transaction::transactionandsavepoint(name, ..). - #58, JSON path operators.
json_getandjson_textwith a dotted path, rendered per dialect. - #54, rename detection.
#[besu(renamed_from = "...")]turns a drop plus an add into oneALTER TABLE ... RENAME COLUMN. Renaming a table still is not modelled. - #57, full-text search.
matches(column, query)andColumn::matchesbind the query and renderto_tsvector(..) @@ plainto_tsquery(..)on PostgreSQL,MATCH ... AGAINSTon MySQL andMATCHon SQLite. Atsvectorcolumn type, declaring the index or virtual table inTableDef, a language parameter, ranking and highlighting are still open. - #62, Studio UI.
besu-studiois a development-only server and single page: it serves the introspected schema, a paged row browser and a one-statement SQL endpoint. It binds loopback only unless--allow-remoteis passed, because the query box runs arbitrary SQL and there is no authentication. See The studio.
Contributing
Trait changes are the expensive part of this list: Driver, Executor and Ddl are public
API, and adding a method is a breaking change for every driver. Open an issue before starting
one of those. A new expression, a new Change variant or a CLI improvement is much easier to
land.
