sqlite_watcher/
lib.rs

1//! This crate provides the basic building blocks to observe changes in a sqlite database
2//! similar to Room (Android) and Core Data (iOS). Additional features such as observable
3//! queries are not included, but can potentially be built using the provided types.
4//!
5//! The crate is agnostic over the implementation of the sqlite connection. Example
6//! implementations are provided for [`rusqlite`](https://crates.io/crates/rusqlite) and
7//! [`sqlx`](https://crates.io/crates/sqlx) which need to be enabled by the respectively named
8//! features.
9//!
10//! # Basic example
11//!
12//! ```rust
13//! use std::collections::BTreeSet;
14//! use std::sync::Arc;
15//! use sqlite_watcher::connection::Connection;
16//! use sqlite_watcher::watcher::{TableObserver, Watcher};
17//!
18//! struct MyObserver{}
19//!
20//! impl TableObserver for MyObserver {
21//!     fn tables(&self) -> Vec<String> {
22//!         vec!["foo".to_owned()]
23//!     }
24//!
25//!     fn on_tables_changed(&self, tables: &BTreeSet<String>) {
26//!         println!("Tables updated: {tables:?}")
27//!     }
28//! }
29//!
30//! // create a watcher
31//! let watcher = Watcher::new().unwrap();
32//! let mut sql_connection = rusqlite::Connection::open_in_memory().unwrap();
33//! let mut connection = Connection::new(sql_connection, Arc::clone(&watcher)).unwrap();
34//! // Create table
35//! connection.execute("CREATE TABLE foo (id INTEGER PRIMARY KEY AUTOINCREMENT, value INTEGER)", ()) .unwrap();
36//! // Register observer
37//! let handle = watcher.add_observer(Box::new(MyObserver{}));
38//! // Sync changes from watcher so we start watching 'foo'
39//! connection.sync_watcher_tables().unwrap();
40//! // Modify table
41//! connection.execute("INSERT INTO foo (value) VALUES (10)", ()) .unwrap();
42//! // Check and publish changes.
43//! connection.publish_watcher_changes().unwrap();
44//! // MyObserver::on_tables_changed should be called at some point.
45//! // Sync changes from watcher so we are up to date
46//! connection.sync_watcher_tables().unwrap();
47//! // Modify table
48//! connection.execute("INSERT INTO foo (value) VALUES (20)", ()) .unwrap();
49//! // Check and publish changes.
50//! connection.publish_watcher_changes().unwrap();
51//! // MyObserver::on_tables_changed should be called at some point.
52//!
53//! ```
54//!
55//! # How it works
56//!
57//! The crate creates a temporary table in each database connection when changes are recorded. For
58//! every table that we observe via a [`TableObserver`], we create temporary triggers for
59//! INSERT, UPDATE and DELETE queries. These queries are create and removed when calling
60//! [`connection::Connection::sync_watcher_tables`] or [`connection::State::sync_tables`].
61//!
62//! When such as query runs, for a given table, we mark it as updated.
63//! [`connection::Connection::publish_watcher_changes()`] or [`connection::State::publish_changes`]
64//! check the tracking table and notifies the [Watcher] of all the tables that have been modified.
65//!
66//! Finally in a background thread, the [Watcher] invokes
67//! [`watcher::TableObserver::on_tables_changed()`]
68//! for each observer which is watching the modified table.
69//!
70//! # Change Granularity
71//!
72//! The [`TableObserver`] is only notified that the table it wants to observe was modified.
73//! It does not include information pertaining to which row was affected or which type of operation
74//! triggered the change (INSERT/UPDATE/DELETE).
75//!
76//! # Multiple Connections
77//!
78//! The [Watcher] can be used with one or multiple connections. Each connection will publish
79//! their changes to the [Watcher] assigned to it.
80//!
81//! # Singe Process
82//!
83//! The only limitation of this model is that it only works for connections that inhabit the same
84//! process space. While sqlite supports being modified by multiple processes, the current observation
85//! does not support this use case.
86//!
87//! # Custom Integrations
88//!
89//! This crate was designed so that it can easily be integrated in existing projects and or
90//! connection libraries. [State] contains everything that is required to patch
91//! an existing connection.
92//!
93//! [Connection] is an example implementation that ties everything together.
94//!
95//! # Async
96//!
97//! While the [Watcher] is a mostly a sync implementation, you can still use the tracking
98//! machinery with async connections. For every method in [State] there is an async counterpart which
99//! ends with the `_async` suffix. There is also an asynchronous connection implementation
100//! ([`ConnectionAsync`]).
101//!
102//! If you need to run async code as part of the [`TableObserver`], it's recommended to either
103//! spawn an async task on the runtime of your choice or channel it to a dedicated async context
104//! via a channel such as [`flume`](https://crates.io/crates/flume).
105//!
106//! [Watcher]: `watcher::Watcher`
107//! [State]: `connection::State`
108//! [`ConnectionAsync`]: `connection::ConnectionAsync`
109//! [Connection]: `connection::Connection`
110//! [`TableObserver`]: `watcher::TableObserver`
111//!
112
113pub mod connection;
114pub mod statement;
115pub mod watcher;