1use crate::planner::{LogicalPlan, Planner, ShowGrantsTarget as PlanShowGrantsTarget};
4use featherdb_catalog::{
5 AlterTableOp, Catalog, Column, ColumnConstraint, ForeignKey, ForeignKeyAction,
6};
7use featherdb_core::{suggest_keyword, ColumnType, Error, QueryContext, QueryError, Result, Value};
8use sqlparser::ast::{self, Statement};
9use sqlparser::dialect::GenericDialect;
10use sqlparser::parser::Parser as SqlParser;
11use sqlparser::tokenizer::{Token, Tokenizer};
12use std::collections::HashMap;
13
14#[derive(Debug, Clone)]
16pub enum ShowGrantsTarget {
17 Table(String),
19 ApiKey(String),
21}
22
23#[derive(Debug, Clone)]
25pub enum CustomStatement {
26 ShowGrants(ShowGrantsTarget),
28}
29
30#[derive(Debug)]
32#[allow(clippy::large_enum_variant)]
33pub enum ParseResult {
34 Standard(Statement),
36 Custom(CustomStatement),
38}
39
40pub struct Parser;
42
43impl Parser {
44 pub fn parse(sql: &str) -> Result<Vec<Statement>> {
46 let dialect = GenericDialect {};
47
48 SqlParser::parse_sql(&dialect, sql).map_err(|e| Self::convert_parse_error(sql, e))
49 }
50
51 pub fn parse_one(sql: &str) -> Result<Statement> {
53 let mut statements = Self::parse(sql)?;
54
55 if statements.is_empty() {
56 return Err(Error::Query(
57 QueryError::new("Empty query", sql, 1, 1)
58 .with_help("Provide a valid SQL statement like SELECT, INSERT, UPDATE, DELETE, or CREATE TABLE")
59 ));
60 }
61
62 if statements.len() > 1 {
63 return Err(Error::Query(
64 QueryError::new("Multiple statements not supported", sql, 1, 1).with_help(
65 "Execute one statement at a time, or use transactions for multiple operations",
66 ),
67 ));
68 }
69
70 Ok(statements.remove(0))
71 }
72
73 pub fn parse_extended(sql: &str) -> Result<ParseResult> {
77 if let Some(custom) = Self::try_parse_custom(sql)? {
79 return Ok(ParseResult::Custom(custom));
80 }
81
82 let stmt = Self::parse_one(sql)?;
84 Ok(ParseResult::Standard(stmt))
85 }
86
87 pub fn parse_to_plan(
93 sql: &str,
94 catalog: &Catalog,
95 ) -> Result<(LogicalPlan, HashMap<usize, LogicalPlan>)> {
96 match Self::parse_extended(sql)? {
97 ParseResult::Standard(stmt) => {
98 let planner = Planner::new(catalog);
99 let plan = planner.plan(&stmt)?;
100 let subquery_plans = planner.take_subquery_plans();
101 Ok((plan, subquery_plans))
102 }
103 ParseResult::Custom(custom) => {
104 Ok((Self::plan_custom(custom, catalog)?, HashMap::new()))
105 }
106 }
107 }
108
109 fn plan_custom(stmt: CustomStatement, catalog: &Catalog) -> Result<LogicalPlan> {
111 match stmt {
112 CustomStatement::ShowGrants(target) => {
113 let plan_target = match target {
115 ShowGrantsTarget::Table(t) => PlanShowGrantsTarget::Table(t),
116 ShowGrantsTarget::ApiKey(k) => PlanShowGrantsTarget::ApiKey(k),
117 };
118 let planner = Planner::new(catalog);
119 planner.plan_show_grants(plan_target)
120 }
121 }
122 }
123
124 pub fn is_custom_read_only(stmt: &CustomStatement) -> bool {
128 match stmt {
129 CustomStatement::ShowGrants(_) => true,
131 }
132 }
133
134 fn try_parse_custom(sql: &str) -> Result<Option<CustomStatement>> {
136 let sql_upper = sql.trim().to_uppercase();
137
138 if sql_upper.starts_with("SHOW GRANTS") {
140 return Self::parse_show_grants(sql).map(Some);
141 }
142
143 Ok(None)
144 }
145
146 fn parse_show_grants(sql: &str) -> Result<CustomStatement> {
152 let sql_trimmed = sql.trim();
153 let sql_upper = sql_trimmed.to_uppercase();
154
155 let rest = sql_trimmed[11..].trim();
157 let rest_upper = sql_upper[11..].trim();
158
159 if rest_upper.starts_with("ON ") {
160 let table_part = rest[3..].trim();
162
163 let table_name = table_part.trim_end_matches(';').trim();
165
166 if table_name.is_empty() {
167 return Err(Error::InvalidQuery {
168 message: "SHOW GRANTS ON requires a table name".to_string(),
169 });
170 }
171
172 Ok(CustomStatement::ShowGrants(ShowGrantsTarget::Table(
173 table_name.to_string(),
174 )))
175 } else if rest_upper.starts_with("FOR ") {
176 let key_part = rest[4..].trim();
178
179 let key_id = if key_part.starts_with('\'') && key_part.len() > 2 {
181 let end = key_part[1..]
183 .find('\'')
184 .ok_or_else(|| Error::InvalidQuery {
185 message: "Unterminated string in SHOW GRANTS FOR".to_string(),
186 })?;
187 key_part[1..=end].to_string()
188 } else {
189 key_part
191 .split_whitespace()
192 .next()
193 .unwrap_or("")
194 .trim_end_matches(';')
195 .to_string()
196 };
197
198 if key_id.is_empty() {
199 return Err(Error::InvalidQuery {
200 message: "SHOW GRANTS FOR requires an API key ID".to_string(),
201 });
202 }
203
204 Ok(CustomStatement::ShowGrants(ShowGrantsTarget::ApiKey(
205 key_id,
206 )))
207 } else {
208 Err(Error::InvalidQuery {
209 message: "SHOW GRANTS requires ON <table> or FOR '<api_key_id>'".to_string(),
210 })
211 }
212 }
213
214 fn convert_parse_error(sql: &str, error: sqlparser::parser::ParserError) -> Error {
216 let error_str = error.to_string();
217 let _ctx = QueryContext::new(sql); let (line, column, message) = Self::extract_position_from_error(&error_str, sql);
221
222 let suggestion = Self::find_keyword_suggestion(sql, &error_str, column);
224
225 let mut query_error = QueryError::new(&message, sql, line, column);
226
227 if let Some(sugg) = suggestion {
228 query_error = query_error.with_suggestion(sugg);
229 }
230
231 if error_str.contains("Expected") {
233 query_error = query_error.with_help("Check the SQL syntax near the indicated position");
234 }
235
236 Error::Query(query_error)
237 }
238
239 fn extract_position_from_error(error: &str, sql: &str) -> (usize, usize, String) {
241 let mut line = 1;
243 let mut column = 1;
244 let mut message = error.to_string();
245
246 if let Some(line_pos) = error.find("Line:") {
248 if let Some(col_pos) = error.find("Column:") {
249 let line_start = line_pos + 5;
251 let line_end = error[line_start..]
252 .find(',')
253 .map(|p| line_start + p)
254 .unwrap_or(error.len());
255 if let Ok(l) = error[line_start..line_end].trim().parse::<usize>() {
256 line = l;
257 }
258
259 let col_start = col_pos + 7;
261 let col_end = error[col_start..]
262 .find(|c: char| !c.is_ascii_digit())
263 .map(|p| col_start + p)
264 .unwrap_or(error.len());
265 if let Ok(c) = error[col_start..col_end].trim().parse::<usize>() {
266 column = c;
267 }
268
269 if let Some(msg_end) = error.find(" at Line:") {
271 message = error[..msg_end].to_string();
272 }
273 }
274 }
275
276 if let Some(near_pos) = error.find("near \"") {
278 let start = near_pos + 6;
279 if let Some(end) = error[start..].find('"') {
280 let token = &error[start..start + end];
281 if let Some(token_pos) = sql.find(token) {
283 let ctx = QueryContext::new(sql);
284 let (l, c) = ctx.position_from_offset(token_pos);
285 line = l;
286 column = c;
287 }
288 }
289 }
290
291 (line, column, message)
292 }
293
294 fn find_keyword_suggestion(sql: &str, _error: &str, column: usize) -> Option<String> {
296 let dialect = GenericDialect {};
298 let mut tokenizer = Tokenizer::new(&dialect, sql);
299
300 if let Ok(tokens) = tokenizer.tokenize() {
301 let mut current_col = 1;
303 for token in tokens {
304 match &token {
305 Token::Word(word) => {
306 let word_str = word.value.as_str();
307 let word_len = word_str.len();
308
309 if current_col <= column && column <= current_col + word_len {
311 if let Some(correct_keyword) = suggest_keyword(word_str) {
313 return Some(format!("Did you mean '{}'?", correct_keyword));
314 }
315 }
316
317 current_col += word_len;
318 }
319 Token::Whitespace(_) => {
320 current_col += 1;
321 }
322 _ => {
323 current_col += 1;
325 }
326 }
327 }
328 }
329
330 let first_word = sql.split_whitespace().next().unwrap_or("");
332 if let Some(correct_keyword) = suggest_keyword(first_word) {
333 return Some(format!("Did you mean '{}'?", correct_keyword));
334 }
335
336 None
337 }
338
339 pub fn convert_data_type(data_type: &ast::DataType) -> Result<ColumnType> {
341 match data_type {
342 ast::DataType::Boolean => Ok(ColumnType::Boolean),
343
344 ast::DataType::SmallInt(_)
345 | ast::DataType::Int(_)
346 | ast::DataType::Integer(_)
347 | ast::DataType::BigInt(_)
348 | ast::DataType::TinyInt(_) => Ok(ColumnType::Integer),
349
350 ast::DataType::Float(_)
351 | ast::DataType::Real
352 | ast::DataType::Double
353 | ast::DataType::DoublePrecision
354 | ast::DataType::Decimal(_)
355 | ast::DataType::Numeric(_) => Ok(ColumnType::Real),
356
357 ast::DataType::Char(len) | ast::DataType::Varchar(len) => {
358 let max_len = len.as_ref().and_then(|l| {
359 if let ast::CharacterLength::IntegerLength { length, .. } = l {
360 Some(*length as usize)
361 } else {
362 None
363 }
364 });
365 Ok(ColumnType::Text { max_len })
366 }
367
368 ast::DataType::Text => Ok(ColumnType::Text { max_len: None }),
369
370 ast::DataType::Binary(_)
371 | ast::DataType::Varbinary(_)
372 | ast::DataType::Blob(_)
373 | ast::DataType::Bytea => Ok(ColumnType::Blob { max_len: None }),
374
375 ast::DataType::Timestamp(_, _) | ast::DataType::Datetime(_) | ast::DataType::Date => {
376 Ok(ColumnType::Timestamp)
377 }
378
379 other => Err(Error::Unsupported {
380 feature: format!("Data type: {:?}", other),
381 }),
382 }
383 }
384
385 pub fn parse_alter_table_ops(
387 table_name: &ast::ObjectName,
388 operations: &[ast::AlterTableOperation],
389 ) -> Result<(String, Vec<AlterTableOp>)> {
390 let tbl_name = table_name
391 .0
392 .first()
393 .map(|i| i.value.clone())
394 .unwrap_or_default();
395
396 let mut ops = Vec::new();
397
398 for operation in operations {
399 let op = Self::convert_alter_operation(operation)?;
400 ops.push(op);
401 }
402
403 Ok((tbl_name, ops))
404 }
405
406 fn convert_alter_operation(operation: &ast::AlterTableOperation) -> Result<AlterTableOp> {
408 match operation {
409 ast::AlterTableOperation::AddColumn {
410 column_def,
411 if_not_exists: _,
412 ..
413 } => {
414 let col_type = Self::convert_data_type(&column_def.data_type)?;
415 let mut constraints = Vec::new();
416
417 for option in &column_def.options {
418 match &option.option {
419 ast::ColumnOption::NotNull => {
420 constraints.push(ColumnConstraint::NotNull);
421 }
422 ast::ColumnOption::Unique { is_primary, .. } => {
423 if *is_primary {
424 constraints.push(ColumnConstraint::PrimaryKey);
425 } else {
426 constraints.push(ColumnConstraint::Unique);
427 }
428 }
429 ast::ColumnOption::Default(expr) => {
430 let value = Self::eval_constant_expr(expr)?;
431 constraints.push(ColumnConstraint::Default(value));
432 }
433 _ => {}
434 }
435 }
436
437 let column = Column {
438 name: column_def.name.value.clone(),
439 column_type: col_type,
440 constraints,
441 position: 0, };
443
444 Ok(AlterTableOp::AddColumn {
447 column,
448 position: None,
449 })
450 }
451
452 ast::AlterTableOperation::DropColumn {
453 column_name,
454 if_exists,
455 ..
456 } => Ok(AlterTableOp::DropColumn {
457 name: column_name.value.clone(),
458 if_exists: *if_exists,
459 }),
460
461 ast::AlterTableOperation::RenameColumn {
462 old_column_name,
463 new_column_name,
464 } => Ok(AlterTableOp::RenameColumn {
465 old_name: old_column_name.value.clone(),
466 new_name: new_column_name.value.clone(),
467 }),
468
469 ast::AlterTableOperation::RenameTable { table_name } => {
470 let new_name = table_name
471 .0
472 .first()
473 .map(|i| i.value.clone())
474 .unwrap_or_default();
475 Ok(AlterTableOp::RenameTable { new_name })
476 }
477
478 ast::AlterTableOperation::AlterColumn { column_name, op } => {
479 Self::convert_alter_column_op(&column_name.value, op)
480 }
481
482 other => Err(Error::Unsupported {
483 feature: format!("ALTER TABLE operation: {:?}", other),
484 }),
485 }
486 }
487
488 fn convert_alter_column_op(
490 column_name: &str,
491 op: &ast::AlterColumnOperation,
492 ) -> Result<AlterTableOp> {
493 match op {
494 ast::AlterColumnOperation::SetNotNull => Ok(AlterTableOp::AlterColumnNullable {
495 name: column_name.to_string(),
496 nullable: false,
497 }),
498
499 ast::AlterColumnOperation::DropNotNull => Ok(AlterTableOp::AlterColumnNullable {
500 name: column_name.to_string(),
501 nullable: true,
502 }),
503
504 ast::AlterColumnOperation::SetDefault { value } => {
505 let default_value = Self::eval_constant_expr(value)?;
506 Ok(AlterTableOp::AlterColumnDefault {
507 name: column_name.to_string(),
508 default: Some(default_value),
509 })
510 }
511
512 ast::AlterColumnOperation::DropDefault => Ok(AlterTableOp::AlterColumnDefault {
513 name: column_name.to_string(),
514 default: None,
515 }),
516
517 other => Err(Error::Unsupported {
518 feature: format!("ALTER COLUMN operation: {:?}", other),
519 }),
520 }
521 }
522
523 fn eval_constant_expr(expr: &ast::Expr) -> Result<Value> {
525 match expr {
526 ast::Expr::Value(v) => Self::convert_value(v),
527 ast::Expr::UnaryOp { op, expr } => {
528 let val = Self::eval_constant_expr(expr)?;
529 match op {
530 ast::UnaryOperator::Minus => match val {
531 Value::Integer(i) => Ok(Value::Integer(-i)),
532 Value::Real(r) => Ok(Value::Real(-r)),
533 _ => Err(Error::InvalidQuery {
534 message: "Cannot negate non-numeric value".into(),
535 }),
536 },
537 _ => Err(Error::Unsupported {
538 feature: format!("Unary operator in constant: {:?}", op),
539 }),
540 }
541 }
542 _ => Err(Error::InvalidQuery {
543 message: format!("Expected constant expression, got: {:?}", expr),
544 }),
545 }
546 }
547
548 fn convert_value(value: &ast::Value) -> Result<Value> {
550 match value {
551 ast::Value::Number(n, _) => {
552 if n.contains('.') {
553 Ok(Value::Real(n.parse().map_err(|_| Error::InvalidQuery {
554 message: format!("Invalid number: {}", n),
555 })?))
556 } else {
557 Ok(Value::Integer(n.parse().map_err(|_| {
558 Error::InvalidQuery {
559 message: format!("Invalid integer: {}", n),
560 }
561 })?))
562 }
563 }
564 ast::Value::SingleQuotedString(s) | ast::Value::DoubleQuotedString(s) => {
565 Ok(Value::Text(s.clone()))
566 }
567 ast::Value::Boolean(b) => Ok(Value::Boolean(*b)),
568 ast::Value::Null => Ok(Value::Null),
569 _ => Err(Error::Unsupported {
570 feature: format!("Value type: {:?}", value),
571 }),
572 }
573 }
574
575 pub fn parse_foreign_key_constraint(
587 constraint: &ast::TableConstraint,
588 ) -> Result<Option<ForeignKey>> {
589 match constraint {
590 ast::TableConstraint::ForeignKey {
591 name,
592 columns,
593 foreign_table,
594 referred_columns,
595 on_delete,
596 on_update,
597 ..
598 } => {
599 let fk_columns: Vec<String> = columns.iter().map(|c| c.value.clone()).collect();
600 let ref_table = foreign_table
601 .0
602 .first()
603 .map(|i| i.value.clone())
604 .unwrap_or_default();
605 let ref_columns: Vec<String> =
606 referred_columns.iter().map(|c| c.value.clone()).collect();
607
608 let mut fk = ForeignKey::new(fk_columns, ref_table, ref_columns);
609
610 if let Some(name_ident) = name {
612 fk = fk.with_name(name_ident.value.clone());
613 }
614
615 if let Some(action) = on_delete {
617 fk = fk.on_delete(Self::convert_referential_action(action));
618 }
619
620 if let Some(action) = on_update {
622 fk = fk.on_update(Self::convert_referential_action(action));
623 }
624
625 Ok(Some(fk))
626 }
627 _ => Ok(None),
628 }
629 }
630
631 pub fn parse_inline_foreign_key(
638 column_name: &str,
639 options: &[ast::ColumnOptionDef],
640 ) -> Result<Option<ForeignKey>> {
641 for option in options {
642 if let ast::ColumnOption::ForeignKey {
643 foreign_table,
644 referred_columns,
645 on_delete,
646 on_update,
647 ..
648 } = &option.option
649 {
650 let ref_table = foreign_table
651 .0
652 .first()
653 .map(|i| i.value.clone())
654 .unwrap_or_default();
655
656 let ref_columns: Vec<String> = if referred_columns.is_empty() {
658 vec![column_name.to_string()]
659 } else {
660 referred_columns.iter().map(|c| c.value.clone()).collect()
661 };
662
663 let mut fk = ForeignKey::new(vec![column_name.to_string()], ref_table, ref_columns);
664
665 if let Some(action) = on_delete {
667 fk = fk.on_delete(Self::convert_referential_action(action));
668 }
669
670 if let Some(action) = on_update {
672 fk = fk.on_update(Self::convert_referential_action(action));
673 }
674
675 return Ok(Some(fk));
676 }
677 }
678 Ok(None)
679 }
680
681 fn convert_referential_action(action: &ast::ReferentialAction) -> ForeignKeyAction {
683 match action {
684 ast::ReferentialAction::NoAction => ForeignKeyAction::NoAction,
685 ast::ReferentialAction::Restrict => ForeignKeyAction::Restrict,
686 ast::ReferentialAction::Cascade => ForeignKeyAction::Cascade,
687 ast::ReferentialAction::SetNull => ForeignKeyAction::SetNull,
688 ast::ReferentialAction::SetDefault => ForeignKeyAction::SetDefault,
689 }
690 }
691
692 pub fn parse_create_table_foreign_keys(
698 columns: &[ast::ColumnDef],
699 constraints: &[ast::TableConstraint],
700 ) -> Result<Vec<ForeignKey>> {
701 let mut foreign_keys = Vec::new();
702
703 for column in columns {
705 if let Some(fk) = Self::parse_inline_foreign_key(&column.name.value, &column.options)? {
706 foreign_keys.push(fk);
707 }
708 }
709
710 for constraint in constraints {
712 if let Some(fk) = Self::parse_foreign_key_constraint(constraint)? {
713 foreign_keys.push(fk);
714 }
715 }
716
717 Ok(foreign_keys)
718 }
719}
720
721#[cfg(test)]
722mod tests {
723 use super::*;
724
725 #[test]
726 fn test_parse_select() {
727 let stmt = Parser::parse_one("SELECT * FROM users").unwrap();
728 assert!(matches!(stmt, Statement::Query(_)));
729 }
730
731 #[test]
732 fn test_parse_insert() {
733 let stmt = Parser::parse_one("INSERT INTO users (id, name) VALUES (1, 'Alice')").unwrap();
734 assert!(matches!(stmt, Statement::Insert { .. }));
735 }
736
737 #[test]
738 fn test_parse_create_table() {
739 let stmt =
740 Parser::parse_one("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)")
741 .unwrap();
742 assert!(matches!(stmt, Statement::CreateTable { .. }));
743 }
744
745 #[test]
746 fn test_parse_error() {
747 let result = Parser::parse_one("SELEKT * FROM users");
748 assert!(result.is_err());
749
750 if let Err(e) = result {
752 let error_str = e.to_string();
753 assert!(error_str.contains("SELECT") || error_str.contains("error"));
755 }
756 }
757
758 #[test]
759 fn test_parse_error_with_typo() {
760 let result = Parser::parse_one("SELECT * FORM users");
761 assert!(result.is_err());
762
763 if let Err(Error::Query(qe)) = result {
764 assert!(
766 qe.suggestion
767 .as_ref()
768 .map(|s| s.contains("FROM"))
769 .unwrap_or(false)
770 || qe.message.contains("FROM")
771 || qe.message.contains("FORM"),
772 "Error should mention FROM typo: {:?}",
773 qe
774 );
775 }
776 }
777
778 #[test]
779 fn test_empty_query_error() {
780 let result = Parser::parse_one("");
781 assert!(result.is_err());
782
783 if let Err(Error::Query(qe)) = result {
784 assert!(qe.help.is_some(), "Empty query error should have help text");
785 }
786 }
787
788 #[test]
789 fn test_multiline_error_position() {
790 let sql = "SELECT *\nFORM users\nWHERE id = 1";
791 let result = Parser::parse_one(sql);
792 assert!(result.is_err());
793 }
795
796 #[test]
801 fn test_parse_create_table_with_foreign_key() {
802 let sql = r#"
803 CREATE TABLE orders (
804 id INTEGER PRIMARY KEY,
805 user_id INTEGER NOT NULL,
806 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
807 )
808 "#;
809 let stmt = Parser::parse_one(sql).unwrap();
810 assert!(matches!(stmt, Statement::CreateTable { .. }));
811
812 if let Statement::CreateTable {
813 columns,
814 constraints,
815 ..
816 } = stmt
817 {
818 let fks = Parser::parse_create_table_foreign_keys(&columns, &constraints).unwrap();
819 assert_eq!(fks.len(), 1);
820 assert_eq!(fks[0].columns, vec!["user_id"]);
821 assert_eq!(fks[0].references_table, "users");
822 assert_eq!(fks[0].references_columns, vec!["id"]);
823 assert_eq!(fks[0].on_delete, ForeignKeyAction::Cascade);
824 }
825 }
826
827 #[test]
828 fn test_parse_inline_foreign_key() {
829 let sql = r#"
830 CREATE TABLE items (
831 id INTEGER PRIMARY KEY,
832 order_id INTEGER REFERENCES orders(id)
833 )
834 "#;
835 let stmt = Parser::parse_one(sql).unwrap();
836
837 if let Statement::CreateTable {
838 columns,
839 constraints,
840 ..
841 } = stmt
842 {
843 let fks = Parser::parse_create_table_foreign_keys(&columns, &constraints).unwrap();
844 assert_eq!(fks.len(), 1);
845 assert_eq!(fks[0].columns, vec!["order_id"]);
846 assert_eq!(fks[0].references_table, "orders");
847 assert_eq!(fks[0].references_columns, vec!["id"]);
848 }
849 }
850
851 #[test]
852 fn test_parse_named_foreign_key_constraint() {
853 let sql = r#"
854 CREATE TABLE orders (
855 id INTEGER PRIMARY KEY,
856 user_id INTEGER,
857 CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES users(id)
858 )
859 "#;
860 let stmt = Parser::parse_one(sql).unwrap();
861
862 if let Statement::CreateTable {
863 columns,
864 constraints,
865 ..
866 } = stmt
867 {
868 let fks = Parser::parse_create_table_foreign_keys(&columns, &constraints).unwrap();
869 assert_eq!(fks.len(), 1);
870 assert_eq!(fks[0].name, Some("fk_orders_user".to_string()));
871 }
872 }
873
874 #[test]
875 fn test_parse_foreign_key_with_on_update() {
876 let sql = r#"
877 CREATE TABLE orders (
878 id INTEGER PRIMARY KEY,
879 user_id INTEGER,
880 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL ON UPDATE CASCADE
881 )
882 "#;
883 let stmt = Parser::parse_one(sql).unwrap();
884
885 if let Statement::CreateTable {
886 columns,
887 constraints,
888 ..
889 } = stmt
890 {
891 let fks = Parser::parse_create_table_foreign_keys(&columns, &constraints).unwrap();
892 assert_eq!(fks.len(), 1);
893 assert_eq!(fks[0].on_delete, ForeignKeyAction::SetNull);
894 assert_eq!(fks[0].on_update, ForeignKeyAction::Cascade);
895 }
896 }
897
898 #[test]
899 fn test_parse_multi_column_foreign_key() {
900 let sql = r#"
901 CREATE TABLE order_items (
902 order_id INTEGER,
903 product_id INTEGER,
904 PRIMARY KEY (order_id, product_id),
905 FOREIGN KEY (order_id, product_id) REFERENCES orders(id, product_id)
906 )
907 "#;
908 let stmt = Parser::parse_one(sql).unwrap();
909
910 if let Statement::CreateTable {
911 columns,
912 constraints,
913 ..
914 } = stmt
915 {
916 let fks = Parser::parse_create_table_foreign_keys(&columns, &constraints).unwrap();
917 assert_eq!(fks.len(), 1);
918 assert_eq!(fks[0].columns, vec!["order_id", "product_id"]);
919 assert_eq!(fks[0].references_columns, vec!["id", "product_id"]);
920 }
921 }
922
923 #[test]
924 fn test_parse_multiple_foreign_keys() {
925 let sql = r#"
926 CREATE TABLE order_items (
927 id INTEGER PRIMARY KEY,
928 order_id INTEGER REFERENCES orders(id),
929 product_id INTEGER,
930 FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE
931 )
932 "#;
933 let stmt = Parser::parse_one(sql).unwrap();
934
935 if let Statement::CreateTable {
936 columns,
937 constraints,
938 ..
939 } = stmt
940 {
941 let fks = Parser::parse_create_table_foreign_keys(&columns, &constraints).unwrap();
942 assert_eq!(fks.len(), 2);
943
944 assert_eq!(fks[0].columns, vec!["order_id"]);
946 assert_eq!(fks[0].references_table, "orders");
947
948 assert_eq!(fks[1].columns, vec!["product_id"]);
950 assert_eq!(fks[1].references_table, "products");
951 assert_eq!(fks[1].on_delete, ForeignKeyAction::Cascade);
952 }
953 }
954
955 #[test]
960 fn test_parse_show_grants_on_table() {
961 let result = Parser::parse_extended("SHOW GRANTS ON users").unwrap();
962 if let ParseResult::Custom(CustomStatement::ShowGrants(ShowGrantsTarget::Table(name))) =
963 result
964 {
965 assert_eq!(name, "users");
966 } else {
967 panic!("Expected ShowGrants Table");
968 }
969 }
970
971 #[test]
972 fn test_parse_show_grants_on_table_with_semicolon() {
973 let result = Parser::parse_extended("SHOW GRANTS ON users;").unwrap();
974 if let ParseResult::Custom(CustomStatement::ShowGrants(ShowGrantsTarget::Table(name))) =
975 result
976 {
977 assert_eq!(name, "users");
978 } else {
979 panic!("Expected ShowGrants Table");
980 }
981 }
982
983 #[test]
984 fn test_parse_show_grants_for_key() {
985 let result = Parser::parse_extended("SHOW GRANTS FOR 'fdb_abc123'").unwrap();
986 if let ParseResult::Custom(CustomStatement::ShowGrants(ShowGrantsTarget::ApiKey(key))) =
987 result
988 {
989 assert_eq!(key, "fdb_abc123");
990 } else {
991 panic!("Expected ShowGrants ApiKey");
992 }
993 }
994
995 #[test]
996 fn test_parse_show_grants_for_key_unquoted() {
997 let result = Parser::parse_extended("SHOW GRANTS FOR my_key").unwrap();
998 if let ParseResult::Custom(CustomStatement::ShowGrants(ShowGrantsTarget::ApiKey(key))) =
999 result
1000 {
1001 assert_eq!(key, "my_key");
1002 } else {
1003 panic!("Expected ShowGrants ApiKey");
1004 }
1005 }
1006
1007 #[test]
1008 fn test_parse_show_grants_error_no_target() {
1009 let result = Parser::parse_extended("SHOW GRANTS");
1010 assert!(result.is_err());
1011 }
1012
1013 #[test]
1014 fn test_parse_show_grants_error_empty_table() {
1015 let result = Parser::parse_extended("SHOW GRANTS ON ");
1016 assert!(result.is_err());
1017 }
1018
1019 #[test]
1020 fn test_parse_grant_statement() {
1021 let result = Parser::parse_one("GRANT SELECT ON users TO test_key");
1023 assert!(result.is_ok());
1024 if let Ok(Statement::Grant { .. }) = result {
1025 } else {
1027 panic!("Expected Grant statement");
1028 }
1029 }
1030
1031 #[test]
1032 fn test_parse_revoke_statement() {
1033 let result = Parser::parse_one("REVOKE SELECT ON users FROM test_key");
1035 assert!(result.is_ok());
1036 if let Ok(Statement::Revoke { .. }) = result {
1037 } else {
1039 panic!("Expected Revoke statement");
1040 }
1041 }
1042}