weavatrix-parse 0.3.4

Dependency-free source tokenizer and structural extractor for repository intelligence
Documentation
use super::extract;
use crate::facts::DeclarationKind;

fn specifiers(source: &str) -> Vec<String> {
    extract(source)
        .imports
        .into_iter()
        .map(|import| import.specifier)
        .collect()
}

#[test]
fn created_objects_carry_their_kind() {
    let source = "CREATE TABLE app.users (id INT);\n\
         create or replace view active_users as select 1;\n\
         CREATE OR REPLACE FUNCTION bump() RETURNS INT AS 'select 1';\n";
    let declared = extract(source)
        .declarations
        .into_iter()
        .map(|item| (item.name, item.kind))
        .collect::<Vec<_>>();
    assert_eq!(
        declared,
        [
            ("app.users".to_owned(), DeclarationKind::Table),
            ("active_users".to_owned(), DeclarationKind::View),
            ("bump".to_owned(), DeclarationKind::Function),
        ],
        "keywords are matched whichever case they are written in"
    );
}

#[test]
fn a_view_depends_on_every_table_it_reads() {
    let source = "CREATE VIEW report AS\n\
         SELECT o.id\n\
         FROM orders o\n\
         JOIN app.customers c ON c.id = o.customer_id\n\
         LEFT JOIN payments ON payments.order_id = o.id;\n";
    assert_eq!(
        specifiers(source),
        ["orders", "app.customers", "payments"],
        "an ON inside a join is a condition, not another table"
    );
}

#[test]
fn writes_and_alterations_are_dependencies_too() {
    let source = "INSERT INTO events (id) VALUES (1);\n\
         UPDATE accounts SET balance = 0;\n\
         ALTER TABLE accounts ADD COLUMN note TEXT;\n\
         CREATE INDEX idx_events_id ON events (id);\n";
    assert_eq!(
        specifiers(source),
        ["events", "accounts", "accounts", "events"],
        "an ON after CREATE INDEX names the indexed table"
    );
}

#[test]
fn a_comment_or_string_never_declares_a_table() {
    let source = "-- CREATE TABLE ghost (id INT);\n\
         /* CREATE TABLE also_ghost (id INT); */\n\
         INSERT INTO log (message) VALUES ('select * from phantom');\n\
         CREATE TABLE real_one (id INT);\n";
    let facts = extract(source);
    assert_eq!(
        facts
            .declarations
            .iter()
            .map(|item| item.name.as_str())
            .collect::<Vec<_>>(),
        ["real_one"]
    );
    assert_eq!(
        specifiers(source),
        ["log"],
        "the table named inside the string literal is text"
    );
}

#[test]
fn a_comma_separated_from_list_names_every_table() {
    assert_eq!(
        specifiers("SELECT * FROM users u, orders o, app.items;"),
        ["users", "orders", "app.items"],
        "an alias is not a table"
    );
}