exocore_store/
ordering.rs

1use std::cmp::Ordering;
2
3use exocore_protos::generated::exocore_store::{ordering_value, OrderingValue, Paging};
4
5use crate::mutation::OperationId;
6
7/// Wraps a trait or entities search result's ordering value so that it can be
8/// easily reversed when required or ignored if it's outside of the requested
9/// paging.
10#[derive(Clone, Debug, PartialEq, Default)]
11pub struct OrderingValueWrapper {
12    pub value: OrderingValue,
13    pub reverse: bool,
14
15    // means that this result should not be returned (probably because it's not withing paging)
16    // and should match less than any other non-ignored result
17    pub ignore: bool,
18}
19
20impl PartialOrd for OrderingValueWrapper {
21    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
22        Some(self.cmp(other))
23    }
24}
25
26impl Ord for OrderingValueWrapper {
27    fn cmp(&self, other: &Self) -> Ordering {
28        // an ignored result should be always be less, unless they are both
29        if self.ignore && other.ignore {
30            return std::cmp::Ordering::Equal;
31        } else if self.ignore {
32            return std::cmp::Ordering::Less;
33        } else if other.ignore {
34            return std::cmp::Ordering::Greater;
35        }
36
37        let cmp = self
38            .value
39            .partial_cmp(&other.value)
40            .expect("expected value to be comparable");
41
42        // reverse if needed
43        if self.reverse {
44            cmp.reverse()
45        } else {
46            cmp
47        }
48    }
49}
50
51impl Eq for OrderingValueWrapper {}
52
53impl OrderingValueWrapper {
54    pub fn is_within_bound(&self, lower: &OrderingValue, higher: &OrderingValue) -> bool {
55        self.value.is_after(lower) && self.value.is_before(higher)
56    }
57
58    pub fn is_score(&self) -> bool {
59        matches!(
60            self.value.value.as_ref(),
61            Some(ordering_value::Value::Float(_score))
62        )
63    }
64
65    pub fn boost_score(&mut self, multiplier: f32) -> (f32, f32) {
66        if let Some(ordering_value::Value::Float(score)) = self.value.value.as_mut() {
67            let before = *score;
68            if !self.reverse {
69                *score *= multiplier;
70            } else {
71                *score /= multiplier;
72            }
73            (before, *score)
74        } else {
75            (0.0, 0.0)
76        }
77    }
78}
79
80/// Extensions of ordering value to add comparison methods.
81pub trait OrderingValueExt {
82    fn partial_cmp(&self, other: &Self) -> Option<Ordering>;
83    fn is_after(&self, other: &Self) -> bool;
84    fn is_before(&self, other: &Self) -> bool;
85    fn is_within_page_bound(&self, page: &Paging) -> bool;
86}
87
88impl OrderingValueExt for OrderingValue {
89    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
90        use std::cmp::Ordering as O;
91
92        use ordering_value::Value as V;
93
94        if self.value == other.value {
95            return self.operation_id.partial_cmp(&other.operation_id);
96        }
97
98        match (self.value.as_ref(), other.value.as_ref()) {
99            (Some(V::Min(_)), _) => Some(O::Less),
100            (_, Some(V::Min(_))) => Some(O::Greater),
101            (Some(V::Max(_)), _) => Some(O::Greater),
102            (_, Some(V::Max(_))) => Some(O::Less),
103            (Some(V::Float(va)), Some(V::Float(vb))) => va.partial_cmp(vb),
104            (Some(V::Uint64(va)), Some(V::Uint64(vb))) => va.partial_cmp(vb),
105            (Some(V::Date(va)), Some(V::Date(vb))) => {
106                if va.seconds != vb.seconds {
107                    va.seconds.partial_cmp(&vb.seconds)
108                } else {
109                    va.nanos.partial_cmp(&vb.nanos)
110                }
111            }
112            _other => None,
113        }
114    }
115
116    fn is_after(&self, other: &Self) -> bool {
117        matches!(self.partial_cmp(other), Some(std::cmp::Ordering::Greater))
118    }
119
120    fn is_before(&self, other: &Self) -> bool {
121        matches!(self.partial_cmp(other), Some(std::cmp::Ordering::Less))
122    }
123
124    fn is_within_page_bound(&self, page: &Paging) -> bool {
125        if let Some(before) = page.before_ordering_value.as_ref() {
126            if !self.is_before(before) {
127                return false;
128            }
129        }
130
131        if let Some(after) = page.after_ordering_value.as_ref() {
132            if !self.is_after(after) {
133                return false;
134            }
135        }
136
137        true
138    }
139}
140
141pub fn value_from_u64(value: u64, operation_id: OperationId) -> OrderingValue {
142    OrderingValue {
143        value: Some(ordering_value::Value::Uint64(value)),
144        operation_id,
145    }
146}
147
148pub fn value_from_f32(value: f32, operation_id: OperationId) -> OrderingValue {
149    OrderingValue {
150        value: Some(ordering_value::Value::Float(value)),
151        operation_id,
152    }
153}
154
155pub fn value_max() -> OrderingValue {
156    OrderingValue {
157        value: Some(ordering_value::Value::Max(true)),
158        operation_id: 0,
159    }
160}
161
162pub fn value_min() -> OrderingValue {
163    OrderingValue {
164        value: Some(ordering_value::Value::Min(true)),
165        operation_id: 0,
166    }
167}