sqrust-rules 0.1.4

Fast SQL linter written in Rust — the Ruff for SQL
Documentation
use sqrust_core::FileContext;
use sqrust_core::Rule;
use sqrust_rules::structure::window_frame_all_rows::WindowFrameAllRows;

fn check(sql: &str) -> Vec<sqrust_core::Diagnostic> {
    let ctx = FileContext::from_source(sql, "test.sql");
    WindowFrameAllRows.check(&ctx)
}

#[test]
fn rule_name_is_correct() {
    assert_eq!(WindowFrameAllRows.name(), "Structure/WindowFrameAllRows");
}

#[test]
fn rows_unbounded_preceding_following_no_partition_one_violation() {
    let diags = check(
        "SELECT SUM(val) OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) FROM t",
    );
    assert_eq!(diags.len(), 1);
}

#[test]
fn rows_unbounded_preceding_following_with_partition_no_violation() {
    let diags = check(
        "SELECT SUM(val) OVER (PARTITION BY grp ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) FROM t",
    );
    assert!(diags.is_empty());
}

#[test]
fn rows_unbounded_preceding_following_with_order_by_no_partition_one_violation() {
    let diags = check(
        "SELECT SUM(val) OVER (ORDER BY id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) FROM t",
    );
    assert_eq!(diags.len(), 1);
}

#[test]
fn rows_unbounded_preceding_following_with_partition_and_order_by_no_violation() {
    let diags = check(
        "SELECT SUM(val) OVER (PARTITION BY grp ORDER BY id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) FROM t",
    );
    assert!(diags.is_empty());
}

#[test]
fn rows_unbounded_preceding_current_row_no_violation() {
    let diags = check(
        "SELECT SUM(val) OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) FROM t",
    );
    assert!(diags.is_empty());
}

#[test]
fn rows_current_row_unbounded_following_no_violation() {
    let diags = check(
        "SELECT SUM(val) OVER (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) FROM t",
    );
    assert!(diags.is_empty());
}

#[test]
fn empty_over_no_violation() {
    let diags = check("SELECT SUM(val) OVER () FROM t");
    assert!(diags.is_empty());
}

#[test]
fn over_with_order_by_only_no_violation() {
    let diags = check("SELECT SUM(val) OVER (ORDER BY id) FROM t");
    assert!(diags.is_empty());
}

#[test]
fn row_number_over_order_by_no_violation() {
    let diags = check("SELECT ROW_NUMBER() OVER (ORDER BY id) FROM t");
    assert!(diags.is_empty());
}

#[test]
fn parse_error_no_violation() {
    let diags = check("NOT VALID SQL @@@");
    assert!(diags.is_empty());
}

#[test]
fn violation_detected_in_subquery() {
    let diags = check(
        "SELECT x FROM (SELECT SUM(val) OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) FROM t) sub",
    );
    assert_eq!(diags.len(), 1);
}

#[test]
fn violation_detected_in_cte() {
    let diags = check(
        "WITH cte AS (SELECT SUM(val) OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) FROM t) SELECT * FROM cte",
    );
    assert_eq!(diags.len(), 1);
}

#[test]
fn message_contains_partition_by_or_entire_table() {
    let diags = check(
        "SELECT SUM(val) OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) FROM t",
    );
    assert_eq!(diags.len(), 1);
    let msg = &diags[0].message;
    assert!(msg.contains("PARTITION BY") || msg.contains("entire table"));
}