Converge a view's own change log to a target effective set by removing** every own change absent from `target`. This is the removal half of set-based view convergence (the add half is [`insert_change`](crate::Repository::insert_change)). A durable view record declares the view's effective change set (its own changes plus everything inherited through its parent chain); any of the view's OWN change
(
&self,
view_name: &str,
target: &HashSet<Hash>,
)
| 513 | /// Idempotent: a view already matching its target removes nothing and |
| 514 | /// returns an empty vector. On success returns the hashes removed. |
| 515 | pub fn retain_view_changes( |
| 516 | &self, |
| 517 | view_name: &str, |
| 518 | target: &HashSet<Hash>, |
| 519 | ) -> Result<Vec<Hash>, RepositoryError> { |
| 520 | let mut txn = self |
| 521 | .pristine |
| 522 | .write_txn() |
| 523 | .map_err(|e| RepositoryError::Database(e.to_string()))?; |
| 524 | |
| 525 | let mut view = txn |
| 526 | .get_view(view_name) |
| 527 | .map_err(|e| RepositoryError::Database(e.to_string()))? |
| 528 | .ok_or_else(|| RepositoryError::ViewNotFound { |
| 529 | name: view_name.to_string(), |
| 530 | })?; |
| 531 | |
| 532 | // Snapshot the view's own change ids first (the iterator borrows the |
| 533 | // txn immutably; collecting frees it for the mutable `del_change`). |
| 534 | let own_ids: Vec<NodeId> = txn |
| 535 | .iter_changes(&view, 0) |
| 536 | .map_err(|e| RepositoryError::Database(e.to_string()))? |
| 537 | .map(|entry| entry.map(|(_seq, change_id, _merkle)| change_id)) |
| 538 | .collect::<Result<_, _>>() |
| 539 | .map_err(|e| RepositoryError::Database(e.to_string()))?; |
| 540 | |
| 541 | // Any own change whose external hash is not in the target set is stale. |
| 542 | let mut to_remove: Vec<(NodeId, Hash)> = Vec::new(); |
| 543 | for change_id in own_ids { |
| 544 | let hash = txn |
| 545 | .get_external(change_id) |
| 546 | .map_err(|e| RepositoryError::Database(e.to_string()))? |
| 547 | .ok_or_else(|| { |
| 548 | RepositoryError::Database(format!( |
| 549 | "change {} has no external hash", |
| 550 | change_id.0 |
| 551 | )) |
| 552 | })?; |
| 553 | if !target.contains(&hash) { |
| 554 | to_remove.push((change_id, hash)); |
| 555 | } |
| 556 | } |
| 557 | |
| 558 | if to_remove.is_empty() { |
| 559 | // Leave the write txn unwritten — nothing to converge. |
| 560 | return Ok(Vec::new()); |
| 561 | } |
| 562 | |
| 563 | // `del_change` re-derives the sequence from `change_id` on each call, so |
| 564 | // it is robust to the resequencing it performs internally; removal order |
| 565 | // does not affect the final set. |
| 566 | let mut removed = Vec::with_capacity(to_remove.len()); |
| 567 | for (change_id, hash) in &to_remove { |
| 568 | let seq = txn |
| 569 | .del_change(&mut view, *change_id, hash) |
| 570 | .map_err(|e| RepositoryError::Database(e.to_string()))?; |
| 571 | if seq.is_some() { |
| 572 | removed.push(*hash); |