Skip to content

Commit 71570c7

Browse files
committed
perf(list): use minimal struct for listing packages
1 parent f92350f commit 71570c7

5 files changed

Lines changed: 87 additions & 61 deletions

File tree

crates/soar-cli/src/install.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -924,15 +924,29 @@ async fn spawn_installation_task(
924924
let permit = ctx.semaphore.clone().acquire_owned().await.unwrap();
925925
let progress_bar = Arc::new(Mutex::new(None));
926926

927+
// Pre-compute the prefix string to avoid cloning the entire Package struct
928+
let prefix = {
929+
let prefix = format!(
930+
"[{}/{}] {}#{}",
931+
idx + 1,
932+
ctx.total_packages,
933+
target.package.pkg_name,
934+
target.package.pkg_id
935+
);
936+
if prefix.len() > fixed_width {
937+
format!("{prefix:.fixed_width$}")
938+
} else {
939+
format!("{prefix:<fixed_width$}")
940+
}
941+
};
942+
927943
let progress_callback = {
928944
let ctx = ctx.clone();
929945
let progress_bar = progress_bar.clone();
930-
let package = target.package.clone();
931946

932947
Arc::new(move |state| {
933948
let mut pb_lock = progress_bar.lock().unwrap();
934-
935-
handle_install_progress(state, &mut pb_lock, &ctx, &package, idx, fixed_width);
949+
handle_install_progress(state, &mut pb_lock, &ctx, &prefix);
936950
})
937951
};
938952

crates/soar-cli/src/list.rs

Lines changed: 44 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@ use soar_core::{
1212
package::query::PackageQuery,
1313
SoarResult,
1414
};
15-
use soar_db::repository::{
16-
core::{CoreRepository, SortDirection},
17-
metadata::MetadataRepository,
15+
use soar_db::{
16+
models::metadata::PackageListing,
17+
repository::{
18+
core::{CoreRepository, SortDirection},
19+
metadata::MetadataRepository,
20+
},
1821
};
1922
use soar_utils::fs::dir_size;
2023
use tabled::{
@@ -367,31 +370,39 @@ pub async fn query_package(query_str: String) -> SoarResult<()> {
367370
Ok(())
368371
}
369372

373+
/// Lightweight struct for listing with repo name attached
374+
struct PackageListingWithRepo {
375+
repo_name: String,
376+
pkg: PackageListing,
377+
}
378+
370379
pub async fn list_packages(repo_name: Option<String>) -> SoarResult<()> {
371380
let state = AppState::new();
372381
let metadata_mgr = state.metadata_manager().await?;
373382
let diesel_db = state.diesel_core_db()?;
374383

375-
let packages: Vec<Package> = if let Some(ref repo_name) = repo_name {
384+
let packages: Vec<PackageListingWithRepo> = if let Some(ref repo_name) = repo_name {
376385
metadata_mgr
377-
.query_repo(repo_name, MetadataRepository::list_all)?
386+
.query_repo(repo_name, MetadataRepository::list_all_minimal)?
378387
.unwrap_or_default()
379388
.into_iter()
380-
.map(|p| {
381-
let mut pkg: Package = p.into();
382-
pkg.repo_name = repo_name.clone();
383-
pkg
389+
.map(|pkg| {
390+
PackageListingWithRepo {
391+
repo_name: repo_name.clone(),
392+
pkg,
393+
}
384394
})
385395
.collect()
386396
} else {
387397
metadata_mgr.query_all_flat(|repo_name, conn| {
388-
let pkgs = MetadataRepository::list_all(conn)?;
398+
let pkgs = MetadataRepository::list_all_minimal(conn)?;
389399
Ok(pkgs
390400
.into_iter()
391-
.map(|p| {
392-
let mut pkg: Package = p.into();
393-
pkg.repo_name = repo_name.to_string();
394-
pkg
401+
.map(|pkg| {
402+
PackageListingWithRepo {
403+
repo_name: repo_name.to_string(),
404+
pkg,
405+
}
395406
})
396407
.collect())
397408
})?
@@ -409,11 +420,11 @@ pub async fn list_packages(repo_name: Option<String>) -> SoarResult<()> {
409420
let mut installed_count = 0;
410421
let mut available_count = 0;
411422

412-
for package in &packages {
423+
for entry in &packages {
413424
let key = (
414-
package.repo_name.clone(),
415-
package.pkg_id.clone(),
416-
package.pkg_name.clone(),
425+
entry.repo_name.clone(),
426+
entry.pkg.pkg_id.clone(),
427+
entry.pkg.pkg_name.clone(),
417428
);
418429
let state_icon = match installed_pkgs.get(&key) {
419430
Some(is_installed) => {
@@ -431,25 +442,27 @@ pub async fn list_packages(repo_name: Option<String>) -> SoarResult<()> {
431442
};
432443

433444
info!(
434-
pkg_name = package.pkg_name,
435-
pkg_id = package.pkg_id,
436-
repo_name = package.repo_name,
437-
pkg_type = package.pkg_type,
438-
version = package.version,
439-
version_upstream = package.version_upstream,
445+
pkg_name = entry.pkg.pkg_name,
446+
pkg_id = entry.pkg.pkg_id,
447+
repo_name = entry.repo_name,
448+
pkg_type = entry.pkg.pkg_type,
449+
version = entry.pkg.version,
450+
version_upstream = entry.pkg.version_upstream,
440451
"[{}] {}#{}:{} | {}{} | {}",
441452
state_icon,
442-
Colored(Blue, &package.pkg_name),
443-
Colored(Cyan, &package.pkg_id),
444-
Colored(Cyan, &package.repo_name),
445-
Colored(LightRed, &package.version),
446-
package
453+
Colored(Blue, &entry.pkg.pkg_name),
454+
Colored(Cyan, &entry.pkg.pkg_id),
455+
Colored(Cyan, &entry.repo_name),
456+
Colored(LightRed, &entry.pkg.version),
457+
entry
458+
.pkg
447459
.version_upstream
448460
.as_ref()
449-
.filter(|_| package.version.starts_with("HEAD"))
461+
.filter(|_| entry.pkg.version.starts_with("HEAD"))
450462
.map(|upstream| format!(":{}", Colored(Yellow, &upstream)))
451463
.unwrap_or_default(),
452-
package
464+
entry
465+
.pkg
453466
.pkg_type
454467
.as_ref()
455468
.map(|pkg_type| format!("{}", Colored(Magenta, &pkg_type)))

crates/soar-cli/src/progress.rs

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use std::sync::atomic::Ordering;
33
use indicatif::{HumanBytes, ProgressBar, ProgressState, ProgressStyle};
44
use nu_ansi_term::Color::Red;
55
use soar_config::display::ProgressStyle as ConfigProgressStyle;
6-
use soar_core::database::models::Package;
76
use soar_dl::types::Progress;
87

98
use crate::{
@@ -110,29 +109,13 @@ pub fn handle_install_progress(
110109
state: Progress,
111110
progress_bar: &mut Option<ProgressBar>,
112111
ctx: &InstallContext,
113-
package: &Package,
114-
idx: usize,
115-
fixed_width: usize,
112+
prefix: &str,
116113
) {
117114
if progress_bar.is_none() {
118115
let pb = ctx
119116
.multi_progress
120117
.insert_from_back(1, create_progress_bar());
121-
122-
let prefix = format!(
123-
"[{}/{}] {}#{}",
124-
idx + 1,
125-
ctx.total_packages,
126-
package.pkg_name,
127-
package.pkg_id
128-
);
129-
let prefix = if prefix.len() > fixed_width {
130-
format!("{prefix:.fixed_width$}")
131-
} else {
132-
format!("{prefix:<fixed_width$}")
133-
};
134-
pb.set_prefix(prefix);
135-
118+
pb.set_prefix(prefix.to_string());
136119
*progress_bar = Some(pb);
137120
}
138121

@@ -183,15 +166,8 @@ pub fn handle_install_progress(
183166
Progress::Aborted => {
184167
let failed_count = ctx.failed.fetch_add(1, Ordering::Relaxed);
185168
if let Some(pb) = progress_bar {
186-
let prefix = format!(
187-
"[{}/{}] {}#{}",
188-
idx + 1,
189-
ctx.total_packages,
190-
package.pkg_name,
191-
package.pkg_id
192-
);
193169
pb.set_style(ProgressStyle::with_template("{prefix} {msg}").unwrap());
194-
pb.set_prefix(prefix);
170+
pb.set_prefix(prefix.to_string());
195171
pb.finish_with_message(format!(
196172
"\n {}",
197173
Colored(Red, "└── Error: Too many failures. Aborted.")

crates/soar-db/src/models/metadata.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,19 @@ impl Queryable<packages::SqlType, Sqlite> for Package {
128128
}
129129
}
130130

131+
/// Lightweight package struct for listing operations.
132+
/// Contains only the fields needed for display, reducing memory usage significantly.
133+
#[derive(Debug, Clone, Queryable, Selectable)]
134+
#[diesel(table_name = packages)]
135+
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
136+
pub struct PackageListing {
137+
pub pkg_id: String,
138+
pub pkg_name: String,
139+
pub pkg_type: Option<String>,
140+
pub version: String,
141+
pub version_upstream: Option<String>,
142+
}
143+
131144
/// Package with repository name attached.
132145
/// This is used when querying across multiple repositories.
133146
#[derive(Debug, Clone)]

crates/soar-db/src/repository/metadata.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use crate::{
1515
models::{
1616
metadata::{
1717
Maintainer, NewMaintainer, NewPackage, NewPackageMaintainer, NewRepository, Package,
18+
PackageListing,
1819
},
1920
types::PackageProvide,
2021
},
@@ -40,6 +41,15 @@ impl MetadataRepository {
4041
.load(conn)
4142
}
4243

44+
/// Lists all packages with only the fields needed for display.
45+
/// This is much more memory-efficient than list_all for large package lists.
46+
pub fn list_all_minimal(conn: &mut SqliteConnection) -> QueryResult<Vec<PackageListing>> {
47+
packages::table
48+
.order(packages::pkg_name.asc())
49+
.select(PackageListing::as_select())
50+
.load(conn)
51+
}
52+
4353
/// Lists packages with pagination and sorting using Diesel DSL.
4454
pub fn list_paginated(
4555
conn: &mut SqliteConnection,

0 commit comments

Comments
 (0)