1use crate::logical_type::{assign_fresh_field_ids, assign_fresh_type_ids};
2use crate::metadata::TableMetadata;
3use crate::transform::compile_table_transform;
4use crate::{
5 DataField, FieldId, LogicalType, LogicalTypeKind, Result, TableError, TableSchema, Value,
6 ValueCodec,
7};
8use cobble::{ColumnEvolution, TransformSpec};
9use serde::{Deserialize, Serialize};
10use std::collections::{HashMap, HashSet};
11
12#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
14pub enum SchemaChange {
15 AddField {
17 name: String,
18 logical_type: LogicalType,
19 },
20 RenameField {
22 field_name: String,
23 new_name: String,
24 },
25 DropField { field_name: String },
27 AlterFieldType {
32 field_name: String,
33 logical_type: LogicalType,
34 },
35 TransformField {
40 field_name: String,
41 logical_type: LogicalType,
42 transform: TransformSpec,
43 },
44}
45
46#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
48pub(crate) struct FieldTransform {
49 pub(crate) field_id: FieldId,
50 pub(crate) transform: TransformSpec,
51}
52
53pub(crate) fn apply_schema_changes(
58 mut schema: TableSchema,
59 changes: Vec<SchemaChange>,
60 mut used_field_ids: HashSet<FieldId>,
61) -> Result<(TableSchema, HashSet<FieldId>, Vec<FieldTransform>)> {
62 if changes.is_empty() {
63 return Err(TableError::InvalidSchema(
64 "schema changes must not be empty".to_string(),
65 ));
66 }
67 let key_ids = schema
68 .primary_key
69 .iter()
70 .chain(&schema.bucket_key)
71 .copied()
72 .collect::<HashSet<_>>();
73
74 let mut added_fields = HashSet::new();
75 let mut field_transforms = HashMap::new();
76 for change in changes {
77 match change {
78 SchemaChange::AddField { name, logical_type } => {
79 if schema.fields.iter().any(|field| field.name == name) {
80 return Err(TableError::InvalidSchema(format!(
81 "duplicate top-level field name: {name}"
82 )));
83 }
84 if !logical_type.nullable {
85 return Err(TableError::InvalidSchema(format!(
86 "added field '{name}' must be nullable"
87 )));
88 }
89 let mut added = DataField {
90 id: FieldId(0),
91 name,
92 logical_type,
93 };
94 let mut next_id = next_field_id(&used_field_ids)?;
95 assign_fresh_field_ids(std::slice::from_mut(&mut added), &mut next_id)?;
96 collect_field_ids(&added, &mut used_field_ids);
97 added_fields.insert(added.id);
98 schema.fields.push(added);
99 }
100 SchemaChange::RenameField {
101 field_name,
102 new_name,
103 } => {
104 let index = field_index(&schema, &field_name)?;
105 if new_name != field_name
106 && schema.fields.iter().any(|field| field.name == new_name)
107 {
108 return Err(TableError::InvalidSchema(format!(
109 "duplicate top-level field name: {new_name}"
110 )));
111 }
112 schema.fields[index].name = new_name;
113 }
114 SchemaChange::DropField { field_name } => {
115 let index = field_index(&schema, &field_name)?;
116 if key_ids.contains(&schema.fields[index].id) {
117 return Err(TableError::InvalidSchema(format!(
118 "key field '{}' cannot be dropped",
119 field_name
120 )));
121 }
122 let field = schema.fields.remove(index);
123 field_transforms.remove(&field.id);
125 }
126 SchemaChange::AlterFieldType {
127 field_name,
128 logical_type,
129 } => {
130 let index = transform_field_index(
131 &schema,
132 &key_ids,
133 &added_fields,
134 &field_transforms,
135 &field_name,
136 )?;
137 let source = schema.fields[index].logical_type.clone();
138 let transform = compile_table_transform(&source, &logical_type)?;
139 schema.fields[index].logical_type = logical_type;
140 if let Some(transform) = transform {
141 field_transforms.insert(schema.fields[index].id, transform);
142 }
143 }
144 SchemaChange::TransformField {
145 field_name,
146 mut logical_type,
147 transform,
148 } => {
149 if transform.transform_type.trim().is_empty() {
150 return Err(TableError::InvalidSchema(
151 "field transform type must not be empty".to_string(),
152 ));
153 }
154 let index = transform_field_index(
155 &schema,
156 &key_ids,
157 &added_fields,
158 &field_transforms,
159 &field_name,
160 )?;
161 let field = &mut schema.fields[index];
162 if logical_type != field.logical_type {
163 let mut new_nested_ids = HashSet::new();
164 collect_type_field_ids(&logical_type, &mut new_nested_ids);
165 if !new_nested_ids.is_empty() {
166 let mut next_id = next_field_id(&used_field_ids)?;
167 assign_fresh_type_ids(&mut logical_type, &mut next_id)?;
168 new_nested_ids.clear();
169 collect_type_field_ids(&logical_type, &mut new_nested_ids);
170 used_field_ids.extend(new_nested_ids);
171 }
172 }
173 field.logical_type = logical_type;
174 field_transforms.insert(field.id, transform);
175 }
176 }
177 }
178 let schema = TableSchema::new(schema.fields, schema.primary_key, schema.bucket_key)?;
179 let mut field_transforms = field_transforms
180 .into_iter()
181 .map(|(field_id, transform)| FieldTransform {
182 field_id,
183 transform,
184 })
185 .collect::<Vec<_>>();
186 field_transforms.sort_by_key(|transform| transform.field_id);
187 Ok((schema, used_field_ids, field_transforms))
188}
189
190pub(crate) fn compile_column_evolution(
192 existing: &TableMetadata,
193 target: &TableMetadata,
194 field_transforms: &[FieldTransform],
195) -> Result<Vec<ColumnEvolution>> {
196 if existing.layout.key_fields != target.layout.key_fields
197 || existing.layout.bucket_fields != target.layout.bucket_fields
198 {
199 return Err(TableError::InvalidSchema(
200 "schema evolution changed the table key".to_string(),
201 ));
202 }
203 let existing_fields = existing
204 .schema
205 .fields
206 .iter()
207 .map(|field| (field.id, field))
208 .collect::<HashMap<_, _>>();
209 let transforms = field_transforms
210 .iter()
211 .map(|transform| (transform.field_id, &transform.transform))
212 .collect::<HashMap<_, _>>();
213 if transforms.len() != field_transforms.len() {
214 return Err(TableError::InvalidSchema(
215 "catalog schema has duplicate field transforms".to_string(),
216 ));
217 }
218 for field in &target.schema.fields {
219 if let Some(previous) = existing_fields.get(&field.id)
220 && previous.logical_type != field.logical_type
221 && !transforms.contains_key(&field.id)
222 {
223 return Err(TableError::InvalidSchema(format!(
224 "schema evolution changed the type of field {} without a transform",
225 field.id.0
226 )));
227 }
228 }
229
230 if target.layout.value_columns.is_empty() {
231 return Ok(vec![ColumnEvolution::Default {
232 value: Vec::new().into(),
233 }]);
234 }
235 let existing_columns = existing
236 .layout
237 .value_columns
238 .iter()
239 .map(|column| (column.field_id, usize::from(column.column_index)))
240 .collect::<HashMap<_, _>>();
241 let target_fields = target
242 .schema
243 .fields
244 .iter()
245 .map(|field| (field.id, &field.logical_type))
246 .collect::<HashMap<_, _>>();
247 target
248 .layout
249 .value_columns
250 .iter()
251 .map(|column| {
252 if let Some(source) = existing_columns.get(&column.field_id) {
253 return Ok(ColumnEvolution::Source {
254 source_index: *source,
255 transform: transforms.get(&column.field_id).map(|spec| (*spec).clone()),
256 });
257 }
258 if transforms.contains_key(&column.field_id) {
259 return Err(TableError::InvalidSchema(format!(
260 "catalog transform cannot target added field {}",
261 column.field_id.0
262 )));
263 }
264 Ok(ColumnEvolution::Default {
265 value: ValueCodec::encode_validated(target_fields[&column.field_id], &Value::Null)?
266 .into(),
267 })
268 })
269 .collect()
270}
271
272pub(crate) fn schema_field_ids(schema: &TableSchema) -> HashSet<FieldId> {
273 let mut field_ids = HashSet::new();
274 for field in &schema.fields {
275 collect_field_ids(field, &mut field_ids);
276 }
277 field_ids
278}
279
280fn field_index(schema: &TableSchema, field_name: &str) -> Result<usize> {
281 schema
282 .fields
283 .iter()
284 .position(|field| field.name == field_name)
285 .ok_or_else(|| TableError::InvalidSchema(format!("field '{field_name}' does not exist")))
286}
287
288fn transform_field_index(
289 schema: &TableSchema,
290 key_ids: &HashSet<FieldId>,
291 added_fields: &HashSet<FieldId>,
292 transforms: &HashMap<FieldId, TransformSpec>,
293 field_name: &str,
294) -> Result<usize> {
295 let index = field_index(schema, field_name)?;
296 let field = &schema.fields[index];
297 if key_ids.contains(&field.id) {
298 return Err(TableError::InvalidSchema(format!(
299 "key field '{field_name}' cannot be transformed"
300 )));
301 }
302 if added_fields.contains(&field.id) {
303 return Err(TableError::InvalidSchema(format!(
304 "added field '{field_name}' cannot be transformed in the same schema version"
305 )));
306 }
307 if transforms.contains_key(&field.id) {
308 return Err(TableError::InvalidSchema(format!(
309 "field '{field_name}' has multiple transforms in one schema version"
310 )));
311 }
312 Ok(index)
313}
314
315fn next_field_id(used_field_ids: &HashSet<FieldId>) -> Result<u32> {
316 used_field_ids
317 .iter()
318 .map(|field_id| field_id.0)
319 .max()
320 .map_or(Ok(0), |field_id| {
321 field_id
322 .checked_add(1)
323 .ok_or_else(|| TableError::InvalidSchema("field id space exhausted".to_string()))
324 })
325}
326
327fn collect_field_ids(field: &DataField, field_ids: &mut HashSet<FieldId>) {
328 field_ids.insert(field.id);
329 collect_type_field_ids(&field.logical_type, field_ids);
330}
331
332fn collect_type_field_ids(logical_type: &LogicalType, field_ids: &mut HashSet<FieldId>) {
333 match &logical_type.kind {
334 LogicalTypeKind::List { element_type } => collect_type_field_ids(element_type, field_ids),
335 LogicalTypeKind::Map {
336 key_type,
337 value_type,
338 } => {
339 collect_type_field_ids(key_type, field_ids);
340 collect_type_field_ids(value_type, field_ids);
341 }
342 LogicalTypeKind::Struct { fields } => {
343 for field in fields {
344 collect_field_ids(field, field_ids);
345 }
346 }
347 LogicalTypeKind::Extension { extension } => {
348 collect_type_field_ids(&extension.physical_type, field_ids);
349 }
350 _ => {}
351 }
352}