Musq
Musq is an asynchronous SQLite toolkit for Rust.
Musq bundles its own SQLite, runs each connection on a dedicated worker thread behind an async API, enables foreign key enforcement by default, and ships with sqlite-vec vector search built in.
Quickstart
//! Quickstart example from the README.
use ;
/// User record fetched from the database.
async
Opening a database
Musq is the options builder. open() and open_in_memory() return a Pool;
queries execute directly on the pool, on a Connection, a PoolConnection, or
a Transaction — all interchangeably.
use ;
let pool = new
.max_connections
.create_if_missing
.journal_mode
.open
.await?;
Defaults: foreign keys on, busy timeout 5s, 10 pool connections, journal mode
left unchanged (set JournalMode::Wal explicitly for WAL). Any pragma can be
set with .pragma(key, value).
Queries
sql! builds a query from a format!-like string. sql_as! does the same and
maps rows to a FromRow type. Interpolated values are always bound as
parameters, never spliced into the SQL.
use ;
let id = 1;
let name = "Bob";
sql!?
.execute
.await?;
let user: User = sql_as!?
.fetch_one
.await?;
Placeholders:
| Placeholder | Expansion |
|---|---|
{expr}, {} |
one bound parameter |
{values:list} |
?, ?, ? — one parameter per element, for IN (...) |
{ident:expr} |
one quoted identifier |
{idents:list} |
comma-separated quoted identifiers |
{insert:values} |
(col, ...) VALUES (?, ...) |
{set:values} |
col = ?, ... |
{where:values} |
col = ? AND ...; col IS NULL for NULLs; 1=1 when empty |
{upsert:values, exclude: a, b} |
col = excluded.col, ... for ON CONFLICT ... DO UPDATE SET |
{raw:expr} |
verbatim SQL; taints the query |
let table_name = "users";
let user_ids = vec!;
let columns = ;
let users: = sql_as!?
.fetch_all
.await?;
Run queries with .execute(), .fetch_one(), .fetch_optional(),
.fetch_all(), or .fetch() (a Stream). Compose dynamic queries with
Query::join, or drop down to QueryBuilder for full control.
Values
Values is an insertion-ordered column/value map consumed by the {insert:},
{set:}, {where:}, and {upsert:} placeholders. Build one with the
values! macro or fluently with Values::new().val(k, v)?. Each value is
encoded immediately, so construction returns Result.
use ;
let user_data = values! ?;
sql!?
.execute
.await?;
let changes = new
.val?
.val?;
sql!?
.execute
.await?;
let filters = values! ?;
let user: User = sql_as!?
.fetch_one
.await?;
let upsert = values! ?;
sql!?
.execute
.await?;
Option::None encodes as SQL NULL everywhere ({where:} renders it as
col IS NULL), and musq::Null is an untyped NULL literal:
async
DB-side computed values come from musq::expr (now_rfc3339_utc, jsonb,
jsonb_text, jsonb_serde, raw):
use expr;
let changes = values! ?;
sql!?
.execute
.await?;
expr::raw(...) taints the resulting query; prefer the curated helpers.
Transactions
Pool::begin and Connection::begin return a Transaction. Calling begin
on an existing transaction creates a savepoint. Dropping an uncommitted
transaction rolls it back. Connection::transaction runs a closure and
commits or rolls back based on its result.
let mut tx = pool.begin.await?;
sql!?
.execute
.await?;
tx.commit.await?;
Types
The Encode and Decode traits convert between Rust and SQLite types:
| Rust type | SQLite type |
|---|---|
bool |
BOOLEAN |
i8, i16, i32, i64 |
INTEGER |
u8, u16, u32 |
INTEGER |
f32, f64 |
REAL |
&str, String, Arc<String> |
TEXT |
&[u8], Vec<u8>, Arc<Vec<u8>> |
BLOB |
bstr::BString |
BLOB |
time::OffsetDateTime |
DATETIME |
time::PrimitiveDateTime |
DATETIME |
time::Date |
DATE |
time::Time |
TIME |
VecF32, VecInt8, VecBit |
BLOB |
Option<T> maps None to NULL. For large strings and blobs, prefer owned
or shared types (String, Vec<u8>, Arc<T>) to avoid copies; blobs can be
decoded directly into Arc<Vec<u8>>. bstr::BString handles text-like BLOBs
that may not be valid UTF-8.
Derived types
#[derive(musq::Codec)] implements both Encode and Decode for enums and
newtype structs (Encode and Decode can also be derived individually).
Enums store as snake-cased strings ("open", "closed") by default:
Or as integers with repr:
Newtype structs store as their inner value:
;
#[derive(musq::Json)] stores any serde-compatible type as JSON text:
Row mapping
#[derive(FromRow)] maps columns to fields by name. Attributes:
#[musq(rename = "...")]— map a field to a differently named column#[musq(rename_all = "...")]— on the struct; case-convert all field names#[musq(default)]— useDefault::default()when the column is absent#[musq(skip)]— always useDefault::default()#[musq(try_from = "T")]— decode asT, then convert withTryFrom#[musq(deserialize_with = "path")]— decode with a customfn(prefix: &str, row: &Row) -> Result<T>#[musq(flatten)],#[musq(flatten, prefix = "...")]— embed a nestedFromRowstruct, optionally prefixing its column names; anOptionnested struct isNoneiff all of its columns are NULL
Vector search
The default vec feature registers sqlite-vec on every connection and
provides the VecF32, VecInt8, and VecBit types. VecF32 binds directly
to vector functions and vec0 tables; VecInt8 and VecBit must be wrapped
in SQL as vec_int8(?) and vec_bit(?).
= { = "0.0.4", = false } # opt out of vec
See the end-to-end example: cargo run -p musq --example vec.
SQLite runtime
Musq supports exactly the SQLite release bundled by its libsqlite3-sys
dependency — currently SQLite 3.53.2 via libsqlite3-sys 0.38.1. Linking
against older or system SQLite libraries is not supported; leave
LIBSQLITE3_SYS_USE_PKG_CONFIG and SQLITE3_* environment variables unset.
Runtime introspection and control:
runtime_info()— SQLite version, source ID, and compile optionsdb_status(kind, reset)— per-connection status counters (page cache, lookaside, schema, statements, ...)wal_checkpoint(schema, mode)— run or inspect WAL checkpoints
Community
Questions, ideas, feature requests: Discord.
Just like whales once used to be land-dwelling quadrupeds, Musq started life as a focused fork of SQLx.