vantage_mongodb/
operation.rs1use bson::{Bson, doc};
28use vantage_expressions::Expressive;
29
30use crate::condition::MongoCondition;
31use crate::types::{AnyMongoType, MongoType};
32
33fn field_name<T>(expr: &(impl Expressive<T> + ?Sized)) -> String {
38 expr.expr().template.clone()
39}
40
41fn to_bson_val(value: impl Into<AnyMongoType>) -> Bson {
43 let any: AnyMongoType = value.into();
44 any.to_bson()
45}
46
47fn negate(cond: MongoCondition) -> MongoCondition {
49 match cond {
50 MongoCondition::Doc(doc) => {
51 let mut negated = bson::Document::new();
52 for (key, val) in doc {
53 match val {
54 Bson::Document(inner) => {
56 negated.insert(key, doc! { "$not": inner });
57 }
58 other => {
60 negated.insert(key, doc! { "$not": { "$eq": other } });
61 }
62 }
63 }
64 MongoCondition::Doc(negated)
65 }
66 MongoCondition::And(conditions) => {
67 MongoCondition::And(conditions.into_iter().map(negate).collect())
68 }
69 other => other,
71 }
72}
73
74pub trait MongoOperation<T>: Expressive<T> {
80 fn eq(&self, value: impl Into<AnyMongoType>) -> MongoCondition
84 where
85 Self: Sized,
86 {
87 MongoCondition::Doc(doc! { field_name(self): { "$eq": to_bson_val(value) } })
88 }
89
90 fn ne(&self, value: impl Into<AnyMongoType>) -> MongoCondition
92 where
93 Self: Sized,
94 {
95 MongoCondition::Doc(doc! { field_name(self): { "$ne": to_bson_val(value) } })
96 }
97
98 fn gt(&self, value: impl Into<AnyMongoType>) -> MongoCondition
100 where
101 Self: Sized,
102 {
103 MongoCondition::Doc(doc! { field_name(self): { "$gt": to_bson_val(value) } })
104 }
105
106 fn gte(&self, value: impl Into<AnyMongoType>) -> MongoCondition
108 where
109 Self: Sized,
110 {
111 MongoCondition::Doc(doc! { field_name(self): { "$gte": to_bson_val(value) } })
112 }
113
114 fn lt(&self, value: impl Into<AnyMongoType>) -> MongoCondition
116 where
117 Self: Sized,
118 {
119 MongoCondition::Doc(doc! { field_name(self): { "$lt": to_bson_val(value) } })
120 }
121
122 fn lte(&self, value: impl Into<AnyMongoType>) -> MongoCondition
124 where
125 Self: Sized,
126 {
127 MongoCondition::Doc(doc! { field_name(self): { "$lte": to_bson_val(value) } })
128 }
129
130 fn in_<I, V>(&self, values: I) -> MongoCondition
132 where
133 Self: Sized,
134 I: IntoIterator<Item = V>,
135 V: Into<AnyMongoType>,
136 {
137 let arr: Vec<Bson> = values.into_iter().map(to_bson_val).collect();
138 MongoCondition::Doc(doc! { field_name(self): { "$in": arr } })
139 }
140
141 fn is_null(&self) -> MongoCondition
144 where
145 Self: Sized,
146 {
147 MongoCondition::Doc(doc! { field_name(self): Bson::Null })
148 }
149
150 fn is_not_null(&self) -> MongoCondition
152 where
153 Self: Sized,
154 {
155 MongoCondition::Doc(doc! { field_name(self): { "$ne": Bson::Null } })
156 }
157}
158
159impl<T, S: Expressive<T>> MongoOperation<T> for S {}
161
162impl Expressive<AnyMongoType> for MongoCondition {
169 fn expr(&self) -> vantage_expressions::Expression<AnyMongoType> {
170 vantage_expressions::Expression::new(format!("{:?}", self), vec![])
173 }
174}
175
176impl MongoCondition {
177 pub fn eq_bool(&self, value: bool) -> MongoCondition {
180 if value {
181 self.clone()
182 } else {
183 negate(self.clone())
184 }
185 }
186
187 pub fn ne_bool(&self, value: bool) -> MongoCondition {
189 if value {
190 negate(self.clone())
191 } else {
192 self.clone()
193 }
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200 use vantage_table::column::core::Column;
201
202 #[test]
203 fn test_column_eq() {
204 let name = Column::<String>::new("name");
205 let cond = name.eq("Alice");
206 match cond {
207 MongoCondition::Doc(doc) => {
208 assert_eq!(doc, doc! { "name": { "$eq": "Alice" } });
209 }
210 _ => panic!("expected Doc"),
211 }
212 }
213
214 #[test]
215 fn test_column_gt() {
216 let price = Column::<i64>::new("price");
217 let cond = price.gt(100i64);
218 match cond {
219 MongoCondition::Doc(doc) => {
220 assert_eq!(doc, doc! { "price": { "$gt": 100i64 } });
221 }
222 _ => panic!("expected Doc"),
223 }
224 }
225
226 #[test]
227 fn test_column_in() {
228 let status = Column::<String>::new("status");
229 let cond = status.in_(vec!["active", "pending"]);
230 match cond {
231 MongoCondition::Doc(doc) => {
232 assert_eq!(doc, doc! { "status": { "$in": ["active", "pending"] } });
233 }
234 _ => panic!("expected Doc"),
235 }
236 }
237
238 #[test]
239 fn test_chaining_gt_eq_false() {
240 let price = Column::<i64>::new("price");
241 let cond = price.gt(10i64).eq_bool(false);
243 match cond {
244 MongoCondition::Doc(doc) => {
245 assert_eq!(doc, doc! { "price": { "$not": { "$gt": 10i64 } } });
246 }
247 _ => panic!("expected Doc"),
248 }
249 }
250
251 #[test]
252 fn test_chaining_gt_eq_true() {
253 let price = Column::<i64>::new("price");
254 let cond = price.gt(10i64).eq_bool(true);
256 match cond {
257 MongoCondition::Doc(doc) => {
258 assert_eq!(doc, doc! { "price": { "$gt": 10i64 } });
259 }
260 _ => panic!("expected Doc"),
261 }
262 }
263
264 #[test]
265 fn test_negate_simple_value() {
266 let cond = MongoCondition::Doc(doc! { "active": true });
267 let negated = negate(cond);
268 match negated {
269 MongoCondition::Doc(doc) => {
270 assert_eq!(doc, doc! { "active": { "$not": { "$eq": true } } });
271 }
272 _ => panic!("expected Doc"),
273 }
274 }
275
276 #[test]
277 fn test_negate_operator() {
278 let cond = MongoCondition::Doc(doc! { "price": { "$gt": 100 } });
279 let negated = negate(cond);
280 match negated {
281 MongoCondition::Doc(doc) => {
282 assert_eq!(doc, doc! { "price": { "$not": { "$gt": 100 } } });
283 }
284 _ => panic!("expected Doc"),
285 }
286 }
287
288 #[test]
289 fn test_condition_is_correct_type() {
290 let price = Column::<i64>::new("price");
291 let cond: MongoCondition = price.gt(100i64);
292 let _: MongoCondition = cond;
293 }
294
295 #[test]
296 fn test_is_null() {
297 let deleted_at = Column::<String>::new("deleted_at");
298 let cond = deleted_at.is_null();
299 match cond {
300 MongoCondition::Doc(doc) => {
301 assert_eq!(doc, doc! { "deleted_at": Bson::Null });
302 }
303 _ => panic!("expected Doc"),
304 }
305 }
306
307 #[test]
308 fn test_is_not_null() {
309 let email = Column::<String>::new("email");
310 let cond = email.is_not_null();
311 match cond {
312 MongoCondition::Doc(doc) => {
313 assert_eq!(doc, doc! { "email": { "$ne": Bson::Null } });
314 }
315 _ => panic!("expected Doc"),
316 }
317 }
318}