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}