sqlparser_mysql/base/
common_parser.rs

1use std::str::FromStr;
2
3use nom::branch::alt;
4use nom::bytes::complete::{tag, tag_no_case, take_until, take_while, take_while1};
5use nom::character::complete::{alpha1, digit1, line_ending, multispace0, multispace1};
6use nom::character::is_alphanumeric;
7use nom::combinator::{map, not, opt, peek, recognize};
8use nom::error::{ErrorKind, ParseError};
9use nom::sequence::{delimited, pair, preceded, terminated, tuple};
10use nom::{IResult, InputLength, Parser};
11
12use base::column::Column;
13use base::{DefaultOrZeroOrOne, OrderType, ParseSQLError};
14
15/// collection of common used parsers
16pub struct CommonParser;
17
18impl CommonParser {
19    fn keyword_follow_char(i: &str) -> IResult<&str, &str, ParseSQLError<&str>> {
20        peek(alt((
21            tag(" "),
22            tag("\n"),
23            tag(";"),
24            tag("("),
25            tag(")"),
26            tag("\t"),
27            tag(","),
28            tag("="),
29            CommonParser::eof,
30        )))(i)
31    }
32
33    fn keywords_part_1(i: &str) -> IResult<&str, &str, ParseSQLError<&str>> {
34        alt((
35            terminated(tag_no_case("ABORT"), Self::keyword_follow_char),
36            terminated(tag_no_case("ACTION"), Self::keyword_follow_char),
37            terminated(tag_no_case("ADD"), Self::keyword_follow_char),
38            terminated(tag_no_case("AFTER"), Self::keyword_follow_char),
39            terminated(tag_no_case("ALL"), Self::keyword_follow_char),
40            terminated(tag_no_case("ALTER"), Self::keyword_follow_char),
41            terminated(tag_no_case("ANALYZE"), Self::keyword_follow_char),
42            terminated(tag_no_case("AND"), Self::keyword_follow_char),
43            terminated(tag_no_case("AS"), Self::keyword_follow_char),
44            terminated(tag_no_case("ASC"), Self::keyword_follow_char),
45            terminated(tag_no_case("ATTACH"), Self::keyword_follow_char),
46            terminated(tag_no_case("AUTOINCREMENT"), Self::keyword_follow_char),
47            terminated(tag_no_case("BEFORE"), Self::keyword_follow_char),
48            terminated(tag_no_case("BEGIN"), Self::keyword_follow_char),
49            terminated(tag_no_case("BETWEEN"), Self::keyword_follow_char),
50            terminated(tag_no_case("BY"), Self::keyword_follow_char),
51            terminated(tag_no_case("CASCADE"), Self::keyword_follow_char),
52            terminated(tag_no_case("CASE"), Self::keyword_follow_char),
53            terminated(tag_no_case("CAST"), Self::keyword_follow_char),
54            terminated(tag_no_case("CHECK"), Self::keyword_follow_char),
55            terminated(tag_no_case("COLLATE"), Self::keyword_follow_char),
56        ))(i)
57    }
58
59    fn keywords_part_2(i: &str) -> IResult<&str, &str, ParseSQLError<&str>> {
60        alt((
61            terminated(tag_no_case("COLUMN"), Self::keyword_follow_char),
62            terminated(tag_no_case("COMMIT"), Self::keyword_follow_char),
63            terminated(tag_no_case("CONFLICT"), Self::keyword_follow_char),
64            terminated(tag_no_case("CONSTRAINT"), Self::keyword_follow_char),
65            terminated(tag_no_case("CREATE"), Self::keyword_follow_char),
66            terminated(tag_no_case("CROSS"), Self::keyword_follow_char),
67            terminated(tag_no_case("CURRENT_DATE"), Self::keyword_follow_char),
68            terminated(tag_no_case("CURRENT_TIME"), Self::keyword_follow_char),
69            terminated(tag_no_case("CURRENT_TIMESTAMP"), Self::keyword_follow_char),
70            terminated(tag_no_case("DATABASE"), Self::keyword_follow_char),
71            terminated(tag_no_case("DEFAULT"), Self::keyword_follow_char),
72            terminated(tag_no_case("DEFERRABLE"), Self::keyword_follow_char),
73            terminated(tag_no_case("DEFERRED"), Self::keyword_follow_char),
74            terminated(tag_no_case("DELETE"), Self::keyword_follow_char),
75            terminated(tag_no_case("DESC"), Self::keyword_follow_char),
76            terminated(tag_no_case("DETACH"), Self::keyword_follow_char),
77            terminated(tag_no_case("DISTINCT"), Self::keyword_follow_char),
78            terminated(tag_no_case("DROP"), Self::keyword_follow_char),
79            terminated(tag_no_case("EACH"), Self::keyword_follow_char),
80            terminated(tag_no_case("ELSE"), Self::keyword_follow_char),
81            terminated(tag_no_case("END"), Self::keyword_follow_char),
82        ))(i)
83    }
84
85    fn keywords_part_3(i: &str) -> IResult<&str, &str, ParseSQLError<&str>> {
86        alt((
87            terminated(tag_no_case("ESCAPE"), Self::keyword_follow_char),
88            terminated(tag_no_case("EXCEPT"), Self::keyword_follow_char),
89            terminated(tag_no_case("EXCLUSIVE"), Self::keyword_follow_char),
90            terminated(tag_no_case("EXISTS"), Self::keyword_follow_char),
91            terminated(tag_no_case("EXPLAIN"), Self::keyword_follow_char),
92            terminated(tag_no_case("FAIL"), Self::keyword_follow_char),
93            terminated(tag_no_case("FOR"), Self::keyword_follow_char),
94            terminated(tag_no_case("FOREIGN"), Self::keyword_follow_char),
95            terminated(tag_no_case("FROM"), Self::keyword_follow_char),
96            terminated(tag_no_case("FULL"), Self::keyword_follow_char),
97            terminated(tag_no_case("FULLTEXT"), Self::keyword_follow_char),
98            terminated(tag_no_case("GLOB"), Self::keyword_follow_char),
99            terminated(tag_no_case("GROUP"), Self::keyword_follow_char),
100            terminated(tag_no_case("HAVING"), Self::keyword_follow_char),
101            terminated(tag_no_case("IF"), Self::keyword_follow_char),
102            terminated(tag_no_case("IGNORE"), Self::keyword_follow_char),
103            terminated(tag_no_case("IMMEDIATE"), Self::keyword_follow_char),
104            terminated(tag_no_case("IN"), Self::keyword_follow_char),
105            terminated(tag_no_case("INDEX"), Self::keyword_follow_char),
106            terminated(tag_no_case("INDEXED"), Self::keyword_follow_char),
107            terminated(tag_no_case("INITIALLY"), Self::keyword_follow_char),
108        ))(i)
109    }
110
111    fn keywords_part_4(i: &str) -> IResult<&str, &str, ParseSQLError<&str>> {
112        alt((
113            terminated(tag_no_case("INNER"), Self::keyword_follow_char),
114            terminated(tag_no_case("INSERT"), Self::keyword_follow_char),
115            terminated(tag_no_case("INSTEAD"), Self::keyword_follow_char),
116            terminated(tag_no_case("INTERSECT"), Self::keyword_follow_char),
117            terminated(tag_no_case("INTO"), Self::keyword_follow_char),
118            terminated(tag_no_case("IS"), Self::keyword_follow_char),
119            terminated(tag_no_case("ISNULL"), Self::keyword_follow_char),
120            terminated(tag_no_case("ORDER"), Self::keyword_follow_char),
121            terminated(tag_no_case("JOIN"), Self::keyword_follow_char),
122            terminated(tag_no_case("KEY"), Self::keyword_follow_char),
123            terminated(tag_no_case("LEFT"), Self::keyword_follow_char),
124            terminated(tag_no_case("LIKE"), Self::keyword_follow_char),
125            terminated(tag_no_case("LIMIT"), Self::keyword_follow_char),
126            terminated(tag_no_case("MATCH"), Self::keyword_follow_char),
127            terminated(tag_no_case("NATURAL"), Self::keyword_follow_char),
128            terminated(tag_no_case("NO"), Self::keyword_follow_char),
129            terminated(tag_no_case("NOT"), Self::keyword_follow_char),
130            terminated(tag_no_case("NOTNULL"), Self::keyword_follow_char),
131            terminated(tag_no_case("NULL"), Self::keyword_follow_char),
132            terminated(tag_no_case("OF"), Self::keyword_follow_char),
133            terminated(tag_no_case("OFFSET"), Self::keyword_follow_char),
134        ))(i)
135    }
136
137    fn keywords_part_5(i: &str) -> IResult<&str, &str, ParseSQLError<&str>> {
138        alt((
139            terminated(tag_no_case("ON"), Self::keyword_follow_char),
140            terminated(tag_no_case("OR"), Self::keyword_follow_char),
141            terminated(tag_no_case("OUTER"), Self::keyword_follow_char),
142            terminated(tag_no_case("PLAN"), Self::keyword_follow_char),
143            terminated(tag_no_case("PRAGMA"), Self::keyword_follow_char),
144            terminated(tag_no_case("PRIMARY"), Self::keyword_follow_char),
145            terminated(tag_no_case("QUERY"), Self::keyword_follow_char),
146            terminated(tag_no_case("RAISE"), Self::keyword_follow_char),
147            terminated(tag_no_case("RECURSIVE"), Self::keyword_follow_char),
148            terminated(tag_no_case("REFERENCES"), Self::keyword_follow_char),
149            terminated(tag_no_case("REGEXP"), Self::keyword_follow_char),
150            terminated(tag_no_case("REINDEX"), Self::keyword_follow_char),
151            terminated(tag_no_case("RELEASE"), Self::keyword_follow_char),
152            terminated(tag_no_case("RENAME"), Self::keyword_follow_char),
153            terminated(tag_no_case("REPLACE"), Self::keyword_follow_char),
154            terminated(tag_no_case("RESTRICT"), Self::keyword_follow_char),
155            terminated(tag_no_case("RIGHT"), Self::keyword_follow_char),
156            terminated(tag_no_case("ROLLBACK"), Self::keyword_follow_char),
157            terminated(tag_no_case("ROW"), Self::keyword_follow_char),
158            terminated(tag_no_case("SAVEPOINT"), Self::keyword_follow_char),
159            terminated(tag_no_case("SELECT"), Self::keyword_follow_char),
160        ))(i)
161    }
162
163    fn keywords_part_6(i: &str) -> IResult<&str, &str, ParseSQLError<&str>> {
164        alt((
165            terminated(tag_no_case("SET"), Self::keyword_follow_char),
166            terminated(tag_no_case("SPATIAL"), Self::keyword_follow_char),
167            terminated(tag_no_case("TABLE"), Self::keyword_follow_char),
168            terminated(tag_no_case("TEMP"), Self::keyword_follow_char),
169            terminated(tag_no_case("TEMPORARY"), Self::keyword_follow_char),
170            terminated(tag_no_case("THEN"), Self::keyword_follow_char),
171            terminated(tag_no_case("TO"), Self::keyword_follow_char),
172            terminated(tag_no_case("TRANSACTION"), Self::keyword_follow_char),
173            terminated(tag_no_case("TRIGGER"), Self::keyword_follow_char),
174            terminated(tag_no_case("UNION"), Self::keyword_follow_char),
175            terminated(tag_no_case("UNIQUE"), Self::keyword_follow_char),
176            terminated(tag_no_case("UPDATE"), Self::keyword_follow_char),
177            terminated(tag_no_case("USING"), Self::keyword_follow_char),
178            terminated(tag_no_case("VACUUM"), Self::keyword_follow_char),
179            terminated(tag_no_case("VALUES"), Self::keyword_follow_char),
180            terminated(tag_no_case("VIEW"), Self::keyword_follow_char),
181            terminated(tag_no_case("VIRTUAL"), Self::keyword_follow_char),
182            terminated(tag_no_case("WHEN"), Self::keyword_follow_char),
183            terminated(tag_no_case("WHERE"), Self::keyword_follow_char),
184            terminated(tag_no_case("WITH"), Self::keyword_follow_char),
185            terminated(tag_no_case("WITHOUT"), Self::keyword_follow_char),
186        ))(i)
187    }
188
189    // Matches any SQL reserved keyword
190    pub fn sql_keyword(i: &str) -> IResult<&str, &str, ParseSQLError<&str>> {
191        alt((
192            Self::keywords_part_1,
193            Self::keywords_part_2,
194            Self::keywords_part_3,
195            Self::keywords_part_4,
196            Self::keywords_part_5,
197            Self::keywords_part_6,
198        ))(i)
199    }
200
201    /// `[index_name]`
202    pub fn opt_index_name(i: &str) -> IResult<&str, Option<String>, ParseSQLError<&str>> {
203        opt(map(
204            delimited(multispace1, CommonParser::sql_identifier, multispace0),
205            String::from,
206        ))(i)
207    }
208
209    #[allow(clippy::type_complexity)]
210    pub fn index_col_name(
211        i: &str,
212    ) -> IResult<&str, (Column, Option<u16>, Option<OrderType>), ParseSQLError<&str>> {
213        let (remaining_input, (column, len_u8, order)) = tuple((
214            terminated(Column::without_alias, multispace0),
215            opt(delimited(tag("("), digit1, tag(")"))),
216            opt(OrderType::parse),
217        ))(i)?;
218        let len = len_u8.map(|l| u16::from_str(l).unwrap());
219
220        Ok((remaining_input, (column, len, order)))
221    }
222
223    #[inline]
224    fn is_sql_identifier(chr: char) -> bool {
225        is_alphanumeric(chr as u8) || chr == '_' || chr == '@'
226    }
227
228    /// first and third are opt
229    pub fn opt_delimited<I: Clone, O1, O2, O3, E: ParseError<I>, F, G, H>(
230        mut first: F,
231        mut second: G,
232        mut third: H,
233    ) -> impl FnMut(I) -> IResult<I, O2, E>
234    where
235        F: Parser<I, O1, E>,
236        G: Parser<I, O2, E>,
237        H: Parser<I, O3, E>,
238    {
239        move |input: I| {
240            let inp = input.clone();
241            match second.parse(input) {
242                Ok((i, o)) => Ok((i, o)),
243                _ => {
244                    let (inp, _) = first.parse(inp)?;
245                    let (inp, o2) = second.parse(inp)?;
246                    third.parse(inp).map(|(i, _)| (i, o2))
247                }
248            }
249        }
250    }
251
252    fn precision_helper(i: &str) -> IResult<&str, (u8, Option<u8>), ParseSQLError<&str>> {
253        let (remaining_input, (m, d)) = tuple((
254            digit1,
255            opt(preceded(tag(","), preceded(multispace0, digit1))),
256        ))(i)?;
257
258        Ok((
259            remaining_input,
260            (m.parse().unwrap(), d.map(|r| r.parse().unwrap())),
261        ))
262    }
263
264    pub fn precision(i: &str) -> IResult<&str, (u8, Option<u8>), ParseSQLError<&str>> {
265        delimited(tag("("), Self::precision_helper, tag(")"))(i)
266    }
267
268    pub fn delim_digit(i: &str) -> IResult<&str, &str, ParseSQLError<&str>> {
269        delimited(tag("("), digit1, tag(")"))(i)
270    }
271
272    pub fn sql_identifier(i: &str) -> IResult<&str, &str, ParseSQLError<&str>> {
273        alt((
274            alt((
275                preceded(
276                    not(peek(CommonParser::sql_keyword)),
277                    recognize(pair(alpha1, take_while(Self::is_sql_identifier))),
278                ),
279                recognize(pair(tag("_"), take_while1(Self::is_sql_identifier))),
280                // variable only
281                recognize(pair(tag("@"), take_while1(Self::is_sql_identifier))),
282            )),
283            delimited(tag("`"), take_while1(Self::is_sql_identifier), tag("`")),
284            delimited(tag("["), take_while1(Self::is_sql_identifier), tag("]")),
285        ))(i)
286    }
287
288    // Parse an unsigned integer.
289    pub fn unsigned_number(i: &str) -> IResult<&str, u64, ParseSQLError<&str>> {
290        map(digit1, |d| FromStr::from_str(d).unwrap())(i)
291    }
292
293    pub fn eof<I: Copy + InputLength, E: ParseError<I>>(input: I) -> IResult<I, I, E> {
294        if input.input_len() == 0 {
295            Ok((input, input))
296        } else {
297            Err(nom::Err::Error(E::from_error_kind(input, ErrorKind::Eof)))
298        }
299    }
300
301    // Parse a terminator that ends a SQL statement.
302    pub fn statement_terminator(i: &str) -> IResult<&str, (), ParseSQLError<&str>> {
303        let (remaining_input, _) = delimited(
304            multispace0,
305            alt((tag(";"), line_ending, CommonParser::eof)),
306            multispace0,
307        )(i)?;
308        Ok((remaining_input, ()))
309    }
310
311    // Parse rule for AS-based aliases for SQL entities.
312    pub fn as_alias(i: &str) -> IResult<&str, &str, ParseSQLError<&str>> {
313        map(
314            tuple((
315                multispace1,
316                opt(pair(tag_no_case("AS"), multispace1)),
317                // FIXME as can starts with number
318                CommonParser::sql_identifier,
319            )),
320            |a| a.2,
321        )(i)
322    }
323
324    pub fn ws_sep_comma(i: &str) -> IResult<&str, &str, ParseSQLError<&str>> {
325        delimited(multispace0, tag(","), multispace0)(i)
326    }
327
328    pub(crate) fn ws_sep_equals(i: &str) -> IResult<&str, &str, ParseSQLError<&str>> {
329        delimited(multispace0, tag("="), multispace0)(i)
330    }
331
332    /// Parse rule for a comment part.
333    /// COMMENT 'comment content'
334    /// or
335    /// COMMENT "comment content"
336    pub fn parse_comment(i: &str) -> IResult<&str, String, ParseSQLError<&str>> {
337        alt((
338            map(
339                preceded(
340                    delimited(multispace0, tag_no_case("COMMENT"), multispace1),
341                    delimited(tag("'"), take_until("'"), tag("'")),
342                ),
343                String::from,
344            ),
345            map(
346                preceded(
347                    delimited(multispace0, tag_no_case("COMMENT"), multispace1),
348                    delimited(tag("\""), take_until("\""), tag("\"")),
349                ),
350                String::from,
351            ),
352        ))(i)
353    }
354
355    /// IF EXISTS
356    pub fn parse_if_exists(i: &str) -> IResult<&str, Option<&str>, ParseSQLError<&str>> {
357        opt(delimited(
358            multispace0,
359            delimited(tag_no_case("IF"), multispace1, tag_no_case("EXISTS")),
360            multispace0,
361        ))(i)
362    }
363
364    /// extract String quoted by `'` or `"`
365    pub fn parse_quoted_string(i: &str) -> IResult<&str, String, ParseSQLError<&str>> {
366        alt((
367            map(delimited(tag("'"), take_until("'"), tag("'")), String::from),
368            map(
369                delimited(tag("\""), take_until("\""), tag("\"")),
370                String::from,
371            ),
372        ))(i)
373    }
374
375    /// extract value from `key [=] 'value'` or `key [=] "value"`
376    pub fn parse_quoted_string_value_with_key(
377        i: &str,
378        key: String,
379    ) -> IResult<&str, String, ParseSQLError<&str>> {
380        alt((
381            map(
382                tuple((
383                    tag_no_case(key.as_str()),
384                    multispace1,
385                    CommonParser::parse_quoted_string,
386                )),
387                |(_, _, value)| value,
388            ),
389            map(
390                tuple((
391                    tag_no_case(key.as_str()),
392                    multispace0,
393                    tag("="),
394                    multispace0,
395                    CommonParser::parse_quoted_string,
396                )),
397                |(_, _, _, _, value)| value,
398            ),
399        ))(i)
400    }
401
402    /// extract value from `key [=] value`
403    pub fn parse_string_value_with_key(
404        i: &str,
405        key: String,
406    ) -> IResult<&str, String, ParseSQLError<&str>> {
407        alt((
408            map(
409                tuple((tag_no_case(key.as_str()), multispace1, Self::sql_identifier)),
410                |(_, _, value)| String::from(value),
411            ),
412            map(
413                tuple((
414                    tag_no_case(key.as_str()),
415                    multispace0,
416                    tag("="),
417                    multispace0,
418                    Self::sql_identifier,
419                )),
420                |(_, _, _, _, value)| String::from(value),
421            ),
422        ))(i)
423    }
424
425    /// extract value from `key [=] value`
426    pub fn parse_digit_value_with_key(
427        i: &str,
428        key: String,
429    ) -> IResult<&str, String, ParseSQLError<&str>> {
430        alt((
431            map(
432                tuple((tag_no_case(key.as_str()), multispace1, Self::sql_identifier)),
433                |(_, _, value)| String::from(value),
434            ),
435            map(
436                tuple((
437                    tag_no_case(key.as_str()),
438                    multispace0,
439                    tag("="),
440                    multispace0,
441                    digit1,
442                )),
443                |(_, _, _, _, value)| String::from(value),
444            ),
445        ))(i)
446    }
447
448    /// extract value from `key [=] {DEFAULT | 0 | 1}`
449    pub fn parse_default_value_with_key(
450        i: &str,
451        key: String,
452    ) -> IResult<&str, DefaultOrZeroOrOne, ParseSQLError<&str>> {
453        alt((
454            map(
455                tuple((
456                    tag_no_case(key.as_str()),
457                    multispace1,
458                    DefaultOrZeroOrOne::parse,
459                )),
460                |(_, _, value)| value,
461            ),
462            map(
463                tuple((
464                    tag_no_case(key.as_str()),
465                    multispace0,
466                    tag("="),
467                    multispace0,
468                    DefaultOrZeroOrOne::parse,
469                )),
470                |(_, _, _, _, value)| value,
471            ),
472        ))(i)
473    }
474}
475
476#[cfg(test)]
477mod tests {
478    use nom::bytes::complete::tag;
479    use nom::IResult;
480
481    use base::CommonParser;
482
483    #[test]
484    fn parse_sql_identifiers() {
485        let id1 = "foo";
486        let id2 = "f_o_o";
487        let id3 = "foo12";
488        let id4 = ":fo oo";
489        let id5 = "primary ";
490        let id6 = "`primary`";
491
492        assert!(CommonParser::sql_identifier(id1).is_ok());
493        assert!(CommonParser::sql_identifier(id2).is_ok());
494        assert!(CommonParser::sql_identifier(id3).is_ok());
495        assert!(CommonParser::sql_identifier(id4).is_err());
496        assert!(CommonParser::sql_identifier(id5).is_err());
497        assert!(CommonParser::sql_identifier(id6).is_ok());
498    }
499
500    fn test_opt_delimited_fn_call(i: &str) -> IResult<&str, &str> {
501        CommonParser::opt_delimited(tag("("), tag("abc"), tag(")"))(i)
502    }
503
504    #[test]
505    fn parse_opt_delimited() {
506        assert_eq!(test_opt_delimited_fn_call("abc"), Ok(("", "abc")));
507        assert_eq!(test_opt_delimited_fn_call("(abc)"), Ok(("", "abc")));
508        assert!(test_opt_delimited_fn_call("(abc").is_err());
509        assert_eq!(test_opt_delimited_fn_call("abc)"), Ok((")", "abc")));
510        assert!(test_opt_delimited_fn_call("ab").is_err());
511    }
512
513    #[test]
514    fn parse_comment() {
515        let res = CommonParser::parse_comment(" COMMENT 'test'");
516        assert_eq!(res.unwrap().1, "test");
517
518        let res = CommonParser::parse_comment(" COMMENT \"test\"");
519        assert_eq!(res.unwrap().1, "test");
520    }
521
522    #[test]
523    fn parse_statement_terminator() {
524        let res = CommonParser::statement_terminator("   ;  ");
525        assert_eq!(res, Ok(("", ())));
526    }
527}