fosk 0.2.0

In-memory SQL-like query engine and lightweight data store for testing and prototyping.
Documentation
use crate::parser::{
    ParseError, QueryParser,
    ast::{
        BoolParser, Column, Function, Literal, NullParser, NumberParser, ParamParser, StringParser,
    },
};
use std::fmt;

#[derive(Clone, PartialEq, Eq, Hash)]
pub enum ScalarExpr {
    Literal(Literal),
    Column(Column),
    Function(Function),
    WildCard,
    WildCardWithCollection(String),
    Parameter,
    Args(Vec<ScalarExpr>),
}

impl ScalarExpr {
    pub fn parse(parser: &mut QueryParser, allow_wildcard: bool) -> Result<ScalarExpr, ParseError> {
        parser.next_non_whitespace();

        if parser.eof() {
            return ParseError::new("Invalid scalar value", parser.position, parser).err();
        }

        if NumberParser::is_number(parser) {
            return NumberParser::parse(parser).map(ScalarExpr::Literal);
        }

        if StringParser::is_string_delimiter(parser) {
            return StringParser::parse(parser).map(ScalarExpr::Literal);
        }

        if BoolParser::is_bool(parser) {
            return BoolParser::parse(parser).map(ScalarExpr::Literal);
        }

        if NullParser::is_null(parser) {
            return NullParser::parse(parser).map(ScalarExpr::Literal);
        }

        if ParamParser::is_param(parser) {
            return ParamParser::parse(parser);
        }

        Column::parse_general_scalar(parser, allow_wildcard)
    }
}

impl fmt::Display for ScalarExpr {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ScalarExpr::Literal(l) => write!(f, "lit: {}", l),
            ScalarExpr::Column(c) => write!(f, "{}", c),
            ScalarExpr::Function(fun) => write!(f, "{}", fun),
            ScalarExpr::WildCard => write!(f, "*"),
            ScalarExpr::WildCardWithCollection(coll) => write!(f, "{}.*", coll),
            ScalarExpr::Parameter => write!(f, "?"),
            ScalarExpr::Args(args) => write!(
                f,
                "({})",
                args.iter()
                    .map(|f| f.to_string())
                    .collect::<Vec<String>>()
                    .join(", ")
            ),
        }
    }
}

impl fmt::Debug for ScalarExpr {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ScalarExpr::Literal(_) => write!(f, "Literal({})", self),
            ScalarExpr::Column(_) => write!(f, "Column({})", self),
            ScalarExpr::Function(_) => write!(f, "Function({})", self),
            ScalarExpr::WildCard => write!(f, "WildCard(*)"),
            ScalarExpr::WildCardWithCollection(coll) => {
                write!(f, "WildCardWithCollection({}.*)", coll)
            }
            ScalarExpr::Parameter => write!(f, "Parameter(?)"),
            ScalarExpr::Args(_) => write!(f, "Parameter({})", self),
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::parser::{
        QueryParser,
        ast::{Column, Function, Literal, ScalarExpr},
    };

    fn parse_scalar(text: &str, allow_wildcard: bool) -> ScalarExpr {
        let mut parser = QueryParser::new(text);
        match ScalarExpr::parse(&mut parser, allow_wildcard) {
            Ok(expr) => expr,
            Err(err) => panic!("expected scalar from {text:?}, got {err:?}"),
        }
    }

    fn parse_error(text: &str, allow_wildcard: bool) -> (usize, usize, String) {
        let mut parser = QueryParser::new(text);
        match ScalarExpr::parse(&mut parser, allow_wildcard) {
            Ok(expr) => panic!("expected scalar parse error from {text:?}, got {expr:?}"),
            Err(err) => (err.start, err.end, err.text),
        }
    }

    #[test]
    pub fn test_scalar_column_name() {
        assert_eq!(
            parse_scalar("column", true),
            ScalarExpr::Column(Column::Name {
                name: "column".to_string()
            })
        );
    }

    #[test]
    pub fn test_scalar_column_name_and_collection() {
        assert_eq!(
            parse_scalar("collection.column", true),
            ScalarExpr::Column(Column::WithCollection {
                collection: "collection".to_string(),
                name: "column".to_string()
            })
        );
    }

    #[test]
    pub fn test_scalar_column_name_prefixed_with_whitespace() {
        assert_eq!(
            parse_scalar("  column", true),
            ScalarExpr::Column(Column::Name {
                name: "column".to_string()
            })
        );
    }

    #[test]
    pub fn test_scalar_null_parser() {
        assert_eq!(
            parse_scalar("null", true),
            ScalarExpr::Literal(Literal::Null)
        );
    }

    #[test]
    pub fn test_scalar_bool_parser_true() {
        assert_eq!(
            parse_scalar("true", true),
            ScalarExpr::Literal(Literal::Bool(true))
        );
    }

    #[test]
    pub fn test_scalar_number_parser_int() {
        assert_eq!(
            parse_scalar("32", true),
            ScalarExpr::Literal(Literal::Int(32))
        );
    }

    #[test]
    pub fn test_scalar_string_parser() {
        assert_eq!(
            parse_scalar("\"identifier\"", true),
            ScalarExpr::Literal(Literal::String("identifier".to_string()))
        );
    }

    #[test]
    pub fn test_scalar_empty() {
        let (start, end, text) = parse_error(" ", true);
        assert_eq!((start, end, text), (1, 1, String::new()));
    }

    #[test]
    pub fn test_scalar_wildcard() {
        assert_eq!(parse_scalar("*", true), ScalarExpr::WildCard);
    }

    #[test]
    pub fn test_scalar_wildcard_with_collection() {
        assert_eq!(
            parse_scalar("collection.*", true),
            ScalarExpr::WildCardWithCollection("collection".to_string())
        );
    }

    #[test]
    pub fn test_scalar_wildcard_not_allowed() {
        let (start, end, text) = parse_error("*", false);
        assert_eq!((start, end, text), (0, 1, "*".to_string()));
    }

    #[test]
    pub fn test_scalar_wildcard_with_collection_not_allowed() {
        let (start, end, text) = parse_error("collection.*", false);
        assert_eq!((start, end, text), (11, 12, "*".to_string()));
    }

    #[test]
    fn display_and_debug_cover_all_scalar_expr_variants() {
        let literal = ScalarExpr::Literal(Literal::Int(1));
        let column = ScalarExpr::Column(Column::Name {
            name: "age".to_string(),
        });
        let function = ScalarExpr::Function(Function {
            name: "sum".to_string(),
            args: vec![column.clone()],
            distinct: false,
        });
        let wildcard = ScalarExpr::WildCard;
        let collection_wildcard = ScalarExpr::WildCardWithCollection("people".to_string());
        let parameter = ScalarExpr::Parameter;
        let args = ScalarExpr::Args(vec![literal.clone(), parameter.clone()]);

        assert_eq!(literal.to_string(), "lit: i: 1");
        assert_eq!(format!("{:?}", literal), "Literal(lit: i: 1)");
        assert_eq!(column.to_string(), "col: age");
        assert_eq!(format!("{:?}", column), "Column(col: age)");
        assert_eq!(function.to_string(), "sum(col: age)");
        assert_eq!(format!("{:?}", function), "Function(sum(col: age))");
        assert_eq!(wildcard.to_string(), "*");
        assert_eq!(format!("{:?}", wildcard), "WildCard(*)");
        assert_eq!(collection_wildcard.to_string(), "people.*");
        assert_eq!(
            format!("{:?}", collection_wildcard),
            "WildCardWithCollection(people.*)"
        );
        assert_eq!(parameter.to_string(), "?");
        assert_eq!(format!("{:?}", parameter), "Parameter(?)");
        assert_eq!(args.to_string(), "(lit: i: 1, ?)");
        assert_eq!(format!("{:?}", args), "Parameter((lit: i: 1, ?))");
    }
}