Skip to main content

iceberg/transaction/
update_properties.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::collections::{HashMap, HashSet};
19use std::sync::Arc;
20
21use async_trait::async_trait;
22
23use crate::table::Table;
24use crate::transaction::action::{ActionCommit, TransactionAction};
25use crate::{Error, ErrorKind, Result, TableUpdate};
26
27/// A transactional action that updates or removes table properties
28///
29/// This action is used to modify key-value pairs in a table's metadata
30/// properties during a transaction. It supports setting new values for existing keys
31/// or adding new keys, as well as removing existing keys. Each key can only be updated
32/// or removed in a single action, not both.
33pub struct UpdatePropertiesAction {
34    updates: HashMap<String, String>,
35    removals: HashSet<String>,
36}
37
38impl UpdatePropertiesAction {
39    /// Creates a new [`UpdatePropertiesAction`] with no updates or removals.
40    pub(crate) fn new() -> Self {
41        UpdatePropertiesAction {
42            updates: HashMap::default(),
43            removals: HashSet::default(),
44        }
45    }
46
47    /// Adds a key-value pair to the update set of this action.
48    ///
49    /// # Arguments
50    ///
51    /// * `key` - The property key to update.
52    /// * `value` - The new value to associate with the key.
53    ///
54    /// # Returns
55    ///
56    /// The updated [`UpdatePropertiesAction`] with the key-value pair added to the update set.
57    pub fn set(mut self, key: String, value: String) -> Self {
58        self.updates.insert(key, value);
59        self
60    }
61
62    /// Adds a key to the removal set of this action.
63    ///
64    /// # Arguments
65    ///
66    /// * `key` - The property key to remove.
67    ///
68    /// # Returns
69    ///
70    /// The updated [`UpdatePropertiesAction`] with the key added to the removal set.
71    pub fn remove(mut self, key: String) -> Self {
72        self.removals.insert(key);
73        self
74    }
75}
76
77#[async_trait]
78impl TransactionAction for UpdatePropertiesAction {
79    async fn commit(self: Arc<Self>, _table: &Table) -> Result<ActionCommit> {
80        if let Some(overlapping_key) = self.removals.iter().find(|k| self.updates.contains_key(*k))
81        {
82            return Err(Error::new(
83                ErrorKind::PreconditionFailed,
84                format!("Key {overlapping_key} is present in both removal set and update set"),
85            ));
86        }
87
88        let updates: Vec<TableUpdate> = vec![
89            TableUpdate::SetProperties {
90                updates: self.updates.clone(),
91            },
92            TableUpdate::RemoveProperties {
93                removals: self.removals.clone().into_iter().collect::<Vec<String>>(),
94            },
95        ];
96
97        Ok(ActionCommit::new(updates, vec![]))
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use std::collections::{HashMap, HashSet};
104
105    use as_any::Downcast;
106
107    use crate::transaction::Transaction;
108    use crate::transaction::action::ApplyTransactionAction;
109    use crate::transaction::tests::make_v2_table;
110    use crate::transaction::update_properties::UpdatePropertiesAction;
111
112    #[test]
113    fn test_update_table_property() {
114        let table = make_v2_table();
115        let tx = Transaction::new(&table);
116        let tx = tx
117            .update_table_properties()
118            .set("a".to_string(), "b".to_string())
119            .remove("b".to_string())
120            .apply(tx)
121            .unwrap();
122
123        assert_eq!(tx.actions.len(), 1);
124
125        let action = (*tx.actions[0])
126            .downcast_ref::<UpdatePropertiesAction>()
127            .unwrap();
128        assert_eq!(
129            action.updates,
130            HashMap::from([("a".to_string(), "b".to_string())])
131        );
132
133        assert_eq!(action.removals, HashSet::from(["b".to_string()]));
134    }
135}