Skip to main content

Session

Struct Session 

Source
pub struct Session<C: Connection> { /* private fields */ }
Expand description

The Session is the central unit-of-work manager.

It tracks objects loaded from or added to the database and coordinates flushing changes back to the database.

Implementations§

Source§

impl<C: Connection> Session<C>

Source

pub fn new(connection: C) -> Self

Create a new session from an existing connection.

Source

pub fn with_config(connection: C, config: SessionConfig) -> Self

Create a new session with custom configuration.

Source

pub fn connection(&self) -> &C

Get a reference to the underlying connection.

Source

pub fn config(&self) -> &SessionConfig

Get the session configuration.

Source

pub fn on_before_flush( &mut self, f: impl FnMut() -> Result<(), Error> + Send + 'static, )

Register a callback to run before flush.

The callback can abort the flush by returning Err.

Source

pub fn on_after_flush( &mut self, f: impl FnMut() -> Result<(), Error> + Send + 'static, )

Register a callback to run after a successful flush.

Source

pub fn on_before_commit( &mut self, f: impl FnMut() -> Result<(), Error> + Send + 'static, )

Register a callback to run before commit (after flush).

The callback can abort the commit by returning Err.

Source

pub fn on_after_commit( &mut self, f: impl FnMut() -> Result<(), Error> + Send + 'static, )

Register a callback to run after a successful commit.

Source

pub fn on_after_rollback( &mut self, f: impl FnMut() -> Result<(), Error> + Send + 'static, )

Register a callback to run after rollback.

Source

pub fn add<M: Model + Clone + Send + Sync + Serialize + 'static>( &mut self, obj: &M, )

Add a new object to the session.

The object will be INSERTed on the next flush() call.

Source

pub fn add_all<'a, M, I>(&mut self, objects: I)
where M: Model + Clone + Send + Sync + Serialize + 'static, I: IntoIterator<Item = &'a M>,

Add multiple objects to the session at once.

This is equivalent to calling add() for each object, but provides a more convenient API for bulk operations.

§Example
let users = vec![user1, user2, user3];
session.add_all(&users);

// Or with an iterator
session.add_all(users.iter());

All objects will be INSERTed on the next flush() call.

Source

pub fn delete<M: Model + 'static>(&mut self, obj: &M)

Delete an object from the session.

The object will be DELETEd on the next flush() call.

Source

pub fn mark_dirty<M: Model + Clone + Send + Sync + Serialize + 'static>( &mut self, obj: &M, )

Mark an object as dirty (modified) so it will be UPDATEd on flush.

This updates the stored values from the object and schedules an UPDATE. Only works for objects that are already tracked as Persistent.

§Example
let mut hero = session.get::<Hero>(1).await?.unwrap();
hero.name = "New Name".to_string();
session.mark_dirty(&hero);  // Schedule for UPDATE
session.flush(cx).await?;   // Execute the UPDATE
Source

pub async fn get<M: Model + Clone + Send + Sync + Serialize + for<'de> Deserialize<'de> + 'static>( &mut self, cx: &Cx, pk: impl Into<Value>, ) -> Outcome<Option<M>, Error>

Get an object by primary key.

First checks the identity map, then queries the database if not found.

Source

pub async fn get_by_pk<M: Model + Clone + Send + Sync + Serialize + for<'de> Deserialize<'de> + 'static>( &mut self, cx: &Cx, pk_values: &[Value], ) -> Outcome<Option<M>, Error>

Get an object by composite primary key.

First checks the identity map, then queries the database if not found.

§Example
// Composite PK lookup
let item = session.get_by_pk::<OrderItem>(&[
    Value::BigInt(order_id),
    Value::BigInt(product_id),
]).await?;
Source

pub async fn get_with_options<M: Model + Clone + Send + Sync + Serialize + for<'de> Deserialize<'de> + 'static>( &mut self, cx: &Cx, pk_values: &[Value], options: &GetOptions, ) -> Outcome<Option<M>, Error>

Get an object by primary key with options.

This is the most flexible form of get() supporting:

  • Composite primary keys via &[Value]
  • with_for_update for row locking
§Example
let options = GetOptions::default().with_for_update(true);
let user = session.get_with_options::<User>(&[Value::BigInt(1)], &options).await?;
Source

pub fn contains<M: Model + 'static>(&self, obj: &M) -> bool

Check if an object is tracked by this session.

Source

pub fn expunge<M: Model + 'static>(&mut self, obj: &M)

Detach an object from the session.

Source

pub fn expunge_all(&mut self)

Detach all objects from the session.

Source

pub fn is_modified<M: Model + Serialize + 'static>(&self, obj: &M) -> bool

Check if an object has pending changes.

Returns true if:

  • Object is new (pending INSERT)
  • Object has been modified since load (pending UPDATE)
  • Object is marked for deletion (pending DELETE)

Returns false if:

  • Object is not tracked
  • Object is clean (unchanged since load)
  • Object is detached or expired
§Example
let user = session.get::<User>(1).await?.unwrap();
assert!(!session.is_modified(&user));  // Fresh from DB

// Modify and re-check
let mut user_mut = user.clone();
user_mut.name = "New Name".to_string();
session.mark_dirty(&user_mut);
assert!(session.is_modified(&user_mut));  // Now dirty
Source

pub fn modified_attributes<M: Model + Serialize + 'static>( &self, obj: &M, ) -> Vec<&'static str>

Get the list of modified attribute names for an object.

Returns the column names that have changed since the object was loaded. Returns an empty vector if:

  • Object is not tracked
  • Object is new (all fields are “modified”)
  • Object is clean (no changes)
§Example
let mut user = session.get::<User>(1).await?.unwrap();
user.name = "New Name".to_string();
session.mark_dirty(&user);

let changed = session.modified_attributes(&user);
assert!(changed.contains(&"name"));
Source

pub fn object_state<M: Model + 'static>(&self, obj: &M) -> Option<ObjectState>

Get the state of a tracked object.

Returns None if the object is not tracked by this session.

Source

pub fn expire<M: Model + 'static>( &mut self, obj: &M, attributes: Option<&[&str]>, )

Expire an object’s cached attributes, forcing reload on next access.

After calling this method, the next get() call for this object will reload from the database instead of returning the cached version.

§Arguments
  • obj - The object to expire.
  • attributes - Optional list of attribute names to expire. If None, all attributes are expired.
§Example
// Expire all attributes
session.expire(&user, None);

// Expire specific attributes
session.expire(&user, Some(&["name", "email"]));

// Next get() will reload from database
let refreshed = session.get::<User>(cx, user.id).await?;
§Notes
  • Expiring an object does not discard pending changes. If the object has been modified but not flushed, those changes remain pending.
  • Expiring a detached or new object has no effect.
Source

pub fn expire_all(&mut self)

Expire all objects in the session.

After calling this method, all tracked objects will be marked as expired. The next access to any object will reload from the database.

§Example
// Expire everything in the session
session.expire_all();

// All subsequent get() calls will reload from database
let user = session.get::<User>(cx, 1).await?;  // Reloads from DB
let team = session.get::<Team>(cx, 1).await?;  // Reloads from DB
§Notes
  • This does not affect new or deleted objects.
  • Pending changes are not discarded.
Source

pub fn is_expired<M: Model + 'static>(&self, obj: &M) -> bool

Check if an object is expired (needs reload from database).

Returns true if the object is marked as expired and will be reloaded on the next access.

Source

pub fn expired_attributes<M: Model + 'static>( &self, obj: &M, ) -> Option<Option<&HashSet<String>>>

Get the list of expired attribute names for an object.

Returns:

  • None if the object is not tracked or not expired
  • Some(None) if all attributes are expired
  • Some(Some(set)) if only specific attributes are expired
Source

pub async fn refresh<M: Model + Clone + Send + Sync + Serialize + for<'de> Deserialize<'de> + 'static>( &mut self, cx: &Cx, obj: &M, ) -> Outcome<Option<M>, Error>

Refresh an object by reloading it from the database.

This method immediately reloads the object from the database, updating the cached copy in the session. Unlike expire(), which defers the reload until the next access, refresh() performs the reload immediately.

§Arguments
  • cx - The async context for database operations.
  • obj - The object to refresh.
§Returns

Returns Ok(Some(refreshed)) if the object was found in the database, Ok(None) if the object no longer exists in the database, or an error.

§Example
// Immediately reload from database
let refreshed = session.refresh(&cx, &user).await?;

if let Some(user) = refreshed {
    println!("Refreshed: {}", user.name);
} else {
    println!("User was deleted from database");
}
§Notes
  • This discards any changes in the session’s cached copy.
  • If the object has pending changes, they will be lost.
  • If the object no longer exists in the database, it is removed from the session.
Source

pub async fn begin(&mut self, cx: &Cx) -> Outcome<(), Error>

Begin a transaction.

Source

pub async fn flush(&mut self, cx: &Cx) -> Outcome<(), Error>

Flush pending changes to the database.

This executes INSERT, UPDATE, and DELETE statements but does NOT commit.

Source

pub async fn commit(&mut self, cx: &Cx) -> Outcome<(), Error>

Commit the current transaction.

Source

pub async fn rollback(&mut self, cx: &Cx) -> Outcome<(), Error>

Rollback the current transaction.

Source

pub async fn load_lazy<T: Model + Clone + Send + Sync + Serialize + for<'de> Deserialize<'de> + 'static>( &mut self, lazy: &Lazy<T>, cx: &Cx, ) -> Outcome<bool, Error>

Load a single lazy relationship.

Fetches the related object from the database and caches it in the Lazy wrapper. If the relationship has already been loaded, returns the cached value.

§Example
session.load_lazy(&hero.team, &cx).await?;
let team = hero.team.get(); // Now available
Source

pub async fn load_many<P, T, F>( &mut self, cx: &Cx, objects: &[P], accessor: F, ) -> Outcome<usize, Error>
where P: Model + 'static, T: Model + Clone + Send + Sync + Serialize + for<'de> Deserialize<'de> + 'static, F: Fn(&P) -> &Lazy<T>,

Batch load lazy relationships for multiple objects.

This method collects all FK values, executes a single query, and populates each Lazy field. This prevents the N+1 query problem when iterating over a collection and accessing lazy relationships.

§Example
// Load 100 heroes
let mut heroes = session.query::<Hero>().all().await?;

// Without batch loading: 100 queries (N+1 problem)
// With batch loading: 1 query
session.load_many(&cx, &mut heroes, |h| &h.team).await?;

// All teams now loaded
for hero in &heroes {
    if let Some(team) = hero.team.get() {
        println!("{} is on {}", hero.name, team.name);
    }
}
Source

pub async fn load_many_to_many<P, Child, FA, FP>( &mut self, cx: &Cx, objects: &mut [P], accessor: FA, parent_pk: FP, link_table: &LinkTableInfo, ) -> Outcome<usize, Error>
where P: Model + 'static, Child: Model + Clone + Send + Sync + Serialize + for<'de> Deserialize<'de> + 'static, FA: Fn(&mut P) -> &mut RelatedMany<Child>, FP: Fn(&P) -> Value,

Batch load many-to-many relationships for multiple parent objects.

This method loads related objects via a link table in a single query, avoiding the N+1 problem for many-to-many relationships.

§Example
// Load 100 heroes
let mut heroes = session.query::<Hero>().all().await?;

// Without batch loading: 100 queries (N+1 problem)
// With batch loading: 1 query via JOIN
let link_info = LinkTableInfo::new("hero_powers", "hero_id", "power_id");
session.load_many_to_many(&cx, &mut heroes, |h| &mut h.powers, |h| h.id.unwrap(), &link_info).await?;

// All powers now loaded
for hero in &heroes {
    if let Some(powers) = hero.powers.get() {
        println!("{} has {} powers", hero.name, powers.len());
    }
}
Source

pub async fn load_many_to_many_pk<P, Child, FA, FP>( &mut self, cx: &Cx, objects: &mut [P], accessor: FA, parent_pk: FP, link_table: &LinkTableInfo, ) -> Outcome<usize, Error>
where P: Model + 'static, Child: Model + Clone + Send + Sync + Serialize + for<'de> Deserialize<'de> + 'static, FA: Fn(&mut P) -> &mut RelatedMany<Child>, FP: Fn(&P) -> Vec<Value>,

Batch load many-to-many relationships for multiple parent objects using composite keys.

This is the generalized form of load_many_to_many that supports composite parent and/or child primary keys via LinkTableInfo::composite(...).

Source

pub async fn load_one_to_many<P, Child, FA, FP>( &mut self, cx: &Cx, objects: &mut [P], accessor: FA, parent_pk: FP, ) -> Outcome<usize, Error>
where P: Model + 'static, Child: Model + Clone + Send + Sync + Serialize + for<'de> Deserialize<'de> + 'static, FA: Fn(&mut P) -> &mut RelatedMany<Child>, FP: Fn(&P) -> Value,

Batch load one-to-many relationships for multiple parent objects.

This populates RelatedMany<Child> where the child table has a foreign key column pointing back to the parent. It runs a single query:

SELECT *, <fk_col> AS __parent_pk FROM <child_table> WHERE <fk_col> IN (...)

and then groups results per parent PK to populate each RelatedMany.

Flush pending link/unlink operations for many-to-many relationships.

This method persists pending link and unlink operations that were tracked via RelatedMany::link() and RelatedMany::unlink() calls.

§Example
// Add a power to a hero
hero.powers.link(&fly_power);

// Remove a power from a hero
hero.powers.unlink(&x_ray_vision);

// Flush the link table operations
let link_info = LinkTableInfo::new("hero_powers", "hero_id", "power_id");
session.flush_related_many(&cx, &mut [hero], |h| &mut h.powers, |h| h.id.unwrap(), &link_info).await?;

Flush pending link/unlink operations for many-to-many relationships (composite keys).

Source

pub fn relate_to_one<Child, Parent, FC, FP, FK>( &self, child: &mut Child, child_accessor: FC, set_fk: FK, parent: &mut Parent, parent_accessor: FP, )
where Child: Model + Clone + 'static, Parent: Model + Clone + 'static, FC: FnOnce(&mut Child) -> &mut Related<Parent>, FP: FnOnce(&mut Parent) -> &mut RelatedMany<Child>, FK: FnOnce(&mut Child),

Relate a child to a parent with bidirectional sync.

Sets the parent on the child (ManyToOne side) and adds the child to the parent’s collection (OneToMany side) if back_populates is defined.

§Example
// Hero has a ManyToOne relationship to Team (hero.team)
// Team has a OneToMany relationship to Hero (team.heroes) with back_populates

session.relate_to_one(
    &mut hero,
    |h| &mut h.team,
    |h| h.team_id = team.id,  // Set FK
    &mut team,
    |t| &mut t.heroes,
);
// Now hero.team is set AND team.heroes includes hero
Source

pub fn unrelate_from_one<Child, Parent, FC, FP, FK>( &self, child: &mut Child, child_accessor: FC, clear_fk: FK, parent: &mut Parent, parent_accessor: FP, )
where Child: Model + Clone + 'static, Parent: Model + Clone + 'static, FC: FnOnce(&mut Child) -> &mut Related<Parent>, FP: FnOnce(&mut Parent) -> &mut RelatedMany<Child>, FK: FnOnce(&mut Child),

Unrelate a child from a parent with bidirectional sync.

Clears the parent on the child and removes the child from the parent’s collection.

§Example
session.unrelate_from_one(
    &mut hero,
    |h| &mut h.team,
    |h| h.team_id = None,  // Clear FK
    &mut team,
    |t| &mut t.heroes,
);
Source

pub fn relate_many_to_many<Left, Right, FL, FR>( &self, left: &mut Left, left_accessor: FL, right: &mut Right, right_accessor: FR, )
where Left: Model + Clone + 'static, Right: Model + Clone + 'static, FL: FnOnce(&mut Left) -> &mut RelatedMany<Right>, FR: FnOnce(&mut Right) -> &mut RelatedMany<Left>,

Relate two objects in a many-to-many relationship with bidirectional sync.

Adds each object to the other’s collection.

§Example
// Hero has ManyToMany to Power via hero_powers link table
// Power has ManyToMany to Hero via hero_powers link table (back_populates)

session.relate_many_to_many(
    &mut hero,
    |h| &mut h.powers,
    &mut power,
    |p| &mut p.heroes,
);
// Now hero.powers includes power AND power.heroes includes hero
Source

pub fn unrelate_many_to_many<Left, Right, FL, FR>( &self, left: &mut Left, left_accessor: FL, right: &mut Right, right_accessor: FR, )
where Left: Model + Clone + 'static, Right: Model + Clone + 'static, FL: FnOnce(&mut Left) -> &mut RelatedMany<Right>, FR: FnOnce(&mut Right) -> &mut RelatedMany<Left>,

Unrelate two objects in a many-to-many relationship with bidirectional sync.

Removes each object from the other’s collection.

Source

pub fn enable_n1_detection(&mut self, threshold: usize)

Enable N+1 query detection with the specified threshold.

When the number of lazy loads for a single relationship reaches the threshold, a warning is emitted suggesting batch loading.

§Example
session.enable_n1_detection(3);  // Warn after 3 lazy loads

// This will trigger a warning:
for hero in &mut heroes {
    hero.team.load(&mut session).await?;
}

// Check stats
if let Some(stats) = session.n1_stats() {
    println!("Potential N+1 issues: {}", stats.potential_n1);
}
Source

pub fn disable_n1_detection(&mut self)

Disable N+1 query detection and clear the tracker.

Source

pub fn n1_detection_enabled(&self) -> bool

Check if N+1 detection is enabled.

Source

pub fn n1_tracker_mut(&mut self) -> Option<&mut N1QueryTracker>

Get mutable access to the N+1 tracker (for recording loads).

Source

pub fn n1_stats(&self) -> Option<N1Stats>

Get N+1 detection statistics.

Source

pub fn reset_n1_tracking(&mut self)

Reset N+1 detection counts (call at start of new request/transaction).

Source

pub fn record_lazy_load( &mut self, parent_type: &'static str, relationship: &'static str, )

Record a lazy load for N+1 detection.

This is called automatically by lazy loading methods.

Source

pub async fn merge<M: Model + Clone + Send + Sync + Serialize + for<'de> Deserialize<'de> + 'static>( &mut self, cx: &Cx, model: M, load: bool, ) -> Outcome<M, Error>

Merge a detached object back into the session.

This method reattaches a detached or externally-created object to the session, copying its state to the session-tracked instance if one exists.

§Behavior
  1. If object with same PK exists in session: Updates the tracked object with values from the provided object and returns a clone of the tracked version.

  2. If load is true and object not in session: Queries the database for an existing row, merges the provided values onto it, and tracks it.

  3. If object not in session or DB: Treats it as new (will INSERT on flush).

§Example
// Object from previous session or external source
let mut detached_user = User { id: Some(1), name: "Updated Name".into(), .. };

// Merge into current session
let attached_user = session.merge(&cx, detached_user, true).await?;

// attached_user is now tracked, changes will be persisted on flush
session.flush(&cx).await?;
§Parameters
  • cx: The async context for database operations.
  • model: The detached model instance to merge.
  • load: If true, load from database when not in identity map.
§Returns

The session-attached version of the object. If the object was already tracked, returns a clone of the updated tracked object. Otherwise, returns a clone of the newly tracked object.

Source

pub async fn merge_without_load<M: Model + Clone + Send + Sync + Serialize + for<'de> Deserialize<'de> + 'static>( &mut self, cx: &Cx, model: M, ) -> Outcome<M, Error>

Merge a detached object without loading from database.

This is a convenience method equivalent to merge(cx, model, false). Use this when you know the object doesn’t exist in the database or you don’t want to query the database.

§Example
let attached = session.merge_without_load(&cx, detached_user).await?;
Source

pub fn pending_new_count(&self) -> usize

Get count of objects pending INSERT.

Source

pub fn pending_delete_count(&self) -> usize

Get count of objects pending DELETE.

Source

pub fn pending_dirty_count(&self) -> usize

Get count of dirty objects pending UPDATE.

Source

pub fn tracked_count(&self) -> usize

Get total tracked object count.

Source

pub fn in_transaction(&self) -> bool

Whether we’re in a transaction.

Source

pub fn debug_state(&self) -> SessionDebugInfo

Dump session state for debugging.

Source

pub async fn bulk_insert<M: Model + Clone + Send + Sync + 'static>( &mut self, cx: &Cx, models: &[M], ) -> Outcome<u64, Error>

Bulk insert multiple model instances without object tracking.

This generates a single multi-row INSERT statement and bypasses the identity map entirely, making it much faster for large batches.

Models are inserted in chunks of batch_size to avoid excessively large SQL statements. The default batch size is 1000.

Returns the total number of rows inserted.

Source

pub async fn bulk_insert_with_batch_size<M: Model + Clone + Send + Sync + 'static>( &mut self, cx: &Cx, models: &[M], batch_size: usize, ) -> Outcome<u64, Error>

Bulk insert with a custom batch size.

Source

pub async fn bulk_update<M: Model + Clone + Send + Sync + 'static>( &mut self, cx: &Cx, models: &[M], ) -> Outcome<u64, Error>

Bulk update multiple model instances without individual tracking.

Each model is updated individually using its primary key, but all updates are executed in a single transaction without going through the identity map or change tracking.

Returns the total number of rows updated.

Trait Implementations§

Source§

impl<C, M> LazyLoader<M> for Session<C>
where C: Connection, M: Model + Clone + Send + Sync + Serialize + for<'de> Deserialize<'de> + 'static,

Source§

fn get( &mut self, cx: &Cx, pk: Value, ) -> impl Future<Output = Outcome<Option<M>, Error>> + Send

Load an object by primary key.

Auto Trait Implementations§

§

impl<C> Freeze for Session<C>
where C: Freeze,

§

impl<C> !RefUnwindSafe for Session<C>

§

impl<C> Send for Session<C>

§

impl<C> !Sync for Session<C>

§

impl<C> Unpin for Session<C>
where C: Unpin,

§

impl<C> UnsafeUnpin for Session<C>
where C: UnsafeUnpin,

§

impl<C> !UnwindSafe for Session<C>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, _span: NoopSpan) -> Self

Instruments this future with a span (no-op when disabled).
Source§

fn in_current_span(self) -> Self

Instruments this future with the current span (no-op when disabled).
Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more