toql_core/
query_parser.rs

1//!
2//! The query parser can turn a string that follows the Toql query syntax into a [Query](../query/struct.Query.html).
3//!
4//! ## Example
5//!
6//! ``` ignore
7//! let  query = QueryParser::parse("*, +username").unwrap();
8//! assert_eq!("*, +username", query.to_string());
9//! ```
10//! Read the guide for more information on the query syntax.
11//!
12//! The parser is written with [Pest](https://pest.rs/) and is fast. It should be used to parse query request from users.
13//! To build a query within your program, build it programmatically with the provided methods.
14//! This avoids typing mistakes and - unlike parsing - cannot fail.
15//!
16use crate::error::ToqlError;
17use crate::query::concatenation::Concatenation;
18use crate::query::field::Field;
19use crate::query::field_filter::FieldFilter;
20use crate::query::field_order::FieldOrder;
21use crate::query::predicate::Predicate;
22use crate::query::query_token::QueryToken;
23use crate::query::selection::Selection;
24use crate::query::wildcard::Wildcard;
25use crate::query::Query;
26use crate::sql_arg::SqlArg;
27use crate::sql_builder::sql_builder_error::SqlBuilderError;
28use toql_query_parser::PestQueryParser;
29
30use pest::Parser;
31
32use toql_query_parser::Rule;
33
34/// The query parser.
35/// It contains only a single static method to turn a string into a Query struct.
36pub struct QueryParser;
37
38enum TokenType {
39    Field,
40    Wildcard,
41    Predicate,
42    Selection,
43    Unknown,
44}
45
46struct TokenInfo {
47    token_type: TokenType,
48    args: Vec<SqlArg>,
49    hidden: bool,
50    order: Option<FieldOrder>,
51    filter: Option<String>,
52    //aggregation: bool,
53    name: String,
54    concatenation: Concatenation,
55}
56
57impl TokenInfo {
58    pub fn new() -> Self {
59        TokenInfo {
60            token_type: TokenType::Unknown,
61            args: Vec::new(),
62            hidden: false,
63            order: None,
64            filter: None,
65            //  aggregation: false,
66            name: String::new(),
67            concatenation: Concatenation::And,
68        }
69    }
70    pub fn build_token(&mut self) -> Result<Option<QueryToken>, ToqlError> {
71        match &self.token_type {
72            TokenType::Field => Ok(Some(QueryToken::Field(Field {
73                name: self.name.to_string(),
74                hidden: self.hidden,
75                order: self.order.clone(),
76                filter: self.build_filter()?,
77                //  aggregation: self.aggregation,
78                concatenation: self.concatenation.clone(),
79            }))),
80            TokenType::Wildcard => Ok(Some(QueryToken::Wildcard(Wildcard {
81                path: self.name.to_string(),
82                concatenation: self.concatenation.clone(),
83            }))),
84            TokenType::Predicate => Ok(Some(QueryToken::Predicate(Predicate {
85                name: self.name.to_string(),
86                args: self.args.drain(..).collect(),
87                concatenation: self.concatenation.clone(),
88            }))),
89            TokenType::Selection => {
90                // validate name
91
92                Ok(Some(QueryToken::Selection(Selection {
93                    name: String::from(if self.name.is_empty() {
94                        "std"
95                    } else {
96                        self.name.as_str()
97                    }),
98                    concatenation: self.concatenation.clone(),
99                })))
100            }
101            _ => Ok(None),
102        }
103    }
104
105    pub fn build_filter(&mut self) -> Result<Option<FieldFilter>, ToqlError> {
106        match &self.filter {
107            Some(f) => {
108                let upc = f.to_uppercase();
109                let filtername = upc.split_ascii_whitespace().next().unwrap_or("");
110                match filtername {
111                    "" => Ok(None),
112                    "EQ" => Ok(Some(FieldFilter::Eq(self.args.pop().ok_or_else(|| {
113                        SqlBuilderError::FilterInvalid(filtername.to_string())
114                    })?))),
115                    "EQN" => Ok(Some(FieldFilter::Eqn)),
116                    "NE" => Ok(Some(FieldFilter::Ne(self.args.pop().ok_or_else(|| {
117                        SqlBuilderError::FilterInvalid(filtername.to_string())
118                    })?))),
119                    "NEN" => Ok(Some(FieldFilter::Nen)),
120                    "GT" => Ok(Some(FieldFilter::Gt(self.args.pop().ok_or_else(|| {
121                        SqlBuilderError::FilterInvalid(filtername.to_string())
122                    })?))),
123                    "GE" => Ok(Some(FieldFilter::Ge(self.args.pop().ok_or_else(|| {
124                        SqlBuilderError::FilterInvalid(filtername.to_string())
125                    })?))),
126                    "LT" => Ok(Some(FieldFilter::Lt(self.args.pop().ok_or_else(|| {
127                        SqlBuilderError::FilterInvalid(filtername.to_string())
128                    })?))),
129                    "LE" => Ok(Some(FieldFilter::Le(self.args.pop().ok_or_else(|| {
130                        SqlBuilderError::FilterInvalid(filtername.to_string())
131                    })?))),
132                    "LK" => Ok(Some(FieldFilter::Lk(self.args.pop().ok_or_else(|| {
133                        SqlBuilderError::FilterInvalid(filtername.to_string())
134                    })?))),
135                    "IN" => Ok(Some(FieldFilter::In(self.args.drain(..).collect()))),
136                    "OUT" => Ok(Some(FieldFilter::Out(self.args.drain(..).collect()))),
137                    "BW" => {
138                        let to = self.args.pop().ok_or_else(|| {
139                            SqlBuilderError::FilterInvalid(filtername.to_string())
140                        })?;
141                        let from = self.args.pop().ok_or_else(|| {
142                            SqlBuilderError::FilterInvalid(filtername.to_string())
143                        })?;
144                        Ok(Some(FieldFilter::Bw(from, to)))
145                    }
146                    _ => {
147                        if upc.starts_with("FN ") {
148                            let filtername = upc.trim_start_matches("FN ");
149                            Ok(Some(FieldFilter::Fn(
150                                filtername.to_string(),
151                                self.args.drain(..).collect(),
152                            )))
153                        } else {
154                            Err(SqlBuilderError::FilterInvalid(f.to_string()).into())
155                        }
156                    }
157                }
158            }
159            None => Ok(None),
160        }
161    }
162}
163
164impl QueryParser {
165    /// Method to parse a string
166    /// This fails if the syntax is wrong. The original PEST error is wrapped with the ToqlError and
167    /// can be used to examine to problem in detail.
168    pub fn parse<M>(toql_query: &str) -> Result<Query<M>, ToqlError> {
169        let pairs = PestQueryParser::parse(Rule::query, toql_query)?;
170
171        let mut query = Query::new();
172
173        let mut token_info = TokenInfo::new();
174
175        for pair in pairs.flatten().into_iter() {
176            let span = pair.clone().as_span();
177            match pair.as_rule() {
178                Rule::field_clause => {
179                    token_info.token_type = TokenType::Field;
180                }
181                Rule::sort => {
182                    let p = span.as_str()[1..].parse::<u8>().unwrap_or(1);
183                    if let Some('+') = span.as_str().chars().next() {
184                        token_info.order = Some(FieldOrder::Asc(p));
185                    } else {
186                        token_info.order = Some(FieldOrder::Desc(p));
187                    }
188                }
189                Rule::hidden => {
190                    token_info.hidden = true;
191                }
192
193                Rule::field_path => {
194                    token_info.name = span.as_str().to_string();
195                }
196                Rule::wildcard => {
197                    token_info.name = span.as_str().trim_end_matches('*').to_string();
198                    // Wildcard path must end with underscore
199                    if !token_info.name.is_empty() && !token_info.name.ends_with('_') {
200                        token_info.name.push('_');
201                    }
202                    token_info.token_type = TokenType::Wildcard;
203                }
204                Rule::filter0_name => {
205                    token_info.filter = Some(span.as_str().to_string());
206                }
207                Rule::filter1_name => {
208                    token_info.filter = Some(span.as_str().to_string());
209                }
210                Rule::filter2_name => {
211                    token_info.filter = Some(span.as_str().to_string());
212                }
213                Rule::filterx_name => {
214                    token_info.filter = Some(span.as_str().to_string());
215                }
216                Rule::filterc_name => {
217                    token_info.filter = Some(span.as_str().to_string());
218                }
219                Rule::num_u64 => {
220                    let v = span.as_str().parse::<u64>().unwrap_or(0); // should not be invalid, todo check range
221                    token_info.args.push(SqlArg::from(v));
222                }
223                Rule::num_i64 => {
224                    let v = span.as_str().parse::<i64>().unwrap_or(0); // should not be invalid, todo check range
225                    token_info.args.push(SqlArg::from(v));
226                }
227                Rule::num_f64 => {
228                    let v = span.as_str().parse::<f64>().unwrap_or(0.0); // should not be invalid, todo check range
229                    token_info.args.push(SqlArg::from(v));
230                }
231                Rule::string => {
232                    let v = span
233                        .as_str()
234                        .trim_start_matches('\'')
235                        .trim_end_matches('\'')
236                        .replace("''", "'");
237                    token_info.args.push(SqlArg::from(v));
238                }
239                Rule::predicate_clause => {
240                    token_info.token_type = TokenType::Predicate;
241                }
242                Rule::predicate_name => {
243                    token_info.name = span.as_str().trim_start_matches('@').to_string();
244                }
245                Rule::selection_clause => {
246                    token_info.token_type = TokenType::Selection;
247                }
248                Rule::selection_name => {
249                    token_info.name = span.as_str().trim_start_matches('@').to_string();
250                }
251                Rule::rpar => {
252                    // Right bracket finishes token, if not warping inner bracket
253                    // E.g ..((+name eq 'fd)),... -> Inner brackets finish token
254                    if let Some(token) = token_info.build_token()? {
255                        query.tokens.push(token);
256                        token_info = TokenInfo::new(); // Restart token builder
257                    }
258                    query.tokens.push(QueryToken::RightBracket);
259                }
260                Rule::lpar => {
261                    query
262                        .tokens
263                        .push(QueryToken::LeftBracket(token_info.concatenation.clone()));
264                }
265                Rule::separator => {
266                    let concat_type = span.as_str().chars().next();
267                    if let Some(token) = token_info.build_token()? {
268                        query.tokens.push(token);
269                        token_info = TokenInfo::new(); // Restart token builder
270                    }
271
272                    token_info.concatenation = if let Some(',') = concat_type {
273                        Concatenation::And
274                    } else {
275                        Concatenation::Or
276                    };
277                }
278
279                _ => {}
280            }
281        }
282        if let Some(token) = token_info.build_token()?
283        // TODO error handling
284        {
285            query.tokens.push(token);
286        }
287        Ok(query)
288    }
289}
290
291#[cfg(test)]
292mod test {
293    use super::QueryParser;
294
295    struct User;
296
297    #[test]
298    fn parse_filters() {
299        let q = QueryParser::parse::<User>(
300            "prop1, prop2 EQN, prop3 NE -1, prop4 NEN, \
301            prop5 GT 1.5, prop6 GE 1.5, prop7 LT 1.5, prop8 LE 1.5",
302        )
303        .unwrap();
304
305        assert_eq!(
306            q.to_string(),
307            "prop1,prop2 EQN,prop3 NE -1,prop4 NEN,\
308            prop5 GT 1.5,prop6 GE 1.5,prop7 LT 1.5,prop8 LE 1.5"
309        );
310
311        let q = QueryParser::parse::<User>(
312            "prop9 LK 'ABC', prop10 BW 1 10, prop11 IN 1 2 3, prop12 OUT 1 2 3, \
313            prop13 FN CUSTOM 'A' 'B' 2",
314        )
315        .unwrap();
316
317        assert_eq!(
318            q.to_string(),
319            "prop9 LK 'ABC',prop10 BW 1 10,prop11 IN 1 2 3,prop12 OUT 1 2 3,\
320            prop13 FN CUSTOM 'A' 'B' 2"
321        );
322    }
323    #[test]
324    fn parse_parens() {
325        let q = QueryParser::parse::<User>("(prop1 eq 1, prop2 eqn); prop3 ne 1").unwrap();
326        assert_eq!(q.to_string(), "(prop1 EQ 1,prop2 EQN);prop3 NE 1");
327
328        let q = QueryParser::parse::<User>("(((prop1 eq 1, prop2 eqn)); prop3 ne 1)").unwrap();
329        assert_eq!(q.to_string(), "(((prop1 EQ 1,prop2 EQN));prop3 NE 1)");
330    }
331    #[test]
332    fn parse_predicate() {
333        let q = QueryParser::parse::<User>("@level1_pred, @pred").unwrap();
334        assert_eq!(q.to_string(), "@level1_pred,@pred");
335    }
336    #[test]
337    fn parse_selection() {
338        let q = QueryParser::parse::<User>("$level1_mut, $mut").unwrap();
339        assert_eq!(q.to_string(), "$level1_mut,$mut");
340    }
341    #[test]
342    fn parse_wildcard_and_field() {
343        let q = QueryParser::parse::<User>("level1_*,*, b").unwrap();
344        assert_eq!(q.to_string(), "level1_*,*,b");
345    }
346    #[test]
347    fn parse_order_and_hidden() {
348        let q = QueryParser::parse::<User>("+1level1_a, -2.b").unwrap();
349        assert_eq!(q.to_string(), "+1level1_a,-2.b");
350    }
351}