1mod 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)] #[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
52pub(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 .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 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 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 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 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 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 #[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 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}