RocketBoost Orbit logo Orbit rocketboost.rs

Blocking API

With the blocking feature, orbit_orm::blocking offers the same queries without async. Each call blocks the calling thread until its query finishes. The feature is off by default, and the install steps are in Installation.

Queries

Build the query with the usual methods, then call blocking() before a terminal method:

use orbit_orm::blocking::Database;

let db = Database::connect("mysql://user:password@127.0.0.1/app")?;
let post = Post::query().blocking().find_or_fail(&db, 1)?;

The terminal methods are get, first, first_or_fail, find, find_or_fail, count, exists, insert, replace, update and delete. They take the same arguments and return the same values as their async versions in Queries. Each one takes &db or a transaction.

How it runs

blocking::Database::connect builds a current-thread Tokio runtime, and the database owns it. Each call runs the async query on that runtime and returns its output. The calling thread is blocked until the query finishes.

Where to call it

Call it from a thread that may block, such as one started with tokio::task::spawn_blocking. Share the database with an Arc:

use std::sync::Arc;

use orbit_orm::blocking::Database;

// Connect on a thread that runs no async code. See "Async threads" below.
let db = Arc::new(Database::connect("mysql://user:password@127.0.0.1/app")?);

let worker = Arc::clone(&db);
let post = tokio::task::spawn_blocking(move || {
    Post::query().blocking().find_or_fail(&*worker, 1)
})
.await
.expect("the blocking task finished")?;

Async threads

A blocking call on a thread that runs async code returns Error::BlockingInRuntime. This covers queries, transaction, and connect. Tokio refuses to block on such a thread, and Orbit turns Tokio's panic into this error, so the thread is not stalled. Tokio still prints its own panic message to stderr before the error is returned. Dropping a blocking::Database on such a thread is safe, because its runtime is shut down in the background.

Transactions

db.transaction takes a synchronous closure, which receives the transaction. Pass the transaction to blocking queries as tx. A query given &db runs on the pool, outside the transaction:

db.transaction(|tx| {
    Post::query().blocking().insert(tx, &[("title", "Hello".into())])?;
    Ok::<_, orbit_orm::Error>(())
})?;
  • Commit. The transaction commits when the closure returns Ok.
  • Rollback on Err. When the closure returns Err, the transaction rolls back and the error is returned. A rollback that fails does not replace that error.
  • Rollback on panic. The closure runs inside catch_unwind. If it panics, the transaction is rolled back on the database's runtime, and then the panic continues to the caller. The rollback runs before the unwind, because dropping the connection during an unwind needs a Tokio context.

Nothing is committed on a panic, so writes made before it are not visible afterwards.

Other calls

  • ping checks that the database answers, and close closes the pool. Both block, and close returns a Result.
  • run(future) blocks on any async future on the database's runtime. Use it for a sqlx query that Orbit does not wrap, such as db.run(sqlx::query("...").execute(db.pool())). Its result is Result<Output, Error>, so the query's own result is inside it.