issuecraft_ql/
lib.rs

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