fmql 0.3.0

A fast and feature-rich file manager written in Rust
Documentation
#[cfg(test)]
use crate::sql::ast::{ComparisonOperator, FileAttribute, FileCondition, FileQuery, FileValue};
use crate::sql::parser::{parse_sql, ParserError};

#[test]
fn test_parse_select_all() {
    let sql = "SELECT * FROM ~/Documents";
    let query = parse_sql(sql).unwrap();
    
    match query {
        FileQuery::Select { path, recursive, attributes, condition } => {
            assert!(path.ends_with("Documents"));
            assert!(!recursive);
            assert_eq!(attributes.len(), 1);
            assert!(matches!(attributes[0], FileAttribute::All));
            assert!(condition.is_none());
        },
        _ => panic!("Expected SELECT query"),
    }
}

#[test]
fn test_parse_select_with_condition() {
    let sql = "SELECT * FROM ~/Documents WHERE extension = '.txt'";
    let query = parse_sql(sql).unwrap();
    
    match query {
        FileQuery::Select { path, recursive, attributes, condition } => {
            assert!(path.ends_with("Documents"));
            assert!(!recursive);
            assert_eq!(attributes.len(), 1);
            assert!(matches!(attributes[0], FileAttribute::All));
            
            match condition {
                Some(FileCondition::Compare { attribute, operator, value }) => {
                    assert!(matches!(attribute, FileAttribute::Extension));
                    assert!(matches!(operator, ComparisonOperator::Eq));
                    assert!(matches!(value, FileValue::String(s) if s == ".txt"));
                },
                _ => panic!("Expected Compare condition"),
            }
        },
        _ => panic!("Expected SELECT query"),
    }
}

#[test]
fn test_parse_select_with_complex_condition() {
    let sql = "SELECT * FROM ~/Documents WHERE extension = '.txt' AND size > 1000";
    let query = parse_sql(sql).unwrap();
    
    match query {
        FileQuery::Select { path, recursive, attributes, condition } => {
            assert!(path.ends_with("Documents"));
            assert!(!recursive);
            assert_eq!(attributes.len(), 1);
            assert!(matches!(attributes[0], FileAttribute::All));
            
            match condition {
                Some(FileCondition::And(left, right)) => {
                    match (*left, *right) {
                        (
                            FileCondition::Compare { 
                                attribute: attr1, 
                                operator: op1, 
                                value: val1 
                            },
                            FileCondition::Compare { 
                                attribute: attr2, 
                                operator: op2, 
                                value: val2 
                            }
                        ) => {
                            assert!(matches!(attr1, FileAttribute::Extension));
                            assert!(matches!(op1, ComparisonOperator::Eq));
                            assert!(matches!(val1, FileValue::String(s) if s == ".txt"));
                            
                            assert!(matches!(attr2, FileAttribute::Size));
                            assert!(matches!(op2, ComparisonOperator::Gt));
                            assert!(matches!(val2, FileValue::Number(n) if n == 1000.0));
                        },
                        _ => panic!("Expected two Compare conditions"),
                    }
                },
                _ => panic!("Expected AND condition"),
            }
        },
        _ => panic!("Expected SELECT query"),
    }
}

#[test]
fn test_parse_select_with_between() {
    let sql = "SELECT * FROM ~/Documents WHERE modified BETWEEN '2025-01-01' AND '2025-03-31'";
    let query = parse_sql(sql).unwrap();
    
    match query {
        FileQuery::Select { path, recursive, attributes, condition } => {
            assert!(path.ends_with("Documents"));
            assert!(!recursive);
            assert_eq!(attributes.len(), 1);
            assert!(matches!(attributes[0], FileAttribute::All));
            
            match condition {
                Some(FileCondition::Between { attribute, lower, upper }) => {
                    assert!(matches!(attribute, FileAttribute::Modified));
                    assert!(matches!(lower, FileValue::String(s) if s == "2025-01-01"));
                    assert!(matches!(upper, FileValue::String(s) if s == "2025-03-31"));
                },
                _ => panic!("Expected BETWEEN condition"),
            }
        },
        _ => panic!("Expected SELECT query"),
    }
}

#[test]
fn test_parse_select_with_like() {
    let sql = "SELECT * FROM ~/Projects WHERE name LIKE '%config%'";
    let query = parse_sql(sql).unwrap();
    
    match query {
        FileQuery::Select { path, recursive, attributes, condition } => {
            assert!(path.ends_with("Projects"));
            assert!(!recursive);
            assert_eq!(attributes.len(), 1);
            assert!(matches!(attributes[0], FileAttribute::All));
            
            match condition {
                Some(FileCondition::Like { attribute, pattern, case_sensitive }) => {
                    assert!(matches!(attribute, FileAttribute::Name));
                    assert_eq!(pattern, "%config%");
                    assert!(!case_sensitive);
                },
                _ => panic!("Expected LIKE condition"),
            }
        },
        _ => panic!("Expected SELECT query"),
    }
}

#[test]
fn test_parse_select_with_like_pattern_variations() {
    let test_cases = vec![
        ("SELECT * FROM ~/Downloads WHERE name LIKE '%meme'", "%meme"),
        ("SELECT * FROM ~/Documents WHERE name LIKE 'meme%'", "meme%"),
        ("SELECT * FROM ~/Pictures WHERE name LIKE '%meme%'", "%meme%"),
        ("SELECT * FROM ~/Videos WHERE name LIKE 'm_me'", "m_me"),
        ("SELECT * FROM ~/Music WHERE name LIKE '_e%e_'", "_e%e_"),
    ];

    for (sql, expected_pattern) in test_cases {
        let query = parse_sql(sql).unwrap();
        match query {
            FileQuery::Select { condition, .. } => {
                match condition {
                    Some(FileCondition::Like { attribute, pattern, case_sensitive }) => {
                        assert!(matches!(attribute, FileAttribute::Name));
                        assert_eq!(pattern, expected_pattern);
                        assert!(!case_sensitive, "Expected case_sensitive to be false for: {}", sql);
                    },
                    _ => panic!("Expected LIKE condition for SQL: {}", sql),
                }
            },
            _ => panic!("Expected SELECT query for SQL: {}", sql),
        }
    }
}

#[test]
fn test_parse_select_with_regexp() {
    let sql = "SELECT * FROM ~/logs WHERE REGEXP(name, '^server_[0-9]+\\.log$')";
    let query = parse_sql(sql).unwrap();
    
    match query {
        FileQuery::Select { path, recursive, attributes, condition } => {
            assert!(path.ends_with("logs"));
            assert!(!recursive);
            assert_eq!(attributes.len(), 1);
            assert!(matches!(attributes[0], FileAttribute::All));
            
            match condition {
                Some(FileCondition::Regexp { attribute, pattern }) => {
                    assert!(matches!(attribute, FileAttribute::Name));
                    assert_eq!(pattern, "^server_[0-9]+\\.log$");
                },
                _ => panic!("Expected REGEXP condition"),
            }
        },
        _ => panic!("Expected SELECT query"),
    }
}

#[test]
fn test_parse_select_with_recursive() {
    let sql = "WITH RECURSIVE SELECT * FROM ~/Projects WHERE name LIKE '%config%'";
    let query = parse_sql(sql).unwrap();
    
    match query {
        FileQuery::Select { path, recursive, attributes, condition } => {
            assert!(path.ends_with("Projects"));
            assert!(recursive);
            assert_eq!(attributes.len(), 1);
            assert!(matches!(attributes[0], FileAttribute::All));
            
            match condition {
                Some(FileCondition::Like { attribute, pattern, case_sensitive }) => {
                    assert!(matches!(attribute, FileAttribute::Name));
                    assert_eq!(pattern, "%config%");
                    assert!(!case_sensitive);
                },
                _ => panic!("Expected LIKE condition"),
            }
        },
        _ => panic!("Expected SELECT query"),
    }
}

#[test]
fn test_parse_update() {
    let sql = "UPDATE ~/executables SET permissions = '755' WHERE extension = '.bin'";
    let query = parse_sql(sql).unwrap();
    
    match query {
        FileQuery::Update { path, updates, condition } => {
            assert!(path.ends_with("executables"));
            assert_eq!(updates.len(), 1);
            
            let update = &updates[0];
            assert!(matches!(update.attribute, FileAttribute::Permissions));
            assert_eq!(update.value, "755");
            
            match condition {
                Some(FileCondition::Compare { attribute, operator, value }) => {
                    assert!(matches!(attribute, FileAttribute::Extension));
                    assert!(matches!(operator, ComparisonOperator::Eq));
                    assert!(matches!(value, FileValue::String(s) if s == ".bin"));
                },
                _ => panic!("Expected Compare condition"),
            }
        },
        _ => panic!("Expected UPDATE query"),
    }
}

#[test]
fn test_parse_update_multiple_attributes() {
    let sql = "UPDATE ~/executables SET owner = 'admin', permissions = '755' WHERE extension = '.bin'";
    let query = parse_sql(sql).unwrap();
    
    match query {
        FileQuery::Update { path, updates, condition } => {
            assert!(path.ends_with("executables"));
            assert_eq!(updates.len(), 2);
            
            assert!(matches!(updates[0].attribute, FileAttribute::Owner));
            assert_eq!(updates[0].value, "admin");
            
            assert!(matches!(updates[1].attribute, FileAttribute::Permissions));
            assert_eq!(updates[1].value, "755");
            
            match condition {
                Some(FileCondition::Compare { attribute, operator, value }) => {
                    assert!(matches!(attribute, FileAttribute::Extension));
                    assert!(matches!(operator, ComparisonOperator::Eq));
                    assert!(matches!(value, FileValue::String(s) if s == ".bin"));
                },
                _ => panic!("Expected Compare condition"),
            }
        },
        _ => panic!("Expected UPDATE query"),
    }
}

#[test]
fn test_parse_invalid_sql() {
    let sql = "INSERT INTO ~/Documents VALUES ('file.txt')";
    let result = parse_sql(sql);
    
    assert!(result.is_err());
    match result {
        Err(ParserError::UnsupportedStatement(_)) => {},
        _ => panic!("Expected UnsupportedStatement error"),
    }
}