Implement package replacement feature across the codebase

- Introduced a new `replaces` field in various structs to support package replacements.
- Updated database schema to include a `replaces` table and corresponding queries.
- Modified the package index to handle replacements and ensure they take precedence over exact package names.
- Enhanced the `create_source_repo_index` function to include replacement entries in the index.
- Updated tests to validate the new replacement functionality, including parsing and metadata generation.
- Refactored relevant functions and structures to accommodate the new `replaces` feature in package specifications and alternatives.
This commit is contained in:
2026-03-16 22:47:35 -05:00
parent 82f3b625ee
commit 2bc9e7a525
14 changed files with 1108 additions and 140 deletions
+65 -1
View File
@@ -87,6 +87,7 @@ struct IndexedPackage {
sha512: String,
provides: Vec<String>,
conflicts: Vec<String>,
replaces: Vec<String>,
runtime_dependencies: Vec<String>,
optional_dependencies: Vec<String>,
archive_files: Vec<String>,
@@ -125,6 +126,7 @@ pub struct BinaryRepoPackageRecord {
pub license: Option<String>,
pub provides: Vec<String>,
pub conflicts: Vec<String>,
pub replaces: Vec<String>,
pub runtime_dependencies: Vec<String>,
pub optional_dependencies: Vec<String>,
}
@@ -317,6 +319,11 @@ impl RepoManager {
name TEXT NOT NULL,
FOREIGN KEY(package_id) REFERENCES packages(id)
);
CREATE TABLE replaces (
package_id INTEGER,
name TEXT NOT NULL,
FOREIGN KEY(package_id) REFERENCES packages(id)
);
CREATE TABLE dependencies (
package_id INTEGER,
kind TEXT NOT NULL,
@@ -338,6 +345,7 @@ impl RepoManager {
"CREATE INDEX idx_packages_name ON packages(name);
CREATE INDEX idx_provides_name ON provides(name);
CREATE INDEX idx_conflicts_name ON conflicts(name);
CREATE INDEX idx_replaces_name ON replaces(name);
CREATE INDEX idx_dependencies_name ON dependencies(name);
CREATE INDEX idx_dependencies_kind ON dependencies(kind);
CREATE INDEX idx_repo_files_path ON files(path);",
@@ -369,6 +377,7 @@ impl RepoManager {
let mut license = None;
let mut provides = Vec::new();
let mut conflicts = Vec::new();
let mut replaces = Vec::new();
let mut runtime_dependencies = Vec::new();
let mut optional_dependencies = Vec::new();
let mut archive_files = Vec::new();
@@ -439,6 +448,14 @@ impl RepoManager {
.map(String::from)
.collect();
}
if let Some(replaces_arr) = metadata.get("replaces").and_then(|v| v.as_array())
{
replaces = replaces_arr
.iter()
.filter_map(|v| v.as_str())
.map(String::from)
.collect();
}
if let Some(runtime_arr) = metadata
.get("dependencies")
.and_then(|v| v.get("runtime"))
@@ -506,6 +523,7 @@ impl RepoManager {
sha512,
provides,
conflicts,
replaces,
runtime_dependencies,
optional_dependencies,
archive_files,
@@ -529,6 +547,7 @@ impl RepoManager {
sha512,
provides,
conflicts,
replaces,
runtime_dependencies,
optional_dependencies,
archive_files,
@@ -570,6 +589,12 @@ impl RepoManager {
params![package_id, conflict],
)?;
}
for replacement in replaces {
conn.execute(
"INSERT INTO replaces (package_id, name) VALUES (?1, ?2)",
params![package_id, replacement],
)?;
}
for dep in runtime_dependencies {
conn.execute(
@@ -1592,6 +1617,26 @@ fn query_package_conflicts(conn: &Connection, package_id: i64) -> Result<Vec<Str
Ok(rows.filter_map(|r| r.ok()).collect())
}
fn query_package_replaces(conn: &Connection, package_id: i64) -> Result<Vec<String>> {
let has_replaces_table: bool = conn
.query_row(
"SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='replaces'",
[],
|r| {
let n: i64 = r.get(0)?;
Ok(n > 0)
},
)
.unwrap_or(false);
if !has_replaces_table {
return Ok(Vec::new());
}
let mut stmt = conn.prepare("SELECT name FROM replaces WHERE package_id = ?1 ORDER BY name")?;
let rows = stmt.query_map(params![package_id], |row| row.get(0))?;
Ok(rows.filter_map(|r| r.ok()).collect())
}
fn query_package_runtime_deps(conn: &Connection, package_id: i64) -> Result<Vec<String>> {
let has_dependencies_table: bool = conn
.query_row(
@@ -1677,13 +1722,26 @@ fn find_cached_binary_repo_packages(
p.license
FROM packages p
WHERE lower(p.name) = lower(?1)
OR EXISTS (
SELECT 1 FROM replaces rp
WHERE rp.package_id = p.id
AND lower(rp.name) = lower(?1)
)
OR EXISTS (
SELECT 1 FROM provides pr
WHERE pr.package_id = p.id
AND lower(pr.name) = lower(?1)
)
ORDER BY
CASE WHEN lower(p.name) = lower(?1) THEN 0 ELSE 1 END,
CASE
WHEN EXISTS (
SELECT 1 FROM replaces rp
WHERE rp.package_id = p.id
AND lower(rp.name) = lower(?1)
) THEN 0
WHEN lower(p.name) = lower(?1) THEN 1
ELSE 2
END,
p.name ASC"
);
let mut stmt = conn.prepare(&sql)?;
@@ -1709,6 +1767,7 @@ fn find_cached_binary_repo_packages(
license: row.get(13)?,
provides: Vec::new(),
conflicts: Vec::new(),
replaces: Vec::new(),
runtime_dependencies: Vec::new(),
optional_dependencies: Vec::new(),
},
@@ -1720,6 +1779,7 @@ fn find_cached_binary_repo_packages(
let (package_id, mut rec) = row?;
rec.provides = query_package_provides(&conn, package_id)?;
rec.conflicts = query_package_conflicts(&conn, package_id)?;
rec.replaces = query_package_replaces(&conn, package_id)?;
rec.runtime_dependencies = query_package_runtime_deps(&conn, package_id)?;
rec.optional_dependencies = query_package_optional_deps(&conn, package_id)?;
out.push(rec);
@@ -1791,6 +1851,7 @@ fn list_cached_binary_repo_packages(
license: row.get(13)?,
provides: Vec::new(),
conflicts: Vec::new(),
replaces: Vec::new(),
runtime_dependencies: Vec::new(),
optional_dependencies: Vec::new(),
},
@@ -1802,6 +1863,7 @@ fn list_cached_binary_repo_packages(
let (package_id, mut rec) = row?;
rec.provides = query_package_provides(&conn, package_id)?;
rec.conflicts = query_package_conflicts(&conn, package_id)?;
rec.replaces = query_package_replaces(&conn, package_id)?;
rec.runtime_dependencies = query_package_runtime_deps(&conn, package_id)?;
rec.optional_dependencies = query_package_optional_deps(&conn, package_id)?;
out.push(rec);
@@ -2754,6 +2816,7 @@ revision = 1
license: None,
provides: Vec::new(),
conflicts: Vec::new(),
replaces: Vec::new(),
runtime_dependencies: Vec::new(),
optional_dependencies: Vec::new(),
};
@@ -2792,6 +2855,7 @@ revision = 1
license: None,
provides: Vec::new(),
conflicts: Vec::new(),
replaces: Vec::new(),
runtime_dependencies: Vec::new(),
optional_dependencies: Vec::new(),
}