• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geo-engine / geoengine / 3929938005

pending completion
3929938005

push

github

GitHub
Merge #713

84930 of 96741 relevant lines covered (87.79%)

79640.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.03
/operators/src/processing/circle_merging_quadtree/aggregates.rs
1
use std::fmt::Display;
2

3
use crate::error::Error;
4
use crate::util::Result;
5

6
use serde::{Deserialize, Serialize};
7

8
const MAX_STRINGS_IN_SAMPLE: usize = 3;
9

10
#[derive(Debug, Clone, PartialEq)]
3✔
11
pub enum AttributeAggregate {
12
    MeanNumber(MeanAggregator),
13
    StringSample(StringSampler),
14
    Null, // Representing a missing aggregate
15
}
16

17
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9✔
18
#[serde(rename_all = "camelCase")]
19
pub enum AttributeAggregateType {
20
    MeanNumber,
21
    StringSample,
22
    Null, // Representing a missing aggregate
23
}
24

25
pub trait AttributeAggregator {
26
    fn merge(&mut self, other: &Self) -> Result<()>;
27
}
28

29
#[derive(Debug, Clone, PartialEq)]
3✔
30
pub struct MeanAggregator {
31
    pub mean: f64,
32
    n: usize,
33
}
34

35
#[derive(Debug, Clone, PartialEq, Eq)]
×
36
pub struct StringSampler {
37
    pub strings: Vec<String>,
38
}
39

40
impl Default for AttributeAggregate {
41
    fn default() -> Self {
×
42
        Self::Null
×
43
    }
×
44
}
45

46
impl MeanAggregator {
47
    pub fn from_value(value: f64) -> Self {
42✔
48
        MeanAggregator { mean: value, n: 1 }
42✔
49
    }
42✔
50
}
51

52
impl Display for AttributeAggregateType {
53
    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
2✔
54
        match self {
2✔
55
            AttributeAggregateType::MeanNumber => fmt.write_str("MeanNumber"),
1✔
56
            AttributeAggregateType::StringSample => fmt.write_str("StringSample"),
1✔
57
            AttributeAggregateType::Null => fmt.write_str("Null"),
×
58
        }
59
    }
2✔
60
}
61

62
impl AttributeAggregate {
63
    /// Merge two aggregates. Sets it to `Null` if there is any error.
64
    pub fn merge(&mut self, other: &AttributeAggregate) {
20✔
65
        // TODO: nulls are ignored by now but we should make this user-defined in the future
20✔
66

20✔
67
        let mut self_and_other = (self, other);
20✔
68

69
        let was_error = match &mut self_and_other {
20✔
70
            (AttributeAggregate::MeanNumber(a), AttributeAggregate::MeanNumber(b)) => a.merge(b),
10✔
71

72
            (AttributeAggregate::StringSample(a), AttributeAggregate::StringSample(b)) => {
2✔
73
                a.merge(b)
2✔
74
            }
75
            // if there is null on the other side, just leave it as it is
76
            (_, AttributeAggregate::Null) => Ok(()),
6✔
77
            // if there is null on this side, just take the other side
78
            (this @ &mut AttributeAggregate::Null, other) => {
1✔
79
                **this = other.clone();
1✔
80
                Ok(())
1✔
81
            }
82
            // for now, if anything weird happens, just set it to null
83
            (this, o) => Err(Error::InvalidType {
1✔
84
                expected: this.variant_type().to_string(),
1✔
85
                found: o.variant_type().to_string(),
1✔
86
            }),
1✔
87
        }
88
        .is_err();
20✔
89

20✔
90
        if was_error {
20✔
91
            *self_and_other.0 = AttributeAggregate::Null;
1✔
92
        }
19✔
93
    }
20✔
94

95
    pub fn variant_type(&self) -> AttributeAggregateType {
2✔
96
        match self {
2✔
97
            AttributeAggregate::MeanNumber(_) => AttributeAggregateType::MeanNumber,
1✔
98
            AttributeAggregate::StringSample(_) => AttributeAggregateType::StringSample,
1✔
99
            AttributeAggregate::Null => AttributeAggregateType::Null,
×
100
        }
101
    }
2✔
102

103
    pub fn mean_number(&self) -> Option<&MeanAggregator> {
4✔
104
        match self {
4✔
105
            AttributeAggregate::MeanNumber(a) => Some(a),
4✔
106
            _ => None,
×
107
        }
108
    }
4✔
109

110
    pub fn string_sample(&self) -> Option<&StringSampler> {
2✔
111
        match self {
2✔
112
            AttributeAggregate::StringSample(a) => Some(a),
2✔
113
            _ => None,
×
114
        }
115
    }
2✔
116

117
    pub fn is_null(&self) -> bool {
1✔
118
        matches!(self, AttributeAggregate::Null)
1✔
119
    }
1✔
120
}
121

122
impl AttributeAggregator for MeanAggregator {
123
    fn merge(&mut self, other: &Self) -> Result<()> {
34✔
124
        self.mean = ((self.mean * self.n as f64) + (other.mean * other.n as f64))
34✔
125
            / (self.n + other.n) as f64;
34✔
126
        self.n += other.n;
34✔
127
        Ok(())
34✔
128
    }
34✔
129
}
130

131
impl StringSampler {
132
    pub fn from_value(value: String) -> Self {
10✔
133
        Self {
10✔
134
            strings: vec![value],
10✔
135
        }
10✔
136
    }
10✔
137
}
138

139
impl AttributeAggregator for StringSampler {
140
    fn merge(&mut self, other: &Self) -> Result<()> {
5✔
141
        // TODO: use sampling
5✔
142

5✔
143
        let discrepancy = MAX_STRINGS_IN_SAMPLE - self.strings.len();
5✔
144

145
        for new_string in other.strings.iter().take(discrepancy) {
5✔
146
            self.strings.push(new_string.clone());
4✔
147
        }
4✔
148

149
        Ok(())
5✔
150
    }
5✔
151
}
152

153
#[cfg(test)]
154
mod tests {
155
    use super::*;
156

157
    #[test]
1✔
158
    #[allow(clippy::float_cmp)]
159
    fn test_mean() {
1✔
160
        let mut mean = MeanAggregator::from_value(4.);
1✔
161
        mean.merge(&MeanAggregator::from_value(2.)).unwrap();
1✔
162

1✔
163
        assert_eq!(mean.mean, 3.);
1✔
164

165
        mean.merge(&MeanAggregator::from_value(3.)).unwrap();
1✔
166

1✔
167
        assert_eq!(mean.mean, 3.);
1✔
168

169
        mean.merge(&MeanAggregator::from_value(7.)).unwrap();
1✔
170

1✔
171
        assert_eq!(mean.mean, 4.);
1✔
172

173
        let mut mean_a = MeanAggregator::from_value(4.);
1✔
174
        let mut mean_b = MeanAggregator::from_value(8.);
1✔
175
        for _ in 0..10 {
11✔
176
            mean_a.merge(&MeanAggregator::from_value(4.)).unwrap();
10✔
177
            mean_b.merge(&MeanAggregator::from_value(8.)).unwrap();
10✔
178
        }
10✔
179
        mean_a.merge(&mean_b).unwrap();
1✔
180

1✔
181
        assert_eq!(mean_a.mean, 6.);
1✔
182
    }
1✔
183

184
    #[test]
1✔
185
    fn test_strings() {
1✔
186
        // TODO: this test will change when sampling is implemented
1✔
187

1✔
188
        let mut strings = StringSampler::from_value("foo".to_string());
1✔
189
        strings
1✔
190
            .merge(&StringSampler::from_value("bar".to_string()))
1✔
191
            .unwrap();
1✔
192
        strings
1✔
193
            .merge(&StringSampler::from_value("baz".to_string()))
1✔
194
            .unwrap();
1✔
195
        strings
1✔
196
            .merge(&StringSampler::from_value("bau".to_string()))
1✔
197
            .unwrap();
1✔
198

1✔
199
        assert_eq!(strings.strings, vec!["foo", "bar", "baz"]);
1✔
200
    }
1✔
201

202
    #[test]
1✔
203
    #[allow(clippy::float_cmp)]
204
    fn test_attribute_aggregate_mean() {
1✔
205
        let mut mean = AttributeAggregate::MeanNumber(MeanAggregator::from_value(1.));
1✔
206
        mean.merge(&AttributeAggregate::MeanNumber(MeanAggregator::from_value(
1✔
207
            3.,
1✔
208
        )));
1✔
209

1✔
210
        assert_eq!(mean.mean_number().unwrap().mean, 2.);
1✔
211

212
        mean.merge(&AttributeAggregate::Null);
1✔
213

1✔
214
        assert_eq!(mean.mean_number().unwrap().mean, 2.);
1✔
215
    }
1✔
216

217
    #[test]
1✔
218
    fn test_attribute_aggregate_strings() {
1✔
219
        let mut strings =
1✔
220
            AttributeAggregate::StringSample(StringSampler::from_value("foo".to_string()));
1✔
221
        strings.merge(&AttributeAggregate::StringSample(
1✔
222
            StringSampler::from_value("bar".to_string()),
1✔
223
        ));
1✔
224

1✔
225
        assert_eq!(
1✔
226
            &strings.string_sample().unwrap().strings,
1✔
227
            &["foo".to_string(), "bar".to_string()]
1✔
228
        );
1✔
229

230
        strings.merge(&AttributeAggregate::Null);
1✔
231

1✔
232
        assert_eq!(
1✔
233
            &strings.string_sample().unwrap().strings,
1✔
234
            &["foo".to_string(), "bar".to_string()]
1✔
235
        );
1✔
236
    }
1✔
237

238
    #[test]
1✔
239
    #[allow(clippy::float_cmp)]
240
    fn test_attribute_aggregate_nulls() {
1✔
241
        let mut mean = AttributeAggregate::Null;
1✔
242
        mean.merge(&AttributeAggregate::MeanNumber(MeanAggregator::from_value(
1✔
243
            42.,
1✔
244
        )));
1✔
245

1✔
246
        assert_eq!(mean.mean_number().unwrap().mean, 42.);
1✔
247

248
        mean.merge(&AttributeAggregate::StringSample(
1✔
249
            StringSampler::from_value("foo".to_string()),
1✔
250
        ));
1✔
251

1✔
252
        assert!(mean.is_null());
1✔
253
    }
1✔
254
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc