coven_database/store/
local_blob_cleanup_intents.rs1use crate::DbError;
4
5pub fn intents_from_changes(
6 blob_decls: &crate::BlobDecls,
7 old_changes: &[coven_foundation::changeset::RowChange],
8 new_changes: &[coven_foundation::changeset::RowChange],
9) -> Result<Vec<LocalBlobCleanupIntent>, crate::BlobDeclError> {
10 if old_changes.len() != new_changes.len() {
11 return Err(crate::BlobDeclError::ChangesetWalkMismatch {
12 old_count: old_changes.len(),
13 new_count: new_changes.len(),
14 });
15 }
16 let mut intents = Vec::new();
17 for (old, new) in old_changes.iter().zip(new_changes) {
18 let old_blob_to_drop = match old.op {
19 coven_foundation::changeset::ChangeOp::Delete => blob_decls.ref_from_change(old)?,
20 coven_foundation::changeset::ChangeOp::Update => {
21 let Some(old_blob) = blob_decls.ref_from_change(old)? else {
22 continue;
23 };
24 let should_drop = match blob_decls.ref_from_change(new)? {
25 Some(new_blob) => {
26 old_blob.namespace != new_blob.namespace || old_blob.id != new_blob.id
27 }
28 None => true,
29 };
30 should_drop.then_some(old_blob)
31 }
32 coven_foundation::changeset::ChangeOp::Insert => None,
33 };
34 if let Some(blob) = old_blob_to_drop {
35 let row_id =
36 old.pk()
37 .ok_or_else(|| crate::BlobDeclError::MissingPublicationPrimaryKey {
38 table: old.table.clone(),
39 })?;
40 intents.push(LocalBlobCleanupIntent::for_row(
41 blob.namespace,
42 blob.id,
43 old.table.clone(),
44 row_id,
45 ));
46 }
47 }
48 Ok(intents)
49}
50use coven_foundation::store_dir::StoreDir;
51
52#[derive(Clone, Debug, PartialEq, Eq)]
55pub struct LocalBlobCleanupIntent {
56 namespace: String,
57 blob_id: String,
58 identity: LocalBlobCleanupIdentity,
59}
60
61#[derive(Clone, Debug, PartialEq, Eq)]
62pub enum LocalBlobCleanupIdentity {
63 Local,
64 Row { table: String, row_id: String },
65 Exact(coven_protocol::store_commit::ObjectHash),
66}
67
68impl LocalBlobCleanupIntent {
69 pub fn local(namespace: impl Into<String>, blob_id: impl Into<String>) -> Self {
70 Self {
71 namespace: namespace.into(),
72 blob_id: blob_id.into(),
73 identity: LocalBlobCleanupIdentity::Local,
74 }
75 }
76
77 pub fn for_row(
78 namespace: impl Into<String>,
79 blob_id: impl Into<String>,
80 table: impl Into<String>,
81 row_id: impl Into<String>,
82 ) -> Self {
83 Self {
84 namespace: namespace.into(),
85 blob_id: blob_id.into(),
86 identity: LocalBlobCleanupIdentity::Row {
87 table: table.into(),
88 row_id: row_id.into(),
89 },
90 }
91 }
92
93 pub fn exact(
94 namespace: impl Into<String>,
95 blob_id: impl Into<String>,
96 locator_hash: coven_protocol::store_commit::ObjectHash,
97 ) -> Self {
98 Self {
99 namespace: namespace.into(),
100 blob_id: blob_id.into(),
101 identity: LocalBlobCleanupIdentity::Exact(locator_hash),
102 }
103 }
104
105 pub fn persisted_identity(&self) -> Result<String, DbError> {
106 match &self.identity {
107 LocalBlobCleanupIdentity::Local => Ok("local".to_string()),
108 LocalBlobCleanupIdentity::Exact(locator_hash) => Ok(locator_hash.to_string()),
109 LocalBlobCleanupIdentity::Row { .. } => Err(DbError::Message(
110 "row-bound local cleanup identity is not durable".to_string(),
111 )),
112 }
113 }
114
115 pub fn from_persisted(
116 namespace: String,
117 blob_id: String,
118 identity: String,
119 ) -> Result<Self, coven_foundation::object_hash::InvalidObjectHash> {
120 if identity == "local" {
121 return Ok(Self::local(namespace, blob_id));
122 }
123 let locator_hash = identity.parse()?;
124 Ok(Self::exact(namespace, blob_id, locator_hash))
125 }
126
127 pub fn namespace(&self) -> &str {
128 &self.namespace
129 }
130
131 pub fn blob_id(&self) -> &str {
132 &self.blob_id
133 }
134
135 pub fn identity(&self) -> &LocalBlobCleanupIdentity {
136 &self.identity
137 }
138
139 pub async fn apply(&self, store_dir: &StoreDir) -> Result<(), DbError> {
140 match &self.identity {
141 LocalBlobCleanupIdentity::Local => store_dir
142 .remove_local_blob(self.namespace(), self.blob_id())
143 .await
144 .map(|_| ())
145 .map_err(DbError::from),
146 LocalBlobCleanupIdentity::Exact(locator_hash) => store_dir
147 .remove_cached_locator(self.namespace(), *locator_hash)
148 .await
149 .map_err(DbError::from),
150 LocalBlobCleanupIdentity::Row { .. } => Err(DbError::Message(
151 "persisted local cleanup intent is row-bound".to_string(),
152 )),
153 }
154 }
155}