#[allow(unused_imports)]
use scythe_codegen as _codegen;
#[allow(unused_imports)]
use syn as _syn;
#[allow(dead_code)]
fn normalize_whitespace(s: &str) -> String {
s.split_whitespace().collect::<Vec<_>>().join(" ")
}
#[test]
fn test_and_in_join_clean() {
let schema_sql = &[
"CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);",
"CREATE TABLE orders (id SERIAL PRIMARY KEY, user_id INT NOT NULL, total INT NOT NULL);",
];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name GetUserOrders\n-- @returns :many\nSELECT u.id, o.total FROM users u JOIN orders o ON u.id = o.user_id AND o.total > 0;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_equal_null_fires() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name GetNullStatus\n-- @returns :many\nSELECT id, name FROM users WHERE status = NULL;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(!_violations.is_empty(), "expected lint violations for equal_null_fires");
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-A01")),
"expected violation SC-A01"
);
}
#[test]
fn test_is_null_clean() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name GetNullStatus\n-- @returns :many\nSELECT id, name FROM users WHERE status IS NULL;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_or_in_join_fires() {
let schema_sql = &[
"CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);",
"CREATE TABLE orders (id SERIAL PRIMARY KEY, user_id INT NOT NULL, total INT NOT NULL);",
];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name GetUserOrders\n-- @returns :many\nSELECT u.id, o.total FROM users u JOIN orders o ON u.id = o.user_id OR u.name = o.total::text;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(!_violations.is_empty(), "expected lint violations for or_in_join_fires");
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-A03")),
"expected violation SC-A03"
);
}
#[test]
fn test_duplicate_query_names_fires() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name GetUser\n-- @returns :one\nSELECT id, name FROM users WHERE id = $1;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_exec_with_returning_fires() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateUser\n-- @returns :exec\nINSERT INTO users (name) VALUES ($1) RETURNING id;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for exec_with_returning_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-C02")),
"expected violation SC-C02"
);
}
#[test]
fn test_exec_without_returning_clean() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateUser\n-- @returns :exec\nINSERT INTO users (name) VALUES ($1);";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_one_with_returning_clean() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateUser\n-- @returns :one\nINSERT INTO users (name) VALUES ($1) RETURNING id;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_bad_query_name_fires() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name doSomething\n-- @returns :many\nSELECT id, name FROM users;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for bad_query_name_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-N03")),
"expected violation SC-N03"
);
}
#[test]
fn test_good_query_name_clean() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name GetUser\n-- @returns :many\nSELECT id, name FROM users;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_lowercase_alias_clean() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name GetUsers\n-- @returns :many\nSELECT id FROM users u;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_non_snake_case_alias_fires() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name GetUsers\n-- @returns :many\nSELECT id AS userId FROM users;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for non_snake_case_alias_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-N01")),
"expected violation SC-N01"
);
}
#[test]
fn test_non_snake_case_table_fires() {
let schema_sql = &["CREATE TABLE \"User-Profiles\" (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name GetProfiles\n-- @returns :many\nSELECT id, name FROM \"User-Profiles\";";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for non_snake_case_table_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-N02")),
"expected violation SC-N02"
);
}
#[test]
fn test_snake_case_alias_clean() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name GetUsers\n-- @returns :many\nSELECT id AS user_id FROM users;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_snake_case_table_clean() {
let schema_sql = &["CREATE TABLE user_accounts (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name GetAccounts\n-- @returns :many\nSELECT id, name FROM user_accounts;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_uppercase_alias_fires() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name GetUsers\n-- @returns :many\nSELECT id FROM users U;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for uppercase_alias_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-N04")),
"expected violation SC-N04"
);
}
#[test]
fn test_in_subquery_clean() {
let schema_sql = &[
"CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);",
"CREATE TABLE orders (id SERIAL PRIMARY KEY, user_id INT NOT NULL, total INT NOT NULL);",
];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name GetUsersWithOrders\n-- @returns :many\nSELECT id, name FROM users WHERE id IN (SELECT user_id FROM orders);";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_like_leading_wildcard_fires() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name SearchUsers\n-- @returns :many\nSELECT id, name FROM users WHERE name LIKE '%test%';";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for like_leading_wildcard_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-P02")),
"expected violation SC-P02"
);
}
#[test]
fn test_like_suffix_wildcard_clean() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql =
"-- @name SearchUsersByPrefix\n-- @returns :many\nSELECT id, name FROM users WHERE name LIKE 'test%';";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_not_in_subquery_fires() {
let schema_sql = &[
"CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);",
"CREATE TABLE orders (id SERIAL PRIMARY KEY, user_id INT NOT NULL, total INT NOT NULL);",
];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name GetUsersWithoutOrders\n-- @returns :many\nSELECT id, name FROM users WHERE id NOT IN (SELECT user_id FROM orders);";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for not_in_subquery_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-P03")),
"expected violation SC-P03"
);
}
#[test]
fn test_order_with_limit_clean() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name GetTopUsers\n-- @returns :many\nSELECT id, name FROM users ORDER BY name LIMIT 10;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_order_without_limit_fires() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name GetUsersSorted\n-- @returns :many\nSELECT id, name FROM users ORDER BY name;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for order_without_limit_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-P01")),
"expected violation SC-P01"
);
}
#[test]
fn test_all_params_used_clean() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name GetUserByNameAndStatus\n-- @returns :many\nSELECT id, name FROM users WHERE name = $1 AND status = $2;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_ambiguous_column_fires() {
let schema_sql = &[
"CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);",
"CREATE TABLE orders (id SERIAL PRIMARY KEY, user_id INT NOT NULL, total INT NOT NULL);",
];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql =
"-- @name GetUserOrders\n-- @returns :many\nSELECT u.id, name FROM users u JOIN orders o ON u.id = o.user_id;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for ambiguous_column_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-S06")),
"expected violation SC-S06"
);
}
#[test]
fn test_delete_with_where_clean() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name DeleteUser\n-- @returns :exec\nDELETE FROM users WHERE id = $1;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_delete_without_where_fires() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name DeleteAllUsers\n-- @returns :exec\nDELETE FROM users;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for delete_without_where_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-S02")),
"expected violation SC-S02"
);
}
#[test]
fn test_has_returning_clean() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateUser\n-- @returns :one\nINSERT INTO users (name) VALUES ($1) RETURNING id;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_missing_returning_fires() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateUser\n-- @returns :one\nINSERT INTO users (name) VALUES ($1);";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for missing_returning_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-S05")),
"expected violation SC-S05"
);
}
#[test]
fn test_qualified_column_clean() {
let schema_sql = &[
"CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);",
"CREATE TABLE orders (id SERIAL PRIMARY KEY, user_id INT NOT NULL, total INT NOT NULL);",
];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql =
"-- @name GetUserOrders\n-- @returns :many\nSELECT u.id FROM users u JOIN orders o ON u.id = o.user_id;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_select_columns_clean() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name GetUsers\n-- @returns :many\nSELECT id, name FROM users;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_select_star_fires() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name GetAllUsers\n-- @returns :many\nSELECT * FROM users;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for select_star_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-S03")),
"expected violation SC-S03"
);
}
#[test]
fn test_unused_params_fires() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql =
"-- @name GetUserByName\n-- @returns :many\nSELECT id, name FROM users WHERE name = $1 AND id > $3;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for unused_params_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-S04")),
"expected violation SC-S04"
);
}
#[test]
fn test_update_with_where_clean() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name UpdateUser\n-- @returns :exec\nUPDATE users SET name = $1 WHERE id = $2;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_update_without_where_fires() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name UpdateAllUsers\n-- @returns :exec\nUPDATE users SET name = $1;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for update_without_where_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-S01")),
"expected violation SC-S01"
);
}
#[test]
fn test_adding_not_nullable_field_clean() {
let schema_sql = &["CREATE TABLE users (id bigint PRIMARY KEY)"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name UpdateUsersAddRequiredEmail\n-- @returns :exec\nALTER TABLE users ADD COLUMN email text NOT NULL DEFAULT ''";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_adding_not_nullable_field_fires() {
let schema_sql = &["CREATE TABLE users (id bigint PRIMARY KEY)"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql =
"-- @name UpdateUsersAddRequiredEmail\n-- @returns :exec\nALTER TABLE users ADD COLUMN email text NOT NULL";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for adding_not_nullable_field_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-MIG18")),
"expected violation SC-MIG18"
);
}
#[test]
fn test_adding_primary_key_constraint_clean() {
let schema_sql = &["CREATE TABLE accounts (id bigint NOT NULL, balance bigint)"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name UpdateAccountsAttachPrimaryKeyIndex\n-- @returns :exec\nALTER TABLE accounts ADD CONSTRAINT accounts_pk PRIMARY KEY USING INDEX accounts_pk_idx";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_adding_primary_key_constraint_fires() {
let schema_sql = &["CREATE TABLE accounts (id bigint NOT NULL, balance bigint)"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name UpdateAccountsAddPrimaryKey\n-- @returns :exec\nALTER TABLE accounts ADD CONSTRAINT accounts_pk PRIMARY KEY (id)";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for adding_primary_key_constraint_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-MIG15")),
"expected violation SC-MIG15"
);
}
#[test]
fn test_alter_column_type_clean() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql =
"-- @name UpdateUsersActiveDefault\n-- @returns :exec\nALTER TABLE users ALTER COLUMN active SET DEFAULT TRUE";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_alter_column_type_fires() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name UpdateUsersIdToBigint\n-- @returns :exec\nALTER TABLE users ALTER COLUMN id TYPE bigint";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for alter_column_type_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-MIG09")),
"expected violation SC-MIG09"
);
}
#[test]
fn test_ban_create_domain_with_constraint_clean() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreatePositiveIntDomain\n-- @returns :exec\nCREATE DOMAIN positive_int AS integer";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_ban_create_domain_with_constraint_fires() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql =
"-- @name CreatePositiveIntDomain\n-- @returns :exec\nCREATE DOMAIN positive_int AS integer CHECK (VALUE > 0)";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for ban_create_domain_with_constraint_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-MIG16")),
"expected violation SC-MIG16"
);
}
#[test]
fn test_ban_drop_column_clean() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateNicknameColumn\n-- @returns :exec\nALTER TABLE users ADD COLUMN nickname text";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_ban_drop_column_fires() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name DeleteLegacyColumn\n-- @returns :exec\nALTER TABLE users DROP COLUMN legacy_id";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for ban_drop_column_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-MIG02")),
"expected violation SC-MIG02"
);
}
#[test]
fn test_ban_drop_database_clean() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateReportingSchema\n-- @returns :exec\nCREATE SCHEMA reporting";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_ban_drop_database_fires() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name DeleteLegacyAppDatabase\n-- @returns :exec\nDROP DATABASE legacy_app";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for ban_drop_database_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-MIG06")),
"expected violation SC-MIG06"
);
}
#[test]
fn test_ban_drop_not_null_clean() {
let schema_sql = &["CREATE TABLE users (id bigint PRIMARY KEY, email text)"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql =
"-- @name UpdateUsersRequireEmail\n-- @returns :exec\nALTER TABLE users ALTER COLUMN email SET NOT NULL";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_ban_drop_not_null_fires() {
let schema_sql = &["CREATE TABLE users (id bigint PRIMARY KEY, email text NOT NULL)"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql =
"-- @name UpdateUsersAllowNullEmail\n-- @returns :exec\nALTER TABLE users ALTER COLUMN email DROP NOT NULL";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for ban_drop_not_null_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-MIG17")),
"expected violation SC-MIG17"
);
}
#[test]
fn test_ban_drop_table_clean() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name DeleteUserSummaryView\n-- @returns :exec\nDROP VIEW user_summary";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_ban_drop_table_fires() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name DeleteUsersTable\n-- @returns :exec\nDROP TABLE users";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for ban_drop_table_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-MIG01")),
"expected violation SC-MIG01"
);
}
#[test]
fn test_ban_truncate_cascade_clean() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name DeleteAllUsers\n-- @returns :exec\nTRUNCATE TABLE users";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_ban_truncate_cascade_fires() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name DeleteAllUsersAndDependents\n-- @returns :exec\nTRUNCATE TABLE users CASCADE";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for ban_truncate_cascade_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-MIG08")),
"expected violation SC-MIG08"
);
}
#[test]
fn test_cartesian_join_clean() {
let schema_sql = &[
"CREATE TABLE users (id SERIAL PRIMARY KEY);",
"CREATE TABLE orders (id SERIAL PRIMARY KEY, user_id INT NOT NULL);",
];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name ListOrdersWithUser\n-- @returns :many\nSELECT users.id AS user_id, orders.id AS order_id FROM users JOIN orders ON orders.user_id = users.id;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_cartesian_join_fires() {
let schema_sql = &[
"CREATE TABLE users (id SERIAL PRIMARY KEY);",
"CREATE TABLE orders (id SERIAL PRIMARY KEY, user_id INT NOT NULL);",
];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name ListAllUserOrderPairs\n-- @returns :many\nSELECT users.id AS user_id, orders.id AS order_id FROM users, orders;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for cartesian_join_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-SEC08")),
"expected violation SC-SEC08"
);
}
#[test]
fn test_check_constraint_always_true_clean() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateAccountsTable\n-- @returns :exec\nCREATE TABLE accounts (id bigint PRIMARY KEY, balance numeric CHECK (balance >= 0))";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_check_constraint_always_true_fires() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateAccountsTable\n-- @returns :exec\nCREATE TABLE accounts (id bigint PRIMARY KEY, balance numeric CHECK (true))";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for check_constraint_always_true_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-CHK01")),
"expected violation SC-CHK01"
);
}
#[test]
fn test_constraint_missing_not_valid_clean() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateOrdersUserFkNotValid\n-- @returns :exec\nALTER TABLE orders ADD CONSTRAINT orders_user_fk FOREIGN KEY (user_id) REFERENCES users (id) NOT VALID";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_constraint_missing_not_valid_fires() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateOrdersUserFk\n-- @returns :exec\nALTER TABLE orders ADD CONSTRAINT orders_user_fk FOREIGN KEY (user_id) REFERENCES users (id)";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for constraint_missing_not_valid_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-MIG05")),
"expected violation SC-MIG05"
);
}
#[test]
fn test_dangerous_function_clean() {
let schema_sql = &["CREATE TABLE files (id SERIAL PRIMARY KEY, path TEXT NOT NULL);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CountFiles\n-- @returns :one\nSELECT count(*) AS total FROM files;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_dangerous_function_fires() {
let schema_sql = &["CREATE TABLE files (id SERIAL PRIMARY KEY, path TEXT NOT NULL);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name GetServerFileSize\n-- @returns :one\nSELECT length(pg_read_file('/etc/passwd')) AS sz;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for dangerous_function_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-SEC01")),
"expected violation SC-SEC01"
);
}
#[test]
fn test_disallowed_unique_constraint_clean() {
let schema_sql = &["CREATE TABLE users (id bigint PRIMARY KEY, email text)"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name UpdateUsersAttachEmailUniqueIndex\n-- @returns :exec\nALTER TABLE users ADD CONSTRAINT users_email_uniq UNIQUE USING INDEX users_email_idx";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_disallowed_unique_constraint_fires() {
let schema_sql = &["CREATE TABLE users (id bigint PRIMARY KEY, email text)"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name UpdateUsersAddEmailUnique\n-- @returns :exec\nALTER TABLE users ADD CONSTRAINT users_email_uniq UNIQUE (email)";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for disallowed_unique_constraint_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-MIG14")),
"expected violation SC-MIG14"
);
}
#[test]
fn test_function_search_path_mutable_clean() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateAddOneFunction\n-- @returns :exec\nCREATE FUNCTION public.add_one(i int) RETURNS int LANGUAGE sql SET search_path = pg_catalog, public AS $$ SELECT i + 1 $$";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_function_search_path_mutable_fires() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateAddOneFunction\n-- @returns :exec\nCREATE FUNCTION public.add_one(i int) RETURNS int LANGUAGE sql AS $$ SELECT i + 1 $$";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for function_search_path_mutable_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-SEC12")),
"expected violation SC-SEC12"
);
}
#[test]
fn test_grant_all_clean() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateReadOnlyGrant\n-- @returns :exec\nGRANT SELECT ON users TO app_role;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_grant_all_fires() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateGrantAll\n-- @returns :exec\nGRANT ALL ON users TO app_role;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(!_violations.is_empty(), "expected lint violations for grant_all_fires");
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-SEC02")),
"expected violation SC-SEC02"
);
}
#[test]
fn test_grant_to_public_clean() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateRoleGrant\n-- @returns :exec\nGRANT SELECT ON users TO app_role;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_grant_to_public_fires() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreatePublicGrant\n-- @returns :exec\nGRANT SELECT ON users TO PUBLIC;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for grant_to_public_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-SEC03")),
"expected violation SC-SEC03"
);
}
#[test]
fn test_literal_password_clean() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateNopwdRole\n-- @returns :exec\nCREATE ROLE nopwd PASSWORD NULL";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_literal_password_fires() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateAppRole\n-- @returns :exec\nCREATE ROLE appuser PASSWORD 'hunter2'";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for literal_password_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-SEC05")),
"expected violation SC-SEC05"
);
}
#[test]
fn test_policy_always_permissive_clean() {
let schema_sql = &["CREATE TABLE tenants (id bigint PRIMARY KEY)"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql =
"-- @name CreateReadablePolicy\n-- @returns :exec\nCREATE POLICY readable ON tenants FOR SELECT USING (true)";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_policy_always_permissive_fires() {
let schema_sql = &["CREATE TABLE tenants (id bigint PRIMARY KEY)"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateAllowAllUpdatePolicy\n-- @returns :exec\nCREATE POLICY allow_all ON tenants FOR UPDATE USING (true)";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for policy_always_permissive_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-RLS02")),
"expected violation SC-RLS02"
);
}
#[test]
fn test_policy_references_user_metadata_clean() {
let schema_sql = &["CREATE TABLE tenants (id bigint PRIMARY KEY, tenant_id text)"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateTrustJwtPolicy\n-- @returns :exec\nCREATE POLICY trust_jwt ON tenants USING ((select auth.jwt()) -> 'app_metadata' ->> 'tenant_id' = tenant_id)";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_policy_references_user_metadata_fires() {
let schema_sql = &["CREATE TABLE tenants (id bigint PRIMARY KEY, tenant_id text)"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateTrustJwtPolicy\n-- @returns :exec\nCREATE POLICY trust_jwt ON tenants USING (auth.jwt() -> 'user_metadata' ->> 'tenant_id' = tenant_id)";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for policy_references_user_metadata_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-RLS01")),
"expected violation SC-RLS01"
);
}
#[test]
fn test_policy_uses_uncached_auth_function_clean() {
let schema_sql = &["CREATE TABLE tenants (id bigint PRIMARY KEY, user_id uuid)"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateTenantPolicy\n-- @returns :exec\nCREATE POLICY tenant ON tenants USING (user_id = (select auth.uid()))";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_policy_uses_uncached_auth_function_fires() {
let schema_sql = &["CREATE TABLE tenants (id bigint PRIMARY KEY, user_id uuid)"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql =
"-- @name CreateTenantPolicy\n-- @returns :exec\nCREATE POLICY tenant ON tenants USING (user_id = auth.uid())";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for policy_uses_uncached_auth_function_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-RLS03")),
"expected violation SC-RLS03"
);
}
#[test]
fn test_prefer_bigint_over_int_clean() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql =
"-- @name CreateAccountsTable\n-- @returns :exec\nCREATE TABLE accounts (id bigint PRIMARY KEY, name text)";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_prefer_bigint_over_int_fires() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateUsersTable\n-- @returns :exec\nCREATE TABLE users (id int PRIMARY KEY, name text)";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for prefer_bigint_over_int_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-MIG10")),
"expected violation SC-MIG10"
);
}
#[test]
fn test_prefer_identity_over_serial_clean() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql =
"-- @name CreateUserNameIndex\n-- @returns :exec\nCREATE INDEX CONCURRENTLY idx_users_name ON users (name)";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_prefer_identity_over_serial_fires() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql =
"-- @name CreateOrdersTable\n-- @returns :exec\nCREATE TABLE orders (id serial PRIMARY KEY, total bigint)";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for prefer_identity_over_serial_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-MIG13")),
"expected violation SC-MIG13"
);
}
#[test]
fn test_prefer_text_over_varchar_clean() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql =
"-- @name CreateTagsTable\n-- @returns :exec\nCREATE TABLE tags (id bigint PRIMARY KEY, label text)";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_prefer_text_over_varchar_fires() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateProductsTable\n-- @returns :exec\nCREATE TABLE products (id bigint PRIMARY KEY, name varchar(255))";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for prefer_text_over_varchar_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-MIG11")),
"expected violation SC-MIG11"
);
}
#[test]
fn test_prefer_timestamptz_clean() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateSessionsTable\n-- @returns :exec\nCREATE TABLE sessions (id bigint PRIMARY KEY, started_at timestamptz)";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_prefer_timestamptz_fires() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateEventsTable\n-- @returns :exec\nCREATE TABLE events (id bigint PRIMARY KEY, occurred_at timestamp)";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for prefer_timestamptz_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-MIG12")),
"expected violation SC-MIG12"
);
}
#[test]
fn test_renaming_column_clean() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateUsernameColumn\n-- @returns :exec\nALTER TABLE users ADD COLUMN username text";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_renaming_column_fires() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql =
"-- @name UpdateUserNickToUsername\n-- @returns :exec\nALTER TABLE users RENAME COLUMN nick TO username";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for renaming_column_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-MIG04")),
"expected violation SC-MIG04"
);
}
#[test]
fn test_renaming_table_clean() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateUsersEmailColumn\n-- @returns :exec\nALTER TABLE users ADD COLUMN email text";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_renaming_table_fires() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name UpdateUsersTableToAccounts\n-- @returns :exec\nALTER TABLE users RENAME TO accounts";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for renaming_table_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-MIG07")),
"expected violation SC-MIG07"
);
}
#[test]
fn test_require_concurrent_index_clean() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateUserEmailIndexConcurrent\n-- @returns :exec\nCREATE INDEX CONCURRENTLY idx_users_email ON users (email)";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_require_concurrent_index_fires() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateUserEmailIndex\n-- @returns :exec\nCREATE INDEX idx_users_email ON users (email)";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for require_concurrent_index_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-MIG03")),
"expected violation SC-MIG03"
);
}
#[test]
fn test_security_definer_clean() {
let schema_sql = &["CREATE TABLE audit_log (id SERIAL PRIMARY KEY);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateBumpAuditFnSafe\n-- @returns :exec\nCREATE FUNCTION bump_audit() RETURNS int AS $$ SELECT 1 $$ LANGUAGE SQL SECURITY DEFINER SET search_path = pg_catalog, public;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_security_definer_fires() {
let schema_sql = &["CREATE TABLE audit_log (id SERIAL PRIMARY KEY);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateBumpAuditFn\n-- @returns :exec\nCREATE FUNCTION bump_audit() RETURNS int AS $$ SELECT 1 $$ LANGUAGE SQL SECURITY DEFINER;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for security_definer_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-SEC10")),
"expected violation SC-SEC10"
);
}
#[test]
fn test_select_star_pii_clean() {
let schema_sql = &["CREATE TABLE products (id SERIAL PRIMARY KEY, name TEXT NOT NULL, price NUMERIC NOT NULL);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name ListProducts\n-- @returns :many\nSELECT id, name, price FROM products";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_select_star_pii_fires() {
let schema_sql =
&["CREATE TABLE users (id SERIAL PRIMARY KEY, username TEXT NOT NULL, password TEXT NOT NULL, email TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name GetAllUsers\n-- @returns :many\nSELECT * FROM users";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for select_star_pii_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-SEC07")),
"expected violation SC-SEC07"
);
}
#[test]
fn test_session_mutation_clean() {
let schema_sql = &["CREATE TABLE orders (id SERIAL PRIMARY KEY, total NUMERIC NOT NULL);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name ListOrders\n-- @returns :many\nSELECT id, total FROM orders";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_session_mutation_fires() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name EscalateToAdmin\n-- @returns :exec\nSET ROLE admin";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for session_mutation_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-SEC11")),
"expected violation SC-SEC11"
);
}
#[test]
fn test_superuser_role_clean() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateReadonlyRole\n-- @returns :exec\nCREATE ROLE readonly NOSUPERUSER LOGIN";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_superuser_role_fires() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CreateSuperRole\n-- @returns :exec\nCREATE ROLE dba SUPERUSER";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for superuser_role_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-SEC04")),
"expected violation SC-SEC04"
);
}
#[test]
fn test_unbounded_like_clean() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name SearchUsersPrefix\n-- @returns :many\nSELECT id FROM users WHERE name LIKE 'alice%';";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_unbounded_like_fires() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name SearchUsersByName\n-- @returns :many\nSELECT id FROM users WHERE name LIKE '%alice%';";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for unbounded_like_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-SEC09")),
"expected violation SC-SEC09"
);
}
#[test]
fn test_unsupported_reg_types_clean() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql =
"-- @name CreateMetaTable\n-- @returns :exec\nCREATE TABLE meta (id bigint PRIMARY KEY, rel regclass)";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_unsupported_reg_types_fires() {
let schema_sql = &[];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql =
"-- @name CreateMetaTable\n-- @returns :exec\nCREATE TABLE meta (id bigint PRIMARY KEY, ns regnamespace)";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for unsupported_reg_types_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-MIG19")),
"expected violation SC-MIG19"
);
}
#[test]
fn test_weak_hash_clean() {
let schema_sql = &["CREATE TABLE documents (id SERIAL PRIMARY KEY, content TEXT NOT NULL);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql =
"-- @name GetDocumentHash\n-- @returns :one\nSELECT md5(content) AS content_hash FROM documents WHERE id = $1";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_weak_hash_fires() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, username TEXT NOT NULL, password TEXT NOT NULL);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql =
"-- @name CheckUserPassword\n-- @returns :one\nSELECT id FROM users WHERE password_hash = md5(password)";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(!_violations.is_empty(), "expected lint violations for weak_hash_fires");
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-SEC06")),
"expected violation SC-SEC06"
);
}
#[test]
fn test_case_null_pattern_fires() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name GetUserStatus\n-- @returns :many\nSELECT id, CASE WHEN status IS NULL THEN 'unknown' ELSE status END AS display_status FROM users;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for case_null_pattern_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-T02")),
"expected violation SC-T02"
);
}
#[test]
fn test_coalesce_clean() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name GetUserStatus\n-- @returns :many\nSELECT id, COALESCE(status, 'unknown') AS display_status FROM users;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_count_one_fires() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CountUsers\n-- @returns :one\nSELECT COUNT(1) AS total FROM users;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(!_violations.is_empty(), "expected lint violations for count_one_fires");
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-T03")),
"expected violation SC-T03"
);
}
#[test]
fn test_count_star_clean() {
let schema_sql = &["CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);"];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name CountUsers\n-- @returns :one\nSELECT COUNT(*) AS total FROM users;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_explicit_join_clean() {
let schema_sql = &[
"CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);",
"CREATE TABLE orders (id SERIAL PRIMARY KEY, user_id INT NOT NULL, total INT NOT NULL);",
];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name GetUserOrders\n-- @returns :many\nSELECT users.id, orders.total FROM users JOIN orders ON users.id = orders.user_id;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
_violations.is_empty(),
"expected no lint violations but got {}",
_violations.len()
);
}
#[test]
fn test_implicit_join_fires() {
let schema_sql = &[
"CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT);",
"CREATE TABLE orders (id SERIAL PRIMARY KEY, user_id INT NOT NULL, total INT NOT NULL);",
];
let catalog = scythe_core::catalog::Catalog::from_ddl(schema_sql).unwrap();
let registry = scythe_lint::default_registry();
let engine = scythe_lint::LintEngine::new(registry);
let mut _violations: Vec<(scythe_lint::Violation, scythe_lint::Severity)> = Vec::new();
_violations.extend(engine.check_catalog(&catalog));
let query_sql = "-- @name GetUserOrders\n-- @returns :many\nSELECT users.id, orders.total FROM users, orders WHERE users.id = orders.user_id;";
let query = scythe_core::parser::parse_query(query_sql).expect("fixture SQL must parse");
let analyzed = scythe_core::analyzer::analyze(&catalog, &query).expect("fixture SQL must analyze");
let ctx = scythe_lint::LintContext {
sql: &query.sql,
stmt: &query.stmt,
analyzed: &analyzed,
catalog: &catalog,
annotations: &query.annotations,
dialect: scythe_core::dialect::SqlDialect::PostgreSQL,
};
_violations.extend(engine.check_query(&ctx));
assert!(
!_violations.is_empty(),
"expected lint violations for implicit_join_fires"
);
assert!(
_violations.iter().any(|v| v.0.rule_id.contains("SC-T01")),
"expected violation SC-T01"
);
}