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