1use async_trait::async_trait;
6
7use crate::{
8 BlobStore, DeleteResult, KeyPrefix, PutResult, Record, RecordKey, Result, Revision, Store,
9};
10
11pub struct AncestryStore<S, B> {
16 inner: S,
17 ancestry: B,
18}
19
20impl<S, B> AncestryStore<S, B> {
21 pub fn new(inner: S, ancestry: B) -> Self {
22 Self { inner, ancestry }
23 }
24
25 pub fn ancestry(&self) -> &B {
28 &self.ancestry
29 }
30
31 pub fn inner(&self) -> &S {
33 &self.inner
34 }
35}
36
37#[async_trait]
38impl<S: Store, B: BlobStore> Store for AncestryStore<S, B> {
39 async fn get(&self, key: &RecordKey) -> Result<Option<Record>> {
40 self.inner.get(key).await
41 }
42
43 async fn put(&self, record: Record, expected: Option<Revision>) -> Result<PutResult> {
44 let body_bytes = record.body.bytes().to_vec();
47 let outcome = self.inner.put(record, expected).await?;
48 if matches!(outcome, PutResult::Committed(_)) {
49 self.ancestry.put_blob(&body_bytes).await?;
50 }
51 Ok(outcome)
52 }
53
54 async fn list(&self, prefix: &KeyPrefix) -> Result<Vec<RecordKey>> {
55 self.inner.list(prefix).await
56 }
57
58 async fn delete(&self, key: &RecordKey, expected: Option<Revision>) -> Result<DeleteResult> {
59 self.inner.delete(key, expected).await
63 }
64}
65
66#[cfg(test)]
67pub(crate) mod tests {
68 use super::*;
69 use crate::store::Conflict;
70 use crate::{Body, ContentHash, CoreError, Identity, Meta, RecordKind, revision::Revision};
71 use std::collections::BTreeMap;
72 use std::sync::Mutex;
73
74 #[derive(Default)]
76 pub(crate) struct Mem {
77 records: Mutex<BTreeMap<RecordKey, Record>>,
78 blobs: Mutex<BTreeMap<ContentHash, Vec<u8>>>,
79 }
80
81 #[async_trait]
82 impl Store for Mem {
83 async fn get(&self, key: &RecordKey) -> Result<Option<Record>> {
84 Ok(self.records.lock().unwrap().get(key).cloned())
85 }
86 async fn put(&self, record: Record, expected: Option<Revision>) -> Result<PutResult> {
87 let mut g = self.records.lock().unwrap();
88 let current = g.get(&record.key).map(|r| r.revision.clone());
89 if current != expected {
90 if let Some(cur) = g.get(&record.key).cloned() {
91 return Ok(PutResult::Conflict(Box::new(Conflict {
92 key: record.key.clone(),
93 expected,
94 current: cur,
95 })));
96 }
97 return Err(CoreError::NotFound(record.key.clone()));
98 }
99 let rev = record.revision.clone();
100 g.insert(record.key.clone(), record);
101 Ok(PutResult::Committed(rev))
102 }
103 async fn list(&self, prefix: &KeyPrefix) -> Result<Vec<RecordKey>> {
104 Ok(self
105 .records
106 .lock()
107 .unwrap()
108 .keys()
109 .filter(|k| prefix.matches(k))
110 .cloned()
111 .collect())
112 }
113 async fn delete(
114 &self,
115 key: &RecordKey,
116 expected: Option<Revision>,
117 ) -> Result<DeleteResult> {
118 let mut g = self.records.lock().unwrap();
119 match g.get(key) {
120 None => Ok(DeleteResult::Deleted),
121 Some(cur) if expected.is_none() || expected.as_ref() == Some(&cur.revision) => {
122 g.remove(key);
123 Ok(DeleteResult::Deleted)
124 }
125 Some(cur) => Ok(DeleteResult::Conflict(Box::new(Conflict {
126 key: key.clone(),
127 expected,
128 current: cur.clone(),
129 }))),
130 }
131 }
132 }
133
134 #[async_trait]
135 impl BlobStore for Mem {
136 async fn put_blob(&self, content: &[u8]) -> Result<ContentHash> {
137 let hash = ContentHash::of(content);
138 self.blobs
139 .lock()
140 .unwrap()
141 .insert(hash.clone(), content.to_vec());
142 Ok(hash)
143 }
144 async fn get_blob(&self, hash: &ContentHash) -> Result<Option<Vec<u8>>> {
145 Ok(self.blobs.lock().unwrap().get(hash).cloned())
146 }
147 async fn list_blobs(&self) -> Result<Vec<ContentHash>> {
148 Ok(self.blobs.lock().unwrap().keys().cloned().collect())
149 }
150 async fn delete_blob(&self, hash: &ContentHash) -> Result<()> {
151 self.blobs.lock().unwrap().remove(hash);
152 Ok(())
153 }
154 }
155
156 pub(crate) fn rec(id: &str, kind: RecordKind, payload: &str) -> Record {
157 let body = Body::Inline(payload.as_bytes().to_vec());
158 Record {
159 revision: Revision::initial(body.bytes()),
160 parent: None,
161 body,
162 kind,
163 key: RecordKey::new("ns", "col", id),
164 meta: Meta {
165 author: Identity::new("t"),
166 origin_system: "test".into(),
167 created: 0,
168 updated: 0,
169 labels: BTreeMap::new(),
170 },
171 links: Vec::new(),
172 }
173 }
174
175 #[tokio::test]
176 async fn put_retains_body_under_revision_hash_and_delegates() {
177 let store = AncestryStore::new(Mem::default(), Mem::default());
178 let r = rec("k", RecordKind::MemoryTier, r#"{"a":1}"#);
179 let rev = r.revision.clone();
180 assert!(matches!(
181 store.put(r.clone(), None).await.unwrap(),
182 PutResult::Committed(_)
183 ));
184
185 let retained = store.ancestry().get_blob(&rev.hash).await.unwrap();
187 assert_eq!(retained.as_deref(), Some(r.body.bytes()));
188
189 assert_eq!(store.get(&r.key).await.unwrap().unwrap().revision, rev);
191 assert_eq!(
192 store.list(&KeyPrefix::default()).await.unwrap(),
193 vec![r.key]
194 );
195 }
196
197 #[tokio::test]
198 async fn conflicting_put_does_not_retain() {
199 let store = AncestryStore::new(Mem::default(), Mem::default());
200 let r = rec("k", RecordKind::MemoryTier, r#"{"a":1}"#);
201 let _ = store.put(r.clone(), None).await.unwrap();
202
203 let other = rec("k", RecordKind::MemoryTier, r#"{"a":2}"#);
205 let other_rev = other.revision.clone();
206 assert!(matches!(
207 store.put(other, None).await.unwrap(),
208 PutResult::Conflict(_)
209 ));
210 assert!(
211 store
212 .ancestry()
213 .get_blob(&other_rev.hash)
214 .await
215 .unwrap()
216 .is_none()
217 );
218 }
219}