orql 0.1.0

A toy SQL parser for a subset of the Oracle dialect.
Documentation
#[test]
fn test_binary_operator_precedence() {
    assert_sql!(expr("1 * 2 + 3"), debug("{{1 * 2} + 3}"));
    assert_sql!(expr("1 / 2 + 3"), debug("{{1 / 2} + 3}"));
    assert_sql!(expr("1 + 2 * 3"), debug("{1 + {2 * 3}}"));
    assert_sql!(expr("1 + 2 / 3"), debug("{1 + {2 / 3}}"));
    assert_sql!(expr("1 + 2 + 3"), debug("{{1 + 2} + 3}"));
    assert_sql!(expr("1 * 2 * 3"), debug("{{1 * 2} * 3}"));
    assert_sql!(expr("1 * 2 / 3"), debug("{{1 * 2} / 3}"));
    assert_sql!(expr("1 / 2 * 3"), debug("{{1 / 2} * 3}"));
    assert_sql!(expr("1 / 2 + 3 * 4"), debug("{{1 / 2} + {3 * 4}}"));

    // ~ the `||` operator the same precedence as `+` or `-` ...
    assert_sql!(expr("1 + 2 || 3"), debug("{{1 + 2} || 3}"));
    // ... and has lower precedence as `*` or `/`
    assert_sql!(expr("1 / 2 || 3"), debug("{{1 / 2} || 3}"));
    assert_sql!(expr("1 || 2 / 3"), debug("{1 || {2 / 3}}"));
}

#[test]
fn test_unary_operator_precedence() {
    assert_sql!(expr("+1"), debug("{+1}"));
    assert_sql!(expr("-1"), debug("{-1}"));
    assert_sql!(expr("-1 + 2"), debug("{{-1} + 2}"));
    assert_sql!(expr("1 + +2 * ?"), debug("{1 + {{+2} * ?}}"));
    assert_sql!(expr("1 + +2"), debug("{1 + {+2}}"));
    assert_sql!(expr("1 + -2 * ?"), debug("{1 + {{-2} * ?}}"));
}

#[test]
fn test_identifiers() {
    assert_sql!(expr(r#"abc"#), "abc");
    assert_sql!(expr(r#"abc.def"#), "abc.def");
    assert_sql!(expr(r#"abc . def"#), "abc.def");
    // ~ quoted select is not considered a keyword
    assert_sql!(expr(r#"abc . "SELECT""#), r#"abc."SELECT""#);
    // ~ dot has highest precedence
    assert_sql!(
        expr(r#"abc + -def.ghi * 123"#),
        debug("{{abc} + {{-{def.ghi}} * 123}}")
    );
}

#[test]
fn test_null() {
    assert_sql!(expr("null"), "NULL");
    assert_sql!(expr("q'|abc|' || null"), debug("{Q'|abc|' || NULL}"));
}

#[test]
fn test_nested_exprs() {
    assert_sql!(expr(r#"(1)"#), debug("(1)"));
    assert_sql!(expr(r#"(((1)))"#), debug("(((1)))"));
    assert_sql!(expr("1 * (2 + 3)"), debug("{1 * ({2 + 3})}"));

    assert_sql!(expr("(1) * 2"), debug("{(1) * 2}"));
    assert_sql!(
        expr("1) * 2"),
        "Unbalanced parentheses [line: 1, column: 2]"
    );
    assert_sql!(
        expr("(1)) * 2"),
        "Unbalanced parentheses [line: 1, column: 4]"
    );

    assert_sql!(expr("((1 + (2)) * 3)"), debug("({({1 + (2)}) * 3})"));

    assert_sql!(expr("(1 * (2 + (3)))"), debug("({1 * ({2 + (3)})})"));
    assert_sql!(
        expr("(1 * (2 + (3))"),
        "Unexpected end-of-file; expected a closing parenthesis [line: 1, column: 15]"
    );
}

#[test]
fn test_function_calls_0() {
    assert_sql!(expr("foo(/* no arguments */)"), debug("{{foo}()}"));
    assert_sql!(
        expr("foo(/* no arguments */)"),
        debug(with_meta("{{foo}( /* no arguments */ )}"))
    );
    assert_sql!(expr("foo(1)"), debug("{{foo}(1)}"));
    assert_sql!(expr("foo((1))"), debug("{{foo}((1))}"));
}

#[test]
fn test_function_calls_1() {
    assert_sql!(expr("foo(1, 'a')"), debug("{{foo}(1, 'a')}"));
    assert_sql!(
        expr("foo(1, 'a', bar(2, 'b', quux()))"),
        debug("{{foo}(1, 'a', {{bar}(2, 'b', {{quux}()})})}")
    );
}

#[test]
fn test_case_exprs_simple() {
    assert_sql!(
        expr("case 1 when 0 then 'yes' end"),
        debug("{CASE 1 {WHEN 0 THEN 'yes'} END}")
    );
    assert_sql!(
        expr("case x when 0 then 'yes' end"),
        debug("{CASE {x} {WHEN 0 THEN 'yes'} END}")
    );
    assert_sql!(
        expr("case 1 when 0 then 'yes' else 'maybe' end"),
        debug("{CASE 1 {WHEN 0 THEN 'yes'} {ELSE 'maybe'} END}")
    );
    assert_sql!(
        expr("case 1 when 0 then 'yes' when 1 then 'no' else 'maybe' end"),
        debug("{CASE 1 {WHEN 0 THEN 'yes'} {WHEN 1 THEN 'no'} {ELSE 'maybe'} END}")
    );
    assert_sql!(
        expr(
            r"case case x when 'a' then 1 else 0 end
               when 0 then 'yes'
               else 'maybe'
                end"
        ),
        debug(
            "{CASE \
               {CASE {x} {WHEN 'a' THEN 1} {ELSE 0} END} \
               {WHEN 0 THEN 'yes'} \
               {ELSE 'maybe'} \
              END}"
        )
    );
    // ~ comments
    assert_sql!(
        expr(
            "/*1*/ case /*2*/ 1 /*3*/ when /*4*/ 0 /*5*/ then /*6*/ 'yes' /*7*/ else /*8*/ 'maybe' /*9*/ end /*0*/"
        ),
        debug(with_meta(
            "{ /*1*/ CASE /*2*/ 1 /*3*/ {WHEN /*4*/ 0 /*5*/ THEN /*6*/ 'yes' /*7*/ } {ELSE /*8*/ 'maybe' /*9*/ } END /*0*/ }"
        ))
    );
}

#[test]
fn test_case_exprs_searched() {
    assert_sql!(
        expr("case when 1 = 1 then 'yes' end"),
        debug("{CASE {WHEN {1 = 1} THEN 'yes'} END}")
    );
    assert_sql!(
        expr("case when 1 = 1 then 'yes' else 'no' end"),
        debug("{CASE {WHEN {1 = 1} THEN 'yes'} {ELSE 'no'} END}")
    );
    assert_sql!(
        expr(
            r"case
               when 1 = 1 then 'yes'
               when name like 'foo%' then 'no'
                end"
        ),
        debug(
            "{CASE \
               {WHEN {1 = 1} THEN 'yes'} \
               {WHEN {{name} LIKE 'foo%'} THEN 'no'} \
                 END}"
        )
    );
    // ~ comments
    assert_sql!(
        expr(
            "/*0*/ case /*1*/ when /*2a*/ 1 /*2b*/ = /*2c*/ 1 /*3*/ then /*4*/ 'yes' /*5*/ else /*6*/ 'no' /*7*/ end /*8*/"
        ),
        with_meta(
            "/*0*/ CASE /*1*/ WHEN /*2a*/ 1 /*2b*/ = /*2c*/ 1 /*3*/ THEN /*4*/ 'yes' /*5*/ ELSE /*6*/ 'no' /*7*/ END /*8*/"
        )
    );
}