From 9415f51435cd6f65c5405b2e6a2114805bb6d1db Mon Sep 17 00:00:00 2001 From: Rabindra Dhakal Date: Mon, 19 Oct 2026 15:50:26 +0545 Subject: [PATCH] fix(db): point maintainer links at the maintainers table --- .../down.sql | 14 ++++++++ .../up.sql | 18 ++++++++++ crates/soar-db/src/connection.rs | 36 ++++++++++++++++++- crates/soar-db/src/repository/metadata.rs | 24 +++++++++++++ 4 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 crates/soar-db/migrations/metadata/2026-10-19-000100-0000_fix_maintainer_links/down.sql create mode 100644 crates/soar-db/migrations/metadata/2026-10-19-000100-0000_fix_maintainer_links/up.sql diff --git a/crates/soar-db/migrations/metadata/2026-10-19-000100-0000_fix_maintainer_links/down.sql b/crates/soar-db/migrations/metadata/2026-10-19-000100-0000_fix_maintainer_links/down.sql new file mode 100644 index 00000000..e8255a4e --- /dev/null +++ b/crates/soar-db/migrations/metadata/2026-10-19-000100-0000_fix_maintainer_links/down.sql @@ -0,0 +1,14 @@ +CREATE TABLE package_maintainers_old ( + maintainer_id INTEGER NOT NULL, + package_id INTEGER NOT NULL, + FOREIGN KEY (maintainer_id) REFERENCES packages (id), + FOREIGN KEY (package_id) REFERENCES packages (id), + UNIQUE (maintainer_id, package_id) +); + +INSERT INTO package_maintainers_old +SELECT maintainer_id, package_id FROM package_maintainers +WHERE maintainer_id IN (SELECT id FROM packages); + +DROP TABLE package_maintainers; +ALTER TABLE package_maintainers_old RENAME TO package_maintainers; diff --git a/crates/soar-db/migrations/metadata/2026-10-19-000100-0000_fix_maintainer_links/up.sql b/crates/soar-db/migrations/metadata/2026-10-19-000100-0000_fix_maintainer_links/up.sql new file mode 100644 index 00000000..3e758cf9 --- /dev/null +++ b/crates/soar-db/migrations/metadata/2026-10-19-000100-0000_fix_maintainer_links/up.sql @@ -0,0 +1,18 @@ +-- Rebuilt rather than altered: SQLite cannot change a foreign key in place. +-- `maintainer_id` referenced `packages`, so a link failed whenever no +-- package happened to share the maintainer's id. +CREATE TABLE package_maintainers_new ( + maintainer_id INTEGER NOT NULL, + package_id INTEGER NOT NULL, + FOREIGN KEY (maintainer_id) REFERENCES maintainers (id) ON DELETE CASCADE, + FOREIGN KEY (package_id) REFERENCES packages (id) ON DELETE CASCADE, + UNIQUE (maintainer_id, package_id) +); + +INSERT INTO package_maintainers_new +SELECT maintainer_id, package_id FROM package_maintainers +WHERE maintainer_id IN (SELECT id FROM maintainers) + AND package_id IN (SELECT id FROM packages); + +DROP TABLE package_maintainers; +ALTER TABLE package_maintainers_new RENAME TO package_maintainers; diff --git a/crates/soar-db/src/connection.rs b/crates/soar-db/src/connection.rs index 024862a6..9f32a029 100644 --- a/crates/soar-db/src/connection.rs +++ b/crates/soar-db/src/connection.rs @@ -282,7 +282,7 @@ mod tests { use diesel_migrations::MigrationHarness; use super::*; - use crate::migration::CORE_MIGRATIONS; + use crate::migration::{CORE_MIGRATIONS, METADATA_MIGRATIONS}; #[derive(QueryableByName)] struct Count { @@ -357,6 +357,40 @@ mod tests { ); } + #[test] + fn upgrading_metadata_keeps_maintainer_links() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("repo.db"); + { + let mut conn = SqliteConnection::establish(path.to_str().unwrap()).unwrap(); + for _ in 0..5 { + conn.run_next_migration(METADATA_MIGRATIONS).unwrap(); + } + for statement in [ + "INSERT INTO packages (id, pkg_name, version, download_url) \ + VALUES (1, 'p', '1', 'https://x')", + "INSERT INTO maintainers (id, contact, name) VALUES (1, 'a@x', 'A')", + "INSERT INTO package_maintainers (maintainer_id, package_id) VALUES (1, 1)", + ] { + sql_query(statement).execute(&mut conn).unwrap(); + } + } + + let mut db = DbConnection::open(&path, DbType::Metadata).unwrap(); + let conn = db.conn(); + let links = sql_query("SELECT count(*) AS n FROM package_maintainers") + .get_result::(conn) + .unwrap(); + assert_eq!(links.n, 1, "upgrading lost a maintainer link"); + let to_maintainers = sql_query( + "SELECT count(*) AS n FROM pragma_foreign_key_list('package_maintainers') \ + WHERE \"from\" = 'maintainer_id' AND \"table\" = 'maintainers'", + ) + .get_result::(conn) + .unwrap(); + assert_eq!(to_maintainers.n, 1); + } + #[test] fn readonly_opens_a_path_with_uri_characters() { let dir = tempfile::tempdir().unwrap(); diff --git a/crates/soar-db/src/repository/metadata.rs b/crates/soar-db/src/repository/metadata.rs index 25b367fe..fb164026 100644 --- a/crates/soar-db/src/repository/metadata.rs +++ b/crates/soar-db/src/repository/metadata.rs @@ -845,6 +845,30 @@ mod import_tests { assert_eq!(size, Some(i64::MAX)); } + #[test] + fn maintainers_link_regardless_of_package_ids() { + let mut db = db_with(&format!( + "[{}]", + entry( + "solo", + r#","maintainers":["Alice (alice@example.com)","Bob (bob@example.com)"]"# + ) + )); + let id: i32 = packages::table + .select(packages::id) + .first(db.conn()) + .unwrap(); + let mut names: Vec<_> = MetadataRepository::get_maintainers(db.conn(), id) + .unwrap() + .into_iter() + .map(|m| m.name) + .collect(); + names.sort(); + assert_eq!(names, ["Alice", "Bob"]); + + MetadataRepository::delete_all(db.conn()).unwrap(); + } + #[test] fn like_escaping_covers_the_escape_character() { assert_eq!(super::escape_like(r"a\b%c_d"), r"a\\b\%c\_d");