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 project with the name '{0}' exists")]
29 ProjectNotFound(String),
30 #[error("No issue with the name '{0}' exists")]
31 IssueNotFound(String),
32 #[error("The issue withe the name '{0}' was already closed. Reason '{1}'")]
33 IssueAlreadyClosed(String, CloseReason),
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 'Completed'";
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 'Fixed'",
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!(result.is_ok(), "Failed: {}", query);
392 }
393 }
394
395 #[test]
396 fn test_case_insensitive_keywords() {
397 let queries = vec![
398 "select * from issues",
399 "SELECT * FROM ISSUES",
400 "SeLeCt * FrOm IsSuEs",
401 "create user alice",
402 "CREATE USER ALICE",
403 ];
404 for query in queries {
405 let result = parse_query(query);
406 assert!(result.is_ok(), "Failed: {}", query);
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!(result.is_ok(), "Failed: {}", query);
420 }
421 }
422
423 #[test]
424 fn test_keywords_as_field_names() {
425 let queries = vec![
426 "SELECT project, user, issue FROM issues",
427 "SELECT * FROM issues WHERE project = 'test'",
428 "SELECT * FROM issues WHERE user = 'alice'",
429 "UPDATE issue test#1 SET comment = 'test'",
430 ];
431 for query in queries {
432 let result = parse_query(query);
433 assert!(result.is_ok(), "Failed: {}", query);
434 }
435 }
436
437 #[test]
438 fn test_all_field_keywords_in_create() {
439 let query = "CREATE ISSUE IN test WITH TITLE 'T' DESCRIPTION 'D' PRIORITY high ASSIGNEE alice LABELS [bug]";
440 let result = parse_query(query);
441 assert!(result.is_ok());
442 }
443
444 #[test]
445 fn test_all_delete_targets() {
446 let queries = vec![
447 "DELETE user alice",
448 "DELETE project backend",
449 "DELETE issue backend#456",
450 "DELETE comment 789",
451 ];
452 for query in queries {
453 let result = parse_query(query);
454 assert!(result.is_ok(), "Failed: {}", query);
455 }
456 }
457
458 #[test]
459 fn test_all_update_targets() {
460 let queries = vec![
461 "UPDATE user alice SET email = 'new@test.com'",
462 "UPDATE project backend SET name = 'New Name'",
463 "UPDATE issue backend#123 SET status = 'closed'",
464 "UPDATE issue backend#456 SET priority = high",
465 "UPDATE comment 789 SET content = 'updated'",
466 ];
467 for query in queries {
468 let result = parse_query(query);
469 assert!(result.is_ok(), "Failed: {}", query);
470 }
471 }
472
473 #[test]
474 fn test_multiple_columns_select() {
475 let query =
476 "SELECT id, title, status, priority, assignee, created_at, updated_at FROM issues";
477 let result = parse_query(query);
478 assert!(result.is_ok());
479 if let Ok(Statement::Select(select)) = result {
480 assert_eq!(select.columns.len(), 7);
481 }
482 }
483
484 #[test]
485 fn test_limit_and_offset_together() {
486 let query = "SELECT * FROM issues LIMIT 50 OFFSET 100";
487 let result = parse_query(query);
488 assert!(result.is_ok());
489 if let Ok(Statement::Select(select)) = result {
490 assert_eq!(select.limit, Some(50));
491 assert_eq!(select.offset, Some(100));
492 }
493 }
494
495 #[test]
496 fn test_order_by_asc_explicit() {
497 let query = "SELECT * FROM issues ORDER BY created_at ASC";
498 let result = parse_query(query);
499 assert!(result.is_ok());
500 if let Ok(Statement::Select(select)) = result {
501 assert!(select.order_by.is_some());
502 let order = select.order_by.unwrap();
503 assert_eq!(order.direction, OrderDirection::Asc);
504 }
505 }
506
507 #[test]
508 fn test_boolean_values() {
509 let queries = vec![
510 "UPDATE issue backend#1 SET active = true",
511 "UPDATE issue backend#1 SET archived = false",
512 "SELECT * FROM issues WHERE active = TRUE",
513 "SELECT * FROM issues WHERE archived = FALSE",
514 ];
515 for query in queries {
516 let result = parse_query(query);
517 assert!(result.is_ok(), "Failed: {}", query);
518 }
519 }
520
521 #[test]
522 fn test_null_values() {
523 let queries = vec![
524 "UPDATE issue backend#1 SET assignee = null",
525 "SELECT * FROM issues WHERE assignee = NULL",
526 ];
527 for query in queries {
528 let result = parse_query(query);
529 assert!(result.is_ok(), "Failed: {}", query);
530 }
531 }
532
533 #[test]
534 fn test_create_comment_variations() {
535 let queries = vec![
536 "CREATE COMMENT ON ISSUE backend#123 WITH 'Simple comment'",
537 "CREATE COMMENT ON ISSUE backend#123 WITH 'Comment' AUTHOR alice",
538 "CREATE COMMENT ON ISSUE backend#456 WITH 'Project issue comment'",
539 ];
540 for query in queries {
541 let result = parse_query(query);
542 assert!(result.is_ok(), "Failed: {}", query);
543 }
544 }
545
546 #[test]
547 fn test_comment_statement() {
548 let query = "COMMENT ON ISSUE backend#123 WITH 'Quick comment'";
549 let result = parse_query(query);
550 assert!(result.is_ok());
551 }
552
553 #[test]
554 fn test_close_with_and_without_reason() {
555 let queries = vec![
556 "CLOSE issue backend#123",
557 "CLOSE issue backend#123 WITH 'Completed'",
558 "CLOSE issue backend#456 WITH 'Duplicate of #455'",
559 ];
560 for query in queries {
561 let result = parse_query(query);
562 assert!(result.is_ok(), "Failed: {}", query);
563 }
564 }
565
566 #[test]
567 fn test_empty_string_value() {
568 let query = "UPDATE issue backend#1 SET description = ''";
569 let result = parse_query(query);
570 assert!(result.is_ok());
571 }
572
573 #[test]
574 fn test_special_characters_in_strings() {
575 let query =
576 r"CREATE ISSUE IN test WITH TITLE 'Special chars: !@#$%^&*()_+-={}[]|:;<>?,./~`'";
577 let result = parse_query(query);
578 assert!(result.is_ok());
579 }
580
581 #[test]
582 fn test_double_quotes_in_strings() {
583 let query = r#"CREATE ISSUE IN test WITH TITLE "Double quoted string""#;
584 let result = parse_query(query);
585 assert!(result.is_ok());
586 }
587
588 #[test]
589 fn test_labels_with_hyphens() {
590 let query =
591 "CREATE ISSUE IN test WITH TITLE 'Test' LABELS [high-priority, bug-fix, ui-component]";
592 let result = parse_query(query);
593 assert!(result.is_ok());
594 }
595
596 #[test]
597 fn test_complex_real_world_query() {
598 let query = r#"
599 SELECT title, status, priority, assignee, created_at
600 FROM issues
601 WHERE (priority = critical OR priority = high)
602 AND status IN ('open', 'in-progress')
603 AND assignee IS NOT NULL
604 AND project = 'backend'
605 ORDER BY priority DESC
606 LIMIT 25
607 OFFSET 0
608 "#;
609 let result = parse_query(query);
610 assert!(result.is_ok());
611 }
612
613 #[test]
614 fn test_minimal_create_user() {
615 let query = "CREATE USER alice";
616 let result = parse_query(query);
617 assert!(result.is_ok());
618 if let Ok(Statement::Create(CreateStatement::User { email, name, .. })) = result {
619 assert!(email.is_none());
620 assert!(name.is_none());
621 }
622 }
623
624 #[test]
625 fn test_minimal_create_project() {
626 let query = "CREATE PROJECT test";
627 let result = parse_query(query);
628 assert!(result.is_ok());
629 }
630
631 #[test]
632 fn test_select_from_all_entities() {
633 for entity in &["users", "projects", "issues", "comments"] {
634 let query = format!("SELECT * FROM {}", entity);
635 let result = parse_query(&query);
636 assert!(result.is_ok(), "Failed: {}", query);
637 }
638 }
639
640 #[test]
641 fn test_issue_id_variations() {
642 let queries = vec![
643 "CLOSE issue a#1",
644 "CLOSE issue my-project#123",
645 "CLOSE issue backend_api#456",
646 ];
647 for query in queries {
648 let result = parse_query(query);
649 assert!(result.is_ok(), "Failed: {}", query);
650 }
651 }
652
653 #[test]
654 fn test_priority_in_different_cases() {
655 let queries = vec![
656 "CREATE ISSUE IN test WITH TITLE 'T' PRIORITY critical",
657 "CREATE ISSUE IN test WITH TITLE 'T' PRIORITY CRITICAL",
658 "CREATE ISSUE IN test WITH TITLE 'T' PRIORITY Critical",
659 ];
660 for query in queries {
661 let result = parse_query(query);
662 assert!(result.is_ok(), "Failed: {}", query);
663 }
664 }
665
666 #[test]
667 fn test_all_comparison_ops_with_strings() {
668 let query = "SELECT * FROM issues WHERE title LIKE '%bug%'";
669 let result = parse_query(query);
670 assert!(result.is_ok());
671 }
672
673 #[test]
674 fn test_single_column_select() {
675 let query = "SELECT title FROM issues";
676 let result = parse_query(query);
677 assert!(result.is_ok());
678 if let Ok(Statement::Select(select)) = result {
679 assert_eq!(select.columns.len(), 1);
680 }
681 }
682
683 #[test]
684 fn test_whitespace_variations() {
685 let queries = vec![
686 "SELECT * FROM issues",
687 "SELECT * FROM issues",
688 "SELECT\t*\tFROM\tissues",
689 "SELECT\n*\nFROM\nissues",
690 ];
691 for query in queries {
692 let result = parse_query(query);
693 assert!(result.is_ok(), "Failed: {}", query);
694 }
695 }
696
697 #[test]
698 fn test_field_update_with_priority() {
699 let query = "UPDATE issue backend#1 SET priority = critical, status = 'open'";
700 let result = parse_query(query);
701 assert!(result.is_ok());
702 }
703
704 #[test]
705 fn test_field_update_with_identifier() {
706 let query = "UPDATE issue backend#1 SET assignee = alice, project = backend";
707 let result = parse_query(query);
708 assert!(result.is_ok());
709 }
710}