taskchampion_sync_server_core/storage.rs
1use chrono::{DateTime, Utc};
2use uuid::Uuid;
3
4/// A representation of stored metadata about a client.
5#[derive(Clone, PartialEq, Eq, Debug)]
6pub struct Client {
7 /// The latest version for this client (may be the nil version)
8 pub latest_version_id: Uuid,
9 /// Data about the latest snapshot for this client
10 pub snapshot: Option<Snapshot>,
11}
12
13/// Metadata about a snapshot, not including the snapshot data itself.
14#[derive(Clone, PartialEq, Eq, Debug)]
15pub struct Snapshot {
16 /// ID of the version at which this snapshot was made
17 pub version_id: Uuid,
18
19 /// Timestamp at which this snapshot was set
20 pub timestamp: DateTime<Utc>,
21
22 /// Number of versions since this snapshot was made
23 pub versions_since: u32,
24}
25
26#[derive(Clone, PartialEq, Eq, Debug)]
27pub struct Version {
28 /// The uuid identifying this version.
29 pub version_id: Uuid,
30 /// The uuid identifying this version's parent.
31 pub parent_version_id: Uuid,
32 /// The data carried in this version.
33 pub history_segment: Vec<u8>,
34}
35
36/// A transaction in the storage backend.
37///
38/// Transactions must be sequentially consistent. That is, the results of transactions performed
39/// in storage must be as if each were executed sequentially in some order. In particular,
40/// un-committed changes must not be read by another transaction, but committed changes must
41/// be visible to subequent transations. Together, this guarantees that `add_version` reliably
42/// constructs a linear sequence of versions.
43///
44/// Transactions with different client IDs cannot share any data, so it is safe to handle them
45/// concurrently.
46///
47/// Changes in a transaction that is dropped without calling `commit` must not appear in any other
48/// transaction.
49#[async_trait::async_trait(?Send)]
50pub trait StorageTxn {
51 /// Get information about the client for this transaction
52 async fn get_client(&mut self) -> anyhow::Result<Option<Client>>;
53
54 /// Create the client for this transaction, with the given latest_version_id. The client must
55 /// not already exist.
56 async fn new_client(&mut self, latest_version_id: Uuid) -> anyhow::Result<()>;
57
58 /// Set the client's most recent snapshot.
59 async fn set_snapshot(&mut self, snapshot: Snapshot, data: Vec<u8>) -> anyhow::Result<()>;
60
61 /// Get the data for the most recent snapshot. The version_id
62 /// is used to verify that the snapshot is for the correct version.
63 async fn get_snapshot_data(&mut self, version_id: Uuid) -> anyhow::Result<Option<Vec<u8>>>;
64
65 /// Get a version, indexed by parent version id
66 async fn get_version_by_parent(
67 &mut self,
68 parent_version_id: Uuid,
69 ) -> anyhow::Result<Option<Version>>;
70
71 /// Get a version, indexed by its own version id
72 async fn get_version(&mut self, version_id: Uuid) -> anyhow::Result<Option<Version>>;
73
74 /// Add a version (that must not already exist), and
75 /// - update latest_version_id from parent_version_id to version_id
76 /// - increment snapshot.versions_since
77 /// Fails if the existing `latest_version_id` is not equal to `parent_version_id`. Check
78 /// this by calling `get_client` earlier in the same transaction.
79 async fn add_version(
80 &mut self,
81 version_id: Uuid,
82 parent_version_id: Uuid,
83 history_segment: Vec<u8>,
84 ) -> anyhow::Result<()>;
85
86 /// Commit any changes made in the transaction. It is an error to call this more than
87 /// once. It is safe to skip this call for read-only operations.
88 async fn commit(&mut self) -> anyhow::Result<()>;
89}
90
91/// A trait for objects able to act as storage. Most of the interesting behavior is in the
92/// [`crate::storage::StorageTxn`] trait.
93#[async_trait::async_trait]
94pub trait Storage: Send + Sync {
95 /// Begin a transaction for the given client ID.
96 async fn txn(&self, client_id: Uuid) -> anyhow::Result<Box<dyn StorageTxn + '_>>;
97}