surreal_sync_runtime/pipeline/cow.rs
1//! Arc copy-on-write batch adapter over [`InPlaceTransform`].
2
3use anyhow::Result;
4use std::sync::Arc;
5use surreal_sync_core::InPlaceTransform;
6use surreal_sync_core::{Change, Relation, Row};
7
8/// Batch of `Arc`-shared items with copy-on-write mutation via [`Arc::make_mut`].
9///
10/// Prefer owned `Vec` + [`InPlaceTransform::transform_rows_inplace`] /
11/// [`InPlaceTransform::transform_changes_inplace`] when sharing is not required.
12/// Use [`CowBatch`] only when the same items may be shared across holders and
13/// mutation must not affect other owners.
14///
15/// # Cost of `apply_inplace`
16///
17/// [`Self::apply_inplace`] always calls [`Arc::make_mut`] before invoking the
18/// transform. When an item's strong count is greater than one, **`make_mut`
19/// clones even if the transform is a no-op** (e.g. [`crate::pipeline::Passthrough`]).
20/// Unique arcs (refcount 1) are mutated without allocation regardless of whether
21/// the transform writes.
22#[derive(Debug, Clone)]
23pub struct CowBatch<T> {
24 /// Shared items; mutated through [`Arc::make_mut`] in [`Self::apply_inplace`].
25 pub items: Vec<Arc<T>>,
26}
27
28impl<T> CowBatch<T> {
29 /// Create a batch from already-shared items.
30 pub fn new(items: Vec<Arc<T>>) -> Self {
31 Self { items }
32 }
33
34 /// Create a batch by wrapping each owned item in a fresh `Arc` (refcount 1).
35 pub fn from_owned(items: Vec<T>) -> Self {
36 Self {
37 items: items.into_iter().map(Arc::new).collect(),
38 }
39 }
40
41 /// Number of items in the batch.
42 pub fn len(&self) -> usize {
43 self.items.len()
44 }
45
46 /// Whether the batch is empty.
47 pub fn is_empty(&self) -> bool {
48 self.items.is_empty()
49 }
50
51 /// Consume the batch, returning the underlying `Arc`s.
52 pub fn into_items(self) -> Vec<Arc<T>> {
53 self.items
54 }
55}
56
57impl CowBatch<Row> {
58 /// Apply an in-place transform with Arc COW semantics.
59 ///
60 /// Always calls [`Arc::make_mut`] per item. Clones when the arc is shared
61 /// (strong count > 1), including when `t` is a no-op such as
62 /// [`crate::pipeline::Passthrough`]. Unique arcs are mutated without allocation.
63 pub fn apply_inplace(&mut self, t: &impl InPlaceTransform) -> Result<()> {
64 for item in &mut self.items {
65 t.transform_row(Arc::make_mut(item))?;
66 }
67 Ok(())
68 }
69}
70
71impl CowBatch<Change> {
72 /// Apply an in-place transform with Arc COW semantics.
73 ///
74 /// Always calls [`Arc::make_mut`] per item. Clones when the arc is shared
75 /// (strong count > 1), including when `t` is a no-op such as
76 /// [`crate::pipeline::Passthrough`]. Unique arcs are mutated without allocation.
77 pub fn apply_inplace(&mut self, t: &impl InPlaceTransform) -> Result<()> {
78 for item in &mut self.items {
79 t.transform_change(Arc::make_mut(item))?;
80 }
81 Ok(())
82 }
83}
84
85impl CowBatch<Relation> {
86 /// Apply an in-place transform with Arc COW semantics for relations.
87 pub fn apply_inplace(&mut self, t: &impl InPlaceTransform) -> Result<()> {
88 for item in &mut self.items {
89 t.transform_relation(Arc::make_mut(item))?;
90 }
91 Ok(())
92 }
93}