surreal_simple_querybuilder/
node_builder.rs

1use std::fmt::Display;
2
3pub trait ToNodeBuilder<T: Display = Self>: Display {
4  fn quoted(&self) -> String {
5    format!("\"{self}\"")
6  }
7
8  /// Draws the start of a relation `->node`
9  ///
10  /// # Example
11  /// ```
12  /// use surreal_simple_querybuilder::prelude::*;
13  ///
14  /// let s = "user".with("project");
15  ///
16  /// assert_eq!("user->project", s);
17  /// ```
18  fn with(&self, relation_or_node: &str) -> String {
19    // write the arrow only if the first character is not a special character.
20    // there are cases where the `node` string that was passed starts with
21    // an arrow or a dot, in which case we do not want to push a new arrow
22    // ourselves.
23    if !relation_or_node.starts_with("->") && !relation_or_node.starts_with(".") {
24      format!("{self}->{relation_or_node}")
25    } else {
26      format!("{self}{relation_or_node}")
27    }
28  }
29
30  /// Draws the end of a relation `<-node`
31  ///
32  /// # Example
33  /// ```
34  /// use surreal_simple_querybuilder::prelude::*;
35  ///
36  /// let s = "user".from("project");
37  ///
38  /// assert_eq!("user<-project", s);
39  /// ```
40  fn from(&self, node: &str) -> String {
41    format!("{self}<-{node}")
42  }
43
44  /// Take the current string and add in front of it the given label name as to
45  /// make a string of the following format `LabelName:CurrentString`
46  ///
47  /// # Example
48  /// ```
49  /// use surreal_simple_querybuilder::prelude::*;
50  ///
51  /// let label = "John".as_named_label("Account");
52  ///
53  /// assert_eq!(label, "Account:John");
54  /// ```
55  fn as_named_label(&self, label_name: &str) -> String {
56    format!("{label_name}:{self}")
57  }
58
59  fn as_param(&self) -> String {
60    self
61      .to_string()
62      .replace(".", "_")
63      .replace("->", "_")
64      .replace("<-", "_")
65  }
66
67  /// # Example
68  /// ```
69  /// use surreal_simple_querybuilder::prelude::*;
70  ///
71  /// let s = "user".equals("John");
72  ///
73  /// // Note that it doesn't add quotes around strings
74  /// assert_eq!("user = John", s);
75  /// ```
76  fn equals(&self, value: &str) -> String {
77    format!("{self} = {value}")
78  }
79
80  /// # Example
81  /// ```
82  /// use surreal_simple_querybuilder::prelude::*;
83  ///
84  /// let s = "age".compares(">=", "45");
85  ///
86  /// assert_eq!("age >= 45", s);
87  /// ```
88  fn compares(&self, operator: &str, value: &str) -> String {
89    format!("{self} {operator} {value}")
90  }
91
92  /// Take the current string and add the given operator plus ` $current_string` after it
93  ///
94  /// # Example
95  /// ```
96  /// use surreal_simple_querybuilder::prelude::*;
97  ///
98  /// let s = "age".compares_parameterized(">=");
99  ///
100  /// assert_eq!("age >= $age", s);
101  /// ```
102  fn compares_parameterized(&self, operator: &str) -> String {
103    format!("{self} {operator} ${}", self.as_param())
104  }
105
106  /// Take the current string and add `= $current_string` after it
107  ///
108  /// # Example
109  /// ```
110  /// use surreal_simple_querybuilder::prelude::*;
111  ///
112  /// let s = "account".equals_parameterized();
113  ///
114  /// assert_eq!("account = $account", s);
115  /// ```
116  fn equals_parameterized(&self) -> String {
117    format!("{self} = ${}", self.as_param())
118  }
119
120  /// Take the current string and add `+= $current_string` after it
121  ///
122  /// # Example
123  /// ```
124  /// use surreal_simple_querybuilder::prelude::*;
125  ///
126  /// let s = "account".plus_equal_parameterized();
127  ///
128  /// assert_eq!("account += $account", s);
129  /// ```
130  fn plus_equal_parameterized(&self) -> String {
131    format!("{self} += ${}", self.as_param())
132  }
133
134  /// Take the current string and add `> $current_string` after it
135  ///
136  /// # Example
137  /// ```
138  /// use surreal_simple_querybuilder::prelude::*;
139  ///
140  /// let s = "age".greater_parameterized();
141  ///
142  /// assert_eq!("age > $age", s);
143  /// ```
144  fn greater_parameterized(&self) -> String {
145    format!("{self} > ${}", self.as_param())
146  }
147
148  /// Take the current string and add `< $current_string` after it
149  ///
150  /// # Example
151  /// ```
152  /// use surreal_simple_querybuilder::prelude::*;
153  ///
154  /// let s = "age".lower_parameterized();
155  ///
156  /// assert_eq!("age < $age", s);
157  /// ```
158  fn lower_parameterized(&self) -> String {
159    format!("{self} < ${}", self.as_param())
160  }
161
162  /// Take the current string and add `> value` after it
163  ///
164  /// # Example
165  /// ```
166  /// use surreal_simple_querybuilder::prelude::*;
167  ///
168  /// let s = "account".greater_than("5");
169  ///
170  /// assert_eq!("account > 5", s);
171  /// ```
172  fn greater_than(&self, value: &str) -> String {
173    format!("{self} > {value}")
174  }
175
176  /// Take the current string and add `+= value` after it
177  ///
178  /// # Example
179  /// ```
180  /// use surreal_simple_querybuilder::prelude::*;
181  ///
182  /// let s = "friends".plus_equal("account:john");
183  ///
184  /// assert_eq!("friends += account:john", s);
185  /// ```
186  fn plus_equal(&self, value: &str) -> String {
187    format!("{self} += {value}")
188  }
189
190  /// # Example
191  /// ```
192  /// use surreal_simple_querybuilder::prelude::*;
193  ///
194  /// let s = "account".contains_one("'c'");
195  ///
196  /// assert_eq!("account CONTAINS 'c'", s);
197  /// ```
198  fn contains_one(&self, value: &str) -> String {
199    format!("{self} CONTAINS {value}")
200  }
201
202  /// # Example
203  /// ```
204  /// use surreal_simple_querybuilder::prelude::*;
205  ///
206  /// let s = "account".contains_not("'z'");
207  ///
208  /// assert_eq!("account CONTAINSNOT 'z'", s);
209  /// ```
210  fn contains_not(&self, value: &str) -> String {
211    format!("{self} CONTAINSNOT {value}")
212  }
213
214  /// # Example
215  /// ```
216  /// use surreal_simple_querybuilder::prelude::*;
217  ///
218  /// let s = "account".contains_all("['a', 'c', 'u']");
219  ///
220  /// assert_eq!("account CONTAINSALL ['a', 'c', 'u']", s);
221  /// ```
222  fn contains_all(&self, values: &str) -> String {
223    format!("{self} CONTAINSALL {values}")
224  }
225
226  /// # Example
227  /// ```
228  /// use surreal_simple_querybuilder::prelude::*;
229  ///
230  /// let s = "account".contains_any("['a', 'c', 'u']");
231  ///
232  /// assert_eq!("account CONTAINSANY ['a', 'c', 'u']", s);
233  /// ```
234  fn contains_any(&self, values: &str) -> String {
235    format!("{self} CONTAINSANY {values}")
236  }
237
238  /// # Example
239  /// ```
240  /// use surreal_simple_querybuilder::prelude::*;
241  ///
242  /// let s = "account".contains_none("['z', 'd', 'f']");
243  ///
244  /// assert_eq!("account CONTAINSNONE ['z', 'd', 'f']", s);
245  /// ```
246  fn contains_none(&self, values: &str) -> String {
247    format!("{self} CONTAINSNONE {values}")
248  }
249
250  /// Take the current string and add `as alias` after it
251  ///
252  /// # Example
253  /// ```
254  /// use surreal_simple_querybuilder::prelude::*;
255  ///
256  /// let s = "account->manage->project".as_alias("account_projects");
257  ///
258  /// assert_eq!("account->manage->project AS account_projects", s);
259  /// ```
260  fn as_alias(&self, alias: &str) -> String {
261    format!("{self} AS {alias}")
262  }
263
264  /// Take the current string, extract the last segment if it is a nested property,
265  /// then add parenthesis around it and add the supplied condition in them.
266  ///
267  /// # Example
268  /// ```
269  /// use surreal_simple_querybuilder::prelude::*;
270  ///
271  /// let path = "account->manage->project";
272  /// let s = path.filter("name = 'a_cool_project'");
273  ///
274  /// assert_eq!("account->manage->(project WHERE name = 'a_cool_project')", s);
275  /// ```
276  ///
277  fn filter(&self, condition: &str) -> String {
278    // This is a default implementation, but since we need the original string
279    // to iterate over the chars the function does two string allocations.
280    let original = self.to_string();
281    let original_size = original.len();
282
283    // this yields the size of the last segment, until a non alphanumeric character
284    // is found.
285    let last_segment_size = original
286      .chars()
287      .rev()
288      .take_while(|c| c.is_alphanumeric())
289      .count();
290
291    let left = &original[..original_size - last_segment_size];
292    let right = &original[original_size - last_segment_size..];
293
294    format!("{left}({right} WHERE {condition})")
295  }
296
297  /// write a comma at the end of the string and append `right` after it.
298  ///
299  /// # Example
300  /// ```
301  /// use surreal_simple_querybuilder::prelude::*;
302  ///
303  /// let select = "*".comma("<-manage<-User as authors");
304  /// let query = format!("select {select} from Files");
305  ///
306  /// assert_eq!("select *, <-manage<-User as authors from Files", query);
307  /// ```
308  fn comma(&self, right: &str) -> String {
309    format!("{self}, {right}")
310  }
311
312  /// write a `count()` around the current string so that it sits between the
313  /// parenthesis.
314  ///
315  /// # Example
316  /// ```
317  /// use surreal_simple_querybuilder::prelude::*;
318  ///
319  /// let count = "id".count();
320  /// let query = format!("select {count} from Files");
321  ///
322  /// assert_eq!("select count(id) from Files", query);
323  /// ```
324  fn count(&self) -> String {
325    format!("count({self})")
326  }
327
328  /// Add the supplied `id` right after the current string in order to get the a
329  /// new string in the following format `current:id`
330  /// # Example
331  /// ```
332  /// use surreal_simple_querybuilder::prelude::*;
333  ///
334  /// let query = "Account".with_id("John");
335  ///
336  /// assert_eq!(query, "Account:John");
337  /// ```
338  fn with_id(&self, id: &str) -> String {
339    format!("{self}:{id}")
340  }
341
342  /// Add the supplied composite `id` right after the current string in order to
343  /// get the a new string in the following format `current:⟨id⟩`. The `⟨` and `⟩`
344  /// are automatically added around the id, if you wish to set a regular id then
345  /// refer to [with_id()](ToNodeBuilder::with_id)
346  ///
347  /// # Example
348  /// ```
349  /// use surreal_simple_querybuilder::prelude::*;
350  ///
351  /// let query = "Account".with_composite_id("John/Doe");
352  ///
353  /// assert_eq!(query, "Account:⟨John/Doe⟩");
354  /// ```
355  fn with_composite_id(&self, id: &str) -> String {
356    format!("{self}:⟨{id}⟩")
357  }
358}
359
360impl<'a> ToNodeBuilder for &'a str {
361  fn filter(&self, condition: &str) -> String {
362    // unlike the default implementation of this trait function, the &str impl
363    // does only one allocation.
364    let original_size = self.len();
365
366    // this yields the size of the last segment, until a non alphanumeric character
367    // is found.
368    let last_segment_size = self
369      .chars()
370      .rev()
371      .take_while(|c| c.is_alphanumeric())
372      .count();
373
374    let left = &self[..original_size - last_segment_size];
375    let right = &self[original_size - last_segment_size..];
376
377    format!("{left}({right} WHERE {condition})")
378  }
379}
380
381pub trait NodeBuilder<T: Display = Self>: Display {
382  /// Draws the start of a relation `->node`
383  ///
384  /// # Example
385  /// ```
386  /// use surreal_simple_querybuilder::prelude::*;
387  ///
388  /// let s = "user".with("project");
389  ///
390  /// assert_eq!("user->project", s);
391  /// ```
392  fn with(&mut self, relation_or_node: &str) -> &mut String;
393
394  /// Allows you to pass a lambda that should mutate the current string when the
395  /// passed `condition` is `true`. If `condition` is `false` then the `action`
396  /// lambda is ignored and the string stays intact.
397  ///
398  /// # Example
399  /// ```
400  /// use surreal_simple_querybuilder::prelude::*;
401  ///
402  /// // demonstrate how the given closure is ignored if the condition is `false`
403  /// let mut label = "John".as_named_label("User");
404  /// let intact = &mut label
405  ///   .if_then(false, |s| s.with("LOVES").with("User"))
406  ///   .with("FRIEND")
407  ///   .with("User");
408  ///
409  /// assert_eq!("User:John->FRIEND->User", *intact);
410  ///
411  /// // demonstrate how the given closure is executed if the condition is `true`
412  /// let mut label = "John".as_named_label("User");
413  /// let modified = &mut label
414  ///   .if_then(true, |s| s.with("LOVES").with("User"))
415  ///   .with("FRIEND")
416  ///   .with("User");
417  ///
418  /// assert_eq!("User:John->LOVES->User->FRIEND->User", *modified);
419  /// ```
420  fn if_then(&mut self, condition: bool, action: fn(&mut Self) -> &mut Self) -> &mut String;
421
422  /// Take the current string add add `> value` after it
423  ///
424  /// # Example
425  /// ```
426  /// use surreal_simple_querybuilder::prelude::*;
427  ///
428  /// let s = "account".greater_than("5");
429  ///
430  /// assert_eq!("account > 5", s);
431  /// ```
432  fn greater_than(&mut self, value: &str) -> &mut String;
433
434  /// Take the current string and add `+= value` after it
435  ///
436  /// # Example
437  /// ```
438  /// use surreal_simple_querybuilder::prelude::*;
439  ///
440  /// let s = "friends".plus_equal("account:john");
441  ///
442  /// assert_eq!("friends += account:john", s);
443  /// ```
444  fn plus_equal(&mut self, value: &str) -> &mut String;
445}
446
447impl NodeBuilder for String {
448  fn with(&mut self, node: &str) -> &mut String {
449    // push the arrow only if the first character is not a special character.
450    // there are cases where the `node` string that was passed starts with
451    // an arrow or a dot, in which case we do not want to push a new arrow
452    // ourselves.
453    if !node.starts_with("->") && !node.starts_with(".") {
454      self.push_str("->");
455    }
456
457    self.push_str(node);
458
459    self
460  }
461
462  fn if_then(&mut self, condition: bool, action: fn(&mut Self) -> &mut Self) -> &mut String {
463    match condition {
464      true => action(self),
465      false => self,
466    }
467  }
468
469  fn greater_than(&mut self, value: &str) -> &mut String {
470    self.push_str(" > ");
471    self.push_str(value);
472
473    self
474  }
475
476  fn plus_equal(&mut self, value: &str) -> &mut String {
477    self.push_str(" += ");
478    self.push_str(value);
479
480    self
481  }
482}
483
484impl ToNodeBuilder for String {}