rent
Learn by example

8. Optimistic concurrency

Mark an RSL field with @rent.version. Updating an entity instance automatically adds its old version to the predicate and increments the stored version:

let published = client
    .update_post_one(&original)
    .published(true)
    .save_one()
    .await?;

assert_eq!(published.version, original.version + 1);

let rejected = client
    .update_post_one(&stale)
    .title("Stale edit")
    .save_one()
    .await;

assert!(matches!(rejected, Err(EntityError::OptimisticLock { .. })));

Imports and bulk edits

An import can use an upsert without leaving an older editor's version token valid:

use generated::post;

let imported = client
    .upsert_post(post::fields::ID)
    .id(published.id)
    .title("Updated by import")
    .body("Import content")
    .published(true)
    .author_id(ada.id)
    .save()
    .await?;

assert_eq!(imported.version, published.version + 1);

The insert branch uses the initial version; the conflict branch increments the existing version. Fields marked @rent.immutable keep their existing values on conflict. The upsert itself is unconditional: choose an entity-targeted update when the write must reject stale input.

Filtered updates also increment the version on each matched row:

client
    .update_post()
    .id_eq(imported.id)
    .published(false)
    .save()
    .await?;

let moderated = client
    .post()
    .id_eq(imported.id)
    .only()
    .await?;

assert_eq!(moderated.version, imported.version + 1);

Saving published after the import, or imported after moderation, returns OptimisticLock. The executable chapter checks both cases. Declare only one version field, using a non-null mutable UInt or BigInt outside the primary key. Version exhaustion rejects the write; it never resets the counter. Raw SQL writers must increment the version as part of their own update statement.

See the @rent.version declaration in crates/rent/examples/tutorial_07_optimistic_locking/schema.rsl. Run cargo run -p rent --example tutorial_07_optimistic_locking. The same stale-write workflow runs under nextest.

On this page