Skip to content

Commit 477519e

Browse files
committed
add unit test
1 parent 444e7d0 commit 477519e

File tree

1 file changed

+109
-7
lines changed

1 file changed

+109
-7
lines changed

crates/iceberg/src/transaction/update_schema.rs

Lines changed: 109 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,20 @@
1515
// specific language governing permissions and limitations
1616
// under the License.
1717

18-
use std::collections::{HashMap, HashSet};
18+
use std::collections::HashSet;
1919
use std::sync::Arc;
2020

2121
use async_trait::async_trait;
22-
use uuid::Uuid;
2322

2423
use crate::error::{Error, ErrorKind, Result};
25-
use crate::spec::{Schema, SchemaBuilder, SchemaRef};
24+
use crate::spec::{Schema, SchemaRef, TableMetadataBuilder};
2625
use crate::table::Table;
27-
use crate::transaction::snapshot::SnapshotProducer;
2826
use crate::transaction::{ActionCommit, TransactionAction};
2927
use crate::{TableRequirement, TableUpdate};
3028

3129
/// UpdateSchemaAction is a transaction action for performing schema evolution to the table.
30+
///
31+
/// TODO(hjiang): Currently only drop column is supported, need to implement a few more schema evolution operation, i.e., add columns, rename columns, change nullability, update default, etc.
3232
pub struct UpdateSchemaAction {
3333
/// Schema update attributes.
3434
case_sensitive: bool,
@@ -90,13 +90,20 @@ impl UpdateSchemaAction {
9090
return Err(Error::new(
9191
ErrorKind::PreconditionFailed,
9292
format!(
93-
"Column '{}' is the table identifier, which canot be dropped.",
93+
"Column '{}' is the table identifier, which cannot be dropped.",
9494
full_name
9595
),
9696
));
9797
}
9898

99+
// Validate not all columns are dropped.
99100
self.deletes.insert(field.id);
101+
if self.schema.field_id_to_fields().len() == self.deletes.len() {
102+
return Err(Error::new(
103+
ErrorKind::PreconditionFailed,
104+
format!("Cannot delete all columns '{}' in the table.", full_name),
105+
));
106+
}
100107

101108
Ok(self)
102109
}
@@ -134,11 +141,106 @@ impl TransactionAction for UpdateSchemaAction {
134141
}
135142

136143
let new_schema = self.get_updated_schema()?;
137-
let new_schema_id = new_schema.schema_id();
138144
updates.push(TableUpdate::AddSchema { schema: new_schema });
139145
updates.push(TableUpdate::SetCurrentSchema {
140-
schema_id: new_schema_id,
146+
schema_id: TableMetadataBuilder::LAST_ADDED,
141147
});
142148
Ok(ActionCommit::new(updates, requirements))
143149
}
144150
}
151+
152+
#[cfg(test)]
153+
mod tests {
154+
use std::collections::{HashMap, HashSet};
155+
use std::sync::Arc;
156+
157+
use tempfile::TempDir;
158+
159+
use crate::io::FileIOBuilder;
160+
use crate::spec::Schema;
161+
use crate::transaction::tests::make_v2_table;
162+
use crate::transaction::{ApplyTransactionAction, Transaction};
163+
use crate::{Catalog, MemoryCatalog, NamespaceIdent, TableCreation};
164+
165+
/// Test util function to get [`TableCreation`].
166+
async fn create_table(
167+
catalog: &mut MemoryCatalog,
168+
schema: Arc<Schema>,
169+
warehouse_location: &str,
170+
) {
171+
let table_name = "test1".to_string();
172+
let namespace_ident = NamespaceIdent::from_vec(vec!["ns1".to_string()]).unwrap();
173+
174+
let table_creation = TableCreation::builder()
175+
.name(table_name.clone())
176+
.location(format!(
177+
"{}/{}/{}",
178+
warehouse_location,
179+
namespace_ident.to_url_string(),
180+
table_name
181+
))
182+
.schema(schema.as_ref().clone())
183+
.build();
184+
185+
catalog
186+
.create_namespace(&namespace_ident, /*properties=*/ HashMap::new())
187+
.await
188+
.unwrap();
189+
catalog
190+
.create_table(&namespace_ident, table_creation)
191+
.await
192+
.unwrap();
193+
}
194+
195+
#[test]
196+
fn test_delete_empty_columns() {
197+
let table = make_v2_table();
198+
let tx = Transaction::new(&table);
199+
let action = tx.update_schema();
200+
assert!(action.deletes.is_empty());
201+
}
202+
203+
#[test]
204+
fn test_fail_to_delete_identifier_column() {
205+
let table = make_v2_table();
206+
let tx = Transaction::new(&table);
207+
let mut action = tx.update_schema();
208+
let res = action.delete_column(vec!["x".to_string()]);
209+
assert!(res.is_err());
210+
}
211+
212+
#[test]
213+
fn test_delete_non_identifier_column() {
214+
let table = make_v2_table();
215+
let tx = Transaction::new(&table);
216+
let mut action = tx.update_schema();
217+
action.delete_column(vec!["z".to_string()]).unwrap();
218+
assert_eq!(action.deletes, HashSet::from([(3)]));
219+
}
220+
221+
// Test column deletion with memory catalog.
222+
#[tokio::test]
223+
async fn test_delete_columns_with_catalog() {
224+
let file_io = FileIOBuilder::new_fs_io().build().unwrap();
225+
let temp_dir = TempDir::new().unwrap();
226+
let warehouse_location = temp_dir.path().to_str().unwrap().to_string();
227+
228+
let table = make_v2_table();
229+
let mut memory_catalog = MemoryCatalog::new(file_io, Some(warehouse_location.clone()));
230+
let schema = table.metadata().current_schema().clone();
231+
create_table(&mut memory_catalog, schema, &warehouse_location).await;
232+
233+
let mut tx = Transaction::new(&table);
234+
let mut action = tx.update_schema();
235+
action.delete_column(vec!["z".to_string()]).unwrap();
236+
tx = action.apply(tx).unwrap();
237+
238+
let table = tx.commit(&memory_catalog).await.unwrap();
239+
let schema = table.metadata().current_schema();
240+
assert!(schema.field_by_id(/*field_id=*/ 1).is_some());
241+
assert!(schema.field_by_id(/*field_id=*/ 2).is_some());
242+
assert!(schema.field_by_id(/*field_id=*/ 3).is_none());
243+
assert_eq!(schema.highest_field_id(), 2);
244+
assert_eq!(schema.identifier_field_ids().len(), 2);
245+
}
246+
}

0 commit comments

Comments
 (0)