1use async_trait::async_trait;
6
7use crate::{
8 BlobStore, DeleteResult, Identity, KeyPrefix, PutResult, Record, RecordKey, Result, Revision,
9 Store,
10};
11
12pub struct AncestryStore<S, B> {
17 inner: S,
18 ancestry: B,
19}
20
21impl<S, B> AncestryStore<S, B> {
22 pub fn new(inner: S, ancestry: B) -> Self {
23 Self { inner, ancestry }
24 }
25
26 pub fn ancestry(&self) -> &B {
29 &self.ancestry
30 }
31
32 pub fn inner(&self) -> &S {
34 &self.inner
35 }
36}
37
38#[async_trait]
39impl<S: Store, B: BlobStore> Store for AncestryStore<S, B> {
40 async fn get(&self, key: &RecordKey) -> Result<Option<Record>> {
41 self.inner.get(key).await
42 }
43
44 async fn put(&self, record: Record, expected: Option<Revision>) -> Result<PutResult> {
45 let body_bytes = record.body.bytes().to_vec();
48 let outcome = self.inner.put(record, expected).await?;
49 if matches!(outcome, PutResult::Committed(_)) {
50 self.ancestry.put_blob(&body_bytes).await?;
51 }
52 Ok(outcome)
53 }
54
55 async fn list(&self, prefix: &KeyPrefix) -> Result<Vec<RecordKey>> {
56 self.inner.list(prefix).await
57 }
58
59 async fn delete_as(
60 &self,
61 key: &RecordKey,
62 expected: Option<Revision>,
63 author: Option<Identity>,
64 ) -> Result<DeleteResult> {
65 self.inner.delete_as(key, expected, author).await
68 }
69
70 async fn get_raw(&self, key: &RecordKey) -> Result<Option<Record>> {
71 self.inner.get_raw(key).await
72 }
73
74 async fn list_raw(&self, prefix: &KeyPrefix) -> Result<Vec<RecordKey>> {
75 self.inner.list_raw(prefix).await
76 }
77
78 async fn put_raw(&self, record: Record, expected: Option<Revision>) -> Result<PutResult> {
79 let body_bytes = record.body.bytes().to_vec();
82 let outcome = self.inner.put_raw(record, expected).await?;
83 if matches!(outcome, PutResult::Committed(_)) {
84 self.ancestry.put_blob(&body_bytes).await?;
85 }
86 Ok(outcome)
87 }
88
89 async fn purge(&self, key: &RecordKey, expected: Revision) -> Result<DeleteResult> {
90 self.inner.purge(key, expected).await
93 }
94}
95
96#[cfg(test)]
97pub(crate) mod tests {
98 use super::*;
99 use crate::store::Conflict;
100 use crate::{
101 BlobEntry, Body, ContentHash, CoreError, Identity, Meta, RecordKind, revision::Revision,
102 };
103 use std::collections::BTreeMap;
104 use std::sync::Mutex;
105
106 #[derive(Default)]
108 pub(crate) struct Mem {
109 records: Mutex<BTreeMap<RecordKey, Record>>,
110 blobs: Mutex<BTreeMap<ContentHash, Vec<u8>>>,
111 }
112
113 #[async_trait]
114 impl Store for Mem {
115 async fn get(&self, key: &RecordKey) -> Result<Option<Record>> {
116 Ok(self.records.lock().unwrap().get(key).cloned())
117 }
118 async fn put(&self, record: Record, expected: Option<Revision>) -> Result<PutResult> {
119 let mut g = self.records.lock().unwrap();
120 let current = g.get(&record.key).map(|r| r.revision.clone());
121 if current != expected {
122 if let Some(cur) = g.get(&record.key).cloned() {
123 return Ok(PutResult::Conflict(Box::new(Conflict {
124 key: record.key.clone(),
125 expected,
126 current: cur,
127 })));
128 }
129 return Err(CoreError::NotFound(record.key.clone()));
130 }
131 let rev = record.revision.clone();
132 g.insert(record.key.clone(), record);
133 Ok(PutResult::Committed(rev))
134 }
135 async fn list(&self, prefix: &KeyPrefix) -> Result<Vec<RecordKey>> {
136 Ok(self
137 .records
138 .lock()
139 .unwrap()
140 .keys()
141 .filter(|k| prefix.matches(k))
142 .cloned()
143 .collect())
144 }
145 async fn delete_as(
146 &self,
147 key: &RecordKey,
148 expected: Option<Revision>,
149 _author: Option<Identity>,
150 ) -> Result<DeleteResult> {
151 let mut g = self.records.lock().unwrap();
152 match g.get(key) {
153 None => Ok(DeleteResult::Deleted),
154 Some(cur) if expected.is_none() || expected.as_ref() == Some(&cur.revision) => {
155 g.remove(key);
156 Ok(DeleteResult::Deleted)
157 }
158 Some(cur) => Ok(DeleteResult::Conflict(Box::new(Conflict {
159 key: key.clone(),
160 expected,
161 current: cur.clone(),
162 }))),
163 }
164 }
165 async fn put_raw(&self, record: Record, expected: Option<Revision>) -> Result<PutResult> {
166 <Self as Store>::put(self, record, expected).await
167 }
168 async fn get_raw(&self, key: &RecordKey) -> Result<Option<Record>> {
169 self.get(key).await
170 }
171 async fn list_raw(&self, prefix: &KeyPrefix) -> Result<Vec<RecordKey>> {
172 self.list(prefix).await
173 }
174 async fn purge(&self, key: &RecordKey, expected: Revision) -> Result<DeleteResult> {
175 self.delete(key, Some(expected)).await
176 }
177 }
178
179 #[async_trait]
180 impl BlobStore for Mem {
181 async fn put_blob(&self, content: &[u8]) -> Result<ContentHash> {
182 let hash = ContentHash::of(content);
183 self.blobs
184 .lock()
185 .unwrap()
186 .insert(hash.clone(), content.to_vec());
187 Ok(hash)
188 }
189 async fn get_blob(&self, hash: &ContentHash) -> Result<Option<Vec<u8>>> {
190 Ok(self.blobs.lock().unwrap().get(hash).cloned())
191 }
192 async fn list_blobs(&self) -> Result<Vec<BlobEntry>> {
193 Ok(self
194 .blobs
195 .lock()
196 .unwrap()
197 .keys()
198 .cloned()
199 .map(|h| BlobEntry::from_system_time(h, std::time::SystemTime::now()))
200 .collect())
201 }
202 async fn delete_blob(&self, hash: &ContentHash) -> Result<()> {
203 self.blobs.lock().unwrap().remove(hash);
204 Ok(())
205 }
206 }
207
208 pub(crate) fn rec(id: &str, kind: RecordKind, payload: &str) -> Record {
209 let body = Body::Inline(payload.as_bytes().to_vec());
210 Record {
211 revision: Revision::initial(body.bytes()),
212 parent: None,
213 body,
214 kind,
215 key: RecordKey::new("ns", "col", id),
216 meta: Meta {
217 author: Identity::new("t"),
218 origin_system: "test".into(),
219 created: 0,
220 updated: 0,
221 labels: BTreeMap::new(),
222 },
223 links: Vec::new(),
224 ancestors: Vec::new(),
225 deleted_at: None,
226 deleted_blob: None,
227 deleted_kind: None,
228 }
229 }
230
231 #[tokio::test]
232 async fn put_retains_body_under_revision_hash_and_delegates() {
233 let store = AncestryStore::new(Mem::default(), Mem::default());
234 let r = rec("k", RecordKind::MemoryTier, r#"{"a":1}"#);
235 let rev = r.revision.clone();
236 assert!(matches!(
237 store.put(r.clone(), None).await.unwrap(),
238 PutResult::Committed(_)
239 ));
240
241 let retained = store.ancestry().get_blob(&rev.hash).await.unwrap();
243 assert_eq!(retained.as_deref(), Some(r.body.bytes()));
244
245 assert_eq!(store.get(&r.key).await.unwrap().unwrap().revision, rev);
247 assert_eq!(
248 store.list(&KeyPrefix::default()).await.unwrap(),
249 vec![r.key]
250 );
251 }
252
253 #[tokio::test]
254 async fn conflicting_put_does_not_retain() {
255 let store = AncestryStore::new(Mem::default(), Mem::default());
256 let r = rec("k", RecordKind::MemoryTier, r#"{"a":1}"#);
257 let _ = store.put(r.clone(), None).await.unwrap();
258
259 let other = rec("k", RecordKind::MemoryTier, r#"{"a":2}"#);
261 let other_rev = other.revision.clone();
262 assert!(matches!(
263 store.put(other, None).await.unwrap(),
264 PutResult::Conflict(_)
265 ));
266 assert!(
267 store
268 .ancestry()
269 .get_blob(&other_rev.hash)
270 .await
271 .unwrap()
272 .is_none()
273 );
274 }
275}