1use crate::blob_records::live_blob_row;
2use crate::blob_records::validate_live_blob_locator;
3use crate::cloud_outbox_records::CloudOutboxRecords;
4use crate::remote_object_records::load_remote_object_on;
5use crate::remote_object_records::persist_exact_remote_object_on;
6use crate::remote_object_records::update_remote_object_on;
7
8use super::*;
9
10pub(crate) fn install_pulled_package_activation_on(
11 conn: &Connection,
12 store_dir: &coven_foundation::store_dir::StoreDir,
13 commit_ref: &StoreBatchCommitRef,
14 domain: SharedLiveSetObjectDomain,
15 object: &ExactObjectRef,
16 package: &AudiencePackage,
17) -> Result<(), DbError> {
18 let object_id = remote_object_id(object);
19 let exists: bool = conn
20 .query_row(
21 "SELECT EXISTS(SELECT 1 FROM remote_objects WHERE object_id = ?1)",
22 [object_id.to_string()],
23 |row| row.get(0),
24 )
25 .map_err(DbError::from)?;
26 if exists {
27 let remote = load_remote_object_on(conn, object_id)?;
28 let mut remote = if matches!(remote, RemoteObjectRecord::CandidateExclusive(_)) {
29 remote.into_activated(commit_ref).map_err(|error| {
30 DbError::context(
31 format!("activate locally prepared pulled package {object_id}"),
32 error,
33 )
34 })?
35 } else {
36 remote
37 };
38 remote
39 .merge_package_activation(&domain, package, commit_ref)
40 .map_err(|error| {
41 DbError::context(
42 format!("merge pulled package activation {object_id}"),
43 error,
44 )
45 })?;
46 update_remote_object_on(conn, object_id, &remote)
47 } else {
48 let remote =
49 RemoteObjectRecord::activated_external_package(domain, package, commit_ref.clone())
50 .map_err(|error| {
51 DbError::context(
52 format!("construct pulled package activation {object_id}"),
53 error,
54 )
55 })?;
56 persist_exact_remote_object_on(conn, store_dir, &remote, "pulled audience package")
57 }
58}
59
60pub(crate) fn install_pulled_merge_membership_activations_on(
61 conn: &Connection,
62 store_dir: &coven_foundation::store_dir::StoreDir,
63 commit_ref: &StoreBatchCommitRef,
64 remotes: &[coven_protocol::remote_object::ClosedRemoteObject],
65) -> Result<(), DbError> {
66 let mut object_ids = BTreeSet::new();
67 for expected in remotes {
68 let object_id = expected.object_id();
69 if !object_ids.insert(object_id) {
70 return Err(DbError::Message(
71 "pulled Merge membership closure repeats an exact object".to_string(),
72 ));
73 }
74 let existing = conn
75 .query_row(
76 "SELECT EXISTS(SELECT 1 FROM remote_objects WHERE object_id = ?1)",
77 [object_id.to_string()],
78 |row| row.get::<_, bool>(0),
79 )
80 .map_err(DbError::from)?;
81 if existing {
82 let mut remote = load_remote_object_on(conn, object_id)?;
83 remote
84 .merge_retained_authority_activation(expected, commit_ref)
85 .map_err(|error| {
86 DbError::context(
87 format!("merge pulled Merge membership authority {object_id}"),
88 error,
89 )
90 })?;
91 update_remote_object_on(conn, object_id, &remote)?;
92 } else {
93 persist_exact_remote_object_on(
94 conn,
95 store_dir,
96 expected,
97 "pulled Merge membership authority",
98 )?;
99 }
100 }
101 Ok(())
102}
103
104impl Database {
105 pub(crate) fn install_pulled_blob_activations_on(
108 conn: &Connection,
109 package: &AudiencePackage,
110 owner: &StoreBatchCommitRef,
111 ) -> Result<(), DbError> {
112 if package.commit_coord() != &owner.coord {
113 return Err(DbError::Message(
114 "pulled blob package coordinate differs from its activating commit".to_string(),
115 ));
116 }
117 for binding in package.blob_bindings() {
118 let stored = binding.blob();
119 let object_id = remote_object_id(stored.object());
120 let exists: bool = conn
121 .query_row(
122 "SELECT EXISTS(SELECT 1 FROM remote_objects WHERE object_id = ?1)",
123 [object_id.to_string()],
124 |row| row.get(0),
125 )
126 .map_err(DbError::from)?;
127 let remote = if exists {
128 let mut remote = load_remote_object_on(conn, object_id)?;
129 remote
130 .merge_blob_activation(stored, owner)
131 .map_err(|error| {
132 DbError::context(format!("merge pulled blob activation {object_id}"), error)
133 })?;
134 remote
135 } else {
136 RemoteObjectRecord::activated_blob(stored, owner.clone())
137 .map_err(|error| {
138 DbError::context(
139 format!("construct pulled blob activation {object_id}"),
140 error,
141 )
142 })?
143 .into_record()
144 };
145 let state = serde_json::to_string(&remote)
146 .map_err(|error| DbError::context("serialize pulled blob activation", error))?;
147 conn.execute(
148 "INSERT INTO remote_objects (object_id, state) VALUES (?1, ?2) \
149 ON CONFLICT(object_id) DO UPDATE SET state = excluded.state",
150 rusqlite::params![object_id.to_string(), state],
151 )
152 .map_err(DbError::from)?;
153 }
154 Ok(())
155 }
156
157 pub(crate) fn row_blob_refs_for_root_on(
158 conn: &Connection,
159 gates: &Gates,
160 tables: &[SyncedTable],
161 root_table: &str,
162 root_id: &str,
163 ) -> Result<Vec<RowBlobRef>, DbError> {
164 let mut rows = gates
165 .subtree_rows(conn, root_table, root_id)
166 .map_err(DbError::from)?
167 .into_iter()
168 .collect::<Vec<_>>();
169 rows.sort();
170 let tables = tables
171 .iter()
172 .map(|table| (table.name(), table))
173 .collect::<BTreeMap<_, _>>();
174 rows.into_iter()
175 .filter_map(|(table_name, row_id)| {
176 tables
177 .get(table_name.as_str())
178 .filter(|table| table.blob().is_some())
179 .map(|table| Self::row_blob_ref_on(conn, gates, table, &row_id))
180 })
181 .collect()
182 }
183
184 pub(crate) fn stored_blob_reference_state_on(
185 conn: &Connection,
186 gates: &Gates,
187 tables: &[SyncedTable],
188 stored: &StoredBlobRef,
189 ) -> Result<StoredBlobReferenceState, DbError> {
190 let exact_object_id = remote_object_id(stored.object()).to_string();
191 let mut statement = conn
192 .prepare(
193 "SELECT table_name, row_id, row_stamp FROM row_blob_locators
194 WHERE remote_object_id = ?1",
195 )
196 .map_err(DbError::from)?;
197 let bindings = statement
198 .query_map([exact_object_id], |row| {
199 Ok((
200 row.get::<_, String>(0)?,
201 row.get::<_, String>(1)?,
202 row.get::<_, String>(2)?,
203 ))
204 })
205 .map_err(DbError::from)?
206 .collect::<Result<Vec<_>, _>>()
207 .map_err(DbError::from)?;
208 drop(statement);
209 let mut unresolved = false;
210 for (table_name, row_id, row_stamp) in bindings {
211 let table = tables
212 .iter()
213 .find(|candidate| candidate.name() == table_name)
214 .ok_or_else(|| {
215 DbError::Message(format!(
216 "stored blob binding names undeclared table {table_name:?}"
217 ))
218 })?;
219 let declaration = table.blob().ok_or_else(|| {
220 DbError::Message(format!(
221 "stored blob binding names table {table_name:?} without a blob declaration"
222 ))
223 })?;
224 let Some(live) = live_blob_row(conn, &table_name, &row_id, declaration)? else {
225 continue;
226 };
227 if live.stamp != row_stamp {
228 continue;
229 }
230 let remote = if !gates.table_is_scoped(&table_name) {
235 gates
236 .root_kept_of(conn, &table_name, &row_id)
237 .map_err(DbError::from)?
238 } else {
239 match crate::live_row_audience(conn, gates, &table_name, &row_id) {
240 Ok(audience) => Some(audience != coven_protocol::circle::Audience::Local),
241 Err(
242 crate::GateError::MissingAudienceRow { .. }
243 | crate::GateError::MissingAudienceParent { .. },
244 ) => None,
245 Err(error) => return Err(DbError::from(error)),
246 }
247 };
248 match remote {
249 Some(false) => continue,
250 None => {
251 unresolved = true;
252 continue;
253 }
254 Some(true) => {}
255 }
256 let reference = Self::row_blob_ref_on(conn, gates, table, &row_id)?;
257 if matches!(reference.authority(), RowBlobAuthority::Remote(_))
258 && reference.stored() == Some(stored)
259 {
260 return Ok(StoredBlobReferenceState::LiveRemote);
261 }
262 }
263 Ok(if unresolved {
264 StoredBlobReferenceState::Unresolved
265 } else {
266 StoredBlobReferenceState::NotLiveRemote
267 })
268 }
269
270 pub(crate) fn row_blob_ref_on(
274 conn: &Connection,
275 gates: &Gates,
276 table: &SyncedTable,
277 row_id: &str,
278 ) -> Result<RowBlobRef, DbError> {
279 Self::live_row_blob_ref_on(conn, gates, table, row_id)?.ok_or_else(|| {
280 DbError::Message(format!(
281 "blob-bearing row {:?}/{row_id:?} does not exist",
282 table.name()
283 ))
284 })
285 }
286
287 pub(crate) fn validate_row_blob_ref_on(
288 conn: &Connection,
289 gates: &Gates,
290 table: &SyncedTable,
291 reference: &RowBlobRef,
292 ) -> Result<(), DbError> {
293 let current = Self::row_blob_ref_on(conn, gates, table, reference.row_id())?;
294 if ¤t != reference {
295 return Err(DbError::Message(format!(
296 "row blob reference {:?}/{:?}/{:?} at {:?} is stale",
297 reference.table(),
298 reference.row_id(),
299 reference.column(),
300 reference.row_stamp()
301 )));
302 }
303 Ok(())
304 }
305
306 pub(crate) fn live_row_blob_ref_on(
311 conn: &Connection,
312 gates: &Gates,
313 table: &SyncedTable,
314 row_id: &str,
315 ) -> Result<Option<RowBlobRef>, DbError> {
316 let declaration = table.blob().ok_or_else(|| {
317 DbError::Message(format!(
318 "synced table {:?} has no blob declaration",
319 table.name()
320 ))
321 })?;
322 let Some(row) = live_blob_row(conn, table.name(), row_id, declaration)? else {
323 return Ok(None);
324 };
325 let audience =
326 gate::live_row_audience(conn, gates, table.name(), row_id).map_err(|error| {
327 DbError::context(
328 format!(
329 "resolve blob row audience for {:?}/{row_id:?}",
330 table.name()
331 ),
332 error,
333 )
334 })?;
335 let (authority, stored) = match RemoteAudience::try_from(audience.clone()) {
336 Err(_) if audience == Audience::Local => (RowBlobAuthority::Local, None),
337 Err(error) => {
338 return Err(DbError::context(
339 format!(
340 "blob row {:?}/{row_id:?} has invalid audience",
341 table.name()
342 ),
343 error,
344 ));
345 }
346 Ok(remote_audience) => {
347 let installed: Option<(String, String)> = conn
348 .query_row(
349 "SELECT binding.audience_authority, locator.remote_object_id
350 FROM row_blob_locators AS binding
351 JOIN blob_locators AS locator
352 ON locator.remote_object_id = binding.remote_object_id
353 WHERE binding.table_name = ?1
354 AND binding.row_id = ?2
355 AND binding.column_name = ?3
356 AND binding.row_stamp = ?4",
357 rusqlite::params![table.name(), row_id, declaration.id_column, row.stamp,],
358 |row| Ok((row.get(0)?, row.get(1)?)),
359 )
360 .optional()
361 .map_err(DbError::from)?;
362 let exact = if let Some((authority_json, remote_object_id)) = installed {
363 let package_authority: coven_protocol::audience_package::PackageAudience =
364 serde_json::from_str(&authority_json).map_err(|error| {
365 DbError::context(format!("remote blob row {:?}/{row_id:?} has invalid audience authority", table.name()), error)
366 })?;
367 let remote_object_id = remote_object_id.parse().map_err(|error| {
368 DbError::context(
369 format!(
370 "remote blob row {:?}/{row_id:?} has invalid prepared object id",
371 table.name()
372 ),
373 error,
374 )
375 })?;
376 let remote = load_remote_object_on(conn, remote_object_id)?;
377 if !remote.is_activated_stored_blob() {
378 return Err(DbError::Message(format!(
379 "remote blob row {:?}/{row_id:?} references a blob without activated ownership",
380 table.name()
381 )));
382 }
383 let locator = crate::blob_records::carried_blob_locator(
384 &remote,
385 &format!(
386 "remote blob row {:?}/{row_id:?} has invalid locator",
387 table.name()
388 ),
389 )?;
390 let stored = StoredBlobRef::new(locator, remote.object().clone()).map_err(
391 |error| {
392 DbError::context(format!("remote blob row {:?}/{row_id:?} has invalid stored blob reference", table.name()), error)
393 },
394 )?;
395 Some((package_authority, stored))
396 } else {
397 CloudOutboxRecords::new(conn)
398 .created_upload_handoff(
399 table.name(),
400 row_id,
401 &declaration.id_column,
402 &row.stamp,
403 )?
404 .map(|handoff| (handoff.authority, handoff.stored))
405 };
406 let Some((package_authority, stored)) = exact else {
407 return RowBlobRef::new(
408 table.name().to_string(),
409 row_id.to_string(),
410 row.stamp,
411 declaration.id_column.clone(),
412 BlobRef {
413 namespace: declaration.namespace.clone(),
414 id: row.blob_id,
415 scope: declaration.scope.clone(),
416 cloud_path: row.cloud_path,
417 provenance: declaration.provenance,
418 fill: declaration.fill,
419 },
420 row.plaintext_size,
421 row.plaintext_hash,
422 RowBlobAuthority::PendingRemote(remote_audience),
423 None,
424 )
425 .map(Some)
426 .map_err(DbError::from);
427 };
428 if package_authority.remote_audience() != remote_audience {
429 return Err(DbError::Message(format!(
430 "remote blob row {:?}/{row_id:?} has audience authority {:?}, expected {remote_audience:?}",
431 table.name(), package_authority
432 )));
433 }
434 validate_live_blob_locator(
435 table.name(),
436 row_id,
437 &declaration.id_column,
438 &row.stamp,
439 &stored,
440 declaration,
441 &row,
442 &remote_audience,
443 )?;
444 (RowBlobAuthority::Remote(package_authority), Some(stored))
445 }
446 };
447 let blob = BlobRef {
448 namespace: declaration.namespace.clone(),
449 id: row.blob_id.clone(),
450 scope: declaration.scope.clone(),
451 cloud_path: row.cloud_path.clone(),
452 provenance: declaration.provenance,
453 fill: declaration.fill,
454 };
455 RowBlobRef::new(
456 table.name().to_string(),
457 row_id.to_string(),
458 row.stamp,
459 declaration.id_column.clone(),
460 blob,
461 row.plaintext_size,
462 row.plaintext_hash,
463 authority,
464 stored,
465 )
466 .map(Some)
467 .map_err(DbError::from)
468 }
469
470 #[cfg(any(test, feature = "test-utils"))]
471 pub async fn row_blob_ref(&self, table: &str, row_id: &str) -> Result<RowBlobRef, DbError> {
472 crate::StoreDatabase::new(self)
473 .row_blob_ref(table, row_id)
474 .await
475 }
476}