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>,
)
| 600 | /// Idempotent: a view already matching its target removes nothing and |
| 601 | /// returns an empty vector. On success returns the hashes removed. |
| 602 | pub fn retain_view_changes( |
| 603 | &self, |
| 604 | view_name: &str, |
| 605 | target: &HashSet<Hash>, |
| 606 | ) -> Result<Vec<Hash>, RepositoryError> { |
| 607 | let mut txn = self |
| 608 | .pristine |
| 609 | .write_txn() |
| 610 | .map_err(|e| RepositoryError::Database(e.to_string()))?; |
| 611 | |
| 612 | let mut view = txn |
| 613 | .get_view(view_name) |
| 614 | .map_err(|e| RepositoryError::Database(e.to_string()))? |
| 615 | .ok_or_else(|| RepositoryError::ViewNotFound { |
| 616 | name: view_name.to_string(), |
| 617 | })?; |
| 618 | |
| 619 | // Snapshot the view's own change ids first (the iterator borrows the |
| 620 | // txn immutably; collecting frees it for the mutable `del_change`). |
| 621 | let own_ids: Vec<NodeId> = txn |
| 622 | .iter_changes(&view, 0) |
| 623 | .map_err(|e| RepositoryError::Database(e.to_string()))? |
| 624 | .map(|entry| entry.map(|(_seq, change_id, _merkle)| change_id)) |
| 625 | .collect::<Result<_, _>>() |
| 626 | .map_err(|e| RepositoryError::Database(e.to_string()))?; |
| 627 | |
| 628 | // Any own change whose external hash is not in the target set is stale. |
| 629 | let mut to_remove: Vec<(NodeId, Hash)> = Vec::new(); |
| 630 | for change_id in own_ids { |
| 631 | let hash = txn |
| 632 | .get_external(change_id) |
| 633 | .map_err(|e| RepositoryError::Database(e.to_string()))? |
| 634 | .ok_or_else(|| { |
| 635 | RepositoryError::Database(format!( |
| 636 | "change {} has no external hash", |
| 637 | change_id.0 |
| 638 | )) |
| 639 | })?; |
| 640 | if !target.contains(&hash) { |
| 641 | to_remove.push((change_id, hash)); |
| 642 | } |
| 643 | } |
| 644 | |
| 645 | if to_remove.is_empty() { |
| 646 | // Leave the write txn unwritten — nothing to converge. |
| 647 | return Ok(Vec::new()); |
| 648 | } |
| 649 | |
| 650 | // `del_change` re-derives the sequence from `change_id` on each call, so |
| 651 | // it is robust to the resequencing it performs internally; removal order |
| 652 | // does not affect the final set. |
| 653 | let mut removed = Vec::with_capacity(to_remove.len()); |
| 654 | for (change_id, hash) in &to_remove { |
| 655 | let seq = txn |
| 656 | .del_change(&mut view, *change_id, hash) |
| 657 | .map_err(|e| RepositoryError::Database(e.to_string()))?; |
| 658 | if seq.is_some() { |
| 659 | removed.push(*hash); |