iceberg/transaction/
update_statistics.rs1use std::collections::HashMap;
19
20use async_trait::async_trait;
21
22use crate::spec::StatisticsFile;
23use crate::table::Table;
24use crate::transaction::{ActionCommit, StatelessAction};
25use crate::{Result, TableUpdate};
26
27#[derive(Clone)]
29pub struct UpdateStatisticsAction {
30 statistics_to_set: HashMap<i64, Option<StatisticsFile>>,
31}
32
33impl UpdateStatisticsAction {
34 pub(crate) fn new() -> Self {
35 Self {
36 statistics_to_set: HashMap::default(),
37 }
38 }
39
40 pub fn set_statistics(mut self, statistics_file: StatisticsFile) -> Self {
51 self.statistics_to_set
52 .insert(statistics_file.snapshot_id, Some(statistics_file));
53 self
54 }
55
56 pub fn remove_statistics(mut self, snapshot_id: i64) -> Self {
66 self.statistics_to_set.insert(snapshot_id, None);
67 self
68 }
69}
70
71#[async_trait]
72impl StatelessAction for UpdateStatisticsAction {
73 async fn commit(&self, _table: &Table) -> Result<ActionCommit> {
74 let mut updates: Vec<TableUpdate> = vec![];
75
76 self.statistics_to_set
77 .iter()
78 .for_each(|(snapshot_id, statistic_file)| {
79 if let Some(statistics) = statistic_file {
80 updates.push(TableUpdate::SetStatistics {
81 statistics: statistics.clone(),
82 })
83 } else {
84 updates.push(TableUpdate::RemoveStatistics {
85 snapshot_id: *snapshot_id,
86 })
87 }
88 });
89
90 Ok(ActionCommit::new(updates, vec![]))
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use std::collections::HashMap;
97
98 use as_any::Downcast;
99
100 use crate::spec::{BlobMetadata, StatisticsFile};
101 use crate::transaction::action::ActionEntry;
102 use crate::transaction::tests::make_v2_table;
103 use crate::transaction::update_statistics::UpdateStatisticsAction;
104 use crate::transaction::{ApplyTransactionAction, Transaction};
105
106 #[test]
107 fn test_update_statistics() {
108 let table = make_v2_table();
109 let tx = Transaction::new(&table);
110
111 let statistics_file_1 = StatisticsFile {
112 snapshot_id: 3055729675574597004i64,
113 statistics_path: "s3://a/b/stats.puffin".to_string(),
114 file_size_in_bytes: 413,
115 file_footer_size_in_bytes: 42,
116 key_metadata: None,
117 blob_metadata: vec![BlobMetadata {
118 r#type: "ndv".to_string(),
119 snapshot_id: 3055729675574597004i64,
120 sequence_number: 1,
121 fields: vec![1],
122 properties: HashMap::new(),
123 }],
124 };
125
126 let statistics_file_2 = StatisticsFile {
127 snapshot_id: 3366729675595277004i64,
128 statistics_path: "s3://a/b/stats.puffin".to_string(),
129 file_size_in_bytes: 413,
130 file_footer_size_in_bytes: 42,
131 key_metadata: None,
132 blob_metadata: vec![BlobMetadata {
133 r#type: "ndv".to_string(),
134 snapshot_id: 3366729675595277004i64,
135 sequence_number: 1,
136 fields: vec![1],
137 properties: HashMap::new(),
138 }],
139 };
140
141 let tx = tx
143 .update_statistics()
144 .set_statistics(statistics_file_1.clone())
145 .set_statistics(statistics_file_2.clone())
146 .remove_statistics(3055729675574597004i64) .apply(tx)
148 .unwrap();
149
150 let action = (*tx.actions[0])
151 .downcast_ref::<ActionEntry<UpdateStatisticsAction>>()
152 .unwrap()
153 .action();
154 assert!(
155 action
156 .statistics_to_set
157 .get(&statistics_file_1.snapshot_id)
158 .unwrap()
159 .is_none()
160 ); assert_eq!(
162 action
163 .statistics_to_set
164 .get(&statistics_file_2.snapshot_id)
165 .unwrap()
166 .clone(),
167 Some(statistics_file_2)
168 );
169 }
170
171 #[test]
172 fn test_set_single_statistics() {
173 let table = make_v2_table();
174 let tx = Transaction::new(&table);
175
176 let statistics_file = StatisticsFile {
177 snapshot_id: 1234567890i64,
178 statistics_path: "s3://a/b/stats1.puffin".to_string(),
179 file_size_in_bytes: 500,
180 file_footer_size_in_bytes: 50,
181 key_metadata: None,
182 blob_metadata: vec![],
183 };
184
185 let tx = tx
187 .update_statistics()
188 .set_statistics(statistics_file.clone())
189 .apply(tx)
190 .unwrap();
191
192 let action = (*tx.actions[0])
193 .downcast_ref::<ActionEntry<UpdateStatisticsAction>>()
194 .unwrap()
195 .action();
196
197 assert_eq!(
199 action
200 .statistics_to_set
201 .get(&statistics_file.snapshot_id)
202 .unwrap()
203 .clone(),
204 Some(statistics_file)
205 );
206 }
207
208 #[test]
209 fn test_no_statistics_set() {
210 let table = make_v2_table();
211 let tx = Transaction::new(&table);
212
213 let tx = tx.update_statistics().apply(tx).unwrap();
215
216 let action = (*tx.actions[0])
217 .downcast_ref::<ActionEntry<UpdateStatisticsAction>>()
218 .unwrap()
219 .action();
220
221 assert!(action.statistics_to_set.is_empty());
223 }
224}