apache_avro/
schema_equality.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use crate::schema::{InnerDecimalSchema, UuidSchema};
19use crate::{
20    Schema,
21    schema::{
22        ArraySchema, DecimalSchema, EnumSchema, FixedSchema, MapSchema, RecordField, RecordSchema,
23        UnionSchema,
24    },
25};
26use log::debug;
27use std::{fmt::Debug, sync::OnceLock};
28
29/// A trait that compares two schemata for equality.
30/// To register a custom one use [set_schemata_equality_comparator].
31pub trait SchemataEq: Debug + Send + Sync {
32    /// Compares two schemata for equality.
33    fn compare(&self, schema_one: &Schema, schema_two: &Schema) -> bool;
34}
35
36/// Compares two schemas according to the Avro specification by using
37/// their canonical forms.
38/// See <https://avro.apache.org/docs/1.11.1/specification/#parsing-canonical-form-for-schemas>
39#[derive(Debug)]
40pub struct SpecificationEq;
41impl SchemataEq for SpecificationEq {
42    fn compare(&self, schema_one: &Schema, schema_two: &Schema) -> bool {
43        schema_one.canonical_form() == schema_two.canonical_form()
44    }
45}
46
47/// Compares two schemas for equality field by field, using only the fields that
48/// are used to construct their canonical forms.
49/// See <https://avro.apache.org/docs/1.11.1/specification/#parsing-canonical-form-for-schemas>
50#[derive(Debug)]
51pub struct StructFieldEq {
52    /// Whether to include custom attributes in the comparison.
53    /// The custom attributes are not used to construct the canonical form of the schema!
54    pub include_attributes: bool,
55}
56
57impl SchemataEq for StructFieldEq {
58    #[rustfmt::skip]
59    fn compare(&self, schema_one: &Schema, schema_two: &Schema) -> bool {
60        if schema_one.name() != schema_two.name() {
61            return false;
62        }
63
64        if self.include_attributes
65            && schema_one.custom_attributes() != schema_two.custom_attributes()
66        {
67            return false;
68        }
69
70        match (schema_one, schema_two) {
71            (Schema::Null, Schema::Null) => true,
72            (Schema::Null, _) => false,
73            (Schema::Boolean, Schema::Boolean) => true,
74            (Schema::Boolean, _) => false,
75            (Schema::Int, Schema::Int) => true,
76            (Schema::Int, _) => false,
77            (Schema::Long, Schema::Long) => true,
78            (Schema::Long, _) => false,
79            (Schema::Float, Schema::Float) => true,
80            (Schema::Float, _) => false,
81            (Schema::Double, Schema::Double) => true,
82            (Schema::Double, _) => false,
83            (Schema::Bytes, Schema::Bytes) => true,
84            (Schema::Bytes, _) => false,
85            (Schema::String, Schema::String) => true,
86            (Schema::String, _) => false,
87            (Schema::BigDecimal, Schema::BigDecimal) => true,
88            (Schema::BigDecimal, _) => false,
89            (Schema::Date, Schema::Date) => true,
90            (Schema::Date, _) => false,
91            (Schema::TimeMicros, Schema::TimeMicros) => true,
92            (Schema::TimeMicros, _) => false,
93            (Schema::TimeMillis, Schema::TimeMillis) => true,
94            (Schema::TimeMillis, _) => false,
95            (Schema::TimestampMicros, Schema::TimestampMicros) => true,
96            (Schema::TimestampMicros, _) => false,
97            (Schema::TimestampMillis, Schema::TimestampMillis) => true,
98            (Schema::TimestampMillis, _) => false,
99            (Schema::TimestampNanos, Schema::TimestampNanos) => true,
100            (Schema::TimestampNanos, _) => false,
101            (Schema::LocalTimestampMicros, Schema::LocalTimestampMicros) => true,
102            (Schema::LocalTimestampMicros, _) => false,
103            (Schema::LocalTimestampMillis, Schema::LocalTimestampMillis) => true,
104            (Schema::LocalTimestampMillis, _) => false,
105            (Schema::LocalTimestampNanos, Schema::LocalTimestampNanos) => true,
106            (Schema::LocalTimestampNanos, _) => false,
107            (
108                Schema::Record(RecordSchema { fields: fields_one, ..}),
109                Schema::Record(RecordSchema { fields: fields_two, ..})
110            ) => {
111                self.compare_fields(fields_one, fields_two)
112            }
113            (Schema::Record(_), _) => false,
114            (
115                Schema::Enum(EnumSchema { symbols: symbols_one, ..}),
116                Schema::Enum(EnumSchema { symbols: symbols_two, .. })
117            ) => {
118                symbols_one == symbols_two
119            }
120            (Schema::Enum(_), _) => false,
121            (
122                Schema::Fixed(FixedSchema { size: size_one, ..}),
123                Schema::Fixed(FixedSchema { size: size_two, .. })
124            ) => {
125                size_one == size_two
126            }
127            (Schema::Fixed(_), _) => false,
128            (
129                Schema::Union(UnionSchema { schemas: schemas_one, ..}),
130                Schema::Union(UnionSchema { schemas: schemas_two, .. })
131            ) => {
132                schemas_one.len() == schemas_two.len()
133                    && schemas_one
134                    .iter()
135                    .zip(schemas_two.iter())
136                    .all(|(s1, s2)| self.compare(s1, s2))
137            }
138            (Schema::Union(_), _) => false,
139            (
140                Schema::Decimal(DecimalSchema { precision: precision_one, scale: scale_one, inner: inner_one }),
141                Schema::Decimal(DecimalSchema { precision: precision_two, scale: scale_two, inner: inner_two })
142            ) => {
143                precision_one == precision_two && scale_one == scale_two && match (inner_one, inner_two) {
144                    (InnerDecimalSchema::Bytes, InnerDecimalSchema::Bytes) => true,
145                    (InnerDecimalSchema::Fixed(FixedSchema { size: size_one, .. }), InnerDecimalSchema::Fixed(FixedSchema { size: size_two, ..})) => {
146                        size_one == size_two
147                    }
148                    _ => false,
149                }
150            }
151            (Schema::Decimal(_), _) => false,
152            (Schema::Uuid(UuidSchema::Bytes), Schema::Uuid(UuidSchema::Bytes)) => true,
153            (Schema::Uuid(UuidSchema::Bytes), _) => false,
154            (Schema::Uuid(UuidSchema::String), Schema::Uuid(UuidSchema::String)) => true,
155            (Schema::Uuid(UuidSchema::String), _) => false,
156            (Schema::Uuid(UuidSchema::Fixed(FixedSchema { size: size_one, ..})), Schema::Uuid(UuidSchema::Fixed(FixedSchema { size: size_two, ..}))) => {
157                size_one == size_two
158            },
159            (Schema::Uuid(UuidSchema::Fixed(_)), _) => false,
160            (
161                Schema::Array(ArraySchema { items: items_one, ..}),
162                Schema::Array(ArraySchema { items: items_two, ..})
163            ) => {
164                self.compare(items_one, items_two)
165            }
166            (Schema::Array(_), _) => false,
167            (Schema::Duration(FixedSchema { size: size_one, ..}), Schema::Duration(FixedSchema { size: size_two, ..})) => size_one == size_two,
168            (Schema::Duration(_), _) => false,
169            (
170                Schema::Map(MapSchema { types: types_one, ..}),
171                Schema::Map(MapSchema { types: types_two, ..})
172            ) => {
173                self.compare(types_one, types_two)
174            }
175            (Schema::Map(_), _) => false,
176            (
177                Schema::Ref { name: name_one },
178                Schema::Ref { name: name_two }
179            ) => {
180                name_one == name_two
181            }
182            (Schema::Ref { .. }, _) => false,
183        }
184    }
185}
186
187impl StructFieldEq {
188    fn compare_fields(&self, fields_one: &[RecordField], fields_two: &[RecordField]) -> bool {
189        fields_one.len() == fields_two.len()
190            && fields_one
191                .iter()
192                .zip(fields_two.iter())
193                .all(|(f1, f2)| f1.name == f2.name && self.compare(&f1.schema, &f2.schema))
194    }
195}
196
197static SCHEMATA_COMPARATOR_ONCE: OnceLock<Box<dyn SchemataEq>> = OnceLock::new();
198
199/// Sets a custom schemata equality comparator.
200///
201/// Returns a unit if the registration was successful or the already
202/// registered comparator if the registration failed.
203///
204/// **Note**: This function must be called before parsing any schema because this will
205/// register the default comparator and the registration is one time only!
206pub fn set_schemata_equality_comparator(
207    comparator: Box<dyn SchemataEq>,
208) -> Result<(), Box<dyn SchemataEq>> {
209    debug!("Setting a custom schemata equality comparator: {comparator:?}.");
210    SCHEMATA_COMPARATOR_ONCE.set(comparator)
211}
212
213pub(crate) fn compare_schemata(schema_one: &Schema, schema_two: &Schema) -> bool {
214    SCHEMATA_COMPARATOR_ONCE
215        .get_or_init(|| {
216            debug!("Going to use the default schemata equality comparator: StructFieldEq.",);
217            Box::new(StructFieldEq {
218                include_attributes: false,
219            })
220        })
221        .compare(schema_one, schema_two)
222}
223
224#[cfg(test)]
225#[allow(non_snake_case)]
226mod tests {
227    use super::*;
228    use crate::schema::{InnerDecimalSchema, Name, RecordFieldOrder};
229    use apache_avro_test_helper::TestResult;
230    use serde_json::Value;
231    use std::collections::BTreeMap;
232
233    const SPECIFICATION_EQ: SpecificationEq = SpecificationEq;
234    const STRUCT_FIELD_EQ: StructFieldEq = StructFieldEq {
235        include_attributes: false,
236    };
237    const STRUCT_FIELD_EQ_WITH_ATTRS: StructFieldEq = StructFieldEq {
238        include_attributes: true,
239    };
240
241    macro_rules! test_primitives {
242        ($primitive:ident) => {
243            paste::item! {
244                #[test]
245                fn [<test_avro_3939_compare_schemata_$primitive>]() {
246                    let specification_eq_res = SPECIFICATION_EQ.compare(&Schema::$primitive, &Schema::$primitive);
247                    let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&Schema::$primitive, &Schema::$primitive);
248                    assert_eq!(specification_eq_res, struct_field_eq_res)
249                }
250            }
251        };
252    }
253
254    test_primitives!(Null);
255    test_primitives!(Boolean);
256    test_primitives!(Int);
257    test_primitives!(Long);
258    test_primitives!(Float);
259    test_primitives!(Double);
260    test_primitives!(Bytes);
261    test_primitives!(String);
262    test_primitives!(BigDecimal);
263    test_primitives!(Date);
264    test_primitives!(TimeMicros);
265    test_primitives!(TimeMillis);
266    test_primitives!(TimestampMicros);
267    test_primitives!(TimestampMillis);
268    test_primitives!(TimestampNanos);
269    test_primitives!(LocalTimestampMicros);
270    test_primitives!(LocalTimestampMillis);
271    test_primitives!(LocalTimestampNanos);
272
273    #[test]
274    fn avro_rs_382_compare_schemata_duration_equal() {
275        let schema_one = Schema::Duration(FixedSchema {
276            name: Name::from("name1"),
277            size: 12,
278            aliases: None,
279            doc: None,
280            default: None,
281            attributes: BTreeMap::new(),
282        });
283        let schema_two = Schema::Duration(FixedSchema {
284            name: Name::from("name1"),
285            size: 12,
286            aliases: None,
287            doc: None,
288            default: None,
289            attributes: BTreeMap::new(),
290        });
291        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
292        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
293        assert_eq!(specification_eq_res, struct_field_eq_res)
294    }
295
296    #[test]
297    fn avro_rs_382_compare_schemata_duration_different_names() {
298        let schema_one = Schema::Duration(FixedSchema {
299            name: Name::from("name1"),
300            size: 12,
301            aliases: None,
302            doc: None,
303            default: None,
304            attributes: BTreeMap::new(),
305        });
306        let schema_two = Schema::Duration(FixedSchema {
307            name: Name::from("name2"),
308            size: 12,
309            aliases: None,
310            doc: None,
311            default: None,
312            attributes: BTreeMap::new(),
313        });
314        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
315        assert!(!specification_eq_res);
316
317        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
318        assert!(!struct_field_eq_res)
319    }
320
321    #[test]
322    fn avro_rs_382_compare_schemata_duration_different_attributes() {
323        let schema_one = Schema::Duration(FixedSchema {
324            name: Name::from("name1"),
325            size: 12,
326            aliases: None,
327            doc: None,
328            default: None,
329            attributes: vec![(String::from("attr1"), serde_json::Value::Bool(true))]
330                .into_iter()
331                .collect(),
332        });
333        let schema_two = Schema::Duration(FixedSchema {
334            name: Name::from("name1"),
335            size: 12,
336            aliases: None,
337            doc: None,
338            default: None,
339            attributes: BTreeMap::new(),
340        });
341        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
342        assert!(specification_eq_res);
343
344        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
345        assert!(struct_field_eq_res);
346
347        let struct_field_eq_with_attrs_res =
348            STRUCT_FIELD_EQ_WITH_ATTRS.compare(&schema_one, &schema_two);
349        assert!(!struct_field_eq_with_attrs_res);
350    }
351
352    #[test]
353    fn avro_rs_382_compare_schemata_duration_different_sizes() {
354        let schema_one = Schema::Duration(FixedSchema {
355            name: Name::from("name1"),
356            size: 8,
357            aliases: None,
358            doc: None,
359            default: None,
360            attributes: BTreeMap::new(),
361        });
362        let schema_two = Schema::Duration(FixedSchema {
363            name: Name::from("name1"),
364            size: 12,
365            aliases: None,
366            doc: None,
367            default: None,
368            attributes: BTreeMap::new(),
369        });
370        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
371        assert!(!specification_eq_res);
372
373        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
374        assert!(!struct_field_eq_res);
375    }
376
377    #[test]
378    fn test_avro_3939_compare_named_schemata_with_different_names() {
379        let schema_one = Schema::Ref {
380            name: Name::from("name1"),
381        };
382
383        let schema_two = Schema::Ref {
384            name: Name::from("name2"),
385        };
386
387        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
388        assert!(!specification_eq_res);
389        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
390        assert!(!struct_field_eq_res);
391
392        assert_eq!(specification_eq_res, struct_field_eq_res);
393    }
394
395    #[test]
396    fn test_avro_3939_compare_schemata_not_including_attributes() {
397        let schema_one = Schema::map_with_attributes(
398            Schema::Boolean,
399            BTreeMap::from_iter([("key1".to_string(), Value::Bool(true))]),
400        );
401        let schema_two = Schema::map_with_attributes(
402            Schema::Boolean,
403            BTreeMap::from_iter([("key2".to_string(), Value::Bool(true))]),
404        );
405        // STRUCT_FIELD_EQ does not include attributes !
406        assert!(STRUCT_FIELD_EQ.compare(&schema_one, &schema_two));
407    }
408
409    #[test]
410    fn test_avro_3939_compare_schemata_including_attributes() {
411        let struct_field_eq = StructFieldEq {
412            include_attributes: true,
413        };
414        let schema_one = Schema::map_with_attributes(
415            Schema::Boolean,
416            BTreeMap::from_iter([("key1".to_string(), Value::Bool(true))]),
417        );
418        let schema_two = Schema::map_with_attributes(
419            Schema::Boolean,
420            BTreeMap::from_iter([("key2".to_string(), Value::Bool(true))]),
421        );
422        assert!(!struct_field_eq.compare(&schema_one, &schema_two));
423    }
424
425    #[test]
426    fn test_avro_3939_compare_map_schemata() {
427        let schema_one = Schema::map(Schema::Boolean);
428        assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
429        assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));
430
431        let schema_two = Schema::map(Schema::Boolean);
432
433        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
434        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
435        assert!(
436            specification_eq_res,
437            "SpecificationEq: Equality of two Schema::Map failed!"
438        );
439        assert!(
440            struct_field_eq_res,
441            "StructFieldEq: Equality of two Schema::Map failed!"
442        );
443        assert_eq!(specification_eq_res, struct_field_eq_res);
444    }
445
446    #[test]
447    fn test_avro_3939_compare_array_schemata() {
448        let schema_one = Schema::array(Schema::Boolean);
449        assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
450        assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));
451
452        let schema_two = Schema::array(Schema::Boolean);
453
454        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
455        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
456        assert!(
457            specification_eq_res,
458            "SpecificationEq: Equality of two Schema::Array failed!"
459        );
460        assert!(
461            struct_field_eq_res,
462            "StructFieldEq: Equality of two Schema::Array failed!"
463        );
464        assert_eq!(specification_eq_res, struct_field_eq_res);
465    }
466
467    #[test]
468    fn test_avro_3939_compare_decimal_schemata() {
469        let schema_one = Schema::Decimal(DecimalSchema {
470            precision: 10,
471            scale: 2,
472            inner: InnerDecimalSchema::Bytes,
473        });
474        assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
475        assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));
476
477        let schema_two = Schema::Decimal(DecimalSchema {
478            precision: 10,
479            scale: 2,
480            inner: InnerDecimalSchema::Bytes,
481        });
482
483        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
484        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
485        assert!(
486            specification_eq_res,
487            "SpecificationEq: Equality of two Schema::Decimal failed!"
488        );
489        assert!(
490            struct_field_eq_res,
491            "StructFieldEq: Equality of two Schema::Decimal failed!"
492        );
493        assert_eq!(specification_eq_res, struct_field_eq_res);
494    }
495
496    #[test]
497    fn test_avro_3939_compare_fixed_schemata() {
498        let schema_one = Schema::Fixed(FixedSchema {
499            name: Name::from("fixed"),
500            doc: None,
501            size: 10,
502            default: None,
503            aliases: None,
504            attributes: BTreeMap::new(),
505        });
506        assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
507        assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));
508
509        let schema_two = Schema::Fixed(FixedSchema {
510            name: Name::from("fixed"),
511            doc: None,
512            size: 10,
513            default: None,
514            aliases: None,
515            attributes: BTreeMap::new(),
516        });
517
518        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
519        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
520        assert!(
521            specification_eq_res,
522            "SpecificationEq: Equality of two Schema::Fixed failed!"
523        );
524        assert!(
525            struct_field_eq_res,
526            "StructFieldEq: Equality of two Schema::Fixed failed!"
527        );
528        assert_eq!(specification_eq_res, struct_field_eq_res);
529    }
530
531    #[test]
532    fn test_avro_3939_compare_enum_schemata() {
533        let schema_one = Schema::Enum(EnumSchema {
534            name: Name::from("enum"),
535            doc: None,
536            symbols: vec!["A".to_string(), "B".to_string()],
537            default: None,
538            aliases: None,
539            attributes: BTreeMap::new(),
540        });
541        assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
542        assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));
543
544        let schema_two = Schema::Enum(EnumSchema {
545            name: Name::from("enum"),
546            doc: None,
547            symbols: vec!["A".to_string(), "B".to_string()],
548            default: None,
549            aliases: None,
550            attributes: BTreeMap::new(),
551        });
552
553        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
554        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
555        assert!(
556            specification_eq_res,
557            "SpecificationEq: Equality of two Schema::Enum failed!"
558        );
559        assert!(
560            struct_field_eq_res,
561            "StructFieldEq: Equality of two Schema::Enum failed!"
562        );
563        assert_eq!(specification_eq_res, struct_field_eq_res);
564    }
565
566    #[test]
567    fn test_avro_3939_compare_ref_schemata() {
568        let schema_one = Schema::Ref {
569            name: Name::from("ref"),
570        };
571        assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
572        assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));
573
574        let schema_two = Schema::Ref {
575            name: Name::from("ref"),
576        };
577
578        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
579        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
580        assert!(
581            specification_eq_res,
582            "SpecificationEq: Equality of two Schema::Ref failed!"
583        );
584        assert!(
585            struct_field_eq_res,
586            "StructFieldEq: Equality of two Schema::Ref failed!"
587        );
588        assert_eq!(specification_eq_res, struct_field_eq_res);
589    }
590
591    #[test]
592    fn test_avro_3939_compare_record_schemata() {
593        let schema_one = Schema::Record(RecordSchema {
594            name: Name::from("record"),
595            doc: None,
596            fields: vec![RecordField {
597                name: "field".to_string(),
598                doc: None,
599                default: None,
600                schema: Schema::Boolean,
601                order: RecordFieldOrder::Ignore,
602                aliases: None,
603                custom_attributes: BTreeMap::new(),
604                position: 0,
605            }],
606            aliases: None,
607            attributes: BTreeMap::new(),
608            lookup: Default::default(),
609        });
610        assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
611        assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));
612
613        let schema_two = Schema::Record(RecordSchema {
614            name: Name::from("record"),
615            doc: None,
616            fields: vec![RecordField {
617                name: "field".to_string(),
618                doc: None,
619                default: None,
620                schema: Schema::Boolean,
621                order: RecordFieldOrder::Ignore,
622                aliases: None,
623                custom_attributes: BTreeMap::new(),
624                position: 0,
625            }],
626            aliases: None,
627            attributes: BTreeMap::new(),
628            lookup: Default::default(),
629        });
630
631        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
632        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
633        assert!(
634            specification_eq_res,
635            "SpecificationEq: Equality of two Schema::Record failed!"
636        );
637        assert!(
638            struct_field_eq_res,
639            "StructFieldEq: Equality of two Schema::Record failed!"
640        );
641        assert_eq!(specification_eq_res, struct_field_eq_res);
642    }
643
644    #[test]
645    fn test_avro_3939_compare_union_schemata() -> TestResult {
646        let schema_one = Schema::Union(UnionSchema::new(vec![Schema::Boolean, Schema::Int])?);
647        assert!(!SPECIFICATION_EQ.compare(&schema_one, &Schema::Boolean));
648        assert!(!STRUCT_FIELD_EQ.compare(&schema_one, &Schema::Boolean));
649
650        let schema_two = Schema::Union(UnionSchema::new(vec![Schema::Boolean, Schema::Int])?);
651
652        let specification_eq_res = SPECIFICATION_EQ.compare(&schema_one, &schema_two);
653        let struct_field_eq_res = STRUCT_FIELD_EQ.compare(&schema_one, &schema_two);
654        assert!(
655            specification_eq_res,
656            "SpecificationEq: Equality of two Schema::Union failed!"
657        );
658        assert!(
659            struct_field_eq_res,
660            "StructFieldEq: Equality of two Schema::Union failed!"
661        );
662        assert_eq!(specification_eq_res, struct_field_eq_res);
663        Ok(())
664    }
665
666    #[test]
667    fn test_uuid_compare_uuid() -> TestResult {
668        let string = Schema::Uuid(UuidSchema::String);
669        let bytes = Schema::Uuid(UuidSchema::Bytes);
670        let mut fixed_schema = FixedSchema {
671            name: Name {
672                name: "some_name".to_string(),
673                namespace: None,
674            },
675            aliases: None,
676            doc: None,
677            size: 16,
678            default: None,
679            attributes: Default::default(),
680        };
681        let fixed = Schema::Uuid(UuidSchema::Fixed(fixed_schema.clone()));
682        fixed_schema
683            .attributes
684            .insert("Something".to_string(), Value::Null);
685        let fixed_different = Schema::Uuid(UuidSchema::Fixed(fixed_schema));
686
687        assert!(SPECIFICATION_EQ.compare(&string, &string));
688        assert!(STRUCT_FIELD_EQ.compare(&string, &string));
689        assert!(SPECIFICATION_EQ.compare(&bytes, &bytes));
690        assert!(STRUCT_FIELD_EQ.compare(&bytes, &bytes));
691        assert!(SPECIFICATION_EQ.compare(&fixed, &fixed));
692        assert!(STRUCT_FIELD_EQ.compare(&fixed, &fixed));
693
694        assert!(!SPECIFICATION_EQ.compare(&string, &bytes));
695        assert!(!STRUCT_FIELD_EQ.compare(&string, &bytes));
696        assert!(!SPECIFICATION_EQ.compare(&bytes, &string));
697        assert!(!STRUCT_FIELD_EQ.compare(&bytes, &string));
698        assert!(!SPECIFICATION_EQ.compare(&string, &fixed));
699        assert!(!STRUCT_FIELD_EQ.compare(&string, &fixed));
700        assert!(!SPECIFICATION_EQ.compare(&fixed, &string));
701        assert!(!STRUCT_FIELD_EQ.compare(&fixed, &string));
702        assert!(!SPECIFICATION_EQ.compare(&bytes, &fixed));
703        assert!(!STRUCT_FIELD_EQ.compare(&bytes, &fixed));
704        assert!(!SPECIFICATION_EQ.compare(&fixed, &bytes));
705        assert!(!STRUCT_FIELD_EQ.compare(&fixed, &bytes));
706
707        assert!(SPECIFICATION_EQ.compare(&fixed, &fixed_different));
708        assert!(STRUCT_FIELD_EQ.compare(&fixed, &fixed_different));
709        assert!(SPECIFICATION_EQ.compare(&fixed_different, &fixed));
710        assert!(STRUCT_FIELD_EQ.compare(&fixed_different, &fixed));
711
712        let strict = StructFieldEq {
713            include_attributes: true,
714        };
715
716        assert!(!strict.compare(&fixed, &fixed_different));
717        assert!(!strict.compare(&fixed_different, &fixed));
718
719        Ok(())
720    }
721}