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_string_with_multiple_escapes() {
323 let query = r"CREATE ISSUE IN test WITH TITLE 'Line1\nLine2\tTab\rReturn\\Backslash'";
324 let result = parse_query(query);
325 assert!(result.is_ok());
326 }
327
328 #[test]
329 fn test_negative_numbers() {
330 let query = "UPDATE issue test#100 SET count = -50";
331 let result = parse_query(query);
332 assert!(result.is_ok());
333 }
334
335 #[test]
336 fn test_float_values() {
337 let query = "UPDATE issue test#100 SET score = 3.14159";
338 let result = parse_query(query);
339 assert!(result.is_ok());
340 }
341
342 #[test]
343 fn test_deeply_nested_filters() {
344 let query = "SELECT * FROM issues WHERE ((a = 1 AND b = 2) OR (c = 3 AND d = 4)) AND e = 5";
345 let result = parse_query(query);
346 assert!(result.is_ok());
347 }
348
349 #[test]
350 fn test_not_with_parentheses() {
351 let query = "SELECT * FROM issues WHERE NOT (status = 'closed' OR status = 'archived')";
352 let result = parse_query(query);
353 assert!(result.is_ok());
354 }
355
356 #[test]
357 fn test_in_with_priorities() {
358 let query = "SELECT * FROM issues WHERE priority IN (critical, high, medium)";
359 let result = parse_query(query);
360 assert!(result.is_ok());
361 }
362
363 #[test]
364 fn test_in_with_strings() {
365 let query = "SELECT * FROM issues WHERE status IN ('open', 'in-progress', 'review')";
366 let result = parse_query(query);
367 assert!(result.is_ok());
368 }
369
370 #[test]
371 fn test_comparison_operators() {
372 let queries = vec![
373 "SELECT * FROM issues WHERE count > 10",
374 "SELECT * FROM issues WHERE count < 5",
375 "SELECT * FROM issues WHERE count >= 10",
376 "SELECT * FROM issues WHERE count <= 5",
377 "SELECT * FROM issues WHERE status != 'closed'",
378 ];
379 for query in queries {
380 let result = parse_query(query);
381 assert!(
382 result.is_ok(),
383 "Failed query '{}' with error {}",
384 query,
385 result.err().unwrap()
386 );
387 }
388 }
389
390 #[test]
391 fn test_case_insensitive_keywords() {
392 let queries = vec![
393 "select * from issues",
394 "SELECT * FROM ISSUES",
395 "SeLeCt * FrOm IsSuEs",
396 "create user alice",
397 "CREATE USER ALICE",
398 ];
399 for query in queries {
400 let result = parse_query(query);
401 assert!(
402 result.is_ok(),
403 "Failed query '{}' with error {}",
404 query,
405 result.err().unwrap()
406 );
407 }
408 }
409
410 #[test]
411 fn test_hyphenated_identifiers() {
412 let queries = vec![
413 "CREATE USER my-user-name",
414 "CREATE PROJECT my-cool-project",
415 "SELECT * FROM issues WHERE project = 'my-backend-api'",
416 ];
417 for query in queries {
418 let result = parse_query(query);
419 assert!(
420 result.is_ok(),
421 "Failed query '{}' with error {}",
422 query,
423 result.err().unwrap()
424 );
425 }
426 }
427
428 #[test]
429 fn test_keywords_as_field_names() {
430 let queries = vec![
431 "SELECT project, user, issue FROM issues",
432 "SELECT * FROM issues WHERE project = 'test'",
433 "SELECT * FROM issues WHERE user = 'alice'",
434 "UPDATE issue test#1 SET comment = 'test'",
435 ];
436 for query in queries {
437 let result = parse_query(query);
438 assert!(
439 result.is_ok(),
440 "Failed query '{}' with error {}",
441 query,
442 result.err().unwrap()
443 );
444 }
445 }
446
447 #[test]
448 fn test_all_field_keywords_in_create() {
449 let query = "CREATE ISSUE IN test WITH TITLE 'T' DESCRIPTION 'D' PRIORITY high ASSIGNEE alice LABELS [bug]";
450 let result = parse_query(query);
451 assert!(result.is_ok());
452 }
453
454 #[test]
455 fn test_all_delete_targets() {
456 let queries = vec![
457 "DELETE user alice",
458 "DELETE project backend",
459 "DELETE issue backend#456",
460 "DELETE comment 789",
461 ];
462 for query in queries {
463 let result = parse_query(query);
464 assert!(
465 result.is_ok(),
466 "Failed query '{}' with error {}",
467 query,
468 result.err().unwrap()
469 );
470 }
471 }
472
473 #[test]
474 fn test_all_update_targets() {
475 let queries = vec![
476 "UPDATE user alice SET email = 'new@test.com'",
477 "UPDATE project backend SET name = 'New Name'",
478 "UPDATE issue backend#123 SET status = 'closed'",
479 "UPDATE issue backend#456 SET priority = high",
480 "UPDATE comment C789 SET content = 'updated'",
481 ];
482 for query in queries {
483 let result = parse_query(query);
484 assert!(
485 result.is_ok(),
486 "Failed query '{}' with error {}",
487 query,
488 result.err().unwrap()
489 );
490 }
491 }
492
493 #[test]
494 fn test_multiple_columns_select() {
495 let query =
496 "SELECT id, title, status, priority, assignee, created_at, updated_at FROM issues";
497 let result = parse_query(query);
498 assert!(result.is_ok());
499 if let Ok(Statement::Select(select)) = result {
500 assert_eq!(select.columns.count(), 7);
501 }
502 }
503
504 #[test]
505 fn test_limit_and_offset_together() {
506 let query = "SELECT * FROM issues LIMIT 50 OFFSET 100";
507 let result = parse_query(query);
508 assert!(result.is_ok());
509 if let Ok(Statement::Select(select)) = result {
510 assert_eq!(select.limit, Some(50));
511 assert_eq!(select.offset, Some(100));
512 }
513 }
514
515 #[test]
516 fn test_order_by_asc_explicit() {
517 let query = "SELECT * FROM issues ORDER BY created_at ASC";
518 let result = parse_query(query);
519 assert!(result.is_ok());
520 if let Ok(Statement::Select(select)) = result {
521 assert!(select.order_by.is_some());
522 let order = select.order_by.unwrap();
523 assert_eq!(order.direction, OrderDirection::Asc);
524 }
525 }
526
527 #[test]
528 fn test_boolean_values() {
529 let queries = vec![
530 "UPDATE issue backend#1 SET active = true",
531 "UPDATE issue backend#1 SET archived = false",
532 "SELECT * FROM issues WHERE active = TRUE",
533 "SELECT * FROM issues WHERE archived = FALSE",
534 ];
535 for query in queries {
536 let result = parse_query(query);
537 assert!(
538 result.is_ok(),
539 "Failed query '{}' with error {}",
540 query,
541 result.err().unwrap()
542 );
543 }
544 }
545
546 #[test]
547 fn test_null_values() {
548 let queries = vec![
549 "UPDATE issue backend#1 SET assignee = null",
550 "SELECT * FROM issues WHERE assignee = NULL",
551 ];
552 for query in queries {
553 let result = parse_query(query);
554 assert!(
555 result.is_ok(),
556 "Failed query '{}' with error {}",
557 query,
558 result.err().unwrap()
559 );
560 }
561 }
562
563 #[test]
564 fn test_comment_statement() {
565 let query = "COMMENT ON ISSUE backend#123 WITH 'Quick comment'";
566 let result = parse_query(query);
567 assert!(result.is_ok());
568 }
569
570 #[test]
571 fn test_close_with_and_without_reason() {
572 let queries = vec![
573 "CLOSE issue backend#123",
574 "CLOSE issue backend#123 WITH done",
575 "CLOSE issue backend#456 WITH duplicate",
576 ];
577 for query in queries {
578 let result = parse_query(query);
579 assert!(
580 result.is_ok(),
581 "Failed query '{}' with error {}",
582 query,
583 result.err().unwrap()
584 );
585 }
586 }
587
588 #[test]
589 fn test_empty_string_value() {
590 let query = "UPDATE issue backend#1 SET description = ''";
591 let result = parse_query(query);
592 assert!(result.is_ok());
593 }
594
595 #[test]
596 fn test_special_characters_in_strings() {
597 let query =
598 r"CREATE ISSUE IN test WITH TITLE 'Special chars: !@#$%^&*()_+-={}[]|:;<>?,./~`'";
599 let result = parse_query(query);
600 assert!(result.is_ok());
601 }
602
603 #[test]
604 fn test_double_quotes_in_strings() {
605 let query = r#"CREATE ISSUE IN test WITH TITLE "Double quoted string""#;
606 let result = parse_query(query);
607 assert!(result.is_ok());
608 }
609
610 #[test]
611 fn test_labels_with_hyphens() {
612 let query =
613 "CREATE ISSUE IN test WITH TITLE 'Test' LABELS [high-priority, bug-fix, ui-component]";
614 let result = parse_query(query);
615 assert!(result.is_ok());
616 }
617
618 #[test]
619 fn test_complex_real_world_query() {
620 let query = r#"
621 SELECT title, status, priority, assignee, created_at
622 FROM issues
623 WHERE (priority = critical OR priority = high)
624 AND status IN ('open', 'in-progress')
625 AND assignee IS NOT NULL
626 AND project = 'backend'
627 ORDER BY priority DESC
628 LIMIT 25
629 OFFSET 0
630 "#;
631 let result = parse_query(query);
632 assert!(result.is_ok());
633 }
634
635 #[test]
636 fn test_minimal_create_user() {
637 let query = "CREATE USER alice";
638 let result = parse_query(query);
639 assert!(result.is_ok());
640 if let Ok(Statement::Create(CreateStatement::User { email, name, .. })) = result {
641 assert!(email.is_none());
642 assert!(name.is_none());
643 }
644 }
645
646 #[test]
647 fn test_minimal_create_project() {
648 let query = "CREATE PROJECT test";
649 let result = parse_query(query);
650 assert!(result.is_ok());
651 }
652
653 #[test]
654 fn test_select_from_all_entities() {
655 for entity in &["users", "projects", "issues", "comments"] {
656 let query = format!("SELECT * FROM {}", entity);
657 let result = parse_query(&query);
658 assert!(
659 result.is_ok(),
660 "Failed query '{}' with error {}",
661 query,
662 result.err().unwrap()
663 );
664 }
665 }
666
667 #[test]
668 fn test_issue_id_variations() {
669 let queries = vec![
670 "CLOSE issue a#1",
671 "CLOSE issue my-project#123",
672 "CLOSE issue backend_api#456",
673 ];
674 for query in queries {
675 let result = parse_query(query);
676 assert!(
677 result.is_ok(),
678 "Failed query '{}' with error {}",
679 query,
680 result.err().unwrap()
681 );
682 }
683 }
684
685 #[test]
686 fn test_priority_in_different_cases() {
687 let queries = vec![
688 "CREATE ISSUE IN test WITH TITLE 'T' PRIORITY critical",
689 "CREATE ISSUE IN test WITH TITLE 'T' PRIORITY CRITICAL",
690 "CREATE ISSUE IN test WITH TITLE 'T' PRIORITY Critical",
691 ];
692 for query in queries {
693 let result = parse_query(query);
694 assert!(
695 result.is_ok(),
696 "Failed query '{}' with error {}",
697 query,
698 result.err().unwrap()
699 );
700 }
701 }
702
703 #[test]
704 fn test_all_comparison_ops_with_strings() {
705 let query = "SELECT * FROM issues WHERE title LIKE '%bug%'";
706 let result = parse_query(query);
707 assert!(result.is_ok());
708 }
709
710 #[test]
711 fn test_single_column_select() {
712 let query = "SELECT title FROM issues";
713 let result = parse_query(query);
714 assert!(result.is_ok());
715 if let Ok(Statement::Select(select)) = result {
716 assert_eq!(select.columns.count(), 1);
717 }
718 }
719
720 #[test]
721 fn test_whitespace_variations() {
722 let queries = vec![
723 "SELECT * FROM issues",
724 "SELECT * FROM issues",
725 "SELECT\t*\tFROM\tissues",
726 "SELECT\n*\nFROM\nissues",
727 ];
728 for query in queries {
729 let result = parse_query(query);
730 assert!(
731 result.is_ok(),
732 "Failed query '{}' with error {}",
733 query,
734 result.err().unwrap()
735 );
736 }
737 }
738
739 #[test]
740 fn test_field_update_with_priority() {
741 let query = "UPDATE issue backend#1 SET priority = critical, status = 'open'";
742 let result = parse_query(query);
743 assert!(result.is_ok());
744 }
745
746 #[test]
747 fn test_field_update_with_identifier() {
748 let query = "UPDATE issue backend#1 SET assignee = alice, project = backend";
749 let result = parse_query(query);
750 assert!(result.is_ok());
751 }
752}