operator/write_dispatcher/
attribution.rs1use std::sync::Arc;
16
17use common_meta::error::{Result, UnexpectedSnafu};
18use partition::partition::RegionMask;
19use session::write_progress::WriteProgress;
20use snafu::ensure;
21
22#[derive(Clone, Debug, Default)]
24pub struct WriteAttribution {
25 spans: Vec<(usize, Option<Arc<WriteProgress>>)>,
26}
27
28impl WriteAttribution {
29 pub fn new(rows: usize, progress: Option<Arc<WriteProgress>>) -> Self {
30 Self {
31 spans: vec![(rows, progress)],
32 }
33 }
34
35 pub fn extend(&mut self, other: Self) {
36 self.spans.extend(other.spans);
37 }
38
39 pub fn row_count(&self) -> usize {
40 self.spans.iter().map(|(rows, _)| rows).sum()
41 }
42
43 pub fn is_tracked(&self) -> bool {
44 self.spans.iter().any(|(_, progress)| progress.is_some())
45 }
46
47 pub fn filter(&self, mask: &RegionMask) -> Result<Self> {
49 ensure!(
50 self.row_count() == mask.array().len(),
51 UnexpectedSnafu {
52 err_msg: "Write ownership does not match the partition mask",
53 }
54 );
55 if !self.is_tracked() {
56 return Ok(Self::default());
57 }
58 let mut offset = 0;
59 let mut selected = Self::default();
60 for (rows, progress) in &self.spans {
61 let count = mask.array().slice(offset, *rows).true_count();
62 if count > 0 {
63 selected.spans.push((count, progress.clone()));
64 }
65 offset += rows;
66 }
67 Ok(selected)
68 }
69
70 pub(crate) fn record(&self, affected_rows: usize) -> Result<()> {
73 if !self.is_tracked() {
74 return Ok(());
75 }
76 ensure!(
77 affected_rows == self.row_count(),
78 UnexpectedSnafu {
79 err_msg: format!(
80 "Write affected {affected_rows} rows, expected {}; cannot attribute the result",
81 self.row_count()
82 ),
83 }
84 );
85 for (rows, progress) in &self.spans {
86 if let Some(progress) = progress {
87 progress.add_written(*rows);
88 }
89 }
90 Ok(())
91 }
92
93 pub(crate) fn for_rows(&self, rows: usize) -> Result<Self> {
95 ensure!(
96 self.spans.len() <= 1,
97 UnexpectedSnafu {
98 err_msg: "Cannot repartition shared ownership without a row mask",
99 }
100 );
101 Ok(Self::new(
102 rows,
103 self.spans.first().and_then(|(_, p)| p.clone()),
104 ))
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use datatypes::arrow::array::BooleanArray;
111
112 use super::*;
113
114 #[test]
115 fn ownership_survives_filtering_and_repartition() {
116 let first = Arc::new(WriteProgress::default());
117 let second = Arc::new(WriteProgress::default());
118 let mut owners = WriteAttribution::new(3, Some(first.clone()));
119 owners.extend(WriteAttribution::new(2, Some(second.clone())));
120 let initial = owners
121 .filter(&RegionMask::new(
122 BooleanArray::from(vec![true, false, true, false, true]),
123 3,
124 ))
125 .unwrap();
126 let retry = initial
127 .filter(&RegionMask::new(
128 BooleanArray::from(vec![false, true, true]),
129 2,
130 ))
131 .unwrap();
132 assert!(retry.record(1).is_err());
133 assert_eq!(first.written(), 0);
134 retry.record(2).unwrap();
135 assert_eq!(first.written(), 1);
136 assert_eq!(second.written(), 1);
137 assert!(owners.for_rows(5).is_err());
138 assert!(
139 owners
140 .filter(&RegionMask::new(BooleanArray::from(vec![true]), 1))
141 .is_err()
142 );
143 }
144}