1pub mod ast;
21mod lexer;
22mod parser;
23
24use crate::error::PyQLSyntaxError;
25
26pub use ast::{Expr, Stmt};
27
28pub fn parse(input: &str) -> Result<Stmt, PyQLSyntaxError> {
29 let tokens = lexer::Lexer::new(input).tokenize()?;
30 parser::Parser::new(tokens).parse_stmt()
31}
32
33pub fn parse_script(input: &str) -> Result<Vec<Stmt>, PyQLSyntaxError> {
35 let tokens = lexer::Lexer::new(input).tokenize()?;
36 parser::Parser::new(tokens).parse_script()
37}
38
39pub fn parse_expr(input: &str) -> Result<Expr, PyQLSyntaxError> {
42 let tokens = lexer::Lexer::new(input).tokenize()?;
43 parser::Parser::new(tokens).parse_expr()
44}
45
46pub fn parse_pointer_expr(input: &str) -> Result<Expr, PyQLSyntaxError> {
57 let tokens = lexer::Lexer::new(input).tokenize()?;
58 parser::Parser::new(tokens).parse_pointer_expr()
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64 use ast::*;
65
66 #[test]
67 fn a_module_under_std_takes_its_full_spelling() {
68 let expr = parse_expr("<std::cal::relative_duration>'30 days'").unwrap();
69 let Expr::TypeCast(cast) = expr else { panic!("{expr:?}") };
70 assert_eq!(cast.ty.as_named(), Some((Some("cal"), "relative_duration")));
71 }
72
73 #[test]
74 fn an_insert_needs_no_shape() {
75 let Stmt::Insert(ins) = parse("insert Person").unwrap() else {
76 panic!()
77 };
78 assert!(ins.shape.is_empty());
79 }
80
81 #[test]
82 fn test_select_bare_type() {
83 let stmt = parse("SELECT Person").unwrap();
84 assert!(matches!(
85 stmt,
86 Stmt::Select(SelectStmt {
87 result: Expr::Path(_),
88 ..
89 })
90 ));
91 }
92
93 #[test]
94 fn test_select_with_shape() {
95 let stmt = parse("SELECT Person { name, age }").unwrap();
96 let Stmt::Select(sel) = stmt else {
97 panic!("not a select")
98 };
99 let Expr::Shape(shape) = sel.result else {
100 panic!("not a shape")
101 };
102 assert_eq!(shape.elements.len(), 2);
103 assert_eq!(shape.elements[0].path, Path::relative("name"));
104 assert_eq!(shape.elements[1].path, Path::relative("age"));
105 }
106
107 #[test]
108 fn test_select_filter_param() {
109 let stmt = parse("SELECT Person { name, age } FILTER .name = $name").unwrap();
110 let Stmt::Select(sel) = stmt else { panic!() };
111 assert!(sel.filter.is_some());
112 let filter = sel.filter.unwrap();
113 let Expr::BinOp(binop) = filter else {
114 panic!("not a binop")
115 };
116 assert_eq!(binop.op, BinOpKind::Eq);
117 assert!(matches!(binop.left, Expr::Path(Path { partial: true, .. })));
118 assert!(matches!(binop.right, Expr::Parameter(_)));
119 }
120
121 #[test]
122 fn test_shape_element_bare_select_without_parens() {
123 let stmt = parse("SELECT Person { primary := SELECT .emails FILTER .primary = true LIMIT 1 }").unwrap();
124 let Stmt::Select(sel) = stmt else { panic!() };
125 let Expr::Shape(shape) = sel.result else {
126 panic!("not a shape")
127 };
128 assert_eq!(shape.elements.len(), 1);
129 let Some(Expr::SubQuery(inner)) = &shape.elements[0].compexpr else {
130 panic!("not a subquery")
131 };
132 let Stmt::Select(inner) = inner.as_ref() else { panic!() };
133 assert!(inner.filter.is_some());
134 assert!(inner.limit.is_some());
135 }
136
137 #[test]
138 fn test_bare_select_stops_at_shape_separator() {
139 let stmt = parse("SELECT Person { a := SELECT .emails LIMIT 1, b := .name }").unwrap();
140 let Stmt::Select(sel) = stmt else { panic!() };
141 let Expr::Shape(shape) = sel.result else {
142 panic!("not a shape")
143 };
144 assert_eq!(shape.elements.len(), 2);
145 assert!(matches!(shape.elements[1].compexpr, Some(Expr::Path(_))));
146 }
147
148 #[test]
149 fn test_bare_with_in_expression_position() {
150 let expr = parse_expr("WITH n := 1 SELECT n").unwrap();
151 assert!(matches!(expr, Expr::SubQuery(_)));
152 }
153
154 #[test]
155 fn test_bare_for_union_in_expression_position() {
156 let expr = parse_expr("FOR x IN {1, 2} UNION (x + 1)").unwrap();
157 assert!(matches!(expr, Expr::SubQuery(_)));
158 }
159
160 #[test]
161 fn test_cast_may_declare_a_parameter_cardinality() {
162 for query in ["<optional std::str>$token", "<required std::str>$token"] {
163 let expr = parse_expr(query).unwrap_or_else(|e| panic!("{query}: {e}"));
164 assert!(matches!(expr, Expr::TypeCast(_)), "{query}");
165 }
166 let expr = parse_expr("1 < 2").unwrap();
168 assert!(matches!(expr, Expr::BinOp(_)));
169 }
170
171 #[test]
172 fn test_keyword_used_as_a_name_keeps_its_written_casing() {
173 let expr = parse_expr(".<order[is OrderAttribute]").unwrap();
174 let Expr::Path(path) = expr else { panic!("not a path") };
175 assert_eq!(path.steps[0], PathStep::Backlink("order".into()));
176 let expr = parse_expr(".<Order[is OrderAttribute]").unwrap();
177 let Expr::Path(path) = expr else { panic!("not a path") };
178 assert_eq!(path.steps[0], PathStep::Backlink("Order".into()));
179 }
180
181 #[test]
182 fn test_string_literal_preserves_multibyte_utf8() {
183 let expr = parse_expr("'I ❤️ Pylon!'").unwrap();
188 assert!(matches!(expr, Expr::Literal(Literal::Str(s)) if s == "I ❤️ Pylon!"));
189 }
190
191 #[test]
192 fn test_positional_param_parsed() {
193 let expr = parse_expr("$0").unwrap();
194 assert!(matches!(expr, Expr::Parameter(n) if n == "0"));
195 }
196
197 #[test]
198 fn test_multiple_positional_params_parsed() {
199 let stmt = parse("SELECT Person FILTER .name = $0 AND .age > $1").unwrap();
200 let Stmt::Select(sel) = stmt else { panic!() };
201 let filter = sel.filter.unwrap();
202 let Expr::BinOp(outer) = filter else {
203 panic!("not a binop")
204 };
205 let Expr::BinOp(left) = outer.left else {
206 panic!("left not binop")
207 };
208 assert!(matches!(left.right, Expr::Parameter(n) if n == "0"));
209 let Expr::BinOp(right) = outer.right else {
210 panic!("right not binop")
211 };
212 assert!(matches!(right.right, Expr::Parameter(n) if n == "1"));
213 }
214
215 #[test]
216 fn test_select_nested_shape() {
217 let stmt = parse("SELECT Person { name, posts { title, body } }").unwrap();
218 let Stmt::Select(sel) = stmt else { panic!() };
219 let Expr::Shape(shape) = sel.result else { panic!() };
220 assert_eq!(shape.elements.len(), 2);
221 let posts = &shape.elements[1];
222 assert_eq!(posts.path, Path::relative("posts"));
223 assert!(posts.nested.is_some());
224 assert_eq!(posts.nested.as_ref().unwrap().len(), 2);
225 }
226
227 #[test]
228 fn test_select_set_literal() {
229 let stmt = parse("SELECT {1, 2, 3}").unwrap();
230 let Stmt::Select(sel) = stmt else { panic!() };
231 let Expr::Set(elems) = sel.result else {
232 panic!("expected Set")
233 };
234 assert_eq!(elems.len(), 3);
235 assert!(matches!(elems[0], Expr::Literal(Literal::Int(1))));
236 }
237
238 #[test]
239 fn test_select_set_literal_single() {
240 let stmt = parse("SELECT {42}").unwrap();
241 let Stmt::Select(sel) = stmt else { panic!() };
242 let Expr::Set(elems) = sel.result else {
243 panic!("expected Set")
244 };
245 assert_eq!(elems.len(), 1);
246 }
247
248 #[test]
249 fn test_analyze_wraps_inner_stmt() {
250 let stmt = parse("analyze select Person { name }").unwrap();
251 let Stmt::Analyze(inner) = stmt else {
252 panic!("expected Analyze")
253 };
254 assert!(matches!(*inner, Stmt::Select(_)));
255 }
256
257 #[test]
258 fn test_analyze_is_case_insensitive_and_not_reserved_elsewhere() {
259 assert!(matches!(parse("ANALYZE select Person").unwrap(), Stmt::Analyze(_)));
260 let stmt = parse("SELECT Person { name }").unwrap();
263 assert!(matches!(stmt, Stmt::Select(_)));
264 }
265
266 #[test]
267 fn test_analyze_wraps_insert_and_update_too() {
268 assert!(matches!(
269 parse("analyze insert Person { name := 'a' }").unwrap(),
270 Stmt::Analyze(inner) if matches!(*inner, Stmt::Insert(_))
271 ));
272 assert!(matches!(
273 parse("analyze update Person set { name := 'a' }").unwrap(),
274 Stmt::Analyze(inner) if matches!(*inner, Stmt::Update(_))
275 ));
276 }
277
278 #[test]
279 fn test_analyze_marker_offsets_mark_root_and_nested_shape_elements() {
280 let query = "analyze select Person { name, posts { title } }";
281 let stmt = parse(query).unwrap();
282 let Stmt::Analyze(inner) = stmt else {
283 panic!("expected Analyze")
284 };
285 let Stmt::Select(sel) = *inner else { panic!() };
286 let Expr::Shape(shape) = sel.result else { panic!() };
287
288 let root_offset = shape.marker_offset.expect("root shape should carry an offset");
290 assert_eq!(&query[root_offset..root_offset + "Person".len()], "Person");
291
292 let name_offset = shape.elements[0]
293 .marker_offset
294 .expect("name element should carry an offset");
295 assert_eq!(&query[name_offset..name_offset + "name".len()], "name");
296
297 let posts = &shape.elements[1];
298 let posts_offset = posts.marker_offset.expect("posts element should carry an offset");
299 assert_eq!(&query[posts_offset..posts_offset + "posts".len()], "posts");
300
301 let title_offset = posts.nested.as_ref().unwrap()[0]
302 .marker_offset
303 .expect("nested element should carry an offset");
304 assert_eq!(&query[title_offset..title_offset + "title".len()], "title");
305 }
306
307 #[test]
308 fn test_select_free_object() {
309 let stmt = parse("SELECT { foo := 'bar', n := 42 }").unwrap();
310 let Stmt::Select(sel) = stmt else { panic!() };
311 let Expr::Shape(sh) = sel.result else {
312 panic!("expected Shape")
313 };
314 assert!(sh.expr.is_none());
315 assert_eq!(sh.elements.len(), 2);
316 assert_eq!(sh.elements[0].path, Path::relative("foo"));
317 assert!(sh.elements[0].compexpr.is_some());
318 }
319
320 #[test]
321 fn test_select_tuple_expr() {
322 let stmt = parse("SELECT (1, 'hello')").unwrap();
323 let Stmt::Select(sel) = stmt else { panic!() };
324 assert!(matches!(sel.result, Expr::Tuple(_)));
325 }
326
327 #[test]
328 fn test_select_scalar_function() {
329 let stmt = parse("SELECT str_lower('HELLO')").unwrap();
330 let Stmt::Select(sel) = stmt else { panic!() };
331 assert!(matches!(sel.result, Expr::FunctionCall(_)));
332 }
333
334 #[test]
335 fn test_shape_splat_shallow() {
336 let stmt = parse("SELECT Person { * }").unwrap();
337 let Stmt::Select(sel) = stmt else { panic!() };
338 let Expr::Shape(sh) = &sel.result else { panic!() };
339 assert_eq!(sh.elements.len(), 1);
340 assert!(matches!(sh.elements[0].splat, Some(ast::Splat::Shallow)));
341 }
342
343 #[test]
344 fn test_shape_splat_deep() {
345 let stmt = parse("SELECT Person { ** }").unwrap();
346 let Stmt::Select(sel) = stmt else { panic!() };
347 let Expr::Shape(sh) = &sel.result else { panic!() };
348 assert_eq!(sh.elements.len(), 1);
349 assert!(matches!(sh.elements[0].splat, Some(ast::Splat::Deep)));
350 }
351
352 #[test]
353 fn test_select_order_by_limit_offset() {
354 let stmt = parse("SELECT Person { name } ORDER BY .name ASC OFFSET 10 LIMIT 5").unwrap();
355 let Stmt::Select(sel) = stmt else { panic!() };
356 assert_eq!(sel.order_by.len(), 1);
357 assert_eq!(sel.order_by[0].direction, SortDirection::Asc);
358 assert!(matches!(sel.offset, Some(Expr::Literal(Literal::Int(10)))));
359 assert!(matches!(sel.limit, Some(Expr::Literal(Literal::Int(5)))));
360 }
361
362 #[test]
363 fn test_select_for_update_defaults_to_blocking() {
364 let stmt = parse("SELECT Person FOR UPDATE").unwrap();
365 let Stmt::Select(sel) = stmt else { panic!() };
366 assert_eq!(
367 sel.lock,
368 Some(LockClause {
369 strength: LockStrength::Update,
370 wait: LockWait::Block
371 })
372 );
373 }
374
375 #[test]
376 fn test_select_for_update_skip_locked() {
377 let stmt = parse("SELECT Person FOR UPDATE SKIP LOCKED").unwrap();
378 let Stmt::Select(sel) = stmt else { panic!() };
379 assert_eq!(
380 sel.lock,
381 Some(LockClause {
382 strength: LockStrength::Update,
383 wait: LockWait::SkipLocked
384 })
385 );
386 }
387
388 #[test]
389 fn test_select_for_update_nowait() {
390 let stmt = parse("SELECT Person FOR UPDATE NOWAIT").unwrap();
391 let Stmt::Select(sel) = stmt else { panic!() };
392 assert_eq!(
393 sel.lock,
394 Some(LockClause {
395 strength: LockStrength::Update,
396 wait: LockWait::NoWait
397 })
398 );
399 }
400
401 #[test]
402 fn test_select_for_share_skip_locked_is_case_insensitive() {
403 let stmt = parse("select Person for share skip locked").unwrap();
404 let Stmt::Select(sel) = stmt else { panic!() };
405 assert_eq!(
406 sel.lock,
407 Some(LockClause {
408 strength: LockStrength::Share,
409 wait: LockWait::SkipLocked
410 })
411 );
412 }
413
414 #[test]
415 fn test_select_for_no_key_update() {
416 let stmt = parse("SELECT Person FOR NO KEY UPDATE").unwrap();
417 let Stmt::Select(sel) = stmt else { panic!() };
418 assert_eq!(
419 sel.lock,
420 Some(LockClause {
421 strength: LockStrength::NoKeyUpdate,
422 wait: LockWait::Block
423 })
424 );
425 }
426
427 #[test]
428 fn test_select_for_key_share() {
429 let stmt = parse("SELECT Person FOR KEY SHARE").unwrap();
430 let Stmt::Select(sel) = stmt else { panic!() };
431 assert_eq!(
432 sel.lock,
433 Some(LockClause {
434 strength: LockStrength::KeyShare,
435 wait: LockWait::Block
436 })
437 );
438 }
439
440 #[test]
441 fn test_select_for_update_comes_after_order_by_limit_offset() {
442 let stmt = parse("SELECT Person { name } ORDER BY .name OFFSET 1 LIMIT 5 FOR UPDATE SKIP LOCKED").unwrap();
443 let Stmt::Select(sel) = stmt else { panic!() };
444 assert_eq!(sel.order_by.len(), 1);
445 assert!(sel.offset.is_some());
446 assert!(sel.limit.is_some());
447 assert_eq!(
448 sel.lock,
449 Some(LockClause {
450 strength: LockStrength::Update,
451 wait: LockWait::SkipLocked
452 })
453 );
454 }
455
456 #[test]
457 fn test_select_with_no_lock_clause_defaults_to_none() {
458 let stmt = parse("SELECT Person").unwrap();
459 let Stmt::Select(sel) = stmt else { panic!() };
460 assert_eq!(sel.lock, None);
461 }
462
463 #[test]
464 fn test_select_for_garbage_strength_is_a_clear_error() {
465 let err = parse("SELECT Person FOR BOGUS").unwrap_err();
466 assert!(err.message.contains("UPDATE"), "unexpected: {}", err.message);
467 }
468
469 #[test]
470 fn test_select_for_no_without_key_is_a_clear_error() {
471 let err = parse("SELECT Person FOR NO UPDATE").unwrap_err();
472 assert!(err.message.contains("KEY"), "unexpected: {}", err.message);
473 }
474
475 #[test]
476 fn test_select_for_skip_without_locked_is_a_clear_error() {
477 let err = parse("SELECT Person FOR UPDATE SKIP").unwrap_err();
478 assert!(err.message.contains("LOCKED"), "unexpected: {}", err.message);
479 }
480
481 #[test]
482 fn test_boolean_operators() {
483 let stmt = parse("SELECT Person FILTER .active = true AND .age >= 18 OR .admin = true").unwrap();
484 assert!(matches!(stmt, Stmt::Select(_)));
485 }
486
487 #[test]
488 fn test_function_call() {
489 let stmt = parse("SELECT count(Person)").unwrap();
490 let Stmt::Select(sel) = stmt else { panic!() };
491 assert!(matches!(sel.result, Expr::FunctionCall(_)));
492 }
493
494 #[test]
495 fn test_type_cast() {
496 let stmt = parse("SELECT <str>$value").unwrap();
497 let Stmt::Select(sel) = stmt else { panic!() };
498 assert!(matches!(sel.result, Expr::TypeCast(_)));
499 }
500
501 #[test]
502 fn test_computed_shape_element() {
503 let stmt = parse("SELECT Person { full_name := .first ++ ' ' ++ .last }").unwrap();
504 let Stmt::Select(sel) = stmt else { panic!() };
505 let Expr::Shape(shape) = sel.result else { panic!() };
506 let el = &shape.elements[0];
507 assert!(el.compexpr.is_some());
508 }
509
510 #[test]
511 fn test_insert() {
512 let stmt = parse("INSERT Person { name := 'Alice', age := 30 }").unwrap();
513 let Stmt::Insert(ins) = stmt else { panic!() };
514 assert_eq!(ins.subject.name, "Person");
515 assert_eq!(ins.shape.len(), 2);
516 }
517
518 #[test]
519 fn test_update() {
520 let stmt = parse("UPDATE Person FILTER .name = 'Alice' SET { age := 31 }").unwrap();
521 assert!(matches!(stmt, Stmt::Update(_)));
522 }
523
524 #[test]
525 fn test_delete() {
526 let stmt = parse("DELETE Person FILTER .name = 'Alice'").unwrap();
527 assert!(matches!(stmt, Stmt::Delete(_)));
528 }
529
530 #[test]
531 fn test_if_else_postfix() {
532 let stmt = parse("SELECT 'yes' IF 1 = 1 ELSE 'no'").unwrap();
533 let Stmt::Select(sel) = stmt else { panic!() };
534 let Expr::IfElse(ie) = sel.result else {
535 panic!("expected IfElse")
536 };
537 assert!(matches!(ie.if_expr, Expr::Literal(Literal::Str(_))));
538 assert!(matches!(ie.condition, Expr::BinOp(_)));
539 assert!(matches!(ie.else_expr, Expr::Literal(Literal::Str(_))));
540 }
541
542 #[test]
543 fn test_if_then_else_prefix() {
544 let stmt = parse("SELECT IF 1 = 1 THEN 'yes' ELSE 'no'").unwrap();
545 let Stmt::Select(sel) = stmt else { panic!() };
546 let Expr::IfElse(ie) = sel.result else {
547 panic!("expected IfElse")
548 };
549 assert!(matches!(ie.if_expr, Expr::Literal(Literal::Str(_))));
550 assert!(matches!(ie.condition, Expr::BinOp(_)));
551 assert!(matches!(ie.else_expr, Expr::Literal(Literal::Str(_))));
552 }
553
554 #[test]
555 fn test_if_then_else_chained() {
556 let stmt = parse("SELECT IF 1 = 1 THEN 'a' ELSE IF 2 = 2 THEN 'b' ELSE 'c'").unwrap();
557 let Stmt::Select(sel) = stmt else { panic!() };
558 let Expr::IfElse(outer) = sel.result else {
559 panic!("expected IfElse")
560 };
561 assert!(matches!(outer.else_expr, Expr::IfElse(_)));
562 }
563
564 #[test]
565 fn test_nested_module_path_in_function_call_is_a_clear_error() {
566 let err = parse("select ext::pgcrypto::digest('encrypt this', 'sha1')").unwrap_err();
572 assert!(err.to_string().contains("too many '::' segments"), "got: {err}");
573 }
574
575 #[test]
576 fn test_nested_module_path_in_type_expr_is_a_clear_error() {
577 let err = parse_expr("x is ext::pgcrypto::SomeType").unwrap_err();
578 assert!(err.to_string().contains("too many '::' segments"), "got: {err}");
579 }
580
581 #[test]
589 fn test_unclosed_paren_names_the_missing_character_not_the_token_variant() {
590 let err = parse("select (1 + 2").unwrap_err();
591 assert_eq!(err.message, "expected ')', found end of input");
592 }
593
594 #[test]
595 fn test_unclosed_brace_names_the_missing_character_not_the_token_variant() {
596 let err = parse("select { 1").unwrap_err();
597 assert_eq!(err.message, "expected '}', found end of input");
598 }
599
600 #[test]
601 fn test_trailing_garbage_after_a_complete_statement_is_a_clear_error() {
602 let err = parse("select 1 select 2").unwrap_err();
603 assert_eq!(err.message, "unexpected 'select' after the end of the query");
604 }
605
606 #[test]
607 fn a_script_parses_every_statement() {
608 let statements = parse_script("delete Person; delete Company; select 1;").expect("a script parses");
609 assert_eq!(statements.len(), 3);
610 }
611
612 #[test]
613 fn a_single_statement_is_a_one_element_script() {
614 assert_eq!(parse_script("select 1").expect("parses").len(), 1);
615 assert_eq!(parse_script("select 1;").expect("parses").len(), 1);
616 }
617
618 #[test]
619 fn a_script_still_rejects_trailing_rubbish() {
620 assert!(parse_script("select 1; )").is_err());
621 }
622
623 #[test]
624 fn test_missing_expression_names_the_offending_token_not_its_debug_form() {
625 let err = parse_expr("1 +").unwrap_err();
626 assert_eq!(err.message, "expected an expression, found end of input");
627 }
628
629 #[test]
630 fn test_bad_statement_start_lists_keywords_in_surface_form() {
631 let err = parse("123").unwrap_err();
632 assert_eq!(
633 err.message,
634 "expected the start of a statement (with, for, select, insert, update, delete, or group), \
635 found integer literal '123'"
636 );
637 }
638}