toql_core/
query.rs

1//! The [Query] represents a Toql query but also comes with query builder methods.
2//!
3//! While it's perfectly possible to use the query builder directly
4//! it is recommended to use the `query!` macro. However sometimes
5//! mixed uses of the macro and the builder may make sense.
6//!
7//! ## Example
8//!
9//! ```ignore
10//! use toql::prelude::{Query, Field};
11//!
12//! let  q = Query::<FooBar>::new()
13//!        .and(Field::from("foo").hide().eq(5).asc(1))
14//!        .and(Field::from("bar").desc(2));
15//!    assert_eq!("+1.foo EQ 5,-2bar", q.to_string());
16//! ```
17//! The above code generated with the [query!](toql_query_macro/macro.query) macro
18//! ```ignore
19//! use toql::prelude::{Query, Field};
20//!
21//! let q :Query<FooBar>= query!(FooBar, "+1.foo EQ 5, -2bar");
22//! assert_eq!("+1.foo EQ 5,-2bar", q.to_string());
23//! ```
24//! The query macro produces a [Query] type, so the result can
25//! modified with builder functions.
26pub mod concatenation;
27pub mod field;
28pub mod field_filter;
29pub mod field_order;
30pub mod field_path;
31pub mod from_key_fields;
32pub mod predicate;
33pub mod query_token;
34pub mod query_with;
35pub mod selection;
36pub mod wildcard;
37
38use crate::query::selection::Selection;
39use std::collections::HashMap;
40use std::collections::HashSet;
41use std::fmt;
42
43use concatenation::Concatenation;
44use field::Field;
45use predicate::Predicate;
46use query_token::QueryToken;
47use query_with::QueryWith;
48use wildcard::Wildcard;
49
50/// A Query allows to create a Toql query programmatically or modify a parsed string query.
51///
52/// This is faster than the [QueryParser](../query_parser/struct.QueryParser.html) and cannot fail.
53/// It should be used whenever possible.
54///
55/// A query can be turned into SQL using the [SQL Builder](../sql_builder/struct.SqlBuilder.html).
56///
57/// To build a big query simply add fields, wildcards ans other (sub)querries with [and()](struct.Query.html#method.and) resp. [or()](struct.Query.html#method.or) function.
58///
59/// Watch out: Logical AND has precendence over OR. So `a OR b AND c` is the same as `a OR (b AND c)`.
60///
61/// **Always parenthesize a user query if you add a permission filter to it**.
62///
63/// Malicious users will try circumvent your permission filter with a simple OR clause at the beginning.
64/// Compare an evil query with a safe one:
65///
66/// Evil: `(*, id nen); id, permission ne ""`
67///
68/// Safe: `((*, id nen); id), permission ne ""`.
69///
70/// In the evil query, the permission is ignored, because the predicate `id nen` is always true and returns all records.
71///
72/// To parenthesize a query use the [parenthesize()](struct.Query.html#method.parenthesize) method.
73///
74/// ``` ignore
75/// let q1 = Query::new().and(Field("b").eq(3)).and(Field("c").eq(2));
76/// let q2 = Query::new().and(Field("a").eq(1)).or(q1.parens());
77///
78/// assert_eq!("a eq 1; (b eq 3, c eq 2)", q2.to_string())
79/// ```
80///
81/// For every fields of a struct the Toql derive generates fields.
82/// For a Toql derived struct it's possible to write
83///
84/// ``` ignore
85/// let q1 = Query::wildcard().and(User::fields().addresses().street().eq("miller street")).and(UserKey(10));
86/// ```
87///
88/// To modify q query with a custom struct see [QueryWith](struct.QueryWith.html)
89///
90///
91///
92use crate::sql_arg::SqlArg;
93#[derive(Debug)]
94pub struct Query<M> {
95    pub(crate) tokens: Vec<QueryToken>,
96    /// Select DISTINCT
97    pub distinct: bool,
98
99    /// Aux params used with query
100    pub aux_params: HashMap<String, SqlArg>, // generic build params
101
102    /// Additional where clause
103    pub where_predicates: Vec<String>,
104
105    /// Query params for additional sql restriction
106    pub where_predicate_params: Vec<SqlArg>,
107
108    /// Additional select columns
109    pub select_columns: Vec<String>,
110
111    /// Additional joins statements
112    pub join_stmts: Vec<String>,
113
114    // Join params for additional sql restriction
115    pub join_stmt_params: Vec<SqlArg>,
116
117    /// Type marker
118    pub type_marker: std::marker::PhantomData<M>,
119}
120
121impl<M> Query<M> {
122    /// Create a new empty query.
123    pub fn new() -> Self {
124        Query::<M> {
125            tokens: vec![],
126            distinct: false,
127            aux_params: HashMap::new(),
128            where_predicates: Vec::new(),
129            where_predicate_params: Vec::new(),
130            select_columns: Vec::new(),
131            join_stmts: Vec::new(),
132            join_stmt_params: Vec::new(),
133            type_marker: std::marker::PhantomData, //  wildcard_scope: None
134        }
135    }
136
137    /// Create a new query from another query.
138    pub fn from<T>(query: T) -> Self
139    where
140        T: Into<Query<M>>,
141    {
142        query.into()
143    }
144
145    /// Create a new query from another query.
146    pub fn clone_for_type<T>(&self) -> Query<T> {
147        Query::<T> {
148            tokens: self.tokens.clone(),
149            distinct: self.distinct,
150            aux_params: self.aux_params.clone(),
151            where_predicates: self.where_predicates.clone(),
152            where_predicate_params: self.where_predicate_params.clone(),
153            select_columns: self.select_columns.clone(),
154            join_stmts: self.join_stmts.clone(),
155            join_stmt_params: self.join_stmt_params.clone(),
156            type_marker: std::marker::PhantomData,
157        }
158    }
159    /// Create a new query for a home path.
160    pub fn traverse<T>(&self, home_path: &str) -> Query<T> {
161        let tokens = self
162            .tokens
163            .iter()
164            .filter_map(|t| match t {
165                QueryToken::Field(field) => {
166                    if field.name.starts_with(home_path) {
167                        let mut field = field.clone();
168                        field.name = field
169                            .name
170                            .trim_start_matches(home_path)
171                            .trim_start_matches('_')
172                            .to_string();
173                        Some(QueryToken::Field(field))
174                    } else {
175                        None
176                    }
177                }
178                QueryToken::Wildcard(wildcard) => {
179                    if wildcard.path.starts_with(home_path) {
180                        let mut wildcard = wildcard.clone();
181                        wildcard.path = wildcard
182                            .path
183                            .trim_start_matches(home_path)
184                            .trim_start_matches('_')
185                            .to_string();
186                        Some(QueryToken::Wildcard(wildcard))
187                    } else {
188                        None
189                    }
190                }
191                _ => None,
192            })
193            .collect::<Vec<_>>();
194
195        Query::<T> {
196            tokens,
197            distinct: self.distinct,
198            aux_params: self.aux_params.clone(),
199            where_predicates: self.where_predicates.clone(),
200            where_predicate_params: self.where_predicate_params.clone(),
201            select_columns: self.select_columns.clone(),
202            join_stmts: self.join_stmts.clone(),
203            join_stmt_params: self.join_stmt_params.clone(),
204            type_marker: std::marker::PhantomData,
205        }
206    }
207
208    /// Create a new query that select all top fields.
209    pub fn wildcard() -> Self {
210        Query::<M> {
211            tokens: vec![QueryToken::Wildcard(Wildcard::new())],
212            distinct: false,
213            //roles: HashSet::new(),
214            aux_params: HashMap::new(),
215            where_predicates: Vec::new(),
216            where_predicate_params: Vec::new(),
217            select_columns: Vec::new(),
218            join_stmts: Vec::new(),
219            join_stmt_params: Vec::new(),
220            type_marker: std::marker::PhantomData, //  wildcard_scope: None
221        }
222    }
223    pub fn selection(name: impl Into<String>) -> Self {
224        Query::<M> {
225            tokens: vec![QueryToken::Selection(Selection::from(name.into()))],
226            distinct: false,
227            //roles: HashSet::new(),
228            aux_params: HashMap::new(),
229            where_predicates: Vec::new(),
230            where_predicate_params: Vec::new(),
231            select_columns: Vec::new(),
232            join_stmts: Vec::new(),
233            join_stmt_params: Vec::new(),
234            type_marker: std::marker::PhantomData, //  wildcard_scope: None
235        }
236    }
237
238    /// Wrap query with parentheses.
239    pub fn parenthesize(mut self) -> Self {
240        if self.tokens.is_empty() {
241            return self;
242        }
243        self.tokens
244            .insert(0, QueryToken::LeftBracket(Concatenation::And));
245        self.tokens.push(QueryToken::RightBracket);
246        self
247    }
248    /// Concatenate field or query with AND.
249    pub fn and<T>(mut self, query: T) -> Self
250    where
251        T: Into<Query<M>>,
252    {
253        // All tokens are by default concatenated with AND
254        self.tokens.append(&mut query.into().tokens);
255        self
256    }
257    /// Concatenate field or query with AND.
258    pub fn and_parentized<T>(mut self, query: T) -> Self
259    where
260        T: Into<Query<M>>,
261    {
262        self.tokens
263            .push(QueryToken::LeftBracket(Concatenation::And));
264        self.tokens.append(&mut query.into().tokens);
265        self.tokens.push(QueryToken::RightBracket);
266        self
267    }
268
269    /// Concatenate field or query with OR.
270    pub fn or<T>(mut self, query: T) -> Self
271    where
272        T: Into<Query<M>>,
273    {
274        // Change first token of query to concatenate with OR
275        let mut query = query.into();
276        if !self.tokens.is_empty() {
277            match query.tokens.get_mut(0) {
278                Some(QueryToken::LeftBracket(c)) => *c = Concatenation::Or,
279                Some(QueryToken::RightBracket) => {}
280                Some(QueryToken::Field(f)) => f.concatenation = Concatenation::Or,
281                Some(QueryToken::Wildcard(w)) => w.concatenation = Concatenation::Or,
282                Some(QueryToken::Predicate(p)) => p.concatenation = Concatenation::Or,
283                Some(QueryToken::Selection(p)) => p.concatenation = Concatenation::Or,
284                None => {}
285            }
286        }
287
288        self.tokens.append(&mut query.tokens);
289
290        self
291    }
292    /// Concatenate field or query with AND.
293    pub fn or_parentized<T>(mut self, query: T) -> Self
294    where
295        T: Into<Query<M>>,
296    {
297        self.tokens.push(QueryToken::LeftBracket(Concatenation::Or));
298        self.tokens.append(&mut query.into().tokens);
299        self.tokens.push(QueryToken::RightBracket);
300        self
301    }
302
303    /// Modifiy the query with an additional stuct.
304    pub fn with(self, query_with: impl QueryWith<M>) -> Self {
305        query_with.with(self)
306    }
307
308    /// Convenence method to add aux params
309    pub fn aux_param<S, A>(mut self, name: S, value: A) -> Self
310    where
311        A: Into<SqlArg>,
312        S: Into<String>,
313    {
314        self.aux_params.insert(name.into(), value.into());
315        self
316    }
317
318    /// Check if query contains path
319    /// Example: Path is 'user_address'
320    /// Valid query paths are 'user_*', 'user_address_*', 'user_address_country_*,'user_address_id'
321    pub fn contains_path(&self, path: &str) -> bool {
322        let p = format!("{}_", path.trim_end_matches('_')); // ensure path ends with _
323        self.tokens.iter().any(|t| {
324            let pth = p.as_str();
325            match t {
326                QueryToken::Field(field) => field.name.starts_with(pth),
327                QueryToken::Wildcard(wildcard) => {
328                    path.starts_with(&wildcard.path) || wildcard.path.starts_with(pth)
329                }
330                _ => false,
331            }
332        })
333    }
334    /// Check if query contains path starting with a subpath
335    /// Example: Path is 'user_address'
336    /// Valid query paths are 'user_address_*', 'user_address_id'
337    pub fn contains_path_starts_with(&self, path: &str) -> bool {
338        let p = format!("{}_", path.trim_end_matches('_')); // ensure path ends with _
339        self.tokens.iter().any(|t| {
340            let pth = p.as_str();
341            match t {
342                QueryToken::Field(field) => field.name.starts_with(pth),
343                QueryToken::Wildcard(wildcard) => wildcard.path.starts_with(pth),
344                _ => false,
345            }
346        })
347    }
348}
349
350/// Asserts that the provided roles contains all required roles.
351/// The first missing role is returned as error.
352pub fn assert_roles(
353    provided_roles: &HashSet<String>,
354    required_roles: &HashSet<String>,
355) -> Result<(), String> {
356    for r in required_roles {
357        if !provided_roles.contains(r) {
358            return Err(r.to_owned());
359        }
360    }
361
362    Ok(())
363}
364
365// Doc: Display  implements automatically .to_string()
366impl<M> fmt::Display for Query<M> {
367    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
368        fn get_concatenation(c: &Concatenation) -> char {
369            match c {
370                Concatenation::And => ',',
371                Concatenation::Or => ';',
372            }
373        }
374
375        let mut s = String::new();
376        let mut concatenation_needed = false;
377
378        for token in &self.tokens {
379            if concatenation_needed {
380                match &token {
381                    QueryToken::LeftBracket(concatenation) => {
382                        s.push(get_concatenation(concatenation))
383                    }
384                    QueryToken::Wildcard(wildcard) => {
385                        s.push(get_concatenation(&wildcard.concatenation))
386                    }
387                    QueryToken::Field(field) => s.push(get_concatenation(&field.concatenation)),
388                    QueryToken::Predicate(field) => s.push(get_concatenation(&field.concatenation)),
389                    QueryToken::Selection(selection) => {
390                        s.push(get_concatenation(&selection.concatenation))
391                    }
392                    _ => {}
393                }
394            }
395            s.push_str(&token.to_string());
396            match token {
397                QueryToken::LeftBracket(..) => concatenation_needed = false,
398                QueryToken::Field(..) => concatenation_needed = true,
399                QueryToken::Wildcard(..) => concatenation_needed = true,
400                QueryToken::Predicate(..) => concatenation_needed = true,
401                QueryToken::Selection(..) => concatenation_needed = true,
402                _ => {}
403            }
404        }
405
406        // To avoid allocation check first if string starts with a separator
407        let t = s.chars().next().unwrap_or(' ');
408        if t == ',' || t == ';' {
409            s = s.trim_start_matches(',').trim_start_matches(';').to_owned();
410        }
411
412        write!(f, "{}", s)
413    }
414}
415
416impl<M> From<Field> for Query<M> {
417    fn from(field: Field) -> Query<M> {
418        let mut q = Query::new();
419        q.tokens.push(QueryToken::Field(field));
420        q
421    }
422}
423
424impl<M> From<Predicate> for Query<M> {
425    fn from(predicate: Predicate) -> Query<M> {
426        let mut q = Query::new();
427        q.tokens.push(QueryToken::Predicate(predicate));
428        q
429    }
430}
431impl<M> From<Selection> for Query<M> {
432    fn from(selection: Selection) -> Query<M> {
433        let mut q = Query::new();
434        q.tokens.push(QueryToken::Selection(selection));
435        q
436    }
437}
438
439impl<M> From<Wildcard> for Query<M> {
440    fn from(wildcard: Wildcard) -> Query<M> {
441        let mut q = Query::new();
442        q.tokens.push(QueryToken::Wildcard(wildcard));
443        q
444    }
445}
446
447impl<M> From<&str> for Query<M> {
448    fn from(string: &str) -> Query<M> {
449        let mut q = Query::new();
450        q.tokens.push(if string.ends_with('*') {
451            QueryToken::Wildcard(Wildcard::from(string))
452        } else {
453            QueryToken::Field(Field::from(string))
454        });
455        q
456    }
457}
458
459impl<M> Default for Query<M> {
460    fn default() -> Self {
461        Self::new()
462    }
463}
464
465#[cfg(test)]
466mod test {
467    use super::{query_with::QueryWith, Field, Predicate, Query, Selection, Wildcard};
468    use crate::sql_arg::SqlArg;
469
470    struct User;
471    struct Level2;
472
473    struct Item;
474
475    impl<T> QueryWith<T> for Item {
476        fn with(&self, query: Query<T>) -> Query<T> {
477            query
478                .and(Field::from("item").eq(1))
479                .aux_param("thing", "item")
480        }
481    }
482
483    #[test]
484    fn build() {
485        assert_eq!(Query::<User>::default().to_string(), "");
486        assert_eq!(Query::<User>::new().to_string(), "");
487
488        let qf = Query::<User>::from(Field::from("prop"));
489        let qp = Query::<User>::from(Predicate::from("pred"));
490        let qs = Query::<User>::from(Selection::from("std"));
491        let qw = Query::<User>::from(Wildcard::from("level1"));
492
493        assert_eq!(qf.to_string(), "prop");
494        assert_eq!(qp.to_string(), "@pred");
495        assert_eq!(qs.to_string(), "$std");
496        assert_eq!(qw.to_string(), "level1_*");
497
498        assert_eq!(
499            qf.clone_for_type::<User>()
500                .and(qp.clone_for_type())
501                .to_string(),
502            "prop,@pred"
503        );
504        assert_eq!(
505            qf.clone_for_type::<User>()
506                .and_parentized(qp.clone_for_type())
507                .to_string(),
508            "prop,(@pred)"
509        );
510        assert_eq!(
511            qf.clone_for_type::<User>()
512                .or(qp.clone_for_type())
513                .to_string(),
514            "prop;@pred"
515        );
516        assert_eq!(
517            qf.clone_for_type::<User>()
518                .or_parentized(qp.clone_for_type())
519                .to_string(),
520            "prop;(@pred)"
521        );
522
523        let q: Query<User> = Query::from(Field::from("level2_prop2"))
524            .and(Query::from(Wildcard::from("level2")))
525            .and(Query::from(Field::from("level3_prop4")))
526            .and(Query::from(Field::from("level1_prop1")))
527            .and(Query::from(Field::from("prop")));
528        assert_eq!(qw.contains_path("level1"), true);
529        assert_eq!(qw.contains_path("level4"), false);
530        assert_eq!(q.traverse::<Level2>("level2").to_string(), "prop2,*");
531
532        let q = qf.with(Item);
533        assert_eq!(q.to_string(), "prop,item EQ 1");
534        assert_eq!(q.aux_params.get("thing"), Some(&SqlArg::from("item")));
535    }
536}