1mod dynamic_array;
2pub use dynamic_array::*;
3
4mod r#enum;
5pub use r#enum::*;
6
7mod map;
11pub use map::*;
12
13mod record;
14pub use record::*;
15
16mod static_array;
20pub use static_array::*;
21
22use crate::{DataSetError, DataSetResult, HashMap};
23use crate::{HashSet, PropertyPath, SchemaFingerprint};
24use std::hash::Hash;
25use std::str::FromStr;
26use uuid::Uuid;
27
28#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
29pub struct SchemaId(u128);
30
31#[derive(Clone, Debug)]
32pub enum SchemaNamedType {
33 Record(SchemaRecord),
34 Enum(SchemaEnum),
35}
36
37impl SchemaNamedType {
38 pub fn fingerprint(&self) -> SchemaFingerprint {
39 match self {
40 SchemaNamedType::Record(x) => x.fingerprint(),
41 SchemaNamedType::Enum(x) => x.fingerprint(),
42 }
43 }
44
45 pub fn name(&self) -> &str {
46 match self {
47 SchemaNamedType::Record(x) => x.name(),
48 SchemaNamedType::Enum(x) => x.name(),
49 }
50 }
51
52 pub fn type_uuid(&self) -> Uuid {
53 match self {
54 SchemaNamedType::Record(x) => x.type_uuid(),
55 SchemaNamedType::Enum(x) => x.type_uuid(),
56 }
57 }
58
59 pub fn as_record(&self) -> DataSetResult<&SchemaRecord> {
60 Ok(self.try_as_record().ok_or(DataSetError::InvalidSchema)?)
61 }
62
63 pub fn try_as_record(&self) -> Option<&SchemaRecord> {
64 match self {
65 SchemaNamedType::Record(x) => Some(x),
66 _ => None,
67 }
68 }
69
70 pub fn as_enum(&self) -> DataSetResult<&SchemaEnum> {
71 Ok(self.try_as_enum().ok_or(DataSetError::InvalidSchema)?)
72 }
73
74 pub fn try_as_enum(&self) -> Option<&SchemaEnum> {
75 match self {
76 SchemaNamedType::Enum(x) => Some(x),
77 _ => None,
78 }
79 }
80
81 pub fn find_post_migration_property_path(
88 old_root_named_type: &SchemaNamedType,
89 old_path: impl AsRef<str>,
90 old_named_types: &HashMap<SchemaFingerprint, SchemaNamedType>,
91 new_root_named_type: &SchemaNamedType,
92 new_named_types: &HashMap<SchemaFingerprint, SchemaNamedType>,
93 new_named_types_by_uuid: &HashMap<Uuid, SchemaFingerprint>,
94 ) -> Option<String> {
95 let mut old_schema = Schema::Record(old_root_named_type.fingerprint());
96 let mut new_schema = Schema::Record(new_root_named_type.fingerprint());
97
98 log::trace!("migrate property name {:?}", old_path.as_ref());
99 let old_split_path = old_path.as_ref().split(".");
100 let mut new_path = PropertyPath::default();
101
102 for old_path_segment in old_split_path {
105 let new_path_segment = Schema::find_post_migration_field_name(
106 &old_schema,
107 old_path_segment,
108 old_named_types,
109 &new_schema,
110 new_named_types,
111 new_named_types_by_uuid,
112 )?;
113
114 new_path = new_path.push(&new_path_segment);
115 let old_s = old_schema.find_field_schema(old_path_segment, old_named_types);
116 let new_s = new_schema.find_field_schema(new_path_segment, new_named_types);
117
118 if let (Some(old_s), Some(new_s)) = (old_s, new_s) {
119 if !Schema::types_are_interchangeable(
120 old_s,
121 new_s,
122 old_named_types,
123 new_named_types,
124 ) {
125 return None;
126 }
127
128 old_schema = old_s.clone();
129 new_schema = new_s.clone();
130 } else {
131 return None;
132 }
133 }
134
135 Some(new_path.path().to_string())
136 }
137
138 pub fn find_property_schema(
139 &self,
140 path: impl AsRef<str>,
141 named_types: &HashMap<SchemaFingerprint, SchemaNamedType>,
142 ) -> Option<Schema> {
143 let mut schema = Schema::Record(self.fingerprint());
144
145 let split_path = path.as_ref().split(".");
146
147 for path_segment in split_path {
149 let s = schema.find_field_schema(path_segment, named_types);
150 if let Some(s) = s {
151 schema = s.clone();
152 } else {
153 return None;
154 }
155 }
156
157 Some(schema)
158 }
159}
160
161#[derive(Clone, Debug, PartialEq)]
164pub enum Schema {
165 Nullable(Box<Schema>),
167 Boolean,
168 I32,
169 I64,
170 U32,
171 U64,
172 F32,
173 F64,
174 Bytes,
176 String,
178 StaticArray(SchemaStaticArray),
180 DynamicArray(SchemaDynamicArray),
181 Map(SchemaMap),
182 AssetRef(SchemaFingerprint),
183 Record(SchemaFingerprint),
185 Enum(SchemaFingerprint),
186}
187
188impl Schema {
189 pub fn is_nullable(&self) -> bool {
190 match self {
191 Schema::Nullable(_) => true,
192 _ => false,
193 }
194 }
195
196 pub fn is_boolean(&self) -> bool {
197 match self {
198 Schema::Boolean => true,
199 _ => false,
200 }
201 }
202
203 pub fn is_i32(&self) -> bool {
204 match self {
205 Schema::I32 => true,
206 _ => false,
207 }
208 }
209
210 pub fn is_i64(&self) -> bool {
211 match self {
212 Schema::I64 => true,
213 _ => false,
214 }
215 }
216
217 pub fn is_u32(&self) -> bool {
218 match self {
219 Schema::U32 => true,
220 _ => false,
221 }
222 }
223
224 pub fn is_u64(&self) -> bool {
225 match self {
226 Schema::U64 => true,
227 _ => false,
228 }
229 }
230
231 pub fn is_f32(&self) -> bool {
232 match self {
233 Schema::F32 => true,
234 _ => false,
235 }
236 }
237
238 pub fn is_f64(&self) -> bool {
239 match self {
240 Schema::F64 => true,
241 _ => false,
242 }
243 }
244
245 pub fn is_bytes(&self) -> bool {
246 match self {
247 Schema::Bytes => true,
248 _ => false,
249 }
250 }
251
252 pub fn is_string(&self) -> bool {
253 match self {
254 Schema::String => true,
255 _ => false,
256 }
257 }
258
259 pub fn is_static_array(&self) -> bool {
260 match self {
261 Schema::StaticArray(_) => true,
262 _ => false,
263 }
264 }
265
266 pub fn is_dynamic_array(&self) -> bool {
267 match self {
268 Schema::DynamicArray(_) => true,
269 _ => false,
270 }
271 }
272
273 pub fn is_map(&self) -> bool {
274 match self {
275 Schema::Map(_) => true,
276 _ => false,
277 }
278 }
279
280 pub fn is_asset_ref(&self) -> bool {
281 match self {
282 Schema::AssetRef(_) => true,
283 _ => false,
284 }
285 }
286
287 pub fn is_record(&self) -> bool {
288 match self {
289 Schema::Record(_) => true,
290 _ => false,
291 }
292 }
293
294 pub fn is_enum(&self) -> bool {
295 match self {
296 Schema::Enum(_) => true,
297 _ => false,
298 }
299 }
300
301 pub fn is_number(&self) -> bool {
302 match self {
303 Schema::I32 | Schema::I64 | Schema::U32 | Schema::U64 | Schema::F32 | Schema::F64 => {
304 true
305 }
306 _ => false,
307 }
308 }
309
310 pub fn types_are_interchangeable(
311 old_parent_schema: &Schema,
312 new_parent_schema: &Schema,
313 old_named_types: &HashMap<SchemaFingerprint, SchemaNamedType>,
314 new_named_types: &HashMap<SchemaFingerprint, SchemaNamedType>,
315 ) -> bool {
316 if old_parent_schema == new_parent_schema {
318 return true;
319 }
320
321 if old_parent_schema.is_number() && new_parent_schema.is_number() {
322 return true;
323 }
324
325 match old_parent_schema {
326 Schema::Nullable(old_inner) => {
327 if let Schema::Nullable(new_inner) = new_parent_schema {
329 Self::types_are_interchangeable(
330 &*old_inner,
331 &*new_inner,
332 old_named_types,
333 new_named_types,
334 )
335 } else {
336 false
337 }
338 }
339 Schema::StaticArray(old_inner) => {
340 if let Schema::StaticArray(new_inner) = new_parent_schema {
341 Self::types_are_interchangeable(
342 old_inner.item_type(),
343 new_inner.item_type(),
344 old_named_types,
345 new_named_types,
346 )
347 } else {
348 false
349 }
350 }
351 Schema::DynamicArray(old_inner) => {
352 if let Schema::DynamicArray(new_inner) = new_parent_schema {
353 Self::types_are_interchangeable(
354 old_inner.item_type(),
355 new_inner.item_type(),
356 old_named_types,
357 new_named_types,
358 )
359 } else {
360 false
361 }
362 }
363 Schema::Map(old_inner) => {
364 if let Schema::Map(new_inner) = new_parent_schema {
365 let keys_are_interchangage = Self::types_are_interchangeable(
366 old_inner.key_type(),
367 new_inner.key_type(),
368 old_named_types,
369 new_named_types,
370 );
371 let values_are_interchangable = Self::types_are_interchangeable(
372 old_inner.value_type(),
373 new_inner.value_type(),
374 old_named_types,
375 new_named_types,
376 );
377 keys_are_interchangage && values_are_interchangable
378 } else {
379 false
380 }
381 }
382 Schema::AssetRef(_) => {
383 if let Schema::AssetRef(_) = new_parent_schema {
384 true
387 } else {
388 false
389 }
390 }
391 Schema::Record(old_inner) => {
392 if let Schema::Record(new_inner) = new_parent_schema {
393 let old_named_type = old_named_types.get(old_inner).unwrap();
394 let new_named_type = new_named_types.get(new_inner).unwrap();
395
396 old_named_type.type_uuid() == new_named_type.type_uuid()
398 } else {
399 false
400 }
401 }
402 Schema::Enum(old_inner) => {
403 if let Schema::Enum(new_inner) = new_parent_schema {
404 let old_named_type = old_named_types.get(old_inner).unwrap();
405 let new_named_type = new_named_types.get(new_inner).unwrap();
406
407 old_named_type.type_uuid() == new_named_type.type_uuid()
408 } else {
409 false
410 }
411 }
412 _ => false,
413 }
414 }
415
416 pub fn find_post_migration_field_name<'a>(
418 old_parent_schema: &Schema,
419 old_property_name: &'a str,
420 old_named_types: &HashMap<SchemaFingerprint, SchemaNamedType>,
421 _new_parent_schema: &Schema,
422 new_named_types: &HashMap<SchemaFingerprint, SchemaNamedType>,
423 new_named_types_by_uuid: &HashMap<Uuid, SchemaFingerprint>,
424 ) -> Option<String> {
425 match old_parent_schema {
426 Schema::Nullable(_) => {
427 if old_property_name == "value" {
428 Some(old_property_name.to_string())
429 } else {
430 None
431 }
432 }
433 Schema::Record(old_schema_fingerprint) => {
434 let old_named_type = old_named_types.get(old_schema_fingerprint).unwrap();
435 let old_schema_record = old_named_type.as_record().unwrap();
436 let old_field = old_schema_record
437 .find_field_from_name(old_property_name.as_ref())
438 .unwrap();
439 let old_record_type_uuid = old_named_type.type_uuid();
440
441 let new_schema_fingerprint =
444 new_named_types_by_uuid.get(&old_record_type_uuid).unwrap();
445 let new_named_type = new_named_types.get(new_schema_fingerprint).unwrap();
446 let new_schema_record = new_named_type.as_record().unwrap();
447
448 new_schema_record
450 .find_field_from_field_uuid(old_field.field_uuid())
451 .map(|x| x.name().to_string())
452 }
453 Schema::StaticArray(_) => {
454 if old_property_name.parse::<u32>().is_ok() {
455 Some(old_property_name.to_string())
456 } else {
457 None
458 }
459 }
460 Schema::DynamicArray(_) => {
461 Uuid::from_str(old_property_name.as_ref()).ok()?;
463 Some(old_property_name.to_string())
464 }
465 Schema::Map(_) => {
466 if old_property_name.ends_with(":key") {
467 Uuid::from_str(&old_property_name[0..old_property_name.len() - 4]).ok()?;
468 Some(old_property_name.to_string())
469 } else if old_property_name.ends_with(":value") {
470 Uuid::from_str(&old_property_name[0..old_property_name.len() - 6]).ok()?;
471 Some(old_property_name.to_string())
472 } else {
473 None
474 }
475 }
476 _ => None,
477 }
478 }
479
480 pub fn find_field_schema<'a>(
482 &'a self,
483 name: impl AsRef<str>,
484 named_types: &'a HashMap<SchemaFingerprint, SchemaNamedType>,
485 ) -> Option<&'a Schema> {
486 match self {
487 Schema::Nullable(x) => {
488 if name.as_ref() == "value" {
489 Some(&*x)
490 } else {
491 None
493 }
494 }
495 Schema::Record(named_type_id) => {
496 let named_type = named_types.get(named_type_id).unwrap();
497 match named_type {
498 SchemaNamedType::Record(x) => x.field_schema(name),
499 SchemaNamedType::Enum(_) => None,
500 }
501 }
502 Schema::StaticArray(x) => {
503 if name.as_ref().parse::<u32>().is_ok() {
504 Some(x.item_type())
505 } else {
506 None
507 }
508 }
509 Schema::DynamicArray(x) => {
510 Uuid::from_str(name.as_ref()).ok()?;
513 Some(x.item_type())
514 }
515 Schema::Map(x) => {
516 if name.as_ref().ends_with(":key") {
517 Uuid::from_str(&name.as_ref()[0..name.as_ref().len() - 4]).ok()?;
518 Some(x.key_type())
519 } else if name.as_ref().ends_with(":value") {
520 Uuid::from_str(&name.as_ref()[0..name.as_ref().len() - 6]).ok()?;
521 Some(x.value_type())
522 } else {
523 None
524 }
525 }
526 _ => None,
527 }
528 }
529
530 pub fn find_referenced_schemas<'a>(
533 named_types: &'a HashMap<SchemaFingerprint, SchemaNamedType>,
534 schema: &'a Schema,
535 referenced_schema_fingerprints: &mut HashSet<SchemaFingerprint>,
536 visit_stack: &mut Vec<&'a Schema>,
537 ) {
538 if visit_stack.contains(&schema) {
539 return;
540 }
541
542 visit_stack.push(&schema);
543 match schema {
545 Schema::Nullable(inner) => Self::find_referenced_schemas(
546 named_types,
547 &*inner,
548 referenced_schema_fingerprints,
549 visit_stack,
550 ),
551 Schema::Boolean => {}
552 Schema::I32 => {}
553 Schema::I64 => {}
554 Schema::U32 => {}
555 Schema::U64 => {}
556 Schema::F32 => {}
557 Schema::F64 => {}
558 Schema::Bytes => {}
559 Schema::String => {}
560 Schema::StaticArray(inner) => Self::find_referenced_schemas(
561 named_types,
562 inner.item_type(),
563 referenced_schema_fingerprints,
564 visit_stack,
565 ),
566 Schema::DynamicArray(inner) => Self::find_referenced_schemas(
567 named_types,
568 inner.item_type(),
569 referenced_schema_fingerprints,
570 visit_stack,
571 ),
572 Schema::Map(inner) => {
573 Self::find_referenced_schemas(
574 named_types,
575 inner.key_type(),
576 referenced_schema_fingerprints,
577 visit_stack,
578 );
579 Self::find_referenced_schemas(
580 named_types,
581 inner.value_type(),
582 referenced_schema_fingerprints,
583 visit_stack,
584 );
585 }
586 Schema::AssetRef(_) => {}
587 Schema::Record(inner) => {
588 referenced_schema_fingerprints.insert(*inner);
589 let record = named_types.get(inner).unwrap().try_as_record().unwrap();
590 for field in record.fields() {
591 Self::find_referenced_schemas(
592 named_types,
593 field.field_schema(),
594 referenced_schema_fingerprints,
595 visit_stack,
596 );
597 }
598 }
599 Schema::Enum(inner) => {
600 referenced_schema_fingerprints.insert(*inner);
601 }
602 }
603 visit_stack.pop();
604 }
605}