iceberg/transaction/
update_statistics.rs1use std::collections::HashMap;
19use std::sync::Arc;
20
21use async_trait::async_trait;
22
23use crate::spec::StatisticsFile;
24use crate::table::Table;
25use crate::transaction::{ActionCommit, TransactionAction};
26use crate::{Result, TableUpdate};
27
28pub 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 TransactionAction for UpdateStatisticsAction {
73 async fn commit(self: Arc<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::tests::make_v2_table;
102 use crate::transaction::update_statistics::UpdateStatisticsAction;
103 use crate::transaction::{ApplyTransactionAction, Transaction};
104
105 #[test]
106 fn test_update_statistics() {
107 let table = make_v2_table();
108 let tx = Transaction::new(&table);
109
110 let statistics_file_1 = StatisticsFile {
111 snapshot_id: 3055729675574597004i64,
112 statistics_path: "s3://a/b/stats.puffin".to_string(),
113 file_size_in_bytes: 413,
114 file_footer_size_in_bytes: 42,
115 key_metadata: None,
116 blob_metadata: vec![BlobMetadata {
117 r#type: "ndv".to_string(),
118 snapshot_id: 3055729675574597004i64,
119 sequence_number: 1,
120 fields: vec![1],
121 properties: HashMap::new(),
122 }],
123 };
124
125 let statistics_file_2 = StatisticsFile {
126 snapshot_id: 3366729675595277004i64,
127 statistics_path: "s3://a/b/stats.puffin".to_string(),
128 file_size_in_bytes: 413,
129 file_footer_size_in_bytes: 42,
130 key_metadata: None,
131 blob_metadata: vec![BlobMetadata {
132 r#type: "ndv".to_string(),
133 snapshot_id: 3366729675595277004i64,
134 sequence_number: 1,
135 fields: vec![1],
136 properties: HashMap::new(),
137 }],
138 };
139
140 let tx = tx
142 .update_statistics()
143 .set_statistics(statistics_file_1.clone())
144 .set_statistics(statistics_file_2.clone())
145 .remove_statistics(3055729675574597004i64) .apply(tx)
147 .unwrap();
148
149 let action = (*tx.actions[0])
150 .downcast_ref::<UpdateStatisticsAction>()
151 .unwrap();
152 assert!(
153 action
154 .statistics_to_set
155 .get(&statistics_file_1.snapshot_id)
156 .unwrap()
157 .is_none()
158 ); assert_eq!(
160 action
161 .statistics_to_set
162 .get(&statistics_file_2.snapshot_id)
163 .unwrap()
164 .clone(),
165 Some(statistics_file_2)
166 );
167 }
168
169 #[test]
170 fn test_set_single_statistics() {
171 let table = make_v2_table();
172 let tx = Transaction::new(&table);
173
174 let statistics_file = StatisticsFile {
175 snapshot_id: 1234567890i64,
176 statistics_path: "s3://a/b/stats1.puffin".to_string(),
177 file_size_in_bytes: 500,
178 file_footer_size_in_bytes: 50,
179 key_metadata: None,
180 blob_metadata: vec![],
181 };
182
183 let tx = tx
185 .update_statistics()
186 .set_statistics(statistics_file.clone())
187 .apply(tx)
188 .unwrap();
189
190 let action = (*tx.actions[0])
191 .downcast_ref::<UpdateStatisticsAction>()
192 .unwrap();
193
194 assert_eq!(
196 action
197 .statistics_to_set
198 .get(&statistics_file.snapshot_id)
199 .unwrap()
200 .clone(),
201 Some(statistics_file)
202 );
203 }
204
205 #[test]
206 fn test_no_statistics_set() {
207 let table = make_v2_table();
208 let tx = Transaction::new(&table);
209
210 let tx = tx.update_statistics().apply(tx).unwrap();
212
213 let action = (*tx.actions[0])
214 .downcast_ref::<UpdateStatisticsAction>()
215 .unwrap();
216
217 assert!(action.statistics_to_set.is_empty());
219 }
220}