Skip to main content

partiql_parser/parse/
mod.rs

1// Copyright Amazon.com, Inc. or its affiliates.
2
3//! Provides the [`parse_partiql`] function to parse a `PartiQL` query.
4
5mod parse_util;
6mod parser_state;
7
8use crate::error::{ParseError, UnexpectedTokenData};
9use crate::lexer;
10use crate::lexer::CommentSkippingLexer;
11use crate::parse::parser_state::ParserState;
12use crate::preprocessor::{PreprocessingPartiqlLexer, BUILT_INS};
13use lalrpop_util as lpop;
14use partiql_ast::ast;
15use partiql_common::node::NodeIdGenerator;
16use partiql_common::syntax::line_offset_tracker::LineOffsetTracker;
17use partiql_common::syntax::location::{ByteOffset, BytePosition, ToLocated};
18use partiql_common::syntax::metadata::LocationMap;
19
20#[allow(clippy::just_underscores_and_digits)] // LALRPOP generates a lot of names like this
21#[allow(clippy::all)]
22#[allow(clippy::pedantic)]
23#[allow(unused_variables)]
24#[allow(dead_code)]
25#[allow(unused_extern_crates)]
26#[allow(explicit_outlives_requirements)]
27mod grammar {
28    include!(concat!(env!("OUT_DIR"), "/partiql.rs"));
29}
30
31type LalrpopError<'input> =
32    lpop::ParseError<ByteOffset, lexer::Token<'input>, ParseError<'input, BytePosition>>;
33type LalrpopResult<'input> = Result<ast::AstNode<ast::TopLevelQuery>, LalrpopError<'input>>;
34type LalrpopErrorRecovery<'input> =
35    lpop::ErrorRecovery<ByteOffset, lexer::Token<'input>, ParseError<'input, BytePosition>>;
36
37#[derive(Debug, Clone)]
38pub(crate) struct AstData {
39    pub ast: ast::AstNode<ast::TopLevelQuery>,
40    pub locations: LocationMap,
41    pub offsets: LineOffsetTracker,
42}
43
44#[derive(Debug, Clone)]
45pub(crate) struct ErrorData<'input> {
46    pub errors: Vec<ParseError<'input, BytePosition>>,
47    pub offsets: LineOffsetTracker,
48}
49
50pub(crate) type AstResult<'input> = Result<AstData, ErrorData<'input>>;
51
52/// Parse `PartiQL` query text into an AST.
53pub(crate) fn parse_partiql(s: &str) -> AstResult<'_> {
54    parse_partiql_with_state(s, ParserState::default())
55}
56
57fn parse_partiql_with_state<'input, Id: NodeIdGenerator>(
58    s: &'input str,
59    mut state: ParserState<'input, Id>,
60) -> AstResult<'input> {
61    let mut offsets = LineOffsetTracker::default();
62    let lexer = PreprocessingPartiqlLexer::new(s, &mut offsets, &BUILT_INS);
63    let lexer = CommentSkippingLexer::new(lexer);
64
65    let result: LalrpopResult<'_> = grammar::TopLevelQueryParser::new().parse(s, &mut state, lexer);
66
67    let ParserState {
68        locations, errors, ..
69    } = state;
70
71    let mut errors: Vec<_> = errors
72        .into_iter()
73        // TODO do something with error_recovery.dropped_tokens?
74        .map(|e| ParseError::from(e.error))
75        .collect();
76
77    match (result, errors.is_empty()) {
78        (Ok(_), false) => Err(ErrorData { errors, offsets }),
79        (Err(e), true) => {
80            let errors = vec![ParseError::from(e)];
81            Err(ErrorData { errors, offsets })
82        }
83        (Err(e), false) => {
84            errors.push(ParseError::from(e));
85            Err(ErrorData { errors, offsets })
86        }
87        (Ok(ast), true) => Ok(AstData {
88            ast,
89            locations,
90            offsets,
91        }),
92    }
93}
94
95impl<'input> From<LalrpopErrorRecovery<'input>> for ParseError<'input, BytePosition> {
96    fn from(error_recovery: LalrpopErrorRecovery<'input>) -> Self {
97        // TODO do something with error_recovery.dropped_tokens?
98        error_recovery.error.into()
99    }
100}
101
102impl<'input> From<LalrpopError<'input>> for ParseError<'input, BytePosition> {
103    #[inline]
104    fn from(error: LalrpopError<'input>) -> Self {
105        match error {
106            // TODO do something with UnrecognizedToken.expected
107            lalrpop_util::ParseError::UnrecognizedToken {
108                token: (start, token, end),
109                expected: _,
110            } => ParseError::UnexpectedToken(
111                UnexpectedTokenData {
112                    token: token.to_string().into(),
113                }
114                .to_located(start.into()..end.into()),
115            ),
116
117            lalrpop_util::ParseError::InvalidToken { location } => {
118                ParseError::Unknown(location.into())
119            }
120
121            // TODO do something with UnrecognizedEof.expected
122            lalrpop_util::ParseError::UnrecognizedEof {
123                location,
124                expected: _,
125            } => ParseError::UnexpectedEndOfInput(location.into()),
126
127            lalrpop_util::ParseError::ExtraToken {
128                token: (start, token, end),
129            } => ParseError::UnexpectedToken(
130                UnexpectedTokenData {
131                    token: token.to_string().into(),
132                }
133                .to_located(start.into()..end.into()),
134            ),
135
136            lalrpop_util::ParseError::User { error } => error,
137        }
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144    fn parse_partiql(s: &str) -> AstResult<'_> {
145        super::parse_partiql(s)
146    }
147
148    macro_rules! parse {
149        ($q:expr) => {{
150            let res = parse_partiql($q);
151            println!("{:#?}", res);
152            match res {
153                Ok(data) => data.ast,
154                _ => panic!("{:?}", res),
155            }
156        }};
157    }
158
159    mod literals {
160        use super::*;
161
162        #[test]
163        fn null() {
164            parse!("NULL");
165        }
166
167        #[test]
168        fn missing() {
169            parse!("MISSING");
170        }
171
172        #[test]
173        fn true_() {
174            parse!("TRUE");
175        }
176
177        #[test]
178        fn false_() {
179            parse!("FALSE");
180        }
181
182        #[test]
183        fn string() {
184            parse!("'foo'");
185            parse!("'embe''ded'");
186        }
187
188        #[test]
189        fn numeric() {
190            parse!("42");
191            parse!("7.");
192            parse!(".00125");
193            parse!("5.5");
194            parse!("17e2");
195            parse!("1.317e-3");
196            parse!("3141.59265e-03");
197        }
198
199        #[test]
200        fn time() {
201            parse!("time '22:12'");
202            parse!("time(10) '22:12'");
203            parse!("time WITH TIME ZONE '22:12'");
204            parse!("time WITHOUT TIME ZONE '22:12'");
205            parse!("time(10) WITH TIME ZONE '22:12'");
206            parse!("time(10) WITHOUT TIME ZONE '22:12'");
207            parse!("time (10) WITH TIME ZONE '22:12'");
208            parse!("time (10) WITHOUT TIME ZONE '22:12'");
209        }
210
211        #[test]
212        fn ion() {
213            parse!(r#" `[{'a':1, 'b':1}, {'a':2}, "foo"]` "#);
214            parse!(
215                r#" ```[{'a':1, 'b':1}, {'a':2}, "foo", 'a`b', "a`b", '''`s''', {{"a`b"}}]``` "#
216            );
217            parse!(
218                r#" `{'a':1, // comment ' "
219                      'b':1} ` "#
220            );
221            parse!(
222                r#" `{'a' // comment ' "
223                       :1, /* 
224                               comment 
225                              */
226                      'b':1} ` "#
227            );
228        }
229    }
230
231    mod non_literal_values {
232        use super::*;
233
234        #[test]
235        fn identifier() {
236            parse!("id");
237            parse!(r#""quoted_id""#);
238        }
239        #[test]
240        fn array() {
241            parse!(r"[]");
242            parse!(r#"[1, 'moo', "some variable", [], 'a', MISSING]"#);
243            // In the interest of compatibility to SQL, PartiQL also allows array constructors to be
244            // denoted with parentheses instead of brackets, when there are at least two elements in the array
245            parse!(r#"(1, 'moo', "some variable", [], 'a', MISSING)"#);
246        }
247        #[test]
248        fn bag() {
249            parse!(r"<<>>");
250            parse!(r"<<1>>");
251            parse!(r"<<1,2>>");
252            parse!(r"<<1, <<>>, 'boo', some_variable, 'a'>>");
253        }
254        #[test]
255        fn tuple() {
256            parse!(r"{}");
257            parse!(r"{a_variable: 1, 'cow': 'moo', 'a': NULL}");
258        }
259    }
260
261    mod expr {
262        use super::*;
263
264        #[test]
265        fn or_simple() {
266            parse!(r"TRUE OR FALSE");
267        }
268
269        #[test]
270        fn or() {
271            parse!(r"t1.super OR test(t2.name, t1.name)");
272        }
273
274        #[test]
275        fn and_simple() {
276            parse!(r"TRUE and FALSE");
277        }
278
279        #[test]
280        fn and() {
281            parse!(r"test(t2.name, t1.name) AND t1.id = t2.id");
282        }
283
284        #[test]
285        fn or_and() {
286            parse!(r"t1.super OR test(t2.name, t1.name) AND t1.id = t2.id");
287        }
288
289        #[test]
290        fn infix() {
291            parse!(r"1 + -2 * +3 % 4^5 / 6 - 7  <= 3.14 AND 'foo' || 'bar' LIKE '%oba%'");
292        }
293
294        #[test]
295        fn expr_in() {
296            parse!(r"a in (1,2,3,4)");
297            parse!(r"a in [1,2,3,4]");
298        }
299
300        #[test]
301        fn expr_between() {
302            parse!(r"a between 2 and 3");
303        }
304    }
305
306    mod pathexpr {
307        use super::*;
308
309        #[test]
310        fn nested() {
311            parse!(r"a.b");
312            parse!(r#"a.b.c['item']."d"[5].e['s'].f[1+2]"#);
313            parse!(r"a.b.*");
314            parse!(r"a.b[*]");
315            parse!(r"@a.b[*]");
316            parse!(r#"@"a".b[*]"#);
317            parse!(r"tables.items[*].product.*.nest");
318            parse!(r#"a.b.c['item']."d"[5].e['s'].f[1+2]"#);
319        }
320
321        #[test]
322        fn tuple() {
323            parse!(r"{'a':1 , 'data': 2}.a");
324            parse!(r"{'a':1 , 'data': 2}.'a'");
325            parse!(r#"{'A':1 , 'data': 2}."A""#);
326            parse!(r"{'A':1 , 'data': 2}['a']");
327            parse!(r"{'attr': 1, 'b':2}[v || w]");
328            parse!(r"{'a':1, 'b':2}.*");
329        }
330
331        #[test]
332        fn array() {
333            parse!(r"[1,2,3][0]");
334            parse!(r"[1,2,3][1 + 1]");
335            parse!(r"[1,2,3][*]");
336        }
337
338        #[test]
339        fn query() {
340            parse!(r"(SELECT a FROM t).a");
341            parse!(r"(SELECT a FROM t).'a'");
342            parse!(r#"(SELECT a FROM t)."a""#);
343            parse!(r"(SELECT a FROM t)['a']");
344            parse!(r"(SELECT a FROM t).*");
345            parse!(r"(SELECT a FROM t)[*]");
346        }
347
348        #[test]
349        fn function_call() {
350            parse!(r"foo(x, y).a");
351            parse!(r"foo(x, y).*");
352            parse!(r"foo(x, y)[*]");
353            parse!(r"foo(x, y)[5]");
354            parse!(r"foo(x, y).a.*");
355            parse!(r"foo(x, y)[*].*.b[5]");
356        }
357
358        #[test]
359        fn test_pathexpr_struct() {
360            let res = parse!(r#"a.b.c['item']."d"[5].e['s'].f[1+2]"#);
361
362            if let ast::AstNode {
363                node:
364                    ast::TopLevelQuery {
365                        query:
366                            ast::AstNode {
367                                node:
368                                    ast::Query {
369                                        set:
370                                            ast::AstNode {
371                                                node: ast::QuerySet::Expr(ref e),
372                                                ..
373                                            },
374                                        ..
375                                    },
376                                ..
377                            },
378                        ..
379                    },
380                ..
381            } = res
382            {
383                if let ast::Expr::Path(p) = &**e {
384                    assert_eq!(9, p.node.steps.len());
385                } else {
386                    panic!("PathExpr test failed!");
387                }
388            } else {
389                panic!("PathExpr test failed!");
390            }
391        }
392
393        #[test]
394        #[should_panic]
395        fn erroneous() {
396            parse!(r"a.b.['item']");
397            parse!(r"a.b.{'a': 1, 'b': 2}.a");
398            parse!(r"a.b.[1, 2, 3][2]");
399            parse!(r"a.b.[*]");
400        }
401    }
402
403    mod sfw {
404        use super::*;
405
406        #[test]
407        fn selectstar() {
408            parse!("SELECT *");
409        }
410
411        #[test]
412        fn select1() {
413            parse!("SELECT g");
414        }
415
416        #[test]
417        fn select_list() {
418            parse!("SELECT g, k as ck, h");
419        }
420
421        #[test]
422        fn fun_call() {
423            parse!(r"fun_call('bar', 1,2,3,4,5,'foo')");
424        }
425
426        #[test]
427        fn select3() {
428            parse!("SELECT g, k, function('2') as fn_result");
429        }
430
431        #[test]
432        fn group() {
433            parse!("SELECT g FROM data GROUP BY a");
434        }
435
436        #[test]
437        fn group_complex() {
438            parse!("SELECT g FROM data GROUP BY a AS x, b + c AS y, foo(d) AS z GROUP AS g");
439        }
440
441        #[test]
442        fn order_by() {
443            parse!(r"SELECT a FROM tb ORDER BY PRESERVE");
444            parse!(r"SELECT a FROM tb ORDER BY rk1");
445            parse!(r"SELECT a FROM tb ORDER BY rk1 ASC, rk2 DESC");
446        }
447
448        #[test]
449        fn where_simple() {
450            parse!(r"SELECT a FROM tb WHERE hk = 1");
451        }
452
453        #[test]
454        fn where_boolean() {
455            parse!(r"SELECT a FROM tb WHERE t1.super OR test(t2.name, t1.name) AND t1.id = t2.id");
456        }
457
458        #[test]
459        fn limit() {
460            parse!(r"SELECT * FROM a LIMIT 10");
461        }
462
463        #[test]
464        fn offset() {
465            parse!(r"SELECT * FROM a OFFSET 10");
466        }
467
468        #[test]
469        fn limit_offset() {
470            parse!(r"SELECT * FROM a LIMIT 10 OFFSET 2");
471        }
472
473        #[test]
474        fn complex() {
475            let q = r"
476            SELECT (
477                SELECT numRec, data
478                FROM delta_full_transactions.deltas delta0,
479                (
480                    SELECT u.id, review, rindex
481                    FROM delta1.data as u CROSS JOIN UNPIVOT u.reviews as review AT rindex
482                ) as data,
483                delta2.numRec as numRec
484            )
485            AS deltas FROM SOURCE_VIEW_DELTA_FULL_TRANSACTIONS delta_full_transactions
486            ";
487            parse!(q);
488        }
489
490        #[test]
491        fn select_with_case() {
492            parse!(r"SELECT a WHERE CASE WHEN x <> 0 THEN y/x > 1.5 ELSE false END");
493            parse!(
494                r"SELECT a,
495                    CASE WHEN a=1 THEN 'one'
496                         WHEN a=2 THEN 'two'
497                         ELSE 'other'
498                    END
499                    FROM test"
500            );
501
502            parse!(
503                r#"SELECT VALUE
504                    {
505                        'locationType': R.LocationType,
506                        'Location': (
507                            CASE WHEN id IS NOT NULL THEN
508                                (SELECT VALUE (CASE WHEN R.LocationType = 'z' THEN n ELSE d END)
509                                FROM R.Scope AS scope WHERE scope.name = id)
510                            ELSE
511                                (SELECT VALUE (CASE WHEN R.LocationType = 'z' THEN n ELSE d END)
512                                FROM R.Scope AS scope WHERE scope.name = someZone)
513                            END
514                        ),
515                        'marketType' : MarketInfo.marketType,
516                    }
517                    FROM UNPIVOT R.returnValueMap.success AS "list" AT symb"#
518            );
519        }
520
521        #[test]
522        fn select_with_cross_join_and_at() {
523            parse!(r"SELECT * FROM a AS a CROSS JOIN c AS c AT q");
524        }
525
526        #[test]
527        fn select_with_at_and_cross_join_and_at() {
528            parse!(r"SELECT * FROM a AS a AT b CROSS JOIN c AS c AT q");
529        }
530
531        #[test]
532        fn multiline_with_comments() {
533            parse!(
534                r"SELECT * FROM hr.employees               -- T1
535                                  UNION
536                                  SELECT title FROM engineering.employees  -- T2"
537            );
538        }
539    }
540
541    mod set_ops {
542        use super::*;
543        use partiql_common::node::NullIdGenerator;
544
545        impl<'input> ParserState<'input, NullIdGenerator> {
546            pub(crate) fn new_null_id() -> ParserState<'input, NullIdGenerator> {
547                ParserState::with_id_gen(NullIdGenerator::default())
548            }
549        }
550
551        fn parse_partiql_null_id(s: &str) -> AstResult<'_> {
552            super::parse_partiql_with_state(s, ParserState::new_null_id())
553        }
554
555        // parse partiql query with all AST nodes having an id of `0` for ease of comparison regardless
556        //   of parse order
557        macro_rules! parse_null_id {
558            ($q:expr) => {{
559                let res = parse_partiql_null_id($q);
560                println!("{:#?}", res);
561                match res {
562                    Ok(data) => data.ast,
563                    _ => panic!("{:?}", res),
564                }
565            }};
566        }
567
568        #[test]
569        fn set_ops() {
570            parse!(
571                r"(SELECT * FROM a LIMIT 10 OFFSET 2) UNION SELECT * FROM b INTERSECT c EXCEPT SELECT * FROM d"
572            );
573        }
574
575        #[test]
576        fn union_prec() {
577            let l = parse_null_id!(r"a union b union c");
578            let r = parse_null_id!(r"(a union b) union c");
579            assert_eq!(l, r);
580        }
581
582        // TODO: will need to fix set op precedence after parse (parser was changed to support `OUTER UNION/INTERSECT/EXCEPT`
583        #[test]
584        #[ignore]
585        fn intersec_prec() {
586            let l = parse_null_id!(r"a union b intersect c");
587            let r = parse_null_id!(r"a union (b intersect c)");
588            assert_eq!(l, r);
589        }
590
591        #[test]
592        fn limit() {
593            let l = parse_null_id!(
594                r"SELECT a FROM b UNION SELECT x FROM y ORDER BY a LIMIT 10 OFFSET 5"
595            );
596            let r = parse_null_id!(
597                r"(SELECT a FROM b UNION SELECT x FROM y) ORDER BY a LIMIT 10 OFFSET 5"
598            );
599            assert_eq!(l, r);
600            let r2 = parse_null_id!(
601                r"SELECT a FROM b UNION (SELECT x FROM y ORDER BY a LIMIT 10 OFFSET 5)"
602            );
603            assert_ne!(l, r2);
604            assert_ne!(r, r2);
605        }
606
607        #[test]
608        fn complex_set() {
609            parse_null_id!(
610                r"(SELECT a1 FROM b1 ORDER BY c1 LIMIT d1 OFFSET e1)
611                   OUTER UNION ALL
612                   (SELECT a2 FROM b2 ORDER BY c2 LIMIT d2 OFFSET e2)
613                   ORDER BY c3 LIMIT d3 OFFSET e3"
614            );
615            parse_null_id!(
616                r"(SELECT a1 FROM b1 ORDER BY c1 LIMIT d1 OFFSET e1)
617                   OUTER INTERSECT ALL
618                   (SELECT a2 FROM b2 ORDER BY c2 LIMIT d2 OFFSET e2)
619                   ORDER BY c3 LIMIT d3 OFFSET e3"
620            );
621            parse_null_id!(
622                r"(SELECT a1 FROM b1 ORDER BY c1 LIMIT d1 OFFSET e1)
623                   OUTER EXCEPT ALL
624                   (SELECT a2 FROM b2 ORDER BY c2 LIMIT d2 OFFSET e2)
625                   ORDER BY c3 LIMIT d3 OFFSET e3"
626            );
627            parse_null_id!(
628                r"(
629                       (SELECT a1 FROM b1 ORDER BY c1 LIMIT d1 OFFSET e1)
630                       UNION DISTINCT
631                       (SELECT a2 FROM b2 ORDER BY c2 LIMIT d2 OFFSET e2)
632                   )
633                   OUTER UNION ALL
634                   (SELECT a3 FROM b3 ORDER BY c3 LIMIT d3 OFFSET e3)
635                   ORDER BY c4 LIMIT d4 OFFSET e4"
636            );
637        }
638    }
639
640    mod case_expr {
641        use super::*;
642
643        #[test]
644        fn searched_case() {
645            parse!(r"CASE WHEN TRUE THEN 2 END");
646            parse!(r"CASE WHEN id IS 1 THEN 2 WHEN titanId IS 2 THEN 3 ELSE 1 END");
647            parse!(r"CASE hello WHEN id IS NOT NULL THEN (SELECT * FROM data) ELSE 1 END");
648        }
649
650        #[test]
651        #[should_panic]
652        fn searched_case_failure() {
653            parse!(r"CASE hello WHEN id IS NOT NULL THEN SELECT * FROM data ELSE 1 END");
654        }
655    }
656
657    mod nonuniform {
658        use super::*;
659
660        #[test]
661        fn position() {
662            parse!(r"position('oB' in 'FooBar')");
663        }
664
665        #[test]
666        fn substring() {
667            parse!(r"substring('FooBar' from 2 for 3)");
668            parse!(r"substring('FooBar' from 2)");
669            parse!(r"substring('FooBar' for 3)");
670        }
671
672        #[test]
673        fn trim() {
674            parse!(r"trim(LEADING 'Foo' from 'FooBar')");
675            parse!(r"trim(leading from '   Bar')");
676            parse!(r"trim(TrAiLiNg 'Bar' from 'FooBar')");
677            parse!(r"trim(TRAILING from 'Bar   ')");
678            parse!(r"trim(BOTH 'Foo' from 'FooBarBar')");
679            parse!(r"trim(botH from '   Bar   ')");
680            parse!(r"trim(from '   Bar   ')");
681        }
682
683        #[test]
684        fn cast() {
685            parse!(r"CAST(9 AS b)");
686            parse!(r"CAST(a AS VARCHAR)");
687            parse!(r"CAST(a AS VARCHAR(20))");
688            parse!(r"CAST(a AS TIME)");
689            parse!(r"CAST(a AS TIME(20))");
690            parse!(r"CAST( TRUE AS INTEGER)");
691            parse!(r"CAST( (4 in (1,2,3,4)) AS INTEGER)");
692            parse!(r"CAST(a AS TIME WITH TIME ZONE)");
693            parse!(r"CAST(a AS TIME WITH TIME ZONE)");
694            parse!(r"CAST(a AS TIME(20) WITH TIME ZONE)");
695        }
696
697        #[test]
698        fn extract() {
699            parse!(r"extract(day from a)");
700            parse!(r"extract(hour from a)");
701            parse!(r"extract(minute from a)");
702            parse!(r"extract(second from a)");
703        }
704
705        #[test]
706        fn agg() {
707            parse!(r"count(a)");
708            parse!(r"count(distinct a)");
709            parse!(r"count(all a)");
710            parse!(r"count(*)");
711        }
712
713        #[test]
714        fn composed() {
715            parse!(
716                r"cast(trim(LEADING 'Foo' from substring('BarFooBar' from 4 for 6)) AS VARCHAR(20))"
717            );
718        }
719    }
720
721    /// In the future, the following identifiers may be converted into reserved keywords. In that case,
722    /// the following tests will need to be modified.
723    mod non_reserved_keywords {
724        use super::*;
725
726        #[test]
727        fn projection_list_trim_spec() {
728            parse!(r"SELECT leading FROM t");
729            parse!(r"SELECT leading, a FROM t");
730            parse!(r"SELECT leading + trailing, b FROM t");
731            parse!(r"SELECT both + leading + trailing, a, b, c FROM t");
732        }
733
734        #[test]
735        fn from_source_trim_spec() {
736            parse!(r"SELECT leading, trailing, both FROM leading, trailing, both");
737        }
738
739        #[test]
740        fn complex_trim() {
741            parse!(
742                r"SELECT leading + trim(leading leading FROM '  hello world'), both FROM leading, trailing, both"
743            );
744        }
745
746        #[test]
747        fn user_public_domain() {
748            parse!(r"SELECT user, puBlIC, DOMAIN FROM USER, pUbLIc, domain");
749            parse!(r"USER");
750            parse!(r"pUbLIC");
751            parse!(r"domain");
752        }
753    }
754
755    mod graph {
756        use super::*;
757
758        #[test]
759        fn no_labels() {
760            parse!(r#"SELECT 1 FROM GRAPH_TABLE (my_graph MATCH ())"#);
761
762            parse!(r#"SELECT 1 FROM GRAPH_TABLE (my_graph MATCH ()) WHERE contains_value('1')"#);
763
764            parse!(
765                r#"SELECT x.info AS info FROM GRAPH_TABLE (my_graph MATCH (x)) WHERE x.name LIKE 'foo'"#
766            );
767            parse!(r#"SELECT 1 FROM GRAPH_TABLE (g MATCH -[]->) "#);
768        }
769
770        #[test]
771        fn lone_match_expr() {
772            parse!(r#"(MyGraph MATCH (x))"#);
773            parse!(r#"(MyGraph MATCH (x), (y) )"#);
774            parse!(r#"(MyGraph MATCH (x), -[u]-> )"#);
775        }
776
777        #[test]
778        fn labelled_nodes() {
779            parse!(r#"SELECT x AS target FROM (my_graph MATCH (x:Label) WHERE x.has_data = true)"#);
780        }
781
782        #[test]
783        fn edges() {
784            parse!(r#"SELECT a,b FROM (g MATCH (a:A) -[e:E]-> (b:B))"#);
785            parse!(r#"SELECT a,b FROM (g MATCH (a:A) -> (b:B))"#);
786            parse!(r#"SELECT a,b FROM (g MATCH (a:A) ~[e:E]~ (b:B))"#);
787            parse!(r#"SELECT a,b FROM (g MATCH (a:A) ~ (b:B))"#);
788            parse!(r#"SELECT a,b FROM (g MATCH (a:A) <-[e:E]- (b:B))"#);
789            parse!(r#"SELECT a,b FROM (g MATCH (a:A) <- (b:B))"#);
790            parse!(r#"SELECT a,b FROM (g MATCH (a:A) ~[e:E]~> (b:B))"#);
791            parse!(r#"SELECT a,b FROM (g MATCH (a:A) ~> (b:B))"#);
792            parse!(r#"SELECT a,b FROM (g MATCH (a:A) <~[e:E]~ (b:B))"#);
793            parse!(r#"SELECT a,b FROM (g MATCH (a:A) <~ (b:B))"#);
794            parse!(r#"SELECT a,b FROM (g MATCH (a:A) <-[e:E]-> (b:B))"#);
795            parse!(r#"SELECT a,b FROM (g MATCH (a:A) <-> (b:B))"#);
796            parse!(r#"SELECT a,b FROM (g MATCH (a:A) -[e:E]- (b:B))"#);
797            parse!(r#"SELECT a,b FROM (g MATCH (a:A) - (b:B))"#);
798        }
799
800        #[test]
801        fn quantifiers() {
802            parse!(r#"SELECT a,b FROM (g MATCH (a:A)-[:edge]->*(b:B))"#);
803            parse!(r#"SELECT a,b FROM (g MATCH (a:A)<-[:edge]-+(b:B))"#);
804            parse!(r#"SELECT a,b FROM (g MATCH (a:A)~[:edge]~{5,}(b:B))"#);
805            parse!(r#"SELECT a,b FROM (g MATCH (a:A)-[e:edge]-{2,6}(b:B))"#);
806            parse!(r#"SELECT a,b FROM (g MATCH (a:A)->*(b:B))"#);
807            parse!(r#"SELECT a,b FROM (g MATCH (a:A)<-+(b:B))"#);
808            parse!(r#"SELECT a,b FROM (g MATCH (a:A)~{5,}(b:B))"#);
809            parse!(r#"SELECT a,b FROM (g MATCH (a:A)-{2,6}(b:B))"#);
810        }
811
812        #[test]
813        fn patterns() {
814            parse!(
815                r#"SELECT the_a.name AS src, the_b.name AS dest FROM (my_graph MATCH (the_a:a) -[the_y:y]-> (the_b:b) WHERE the_y.score > 10)"#
816            );
817            parse!(r#""SELECT a,b FROM (g MATCH (a)-[:has]->()-[:contains]->(b))""#);
818            parse!(
819                r#"SELECT a,b FROM GRAPH_TABLE (g MATCH (a) -[:has]-> (x), (x)-[:contains]->(b))"#
820            );
821        }
822
823        #[test]
824        fn path_var() {
825            parse!(r#"SELECT a,b FROM GRAPH_TABLE (g MATCH p = (a:A) -[e:E]-> (b:B))"#);
826        }
827
828        #[test]
829        fn paranthesized() {
830            parse!(
831                r#"SELECT a,b FROM GRAPH_TABLE (g MATCH ((a:A)-[e:Edge]->(b:A) WHERE a.owner=b.owner){2,5})"#
832            );
833            parse!(
834                r#"SELECT a,b FROM GRAPH_TABLE (g MATCH pathVar = (a:A)(()-[e:Edge]->()){1,3}(b:B))"#
835            );
836            parse!(r#"SELECT a,b FROM GRAPH_TABLE (g MATCH pathVar = (a:A)(-[e:Edge]->)*(b:B))"#);
837        }
838
839        #[test]
840        fn filters() {
841            parse!(
842                r#"SELECT u as banCandidate
843                   FROM (g MATCH
844                            (p:Post Where p.isFlagged = true)
845                            <-[:createdPost]-
846                            (u:User WHERE u.isBanned = false AND u.karma < 20)
847                            -[:createdComment]->
848                            (c:Comment WHERE c.isFlagged = true)
849                          WHERE p.title LIKE '%considered harmful%')"#
850            );
851            parse!(
852                r#"SELECT u as banCandidate
853                   FROM (g MATCH
854                            (p:Post Where p.isFlagged = true)
855                            <-[:createdPost]-
856                            (u:User WHERE u.isBanned = false AND u.karma < 20)
857                            -[:createdComment]->
858                            (c:Comment WHERE c.isFlagged = true)
859                         )
860                   WHERE p.title LIKE '%considered harmful%'"#
861            );
862        }
863
864        #[test]
865        fn path_mode() {
866            parse!(
867                r#"SELECT p FROM (g MATCH p = TRAIL (a WHERE a.owner='Dave') -[t:Transfer]-> * (b WHERE b.owner='Aretha'))"#
868            );
869            parse!(
870                r#"SELECT p FROM (g MATCH p = SIMPLE (a WHERE a.owner='Dave') -[t:Transfer]-> * (b WHERE b.owner='Aretha'))"#
871            );
872            parse!(
873                r#"SELECT p FROM (g MATCH p = ACYCLIC (a WHERE a.owner='Dave') -[t:Transfer]-> * (b WHERE b.owner='Aretha'))"#
874            );
875        }
876
877        #[test]
878        fn search_prefix() {
879            parse!(
880                r#"SELECT p FROM (g MATCH p = ANY SHORTEST (a WHERE a.owner='Dave') -[t:Transfer]-> * (b WHERE b.owner='Aretha'))"#
881            );
882            parse!(
883                r#"SELECT p FROM (g MATCH p = ALL SHORTEST (a WHERE a.owner='Dave') -[t:Transfer]-> * (b WHERE b.owner='Aretha'))"#
884            );
885            parse!(
886                r#"SELECT p FROM (g MATCH p = ANY (a WHERE a.owner='Dave') -[t:Transfer]-> * (b WHERE b.owner='Aretha'))"#
887            );
888            parse!(
889                r#"SELECT p FROM (g MATCH p = ANY 5 (a WHERE a.owner='Dave') -[t:Transfer]-> * (b WHERE b.owner='Aretha'))"#
890            );
891            parse!(
892                r#"SELECT p FROM (g MATCH p = SHORTEST 5 (a WHERE a.owner='Dave') -[t:Transfer]-> * (b WHERE b.owner='Aretha'))"#
893            );
894            parse!(
895                r#"SELECT p FROM (g MATCH p = SHORTEST 5 GROUP (a WHERE a.owner='Dave') -[t:Transfer]-> * (b WHERE b.owner='Aretha'))"#
896            );
897        }
898
899        #[test]
900        fn match_and_join() {
901            parse!(
902                r#"SELECT a,b,c, t1.x as x, t2.y as y FROM GRAPH_TABLE (g MATCH (a) -> (b), (a) -> (c)), table1 as t1, table2 as t2"#
903            );
904            parse!(
905                r#"SELECT a,b,c, t1.x as x, t2.y as y FROM table1 as t1, table2 as t2, GRAPH_TABLE (g MATCH (a) -> (b), (a) -> (c))"#
906            );
907        }
908
909        #[test]
910        fn union() {
911            parse!(r#"(MyGraph MATCH (x)) UNION SELECT * FROM tbl1"#);
912            parse!(r#"SELECT * FROM tbl1 UNION (MyGraph MATCH (x))"#);
913        }
914
915        #[test]
916        fn etc() {
917            parse!("SELECT * FROM (g MATCH ALL SHORTEST ( (x)-[e]->*(y) ))");
918            parse!("SELECT * FROM (g MATCH ALL SHORTEST ( TRAIL (x)-[e]->*(y) ))");
919        }
920    }
921
922    mod errors {
923        use super::*;
924        use crate::error::{LexError, UnexpectedToken, UnexpectedTokenData};
925        use partiql_common::syntax::location::{Located, Location};
926        use std::borrow::Cow;
927
928        #[test]
929        fn eof() {
930            let res = parse_partiql(r"SELECT");
931            assert!(res.is_err());
932            let err_data = res.unwrap_err();
933            assert_eq!(1, err_data.errors.len());
934            assert_eq!(
935                err_data.errors[0],
936                ParseError::UnexpectedEndOfInput(BytePosition::from(6))
937            );
938        }
939
940        #[test]
941        fn unterminated_ion_unicode() {
942            let q = r"/`܋";
943            let res = parse_partiql(q);
944            assert!(res.is_err());
945            let err_data = res.unwrap_err();
946            assert_eq!(2, err_data.errors.len());
947            assert_eq!(
948                err_data.errors[0],
949                ParseError::UnexpectedToken(UnexpectedToken {
950                    inner: UnexpectedTokenData {
951                        token: Cow::from("/")
952                    },
953                    location: Location {
954                        start: BytePosition::from(0),
955                        end: BytePosition::from(1),
956                    },
957                })
958            );
959            assert_eq!(
960                err_data.errors[1],
961                ParseError::LexicalError(Located {
962                    inner: LexError::UnterminatedDocLiteral,
963                    location: Location {
964                        start: BytePosition::from(1),
965                        end: BytePosition::from(4),
966                    },
967                })
968            );
969        }
970    }
971}