Skip to main content

sofie_db/
commit_hooks.rs

1use std::any::Any;
2use std::sync::{Arc, RwLock};
3
4use sofie_core::{Uuid, unpoisoned};
5
6use crate::{Db, Driver};
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub struct ChangedRecord {
10    pub module: &'static str,
11    pub aggregate: &'static str,
12    /// `None` when the commit changed records it cannot name, such as the default it cleared.
13    pub id: Option<Uuid>,
14}
15
16/// Runs after a commit that wrote aggregates, which already stands, so it cannot fail it. It also
17/// runs when the caller is dropped mid-commit, landed or not; slow work goes to a task.
18pub trait CommitHook: Any + Send + Sync {
19    fn committed(&self, changes: &[ChangedRecord]);
20}
21
22/// Seen by every clone of `db`, made before or after.
23pub fn add_commit_hook(db: &Db, hook: Arc<dyn CommitHook>) {
24    unpoisoned::write(&db.commit_hooks.0).push(hook);
25}
26
27#[must_use]
28pub fn commit_hook<T: CommitHook>(db: &Db) -> Option<Arc<T>> {
29    let hooks = unpoisoned::read(&db.commit_hooks.0);
30    hooks.iter().find_map(|hook| {
31        let hook: Arc<dyn Any + Send + Sync> = hook.clone();
32        hook.downcast().ok()
33    })
34}
35
36/// For a cache keeping a table of its own and notifying other instances.
37#[must_use]
38pub fn cache_driver(db: &Db) -> &dyn Driver {
39    db.driver()
40}
41
42#[derive(Default)]
43pub(crate) struct CommitHooks(RwLock<Vec<Arc<dyn CommitHook>>>);
44
45impl CommitHooks {
46    pub(crate) fn run(&self, changes: &[ChangedRecord]) {
47        if changes.is_empty() {
48            return;
49        }
50        let hooks = unpoisoned::read(&self.0);
51        for hook in hooks.iter() {
52            hook.committed(changes);
53        }
54    }
55}