1use crate::{error::Error, prelude::Relation};
2
3impl Relation {
4 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}