iceberg/transaction/
action.rs1use std::mem::take;
19use std::sync::Arc;
20
21use as_any::AsAny;
22use async_trait::async_trait;
23
24use crate::table::Table;
25use crate::transaction::Transaction;
26use crate::{Result, TableRequirement, TableUpdate};
27
28pub(crate) type BoxedTransactionAction = Arc<dyn TransactionAction>;
30
31#[async_trait]
37pub trait TransactionAction: AsAny + Sync + Send {
38 async fn commit(self: Arc<Self>, table: &Table) -> Result<ActionCommit>;
57}
58
59pub trait ApplyTransactionAction {
64 fn apply(self, tx: Transaction) -> Result<Transaction>;
74}
75
76impl<T: TransactionAction + 'static> ApplyTransactionAction for T {
77 fn apply(self, mut tx: Transaction) -> Result<Transaction>
78 where Self: Sized {
79 tx.actions.push(Arc::new(self));
80 Ok(tx)
81 }
82}
83
84pub struct ActionCommit {
89 updates: Vec<TableUpdate>,
90 requirements: Vec<TableRequirement>,
91}
92
93impl ActionCommit {
94 pub fn new(updates: Vec<TableUpdate>, requirements: Vec<TableRequirement>) -> Self {
96 Self {
97 updates,
98 requirements,
99 }
100 }
101
102 pub fn take_updates(&mut self) -> Vec<TableUpdate> {
104 take(&mut self.updates)
105 }
106
107 pub fn take_requirements(&mut self) -> Vec<TableRequirement> {
109 take(&mut self.requirements)
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use std::str::FromStr;
116 use std::sync::Arc;
117
118 use as_any::Downcast;
119 use async_trait::async_trait;
120 use uuid::Uuid;
121
122 use crate::table::Table;
123 use crate::transaction::Transaction;
124 use crate::transaction::action::{ActionCommit, ApplyTransactionAction, TransactionAction};
125 use crate::transaction::tests::make_v2_table;
126 use crate::{Result, TableRequirement, TableUpdate};
127
128 struct TestAction;
129
130 #[async_trait]
131 impl TransactionAction for TestAction {
132 async fn commit(self: Arc<Self>, _table: &Table) -> Result<ActionCommit> {
133 Ok(ActionCommit::new(
134 vec![TableUpdate::SetLocation {
135 location: String::from("s3://bucket/prefix/table/"),
136 }],
137 vec![TableRequirement::UuidMatch {
138 uuid: Uuid::from_str("9c12d441-03fe-4693-9a96-a0705ddf69c1")?,
139 }],
140 ))
141 }
142 }
143
144 #[tokio::test]
145 async fn test_commit_transaction_action() {
146 let table = make_v2_table();
147 let action = TestAction;
148
149 let mut action_commit = Arc::new(action).commit(&table).await.unwrap();
150
151 let updates = action_commit.take_updates();
152 let requirements = action_commit.take_requirements();
153
154 assert_eq!(updates[0], TableUpdate::SetLocation {
155 location: String::from("s3://bucket/prefix/table/")
156 });
157 assert_eq!(requirements[0], TableRequirement::UuidMatch {
158 uuid: Uuid::from_str("9c12d441-03fe-4693-9a96-a0705ddf69c1").unwrap()
159 });
160 }
161
162 #[test]
163 fn test_apply_transaction_action() {
164 let table = make_v2_table();
165 let action = TestAction;
166 let tx = Transaction::new(&table);
167
168 let updated_tx = action.apply(tx).unwrap();
169 assert_eq!(updated_tx.actions.len(), 1);
171
172 (*updated_tx.actions[0])
173 .downcast_ref::<TestAction>()
174 .expect("TestAction was not applied to Transaction!");
175 }
176
177 #[test]
178 fn test_action_commit() {
179 let location = String::from("s3://bucket/prefix/table/");
181 let uuid = Uuid::new_v4();
182 let updates = vec![TableUpdate::SetLocation { location }];
183 let requirements = vec![TableRequirement::UuidMatch { uuid }];
184
185 let mut action_commit = ActionCommit::new(updates.clone(), requirements.clone());
186
187 let taken_updates = action_commit.take_updates();
188 let taken_requirements = action_commit.take_requirements();
189
190 assert_eq!(taken_updates, updates);
192 assert_eq!(taken_requirements, requirements);
193
194 assert!(action_commit.take_updates().is_empty());
195 assert!(action_commit.take_requirements().is_empty());
196 }
197}