Skip to main content

reifydb_core/encoded/shape/
evolution.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use reifydb_type::value::constraint::TypeConstraint;
5
6use crate::encoded::shape::RowShape;
7
8#[derive(Debug, Clone)]
9pub enum FieldMapping {
10	Direct {
11		source_index: usize,
12	},
13
14	UseDefault,
15
16	Removed,
17}
18
19#[derive(Debug)]
20pub struct ShapeResolver {
21	source: RowShape,
22
23	target: RowShape,
24
25	mappings: Vec<FieldMapping>,
26}
27
28impl ShapeResolver {
29	pub fn new(source: RowShape, target: RowShape) -> Option<Self> {
30		if source.fingerprint() == target.fingerprint() {
31			return Some(Self {
32				mappings: (0..target.field_count())
33					.map(|i| FieldMapping::Direct {
34						source_index: i,
35					})
36					.collect(),
37				source,
38				target,
39			});
40		}
41
42		let mut mappings = Vec::with_capacity(target.field_count());
43
44		for target_field in target.fields() {
45			if let Some((shape_idx, source_field)) =
46				source.fields().iter().enumerate().find(|(_, f)| f.name == target_field.name)
47			{
48				if !Self::types_compatible(&source_field.constraint, &target_field.constraint) {
49					return None;
50				}
51				mappings.push(FieldMapping::Direct {
52					source_index: shape_idx,
53				});
54			} else {
55				mappings.push(FieldMapping::UseDefault);
56			}
57		}
58
59		Some(Self {
60			source,
61			target,
62			mappings,
63		})
64	}
65
66	fn types_compatible(source: &TypeConstraint, target: &TypeConstraint) -> bool {
67		let shape_type = source.get_type();
68		let target_type = target.get_type();
69
70		if shape_type == target_type {
71			return true;
72		}
73
74		false
75	}
76
77	pub fn source(&self) -> &RowShape {
78		&self.source
79	}
80
81	pub fn target(&self) -> &RowShape {
82		&self.target
83	}
84
85	pub fn mappings(&self) -> &[FieldMapping] {
86		&self.mappings
87	}
88
89	pub fn is_identity(&self) -> bool {
90		self.source.fingerprint() == self.target.fingerprint()
91	}
92}
93
94#[cfg(test)]
95mod tests {
96	use reifydb_type::value::r#type::Type;
97
98	use super::*;
99	use crate::encoded::shape::RowShapeField;
100
101	#[test]
102	fn test_resolver_identity() {
103		let fields = vec![
104			RowShapeField::unconstrained("a", Type::Int4),
105			RowShapeField::unconstrained("b", Type::Utf8),
106		];
107
108		let shape = RowShape::new(fields);
109		let resolver = ShapeResolver::new(shape.clone(), shape.clone()).unwrap();
110
111		assert!(resolver.is_identity());
112		assert_eq!(resolver.mappings().len(), 2);
113	}
114
115	#[test]
116	fn test_resolver_added_field() {
117		let source_fields = vec![RowShapeField::unconstrained("a", Type::Int4)];
118
119		let target_fields = vec![
120			RowShapeField::unconstrained("a", Type::Int4),
121			RowShapeField::unconstrained("b", Type::Utf8), // new field
122		];
123
124		let source = RowShape::new(source_fields);
125		let target = RowShape::new(target_fields);
126
127		let resolver = ShapeResolver::new(source, target).unwrap();
128
129		assert!(!resolver.is_identity());
130		assert!(matches!(
131			resolver.mappings()[0],
132			FieldMapping::Direct {
133				source_index: 0
134			}
135		));
136		assert!(matches!(resolver.mappings()[1], FieldMapping::UseDefault));
137	}
138
139	#[test]
140	fn test_resolver_incompatible_types() {
141		let source_fields = vec![RowShapeField::unconstrained("a", Type::Int4)];
142		let target_fields = vec![RowShapeField::unconstrained("a", Type::Utf8)]; // type changed
143
144		let source = RowShape::new(source_fields);
145		let target = RowShape::new(target_fields);
146
147		// Should return None due to incompatible types
148		assert!(ShapeResolver::new(source, target).is_none());
149	}
150}