querydown 0.0.1

An expressive DSL for writing relational database queries that compile to SQL
Documentation
use chumsky::prelude::*;

use crate::syntax_tree::*;

use super::{
    conditions::{condition_set, implicit_condition_set},
    expressions::expression,
    utils::QdParser,
};

pub fn top_level_condition_set() -> impl QdParser<ConditionSet> {
    choice((
        discerned_condition_set(),
        implicit_condition_set(discerned_condition_set(), discerned_expression()),
    ))
}

pub fn discerned_expression() -> impl QdParser<Expression> {
    make_discerned_expression(molecule())
}

pub fn discerned_condition_set() -> impl QdParser<ConditionSet> {
    make_discerned_condition_set(molecule())
}

#[derive(Debug, Clone)]
pub enum Molecule {
    Expression(Expression),
    ConditionSet(ConditionSet),
}

fn molecule() -> impl QdParser<Molecule> {
    recursive(|molecule| {
        choice((
            condition_set(make_discerned_expression(molecule.clone())).map(Molecule::ConditionSet),
            expression(make_discerned_condition_set(molecule)).map(Molecule::Expression),
        ))
    })
}

fn make_discerned_expression(molecule: impl QdParser<Molecule>) -> impl QdParser<Expression> {
    molecule.try_map(|v, span| match v {
        Molecule::Expression(e) => Ok(e),
        Molecule::ConditionSet(_) => Err(Simple::custom(
            span,
            "Expected expression, got condition set",
        )),
    })
}

fn make_discerned_condition_set(molecule: impl QdParser<Molecule>) -> impl QdParser<ConditionSet> {
    molecule.try_map(|v, span| match v {
        Molecule::ConditionSet(e) => Ok(e),
        Molecule::Expression(_) => Err(Simple::custom(
            span,
            "Expected condition set, got expression",
        )),
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_discerned_expression() {
        assert_eq!(
            discerned_expression().parse("1"),
            Ok(Expression {
                base: Value::Literal(Literal::Number("1".to_string())),
                compositions: vec![],
            })
        );
        assert_eq!(
            discerned_expression().parse("@true"),
            Ok(Expression {
                base: Value::Literal(Literal::True),
                compositions: vec![]
            })
        );
        assert_eq!(
            discerned_expression().parse("foo"),
            Ok(Expression {
                base: (Value::Path(vec![PathPart::Column("foo".to_string())])),
                compositions: vec![],
            })
        );
        assert_eq!(
            discerned_expression().parse("foo|bar(2)%baz"),
            Ok(Expression {
                base: Value::Path(vec![PathPart::Column("foo".to_string())]),
                compositions: vec![
                    Composition {
                        function: Function {
                            name: "bar".to_string(),
                            dimension: FunctionDimension::Scalar
                        },
                        argument: Some(Expression {
                            base: Value::Literal(Literal::Number("2".to_string())),
                            compositions: vec![]
                        }),
                    },
                    Composition {
                        function: Function {
                            name: "baz".to_string(),
                            dimension: FunctionDimension::Aggregate
                        },
                        argument: None,
                    }
                ],
            })
        );
        assert_eq!(
            discerned_expression().parse("foo .bar"),
            Ok(Expression {
                base: Value::Path(vec![
                    PathPart::Column("foo".to_string()),
                    PathPart::Column("bar".to_string()),
                ]),
                compositions: vec![],
            })
        );
        assert_eq!(
            discerned_expression().parse("#foo(bar)"),
            Ok(Expression {
                base: Value::Path(vec![PathPart::TableWithMany(TableWithMany {
                    table: "foo".to_string(),
                    linking_column: Some("bar".to_string()),
                    condition_set: ConditionSet::default(),
                }),]),
                compositions: vec![],
            })
        );
        assert_eq!(
            discerned_expression().parse("#foo(bar){a:1}"),
            Ok(Expression {
                base: Value::Path(vec![PathPart::TableWithMany(TableWithMany {
                    table: "foo".to_string(),
                    linking_column: Some("bar".to_string()),
                    condition_set: ConditionSet {
                        conjunction: Conjunction::And,
                        entries: vec![ConditionSetEntry::Comparison(Comparison {
                            left: ComparisonPart::Expression(Expression {
                                base: Value::Path(vec![PathPart::Column("a".to_string())]),
                                compositions: vec![],
                            }),
                            operator: Operator::Eq,
                            right: ComparisonPart::Expression(Expression {
                                base: Value::Literal(Literal::Number("1".to_string())),
                                compositions: vec![],
                            }),
                        })],
                    },
                }),]),
                compositions: vec![],
            })
        );

        assert!(discerned_expression().parse("{a:1}").is_err());
    }

    #[test]
    fn test_discerned_condition_set() {
        assert_eq!(
            discerned_condition_set().parse("{a:1}"),
            Ok(ConditionSet {
                conjunction: Conjunction::And,
                entries: vec![ConditionSetEntry::Comparison(Comparison {
                    left: ComparisonPart::Expression(Expression {
                        base: Value::Path(vec![PathPart::Column("a".to_string())]),
                        compositions: vec![],
                    }),
                    operator: Operator::Eq,
                    right: ComparisonPart::Expression(Expression {
                        base: Value::Literal(Literal::Number("1".to_string())),
                        compositions: vec![],
                    }),
                })],
            })
        );
    }
}