Skip to main content

darwen/ops/
union.rs

1use crate::{error::Error, prelude::Relation};
2
3impl Relation {
4    /// Returns the union of two compatible relations (`⋃`).
5    ///
6    /// `a`
7    ///
8    /// | `value` |
9    /// | --- |
10    /// | `1` |
11    /// | `2` |
12    ///
13    /// `b`
14    ///
15    /// | `value` |
16    /// | --- |
17    /// | `2` |
18    /// | `3` |
19    ///
20    /// Output
21    ///
22    /// | `value` |
23    /// | --- |
24    /// | `1` |
25    /// | `2` |
26    /// | `3` |
27    ///
28    /// # Errors
29    ///
30    /// Returns [`Error::HeadingMismatch`] if the relations do not have the same
31    /// heading.
32    ///
33    /// # Example
34    ///
35    /// ```rust
36    /// use darwen::prelude::{AttributeName, Heading, Relation, Scalar, ScalarType, Tuple};
37    ///
38    /// let a = Relation::new_from_iter(
39    ///     Heading::try_from(vec![(AttributeName::from("value"), ScalarType::Integer)])?,
40    ///     vec![
41    ///         Tuple::try_from(vec![(AttributeName::from("value"), Scalar::Integer(1))])?,
42    ///         Tuple::try_from(vec![(AttributeName::from("value"), Scalar::Integer(2))])?,
43    ///     ],
44    /// )?;
45    /// let b = Relation::new_from_iter(
46    ///     Heading::try_from(vec![(AttributeName::from("value"), ScalarType::Integer)])?,
47    ///     vec![
48    ///         Tuple::try_from(vec![(AttributeName::from("value"), Scalar::Integer(2))])?,
49    ///         Tuple::try_from(vec![(AttributeName::from("value"), Scalar::Integer(3))])?,
50    ///     ],
51    /// )?;
52    ///
53    /// let union = a.union(&b)?;
54    ///
55    /// assert_eq!(
56    ///     union,
57    ///     Relation::new_from_iter(
58    ///         Heading::try_from(vec![(AttributeName::from("value"), ScalarType::Integer)])?,
59    ///         vec![
60    ///             Tuple::try_from(vec![(AttributeName::from("value"), Scalar::Integer(1))])?,
61    ///             Tuple::try_from(vec![(AttributeName::from("value"), Scalar::Integer(2))])?,
62    ///             Tuple::try_from(vec![(AttributeName::from("value"), Scalar::Integer(3))])?,
63    ///         ],
64    ///     )?
65    /// );
66    /// # Ok::<(), darwen::prelude::Error>(())
67    /// ```
68    pub fn union(&self, other: &Relation) -> Result<Relation, Error> {
69        if self.heading != other.heading {
70            return Err(Error::HeadingMismatch);
71        }
72        let mut relation = Relation::new(self.heading.clone());
73        for tuple in self.body.iter().chain(other.body.iter()) {
74            relation.insert(tuple.clone())?;
75        }
76        Ok(relation)
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use crate::{
83        prelude::{Heading, Scalar, ScalarType, Tuple},
84        types::AttributeName,
85    };
86
87    use super::*;
88
89    #[test]
90    fn test_union() {
91        let relation = Relation::new_from_iter(
92            Heading::try_from(vec![(AttributeName::from("foo"), ScalarType::Integer)]).unwrap(),
93            vec![
94                Tuple::try_from(vec![(AttributeName::from("foo"), Scalar::Integer(1))]).unwrap(),
95                Tuple::try_from(vec![(AttributeName::from("foo"), Scalar::Integer(2))]).unwrap(),
96                Tuple::try_from(vec![(AttributeName::from("foo"), Scalar::Integer(3))]).unwrap(),
97            ],
98        )
99        .unwrap();
100        let other = Relation::new_from_iter(
101            Heading::try_from(vec![(AttributeName::from("foo"), ScalarType::Integer)]).unwrap(),
102            vec![
103                Tuple::try_from(vec![(AttributeName::from("foo"), Scalar::Integer(2))]).unwrap(),
104                Tuple::try_from(vec![(AttributeName::from("foo"), Scalar::Integer(3))]).unwrap(),
105                Tuple::try_from(vec![(AttributeName::from("foo"), Scalar::Integer(4))]).unwrap(),
106            ],
107        )
108        .unwrap();
109
110        assert_eq!(
111            relation.union(&other).unwrap(),
112            Relation::new_from_iter(
113                Heading::try_from(vec![(AttributeName::from("foo"), ScalarType::Integer)]).unwrap(),
114                vec![
115                    Tuple::try_from(vec![(AttributeName::from("foo"), Scalar::Integer(1))])
116                        .unwrap(),
117                    Tuple::try_from(vec![(AttributeName::from("foo"), Scalar::Integer(2))])
118                        .unwrap(),
119                    Tuple::try_from(vec![(AttributeName::from("foo"), Scalar::Integer(3))])
120                        .unwrap(),
121                    Tuple::try_from(vec![(AttributeName::from("foo"), Scalar::Integer(4))])
122                        .unwrap(),
123                ],
124            )
125            .unwrap()
126        );
127    }
128
129    #[test]
130    fn test_union_rejects_incompatible_headings_even_when_other_is_empty() -> Result<(), Error> {
131        let lhs = Relation::new_from_iter(
132            Heading::try_from(vec![(AttributeName::from("foo"), ScalarType::Integer)]).unwrap(),
133            vec![Tuple::try_from(vec![(AttributeName::from("foo"), Scalar::Integer(1))]).unwrap()],
134        )?;
135        let rhs = Relation::new_from_iter(
136            Heading::try_from(vec![(AttributeName::from("bar"), ScalarType::Integer)]).unwrap(),
137            Vec::new(),
138        )?;
139
140        assert_eq!(lhs.union(&rhs), Err(Error::HeadingMismatch));
141        Ok(())
142    }
143
144    #[test]
145    fn test_union_with_empty_relation_is_identity() -> Result<(), Error> {
146        let lhs = Relation::new_from_iter(
147            Heading::try_from(vec![(AttributeName::from("foo"), ScalarType::Integer)]).unwrap(),
148            vec![Tuple::try_from(vec![(AttributeName::from("foo"), Scalar::Integer(1))]).unwrap()],
149        )?;
150        let rhs = Relation::new_from_iter(
151            Heading::try_from(vec![(AttributeName::from("foo"), ScalarType::Integer)]).unwrap(),
152            Vec::new(),
153        )?;
154
155        assert_eq!(lhs.union(&rhs)?, lhs);
156        Ok(())
157    }
158}