Skip to content

Commit ca02602

Browse files
feat: add column mapping support for add_column
When column mapping is enabled (mode = name or id), newly added columns via ALTER TABLE ADD COLUMN are now assigned column mapping metadata (unique ID and UUID-based physical name). The maxColumnId table property is updated in the evolved metadata configuration. Reuses the existing assign_field_column_mapping function (now pub(crate)) which handles recursive assignment for nested types.
1 parent 3adf195 commit ca02602

6 files changed

Lines changed: 331 additions & 40 deletions

File tree

kernel/src/actions/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,14 @@ impl Metadata {
355355
})
356356
}
357357

358+
/// Returns a new Metadata with the configuration replaced, preserving all other fields.
359+
pub(crate) fn with_configuration(self, configuration: HashMap<String, String>) -> Self {
360+
Self {
361+
configuration,
362+
..self
363+
}
364+
}
365+
358366
#[cfg(test)]
359367
#[allow(clippy::too_many_arguments)]
360368
pub(crate) fn new_unchecked(

kernel/src/table_features/column_mapping.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ pub(crate) fn get_column_mapping_mode_from_properties(
245245
/// arrays, and maps. Each field is assigned a new unique ID and physical name.
246246
///
247247
/// Fields with pre-existing column mapping metadata (id or physicalName) are rejected
248-
/// to avoid conflicts. ALTER TABLE will need different handling in the future.
248+
/// to avoid conflicts.
249249
///
250250
/// # Arguments
251251
///
@@ -272,7 +272,10 @@ pub(crate) fn assign_column_mapping_metadata(
272272
///
273273
/// Rejects fields with pre-existing column mapping metadata. Otherwise, assigns a new
274274
/// unique ID and physical name (incrementing `max_id`).
275-
fn assign_field_column_mapping(field: &StructField, max_id: &mut i64) -> DeltaResult<StructField> {
275+
pub(crate) fn assign_field_column_mapping(
276+
field: &StructField,
277+
max_id: &mut i64,
278+
) -> DeltaResult<StructField> {
276279
let has_id = field
277280
.get_config_value(&ColumnMetadataKey::ColumnMappingId)
278281
.is_some();
@@ -282,13 +285,12 @@ fn assign_field_column_mapping(field: &StructField, max_id: &mut i64) -> DeltaRe
282285

283286
// For CREATE TABLE, reject any pre-existing column mapping metadata.
284287
// This avoids conflicts between user-provided IDs/physical names and the ones we assign.
285-
// ALTER TABLE (adding columns) will need different handling in the future.
286288
// TODO: Also check for nested column IDs (`delta.columnMapping.nested.ids`) once
287289
// Iceberg compatibility (IcebergCompatV2+) is supported. See issue #1125.
288290
if has_id || has_physical_name {
289291
return Err(Error::generic(format!(
290292
"Field '{}' already has column mapping metadata. \
291-
Pre-existing column mapping metadata is not supported for CREATE TABLE.",
293+
Pre-existing column mapping metadata is not supported when adding columns.",
292294
field.name
293295
)));
294296
}

kernel/src/table_features/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ pub(crate) use column_mapping::get_any_level_column_physical_name;
44
pub use column_mapping::validate_schema_column_mapping;
55
pub use column_mapping::ColumnMappingMode;
66
pub(crate) use column_mapping::{
7-
assign_column_mapping_metadata, column_mapping_mode, get_column_mapping_mode_from_properties,
8-
get_field_column_mapping_info, physical_to_logical_column_name,
7+
assign_column_mapping_metadata, assign_field_column_mapping, column_mapping_mode,
8+
get_column_mapping_mode_from_properties, get_field_column_mapping_info,
9+
physical_to_logical_column_name,
910
};
1011
use delta_kernel_derive::internal_api;
1112
use itertools::Itertools;
@@ -21,6 +22,7 @@ use crate::schema::derive_macro_utils::ToDataType;
2122
use crate::schema::DataType;
2223
use crate::table_properties::TableProperties;
2324
use crate::{DeltaResult, Error};
25+
2426
mod column_mapping;
2527
mod timestamp_ntz;
2628

kernel/src/transaction/builder/alter_table.rs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ impl AlterTableTransactionBuilder<Ready> {
7272
///
7373
/// The field must not already exist in the schema (case-insensitive). The field must be
7474
/// nullable because existing data files do not contain this column and will read NULL for it.
75-
/// These constraints are validated during [`build()`](AlterTableTransactionBuilder::build).
75+
/// If column mapping is enabled, the builder automatically assigns a new column ID and physical
76+
/// name at build time. These constraints are validated during
77+
/// [`build()`](AlterTableTransactionBuilder::build).
7678
pub fn add_column(mut self, field: StructField) -> AlterTableTransactionBuilder<Modifying> {
7779
self.operations.push(SchemaOperation::AddColumn { field });
7880
self.transition()
@@ -122,15 +124,43 @@ impl AlterTableTransactionBuilder<Modifying> {
122124
table_config.ensure_operation_supported(Operation::Write)?;
123125

124126
let schema = Arc::unwrap_or_clone(table_config.logical_schema());
127+
let column_mapping_mode = table_config.column_mapping_mode();
128+
let current_max_column_id = table_config
129+
.metadata()
130+
.configuration()
131+
.get(crate::table_properties::COLUMN_MAPPING_MAX_COLUMN_ID)
132+
.map(|v| {
133+
v.parse::<i64>().map_err(|_| {
134+
crate::Error::generic(format!(
135+
"Invalid delta.columnMapping.maxColumnId value: '{v}'"
136+
))
137+
})
138+
})
139+
.transpose()?;
125140
let SchemaEvolutionResult {
126141
schema: evolved_schema,
127-
} = apply_schema_operations(schema, self.operations, table_config.column_mapping_mode())?;
142+
new_max_column_id,
143+
} = apply_schema_operations(
144+
schema,
145+
self.operations,
146+
column_mapping_mode,
147+
current_max_column_id,
148+
)?;
128149

129-
let evolved_metadata = table_config
150+
let mut evolved_metadata = table_config
130151
.metadata()
131152
.clone()
132153
.with_schema(evolved_schema.clone())?;
133154

155+
if let Some(new_id) = new_max_column_id {
156+
let mut config = table_config.metadata().configuration().clone();
157+
config.insert(
158+
crate::table_properties::COLUMN_MAPPING_MAX_COLUMN_ID.to_string(),
159+
new_id.to_string(),
160+
);
161+
evolved_metadata = evolved_metadata.with_configuration(config);
162+
}
163+
134164
// Validates the evolved metadata against the protocol.
135165
let evolved_table_config = TableConfiguration::try_new_with_schema(
136166
evolved_metadata,

0 commit comments

Comments
 (0)