orql 0.1.0

A toy SQL parser for a subset of the Oracle dialect.
Documentation
#[rustfmt::skip]
#[test]
fn test_parse_condition() {
    assert_sql!(cond("1 = 1"), debug_verbose("Condition::Compare{1 = 1}"));
    assert_sql!(cond("((1) = 1)"), debug_verbose("(Condition::Compare{(1) = 1})"));
    assert_sql!(cond("(1) = 1"), debug("{(1) = 1}"));
    assert_sql!(cond("1 <> 1"), debug("{1 <> 1}"));
    assert_sql!(cond("1 <= 1"), debug("{1 <= 1}"));
    assert_sql!(cond("1 < 1"), debug("{1 < 1}"));
    assert_sql!(cond("1 > 1"), debug("{1 > 1}"));
    assert_sql!(cond("1 >= 1"), debug("{1 >= 1}"));
    assert_sql!(cond("1 != 1"), debug("{1 != 1}"));
    assert_sql!(cond("1 ^= 1"), debug("{1 ^= 1}"));
    assert_sql!(cond("1 + 2 ^= 3 + 4"), debug_verbose("Condition::Compare{Expr::Binary{1 + 2} ^= Expr::Binary{3 + 4}}"));
    assert_sql!(cond("1 + 2 ^= 3 + 4"), debug("{{1 + 2} ^= {3 + 4}}"));
    assert_sql!(cond("1 * 2 ^= 3 + 4"), debug("{{1 * 2} ^= {3 + 4}}"));
    assert_sql!(cond("1 + 2 ^= 3 * 4"), debug("{{1 + 2} ^= {3 * 4}}"));
    assert_sql!(cond("1 = 1 AND 2 = 2"), debug("{{1 = 1} AND {2 = 2}}"));
    assert_sql!(cond("1 = 1 OR 2 = 2"), debug("{{1 = 1} OR {2 = 2}}"));
    assert_sql!(cond("1 = 1 AND 2 = 2 OR 3 = 3"), debug("{{{1 = 1} AND {2 = 2}} OR {3 = 3}}"));
    assert_sql!(cond("1 = 1 OR 2 = 2 AND 3 = 3"), debug("{{1 = 1} OR {{2 = 2} AND {3 = 3}}}"));
    assert_sql!(cond("NOT 1 = 1"), debug("{NOT {1 = 1}}"));
    assert_sql!(cond("(NOT (1 = 1))"), debug_verbose("(Condition::Not{NOT (Condition::Compare{1 = 1})})"));
    assert_sql!(cond("(NOT 1 = 1)"), debug("({NOT {1 = 1}})"));
    assert_sql!(cond("NOT 1 = 1 AND 2 = 2"), debug("{{NOT {1 = 1}} AND {2 = 2}}"));
    assert_sql!(cond("NOT (1 = 1 AND 2 = 2)"), debug("{NOT ({{1 = 1} AND {2 = 2}})}"));
    assert_sql!(cond("1 = 1 AND NOT 2 = 2"), debug("{{1 = 1} AND {NOT {2 = 2}}}"));
    assert_sql!(cond("(1, 2) = (1, 2) AND not (3, 4) = (5, 6)"), debug("{{(1, 2) = (1, 2)} AND {NOT {(3, 4) = (5, 6)}}}"));
}

#[rustfmt::skip]
#[test]
fn test_parse_invalid_conditions() {
    assert_sql!(
        cond("(1, 2) = ((1, 2), NOT 1 = 2)"),
        "Unexpected NOT; expected a value or an expression [line: 1, column: 19]");
    assert_sql!(
        cond("(1, 2) = ((1, 2), EXISTS (select 1 from dual))"),
        "Unexpected EXISTS; expected a value or an expression [line: 1, column: 19]");
}

#[rustfmt::skip]
#[test]
fn test_parse_expr_lists() {
    assert_sql!(cond("(1, 2) = ((3, 4))"), debug("{(1, 2) = ((3, 4))}"));
}

#[rustfmt::skip]
#[test]
#[ignore = "fix me"] // XXX
fn test_parse_nested_cond_with_expr_lists() {
    assert_sql!(cond("((1, 2) = ((3, 4)))"), debug("({(1, 2) = ((3, 4))})"));
}

#[rustfmt::skip]
#[test]
fn test_comparison_operators() {
    // ~ comparison operators have lower precedence than `+`, `-`, `||`, `*`, and `/`

    assert_sql!(cond("1 + 2 < 3"), debug("{{1 + 2} < 3}"));
    assert_sql!(cond("1 < 2 + 3"), debug("{1 < {2 + 3}}"));
    assert_sql!(cond("1 + 2 >= 3 * 4"), debug("{{1 + 2} >= {3 * 4}}"));
    assert_sql!(cond("1 + 2 <> 3 * 4"), debug("{{1 + 2} <> {3 * 4}}"));

    assert_sql!(cond("1 + 2 + 3 = 6"), debug("{{{1 + 2} + 3} = 6}"));

    assert_sql!(cond("1 + 2 * 3 <> 6"), debug("{{1 + {2 * 3}} <> 6}"));
    assert_sql!(cond("(1 + 2) * 3 <> 6"), debug("{{({1 + 2}) * 3} <> 6}"));

    assert_sql!(cond("6 = 1 - 2 / 3"), debug("{6 = {1 - {2 / 3}}}"));
    assert_sql!(cond("6 = (1 + 2) / 3"), debug("{6 = {({1 + 2}) / 3}}"));

    assert_sql!(cond("1 + 2 <> 3 * 4"), debug("{{1 + 2} <> {3 * 4}}"));
    assert_sql!(cond("'a' || 'b' <> 'a' || 'c'"), debug("{{'a' || 'b'} <> {'a' || 'c'}}"));

    assert_sql!(cond("1 + 2 < 3"), debug("{{1 + 2} < 3}"));
    assert_sql!(cond("1 < 2 + 3"), debug("{1 < {2 + 3}}"));

    assert_sql!(cond("1 != ALL 2"), debug("{1 != ALL 2}"));
    assert_sql!(cond("1 ^= ALL 2"), debug("{1 ^= ALL 2}"));
    assert_sql!(cond("1 <> ANY 2"), debug("{1 <> ANY 2}"));
    assert_sql!(cond("1 <> SOME (2, 3)"), debug("{1 <> SOME (2, 3)}"));
    assert_sql!(cond("1 <> \"SOME\".x"), debug("{1 <> {\"SOME\".x}}"));
}

#[rustfmt::skip]
#[test]
fn test_comparison_expressions_nesting() {
    assert_sql!(cond("(((1 = 1)))"), debug("((({1 = 1})))"));
    assert_sql!(cond("(((1 = 1 AND 2 <> 3)))"), debug("((({{1 = 1} AND {2 <> 3}})))"));
    assert_sql!(cond("(((1 = 1) AND (2 <> 3)))"), debug("(({({1 = 1}) AND ({2 <> 3})}))"));
    assert_sql!(cond("(1, 2) = ((2, 3))"), debug("{(1, 2) = ((2, 3))}"));
}

#[rustfmt::skip]
#[test]
fn test_comparison_operators_with_parens() {
    assert_sql!(cond("(1 + 2) <> (3 * 4)"), debug("{({1 + 2}) <> ({3 * 4})}"));
    assert_sql!(cond("(1 + 2, 3) <> (3 * 4, 5)"), debug("{({1 + 2}, 3) <> ({3 * 4}, 5)}"));
    assert_sql!(cond("(1 + 2, (3)) <> ((3 * 4), 5)"), debug("{({1 + 2}, (3)) <> (({3 * 4}), 5)}"));
}

#[rustfmt::skip]
#[test]
fn test_comparison_operators_with_subqueries() {
    assert_sql!(
        cond("(select 1 from dual) <> (select 2 from dual)"),
        debug("{(SELECT 1 FROM {dual}) <> (SELECT 2 FROM {dual})}"));
    assert_sql!(
        cond("((select 1 from dual)) <> ((select 2 from dual))"),
        debug("{((SELECT 1 FROM {dual})) <> ((SELECT 2 FROM {dual}))}"));
}

#[rustfmt::skip]
#[test]
fn test_condition_comments() {
    assert_sql!(
        cond("/*a*/ 1 /*b*/ = /*c*/ 2 /*d*/ AND /*e*/ 3 /*f*/ <> /*g*/ 4 /*h*/"),
        with_meta("/*a*/ 1 /*b*/ = /*c*/ 2 /*d*/ AND /*e*/ 3 /*f*/ <> /*g*/ 4 /*h*/"));

    assert_sql!(
        cond("/*a*/ ( /*b*/ 1 /*c*/, /*d*/ 2 /*e*/) /*f*/ = /*g*/ ( /*h*/ ( /*i*/ 4 /*j*/ , /*k*/ 5 /*l*/ ) /*m*/) /*n*/ "),
        with_meta("/*a*/ ( /*b*/ 1 /*c*/ , /*d*/ 2 /*e*/ ) /*f*/ = /*g*/ ( /*h*/ ( /*i*/ 4 /*j*/ , /*k*/ 5 /*l*/ ) /*m*/ ) /*n*/"));
}

#[rustfmt::skip]
#[test]
fn test_is_null_condition() {
    assert_sql!(cond("column IS NULL"), debug("{{column} IS NULL}"));
    assert_sql!(cond("column IS not NULL"), debug("{{column} IS NOT NULL}"));

    assert_sql!(
        cond("/*1*/ column /*2*/ IS /*3*/ not /*4*/ NULL /*5*/"),
        debug(with_meta("{{ /*1*/ column /*2*/ } IS /*3*/ NOT /*4*/ NULL /*5*/ }")));
}

#[rustfmt::skip]
#[test]
fn test_is_float_condition() {
    assert_sql!(
        cond("column IS nan OR column is infinite"),
        debug("{{{column} IS NAN} OR {{column} IS INFINITE}}"));

    assert_sql!(
        cond("column is not null and column IS not nan AND column is not infinite"),
        debug("{{{{column} IS NOT NULL} AND {{column} IS NOT NAN}} AND {{column} IS NOT INFINITE}}"));

    assert_sql!(
        cond("/*1*/ column /*2*/ IS /*3*/ not /*4*/ NAN /*5*/"),
        debug(with_meta("{{ /*1*/ column /*2*/ } IS /*3*/ NOT /*4*/ NAN /*5*/ }")));
    assert_sql!(
        cond("/*1*/ column /*2*/ IS /*3*/ not /*4*/ INFINITE /*5*/"),
        debug(with_meta("{{ /*1*/ column /*2*/ } IS /*3*/ NOT /*4*/ INFINITE /*5*/ }")));
    assert_sql!(
        cond("/*1*/ column /*2*/ IS /*3*/ /*4*/ INFINITE /*5*/"),
        debug(with_meta("{{ /*1*/ column /*2*/ } IS /*3*/ /*4*/ INFINITE /*5*/ }")));
}

#[rustfmt::skip]
#[test]
fn test_like_conditions() {
    assert_sql!(
        cond("t.name LIKE 'a%'"),
        debug("{{t.name} LIKE 'a%'}"));
    // ~ in parens
    assert_sql!(
        cond("(t.name LIKE 'a%')"),
        debug("({{t.name} LIKE 'a%'})"));

    assert_sql!(
        cond("t.name LIKE t.pattern"),
        debug("{{t.name} LIKE {t.pattern}}"));

    assert_sql!(
        cond(r#"t.name LIKE 'a\%' escape '\'"#),
        debug(r#"{{t.name} LIKE 'a\%' ESCAPE '\'}"#));
    assert_sql!(
        cond(r#"t.name LIKE 'a\%' escape t.esc"#),
        debug(r#"{{t.name} LIKE 'a\%' ESCAPE {t.esc}}"#));

    assert_sql!(
        cond(r#"name LIKE escape"#),
        debug(r#"{{name} LIKE {escape}}"#));
    assert_sql!(
        cond(r#"name LIKE escape escape '\'"#),
        debug(r#"{{name} LIKE {escape} ESCAPE '\'}"#));

    // ~ _NOT_ LIKE ...
    assert_sql!(
        cond("t.name not LIKE 'a%'"),
        debug("{{t.name} NOT LIKE 'a%'}"));
    // ~ in parens
    assert_sql!(
        cond("(t.name not LIKE 'a%')"),
        debug("({{t.name} NOT LIKE 'a%'})"));
    assert_sql!(
        cond("(t.name not LIKE2 'a%')"),
        debug("({{t.name} NOT LIKE2 'a%'})"));
    assert_sql!(
        cond("(t.name not LIKE4 'a%')"),
        debug("({{t.name} NOT LIKE4 'a%'})"));
    assert_sql!(
        cond("(t.name not LIKEC 'a%')"),
        debug("({{t.name} NOT LIKEC 'a%'})"));

    assert_sql!(
        cond("(/*0*/ t.name /*1*/ not /*2*/ LIKEC /*3*/ 'a%' /*4*/)"),
        debug(with_meta("({{ /*0*/ t.name /*1*/ } NOT /*2*/ LIKEC /*3*/ 'a%' /*4*/ })")));
    assert_sql!(
        cond("(/*0*/ t.name /*1*/ not /*2*/ LIKEC /*3*/ 'a$%%' /*4*/ escape /*5*/ '$' /*6*/)"),
        debug(with_meta("({{ /*0*/ t.name /*1*/ } NOT /*2*/ LIKEC /*3*/ 'a$%%' /*4*/ ESCAPE /*5*/ '$' /*6*/ })")));
}

#[rustfmt::skip]
#[test]
fn test_between_conditions() {
    assert_sql!(
        cond("t.name between 1 + 2 and 3 * 4"),
        debug("{{t.name} BETWEEN {1 + 2} AND {3 * 4}}"));
    assert_sql!(
        cond("t.name between 1 + 2 and 3 * 4"),
        debug("{{t.name} BETWEEN {1 + 2} AND {3 * 4}}"));
    assert_sql!(
        cond("((t.name) between 1 + 2 and 3 * 4)"),
        debug("({({t.name}) BETWEEN {1 + 2} AND {3 * 4}})"));

    assert_sql!(
        cond("t.name not between 1 + 2 and 3 * 4"),
        debug("{{t.name} NOT BETWEEN {1 + 2} AND {3 * 4}}"));
    assert_sql!(
        cond("(t.name) not between 1 + 2 and 3 * 4"),
        debug("{({t.name}) NOT BETWEEN {1 + 2} AND {3 * 4}}"));

    assert_sql!(
        cond("t.name /*1*/ not /*2*/ between /*3*/ 1 + 2 /*4*/ and /*5*/ 3 * 4 /*6*/"),
        debug(with_meta("{{t.name /*1*/ } NOT /*2*/ BETWEEN /*3*/ {1 + 2 /*4*/ } AND /*5*/ {3 * 4 /*6*/ }}")));
}

#[rustfmt::skip]
#[test]
fn test_in_conditions() {
    assert_sql!(
        cond("1 IN (1)"),
        debug("{1 IN (1)}"));
    assert_sql!(
        cond("1 IN (1, 2)"),
        debug("{1 IN (1, 2)}"));
    assert_sql!(
        cond("1 IN (select 1 from dual)"),
        debug("{1 IN (SELECT 1 FROM {dual})}"));
    assert_sql!(
        cond("(select 1 from dual) IN (select 1 from dual)"),
        debug("{(SELECT 1 FROM {dual}) IN (SELECT 1 FROM {dual})}"));
    assert_sql!(
        cond("(1, 2) IN ((1, 2), (3, 4))"),
        debug("{(1, 2) IN ((1, 2), (3, 4))}"));
    assert_sql!(
        cond("(1, 2) IN ((1, 2), (3, 4))"),
        debug("{(1, 2) IN ((1, 2), (3, 4))}"));
    assert_sql!(
        cond("(1, 2) IN ((1, 2), (((3)), 4))"),
        debug("{(1, 2) IN ((1, 2), (((3)), 4))}"));
}

#[rustfmt::skip]
#[test]
fn test_invalid_in_conditions() {
    assert_sql!(
        cond("1 IN 1"),
        debug("Unexpected `1`; expected an opening parenthesis (starting an expression list or a sub-query) [line: 1, column: 6]"));
    assert_sql!(
        cond("((1, 2), (3, 4)) IN ((1, 2), (3, 4))"),
        debug("Unexpected nested expression list group; expected expression or expression list [line: 1, column: 2]"));
    assert_sql!(
        cond("(1, 2) IN (((1, 2)), (3, 4))"),
        debug("Unexpected nested expression list group; expected expression or expression list [line: 1, column: 12]"));
}