sqlsrv 0.14.0

Utility functions for managing SQLite connections in a server application.
Documentation
//! The change log hook is an abstraction on top of SQLite hooks that only
//! returns committed changes.
//!
//! SQLite hooks are called each time a table is updated within a transaction,
//! which means that if a rollback occurs, all those updates are reverted.  In
//! order to only return the committed changes the change log hook will keep
//! record of all the changes and if a rollback occurs all those changes are
//! discarded.  If a commit occurs the list of changes is passed to the
//! application.
//!
//! There's a potential issue with this:  If the application performs a massive
//! amount of updates within a transaction (like deleting 1 billion rows), the
//! hook's change log may require _a lot_ of memory.
//!
//! As a workaround, applications can limit the amount of updates that it
//! allows within a transaction.  (For instance, removing 1 billion rows could
//! be done in batches of one thousand rows).
//!
//! If tables that may receive a massive amount of updates within a transaction
//! do not need to be reported in the change log, the application can implement
//! a `FromStr` that returns an error for that table.  (Returning errors from
//! the `FromStr` implementation will cause the row event not to be included in
//! the change log).
//!
//! Applications can also use the rawhook, to get closer to the low-level API.
//! This still means that the same problem exists, but it allows the
//! application greater freedom in how it chooses to solve it.

use std::{str::FromStr, sync::Arc};

use parking_lot::Mutex;

use rusqlite::Connection;

pub use crate::rawhook::Action;

#[derive(Debug)]
pub struct Change<D, T> {
  pub action: Action,
  pub database: D,
  pub table: T,
  pub rowid: i64
}

/// Application callback used to process committed changes to the database.
pub trait ChangeLogHook {
  type Database;
  type Table;

  /// Changelog callback.
  ///
  /// Will be called whenevert a set of actions are being _committed_.
  /// Return `true` to change the commit to a rollback.
  fn changelog(
    &mut self,
    log: Vec<Change<Self::Database, Self::Table>>
  ) -> bool;
}

pub fn hook<D, T>(
  conn: &Connection,
  mut cb: Box<dyn ChangeLogHook<Database = D, Table = T> + Send>
) -> Result<(), rusqlite::Error>
where
  D: FromStr + Send + Sized + 'static,
  T: FromStr + Send + Sized + 'static
{
  //
  // Allocate a shared changelog used to keep track of pending changes.
  //
  let changelog = Arc::new(Mutex::new(Vec::new()));

  let chlog = Arc::clone(&changelog);
  conn.commit_hook(Some(move || {
    //println!("commit_hook");
    let mut g = chlog.lock();
    let log = std::mem::take(&mut *g);
    drop(g);

    // Pass log to application call-back
    // Returning true will change commit to a rollback.
    cb.changelog(log)
  }))?;

  let chlog = Arc::clone(&changelog);
  conn.rollback_hook(Some(move || {
    //println!("rollback_hook");
    let mut g = chlog.lock();
    g.clear();
    drop(g);
  }))?;

  conn.update_hook(Some(move |action, db: &str, tbl: &str, rowid| {
    let (Ok(database), Ok(table)) = (D::from_str(db), T::from_str(tbl)) else {
      return;
    };
    let Ok(action) = Action::try_from(action) else {
      // Just ignore unknown actions
      return;
    };
    changelog.lock().push(Change {
      action,
      database,
      table,
      rowid
    });
  }))?;

  Ok(())
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :