1mod ast;
2mod error;
3mod lexer;
4mod parser;
5
6use std::fmt::Display;
7
8pub use ast::*;
9use async_trait::async_trait;
10pub use error::{ParseError, ParseResult};
11use parser::Parser;
12
13pub fn parse_query(query: &str) -> ParseResult<Statement> {
14 let mut parser = Parser::new(query);
15 parser.parse()
16}
17
18#[derive(thiserror::Error, Debug)]
19pub enum IqlError {
20 #[error("IQL query could not be parsed: {0}")]
21 MalformedIql(#[from] ParseError),
22 #[error("Not implemented")]
23 NotImplemented,
24 #[error("This action is not supported by the chosen backend")]
25 NotSupported,
26 #[error("A project with the name '{0}' already exists")]
27 ProjectAlreadyExists(String),
28 #[error("No item of type '{kind}' with the id '{id}' exists")]
29 ItemNotFound { kind: String, id: String },
30 #[error("The issue withe the name '{0}' was already closed. Reason '{1}'")]
31 IssueAlreadyClosed(String, CloseReason),
32 #[error("Field not found: {0}")]
33 FieldNotFound(String),
34 #[error("{0}")]
35 ImplementationSpecific(String),
36}
37
38#[async_trait]
39pub trait ExecutionEngine {
40 async fn execute(&mut self, query: &str) -> Result<ExecutionResult, IqlError>;
41}
42
43#[derive(Debug, Clone)]
44pub struct ExecutionResult {
45 pub affected_rows: u128,
46 pub info: Option<String>,
47}
48
49impl Display for ExecutionResult {
50 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 write!(f, "Affected Rows: {}", self.affected_rows)?;
52 if let Some(info) = &self.info {
53 write!(f, "\nInfo: {}", info)?;
54 }
55 Ok(())
56 }
57}
58
59impl From<String> for ExecutionResult {
60 fn from(s: String) -> Self {
61 Self {
62 affected_rows: 0,
63 info: Some(s),
64 }
65 }
66}
67
68impl From<&str> for ExecutionResult {
69 fn from(s: &str) -> Self {
70 Self {
71 affected_rows: 0,
72 info: Some(s.to_string()),
73 }
74 }
75}
76
77impl ExecutionResult {
78 pub fn new(rows: u128) -> Self {
79 Self {
80 affected_rows: rows,
81 info: None,
82 }
83 }
84
85 pub fn one() -> Self {
86 Self {
87 affected_rows: 1,
88 info: None,
89 }
90 }
91
92 pub fn zero() -> Self {
93 Self {
94 affected_rows: 0,
95 info: None,
96 }
97 }
98
99 pub fn with_info(mut self, info: &str) -> Self {
100 self.info = Some(info.to_string());
101 self
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn test_parse_create_user() {
111 let query = "CREATE USER john_doe WITH EMAIL 'john@example.com' NAME 'John Doe'";
112 let result = parse_query(query);
113 assert!(result.is_ok());
114
115 if let Ok(Statement::Create(CreateStatement::User {
116 username,
117 email,
118 name,
119 })) = result
120 {
121 assert_eq!(username, "john_doe");
122 assert_eq!(email, Some("john@example.com".to_string()));
123 assert_eq!(name, Some("John Doe".to_string()));
124 } else {
125 panic!("Expected CreateStatement::User");
126 }
127 }
128
129 #[test]
130 fn test_parse_create_project() {
131 let query = "CREATE PROJECT my-project WITH NAME 'My Project' DESCRIPTION 'A test project'";
132 let result = parse_query(query);
133 assert!(result.is_ok());
134 }
135
136 #[test]
137 fn test_parse_create_issue() {
138 let query = "CREATE ISSUE IN my-project WITH TITLE 'Bug found' DESCRIPTION 'Something broke' PRIORITY high ASSIGNEE john_doe";
139 let result = parse_query(query);
140 assert!(result.is_ok());
141 }
142
143 #[test]
144 fn test_parse_select_all() {
145 let query = "SELECT * FROM issues";
146 let result = parse_query(query);
147 assert!(result.is_ok());
148 }
149
150 #[test]
151 fn test_parse_select_with_where() {
152 let query = "SELECT * FROM issues WHERE status = 'open' AND priority = high";
153 let result = parse_query(query);
154 assert!(result.is_ok());
155 }
156
157 #[test]
158 fn test_parse_update() {
159 let query = "UPDATE issue my-project#123 SET status = 'closed', priority = low";
160 let result = parse_query(query);
161 assert!(result.is_ok());
162 }
163
164 #[test]
165 fn test_parse_delete() {
166 let query = "DELETE issue my-project#456";
167 let result = parse_query(query);
168 assert!(result.is_ok());
169 }
170
171 #[test]
172 fn test_parse_assign() {
173 let query = "ASSIGN issue my-project#789 TO alice";
174 let result = parse_query(query);
175 assert!(result.is_ok());
176 }
177
178 #[test]
179 fn test_parse_close() {
180 let query = "CLOSE issue my-project#101";
181 let result = parse_query(query);
182 assert!(result.is_ok());
183 }
184
185 #[test]
186 fn test_parse_comment() {
187 let query = "COMMENT ON issue my-project#202 WITH 'This is a comment'";
188 let result = parse_query(query);
189 assert!(result.is_ok());
190 }
191
192 #[test]
193 fn test_parse_complex_query() {
194 let query = "SELECT title, status, assignee FROM issues WHERE project = 'backend' AND (priority = high OR status = 'critical') ORDER BY created_at DESC LIMIT 10";
195 let result = parse_query(query);
196 if let Err(ref e) = result {
197 eprintln!("Parse error: {}", e);
198 }
199 assert!(result.is_ok());
200 }
201
202 #[test]
203 fn test_parse_project_qualified_issue() {
204 let query = "CLOSE issue my-project#42 WITH done";
205 let result = parse_query(query);
206 assert!(result.is_ok());
207 }
208
209 #[test]
210 fn test_parse_labels() {
211 let query = "CREATE ISSUE IN frontend WITH TITLE 'Test' LABELS [bug, urgent, frontend]";
212 let result = parse_query(query);
213 assert!(result.is_ok());
214 }
215
216 #[test]
217 fn test_parse_multiple_field_updates() {
218 let query = "UPDATE issue my-project#100 SET status = 'closed', priority = medium, assignee = 'bob'";
219 let result = parse_query(query);
220 assert!(result.is_ok());
221 }
222
223 #[test]
224 fn test_parse_in_operator() {
225 let query = "SELECT * FROM issues WHERE priority IN (critical, high)";
226 let result = parse_query(query);
227 assert!(result.is_ok());
228 }
229
230 #[test]
231 fn test_parse_is_null() {
232 let query = "SELECT * FROM issues WHERE assignee IS NULL";
233 let result = parse_query(query);
234 assert!(result.is_ok());
235 }
236
237 #[test]
238 fn test_parse_is_not_null() {
239 let query = "SELECT * FROM issues WHERE assignee IS NOT NULL";
240 let result = parse_query(query);
241 assert!(result.is_ok());
242 }
243
244 #[test]
245 fn test_parse_not_operator() {
246 let query = "SELECT * FROM issues WHERE NOT status = 'closed'";
247 let result = parse_query(query);
248 assert!(result.is_ok());
249 }
250
251 #[test]
252 fn test_parse_like_operator() {
253 let query = "SELECT * FROM issues WHERE title LIKE '%bug%'";
254 let result = parse_query(query);
255 assert!(result.is_ok());
256 }
257
258 #[test]
259 fn test_parse_order_asc() {
260 let query = "SELECT * FROM issues ORDER BY created_at ASC";
261 let result = parse_query(query);
262 assert!(result.is_ok());
263 }
264
265 #[test]
266 fn test_parse_offset() {
267 let query = "SELECT * FROM issues LIMIT 10 OFFSET 20";
268 let result = parse_query(query);
269 assert!(result.is_ok());
270 }
271
272 #[test]
273 fn test_parse_all_priorities() {
274 let queries = vec![
275 "CREATE ISSUE IN test WITH TITLE 'Test' PRIORITY critical",
276 "CREATE ISSUE IN test WITH TITLE 'Test' PRIORITY high",
277 "CREATE ISSUE IN test WITH TITLE 'Test' PRIORITY medium",
278 "CREATE ISSUE IN test WITH TITLE 'Test' PRIORITY low",
279 ];
280
281 for query in queries {
282 let result = parse_query(query);
283 assert!(result.is_ok(), "Failed to parse: {}", query);
284 }
285 }
286
287 #[test]
288 fn test_parse_all_entity_types() {
289 let queries = vec![
290 "SELECT * FROM users",
291 "SELECT * FROM projects",
292 "SELECT * FROM issues",
293 "SELECT * FROM comments",
294 ];
295
296 for query in queries {
297 let result = parse_query(query);
298 assert!(result.is_ok(), "Failed to parse: {}", query);
299 }
300 }
301
302 #[test]
303 fn test_integration_workflow() {
304 let queries = vec![
305 "CREATE USER alice WITH EMAIL 'alice@test.com' NAME 'Alice'",
306 "CREATE PROJECT backend WITH NAME 'Backend' OWNER alice",
307 "CREATE ISSUE IN backend WITH TITLE 'Bug fix' PRIORITY high ASSIGNEE alice",
308 "SELECT * FROM issues WHERE assignee = 'alice'",
309 "ASSIGN issue backend#1 TO alice",
310 "COMMENT ON ISSUE backend#1 WITH 'Working on it'",
311 "UPDATE issue backend#1 SET status = 'in-progress'",
312 "CLOSE issue backend#1 WITH done",
313 ];
314
315 for query in queries {
316 let result = parse_query(query);
317 assert!(result.is_ok(), "Failed to parse: {}", query);
318 }
319 }
320
321 #[test]
322 fn test_empty_labels() {
323 let query = "CREATE ISSUE IN test WITH TITLE 'Test' LABELS []";
324 let result = parse_query(query);
325 assert!(result.is_ok());
326 if let Ok(Statement::Create(CreateStatement::Issue { labels, .. })) = result {
327 assert_eq!(labels.len(), 0);
328 }
329 }
330
331 #[test]
332 fn test_string_with_multiple_escapes() {
333 let query = r"CREATE ISSUE IN test WITH TITLE 'Line1\nLine2\tTab\rReturn\\Backslash'";
334 let result = parse_query(query);
335 assert!(result.is_ok());
336 }
337
338 #[test]
339 fn test_negative_numbers() {
340 let query = "UPDATE issue test#100 SET count = -50";
341 let result = parse_query(query);
342 assert!(result.is_ok());
343 }
344
345 #[test]
346 fn test_float_values() {
347 let query = "UPDATE issue test#100 SET score = 3.14159";
348 let result = parse_query(query);
349 assert!(result.is_ok());
350 }
351
352 #[test]
353 fn test_deeply_nested_filters() {
354 let query = "SELECT * FROM issues WHERE ((a = 1 AND b = 2) OR (c = 3 AND d = 4)) AND e = 5";
355 let result = parse_query(query);
356 assert!(result.is_ok());
357 }
358
359 #[test]
360 fn test_not_with_parentheses() {
361 let query = "SELECT * FROM issues WHERE NOT (status = 'closed' OR status = 'archived')";
362 let result = parse_query(query);
363 assert!(result.is_ok());
364 }
365
366 #[test]
367 fn test_in_with_priorities() {
368 let query = "SELECT * FROM issues WHERE priority IN (critical, high, medium)";
369 let result = parse_query(query);
370 assert!(result.is_ok());
371 }
372
373 #[test]
374 fn test_in_with_strings() {
375 let query = "SELECT * FROM issues WHERE status IN ('open', 'in-progress', 'review')";
376 let result = parse_query(query);
377 assert!(result.is_ok());
378 }
379
380 #[test]
381 fn test_comparison_operators() {
382 let queries = vec![
383 "SELECT * FROM issues WHERE count > 10",
384 "SELECT * FROM issues WHERE count < 5",
385 "SELECT * FROM issues WHERE count >= 10",
386 "SELECT * FROM issues WHERE count <= 5",
387 "SELECT * FROM issues WHERE status != 'closed'",
388 ];
389 for query in queries {
390 let result = parse_query(query);
391 assert!(
392 result.is_ok(),
393 "Failed query '{}' with error {}",
394 query,
395 result.err().unwrap()
396 );
397 }
398 }
399
400 #[test]
401 fn test_case_insensitive_keywords() {
402 let queries = vec![
403 "select * from issues",
404 "SELECT * FROM ISSUES",
405 "SeLeCt * FrOm IsSuEs",
406 "create user alice",
407 "CREATE USER ALICE",
408 ];
409 for query in queries {
410 let result = parse_query(query);
411 assert!(
412 result.is_ok(),
413 "Failed query '{}' with error {}",
414 query,
415 result.err().unwrap()
416 );
417 }
418 }
419
420 #[test]
421 fn test_hyphenated_identifiers() {
422 let queries = vec![
423 "CREATE USER my-user-name",
424 "CREATE PROJECT my-cool-project",
425 "SELECT * FROM issues WHERE project = 'my-backend-api'",
426 ];
427 for query in queries {
428 let result = parse_query(query);
429 assert!(
430 result.is_ok(),
431 "Failed query '{}' with error {}",
432 query,
433 result.err().unwrap()
434 );
435 }
436 }
437
438 #[test]
439 fn test_keywords_as_field_names() {
440 let queries = vec![
441 "SELECT project, user, issue FROM issues",
442 "SELECT * FROM issues WHERE project = 'test'",
443 "SELECT * FROM issues WHERE user = 'alice'",
444 "UPDATE issue test#1 SET comment = 'test'",
445 ];
446 for query in queries {
447 let result = parse_query(query);
448 assert!(
449 result.is_ok(),
450 "Failed query '{}' with error {}",
451 query,
452 result.err().unwrap()
453 );
454 }
455 }
456
457 #[test]
458 fn test_all_field_keywords_in_create() {
459 let query = "CREATE ISSUE IN test WITH TITLE 'T' DESCRIPTION 'D' PRIORITY high ASSIGNEE alice LABELS [bug]";
460 let result = parse_query(query);
461 assert!(result.is_ok());
462 }
463
464 #[test]
465 fn test_all_delete_targets() {
466 let queries = vec![
467 "DELETE user alice",
468 "DELETE project backend",
469 "DELETE issue backend#456",
470 "DELETE comment 789",
471 ];
472 for query in queries {
473 let result = parse_query(query);
474 assert!(
475 result.is_ok(),
476 "Failed query '{}' with error {}",
477 query,
478 result.err().unwrap()
479 );
480 }
481 }
482
483 #[test]
484 fn test_all_update_targets() {
485 let queries = vec![
486 "UPDATE user alice SET email = 'new@test.com'",
487 "UPDATE project backend SET name = 'New Name'",
488 "UPDATE issue backend#123 SET status = 'closed'",
489 "UPDATE issue backend#456 SET priority = high",
490 "UPDATE comment C789 SET content = 'updated'",
491 ];
492 for query in queries {
493 let result = parse_query(query);
494 assert!(
495 result.is_ok(),
496 "Failed query '{}' with error {}",
497 query,
498 result.err().unwrap()
499 );
500 }
501 }
502
503 #[test]
504 fn test_multiple_columns_select() {
505 let query =
506 "SELECT id, title, status, priority, assignee, created_at, updated_at FROM issues";
507 let result = parse_query(query);
508 assert!(result.is_ok());
509 if let Ok(Statement::Select(select)) = result {
510 assert_eq!(select.columns.len(), 7);
511 }
512 }
513
514 #[test]
515 fn test_limit_and_offset_together() {
516 let query = "SELECT * FROM issues LIMIT 50 OFFSET 100";
517 let result = parse_query(query);
518 assert!(result.is_ok());
519 if let Ok(Statement::Select(select)) = result {
520 assert_eq!(select.limit, Some(50));
521 assert_eq!(select.offset, Some(100));
522 }
523 }
524
525 #[test]
526 fn test_order_by_asc_explicit() {
527 let query = "SELECT * FROM issues ORDER BY created_at ASC";
528 let result = parse_query(query);
529 assert!(result.is_ok());
530 if let Ok(Statement::Select(select)) = result {
531 assert!(select.order_by.is_some());
532 let order = select.order_by.unwrap();
533 assert_eq!(order.direction, OrderDirection::Asc);
534 }
535 }
536
537 #[test]
538 fn test_boolean_values() {
539 let queries = vec![
540 "UPDATE issue backend#1 SET active = true",
541 "UPDATE issue backend#1 SET archived = false",
542 "SELECT * FROM issues WHERE active = TRUE",
543 "SELECT * FROM issues WHERE archived = FALSE",
544 ];
545 for query in queries {
546 let result = parse_query(query);
547 assert!(
548 result.is_ok(),
549 "Failed query '{}' with error {}",
550 query,
551 result.err().unwrap()
552 );
553 }
554 }
555
556 #[test]
557 fn test_null_values() {
558 let queries = vec![
559 "UPDATE issue backend#1 SET assignee = null",
560 "SELECT * FROM issues WHERE assignee = NULL",
561 ];
562 for query in queries {
563 let result = parse_query(query);
564 assert!(
565 result.is_ok(),
566 "Failed query '{}' with error {}",
567 query,
568 result.err().unwrap()
569 );
570 }
571 }
572
573 #[test]
574 fn test_comment_statement() {
575 let query = "COMMENT ON ISSUE backend#123 WITH 'Quick comment'";
576 let result = parse_query(query);
577 assert!(result.is_ok());
578 }
579
580 #[test]
581 fn test_close_with_and_without_reason() {
582 let queries = vec![
583 "CLOSE issue backend#123",
584 "CLOSE issue backend#123 WITH done",
585 "CLOSE issue backend#456 WITH duplicate",
586 ];
587 for query in queries {
588 let result = parse_query(query);
589 assert!(
590 result.is_ok(),
591 "Failed query '{}' with error {}",
592 query,
593 result.err().unwrap()
594 );
595 }
596 }
597
598 #[test]
599 fn test_empty_string_value() {
600 let query = "UPDATE issue backend#1 SET description = ''";
601 let result = parse_query(query);
602 assert!(result.is_ok());
603 }
604
605 #[test]
606 fn test_special_characters_in_strings() {
607 let query =
608 r"CREATE ISSUE IN test WITH TITLE 'Special chars: !@#$%^&*()_+-={}[]|:;<>?,./~`'";
609 let result = parse_query(query);
610 assert!(result.is_ok());
611 }
612
613 #[test]
614 fn test_double_quotes_in_strings() {
615 let query = r#"CREATE ISSUE IN test WITH TITLE "Double quoted string""#;
616 let result = parse_query(query);
617 assert!(result.is_ok());
618 }
619
620 #[test]
621 fn test_labels_with_hyphens() {
622 let query =
623 "CREATE ISSUE IN test WITH TITLE 'Test' LABELS [high-priority, bug-fix, ui-component]";
624 let result = parse_query(query);
625 assert!(result.is_ok());
626 }
627
628 #[test]
629 fn test_complex_real_world_query() {
630 let query = r#"
631 SELECT title, status, priority, assignee, created_at
632 FROM issues
633 WHERE (priority = critical OR priority = high)
634 AND status IN ('open', 'in-progress')
635 AND assignee IS NOT NULL
636 AND project = 'backend'
637 ORDER BY priority DESC
638 LIMIT 25
639 OFFSET 0
640 "#;
641 let result = parse_query(query);
642 assert!(result.is_ok());
643 }
644
645 #[test]
646 fn test_minimal_create_user() {
647 let query = "CREATE USER alice";
648 let result = parse_query(query);
649 assert!(result.is_ok());
650 if let Ok(Statement::Create(CreateStatement::User { email, name, .. })) = result {
651 assert!(email.is_none());
652 assert!(name.is_none());
653 }
654 }
655
656 #[test]
657 fn test_minimal_create_project() {
658 let query = "CREATE PROJECT test";
659 let result = parse_query(query);
660 assert!(result.is_ok());
661 }
662
663 #[test]
664 fn test_select_from_all_entities() {
665 for entity in &["users", "projects", "issues", "comments"] {
666 let query = format!("SELECT * FROM {}", entity);
667 let result = parse_query(&query);
668 assert!(
669 result.is_ok(),
670 "Failed query '{}' with error {}",
671 query,
672 result.err().unwrap()
673 );
674 }
675 }
676
677 #[test]
678 fn test_issue_id_variations() {
679 let queries = vec![
680 "CLOSE issue a#1",
681 "CLOSE issue my-project#123",
682 "CLOSE issue backend_api#456",
683 ];
684 for query in queries {
685 let result = parse_query(query);
686 assert!(
687 result.is_ok(),
688 "Failed query '{}' with error {}",
689 query,
690 result.err().unwrap()
691 );
692 }
693 }
694
695 #[test]
696 fn test_priority_in_different_cases() {
697 let queries = vec![
698 "CREATE ISSUE IN test WITH TITLE 'T' PRIORITY critical",
699 "CREATE ISSUE IN test WITH TITLE 'T' PRIORITY CRITICAL",
700 "CREATE ISSUE IN test WITH TITLE 'T' PRIORITY Critical",
701 ];
702 for query in queries {
703 let result = parse_query(query);
704 assert!(
705 result.is_ok(),
706 "Failed query '{}' with error {}",
707 query,
708 result.err().unwrap()
709 );
710 }
711 }
712
713 #[test]
714 fn test_all_comparison_ops_with_strings() {
715 let query = "SELECT * FROM issues WHERE title LIKE '%bug%'";
716 let result = parse_query(query);
717 assert!(result.is_ok());
718 }
719
720 #[test]
721 fn test_single_column_select() {
722 let query = "SELECT title FROM issues";
723 let result = parse_query(query);
724 assert!(result.is_ok());
725 if let Ok(Statement::Select(select)) = result {
726 assert_eq!(select.columns.len(), 1);
727 }
728 }
729
730 #[test]
731 fn test_whitespace_variations() {
732 let queries = vec![
733 "SELECT * FROM issues",
734 "SELECT * FROM issues",
735 "SELECT\t*\tFROM\tissues",
736 "SELECT\n*\nFROM\nissues",
737 ];
738 for query in queries {
739 let result = parse_query(query);
740 assert!(
741 result.is_ok(),
742 "Failed query '{}' with error {}",
743 query,
744 result.err().unwrap()
745 );
746 }
747 }
748
749 #[test]
750 fn test_field_update_with_priority() {
751 let query = "UPDATE issue backend#1 SET priority = critical, status = 'open'";
752 let result = parse_query(query);
753 assert!(result.is_ok());
754 }
755
756 #[test]
757 fn test_field_update_with_identifier() {
758 let query = "UPDATE issue backend#1 SET assignee = alice, project = backend";
759 let result = parse_query(query);
760 assert!(result.is_ok());
761 }
762}