reifydb_core/encoded/schema/
evolution.rs1use reifydb_type::value::constraint::TypeConstraint;
10
11use crate::encoded::schema::Schema;
12
13#[derive(Debug, Clone)]
15pub enum FieldMapping {
16 Direct {
18 source_index: usize,
19 },
20 UseDefault,
22 Removed,
24}
25
26#[derive(Debug)]
31pub struct SchemaResolver {
32 source: Schema,
34 target: Schema,
36 mappings: Vec<FieldMapping>,
38}
39
40impl SchemaResolver {
41 pub fn new(source: Schema, target: Schema) -> Option<Self> {
46 if source.fingerprint() == target.fingerprint() {
48 return Some(Self {
49 mappings: (0..target.field_count())
50 .map(|i| FieldMapping::Direct {
51 source_index: i,
52 })
53 .collect(),
54 source,
55 target,
56 });
57 }
58
59 let mut mappings = Vec::with_capacity(target.field_count());
60
61 for target_field in target.fields() {
62 if let Some((source_idx, source_field)) =
63 source.fields().iter().enumerate().find(|(_, f)| f.name == target_field.name)
64 {
65 if !Self::types_compatible(&source_field.constraint, &target_field.constraint) {
67 return None; }
69 mappings.push(FieldMapping::Direct {
70 source_index: source_idx,
71 });
72 } else {
73 mappings.push(FieldMapping::UseDefault);
75 }
76 }
77
78 Some(Self {
79 source,
80 target,
81 mappings,
82 })
83 }
84
85 fn types_compatible(source: &TypeConstraint, target: &TypeConstraint) -> bool {
88 let source_type = source.get_type();
89 let target_type = target.get_type();
90
91 if source_type == target_type {
92 return true;
93 }
94
95 false
98 }
99
100 pub fn source(&self) -> &Schema {
102 &self.source
103 }
104
105 pub fn target(&self) -> &Schema {
107 &self.target
108 }
109
110 pub fn mappings(&self) -> &[FieldMapping] {
112 &self.mappings
113 }
114
115 pub fn is_identity(&self) -> bool {
117 self.source.fingerprint() == self.target.fingerprint()
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use reifydb_type::value::r#type::Type;
124
125 use super::*;
126 use crate::encoded::schema::SchemaField;
127
128 #[test]
129 fn test_resolver_identity() {
130 let fields =
131 vec![SchemaField::unconstrained("a", Type::Int4), SchemaField::unconstrained("b", Type::Utf8)];
132
133 let schema = Schema::new(fields);
134 let resolver = SchemaResolver::new(schema.clone(), schema.clone()).unwrap();
135
136 assert!(resolver.is_identity());
137 assert_eq!(resolver.mappings().len(), 2);
138 }
139
140 #[test]
141 fn test_resolver_added_field() {
142 let source_fields = vec![SchemaField::unconstrained("a", Type::Int4)];
143
144 let target_fields = vec![
145 SchemaField::unconstrained("a", Type::Int4),
146 SchemaField::unconstrained("b", Type::Utf8), ];
148
149 let source = Schema::new(source_fields);
150 let target = Schema::new(target_fields);
151
152 let resolver = SchemaResolver::new(source, target).unwrap();
153
154 assert!(!resolver.is_identity());
155 assert!(matches!(
156 resolver.mappings()[0],
157 FieldMapping::Direct {
158 source_index: 0
159 }
160 ));
161 assert!(matches!(resolver.mappings()[1], FieldMapping::UseDefault));
162 }
163
164 #[test]
165 fn test_resolver_incompatible_types() {
166 let source_fields = vec![SchemaField::unconstrained("a", Type::Int4)];
167 let target_fields = vec![SchemaField::unconstrained("a", Type::Utf8)]; let source = Schema::new(source_fields);
170 let target = Schema::new(target_fields);
171
172 assert!(SchemaResolver::new(source, target).is_none());
174 }
175}