Skip to content

Commit a19e319

Browse files
committed
refactor: fetch source_document alongside, and only what's required
Instead of fully fetching all information of an SBOM and then just using the ID, this fetches the bare minimum. But then also fetches the source_document in the same step. Passing on to other parts which need it. This should prevent one N+1 select issues.
1 parent 2abb2a4 commit a19e319

File tree

6 files changed

+69
-69
lines changed

6 files changed

+69
-69
lines changed

modules/fundamental/src/purl/model/details/purl.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ use trustify_cvss::cvss3::{score::Score as Cvss3Score, severity::Severity};
2828
use trustify_entity::{
2929
advisory, advisory_vulnerability_score, base_purl, cpe, license, organization, product,
3030
product_status, product_version, product_version_range, purl_status, qualified_purl, sbom,
31-
sbom_license_expanded, sbom_package, sbom_package_license, sbom_package_purl_ref, status,
32-
version_range, versioned_purl, vulnerability,
31+
sbom_license_expanded, sbom_node, sbom_package, sbom_package_license, sbom_package_purl_ref,
32+
status, version_range, versioned_purl, vulnerability,
3333
};
3434
use trustify_module_ingestor::common::{Deprecation, DeprecationForExt};
3535
use utoipa::ToSchema;
@@ -455,7 +455,7 @@ impl PurlLicenseSummary {
455455
let entry = summaries.entry(row.sbom.sbom_id);
456456
if let Entry::Vacant(entry) = entry {
457457
let summary = PurlLicenseSummary {
458-
sbom: SbomHead::from_entity(&row.sbom, None, tx).await?,
458+
sbom: SbomHead::from_entity(&row.sbom, &row.sbom_node, tx).await?,
459459
licenses: vec![],
460460
};
461461

@@ -476,13 +476,15 @@ impl PurlLicenseSummary {
476476
#[derive(Debug)]
477477
pub struct LicenseCatcher {
478478
sbom: sbom::Model,
479+
sbom_node: sbom_node::Model,
479480
license: license::Model,
480481
}
481482

482483
impl FromQueryResult for LicenseCatcher {
483484
fn from_query_result(res: &QueryResult, _pre: &str) -> Result<Self, DbErr> {
484485
Ok(Self {
485486
sbom: Self::from_query_result_multi_model(res, "", sbom::Entity)?,
487+
sbom_node: Self::from_query_result_multi_model(res, "", sbom_node::Entity)?,
486488
license: Self::from_query_result_multi_model(res, "", license::Entity)?,
487489
})
488490
}
@@ -492,6 +494,7 @@ impl FromQueryResultMultiModel for LicenseCatcher {
492494
fn try_into_multi_model<E: EntityTrait>(select: Select<E>) -> Result<Select<E>, DbErr> {
493495
select
494496
.try_model_columns(sbom::Entity)?
497+
.try_model_columns(sbom_node::Entity)?
495498
.try_model_columns(license::Entity)
496499
}
497500
}

modules/fundamental/src/sbom/endpoints/mod.rs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use crate::{
2222
service::{SbomService, sbom::FetchOptions},
2323
},
2424
sbom_group::service::SbomGroupService,
25+
source_document::model::SourceDocument,
2526
};
2627
use actix_web::{HttpResponse, Responder, delete, get, http::header, post, web};
2728
use config::Config;
@@ -330,7 +331,7 @@ all!(GetSbomAdvisories -> ReadSbom, ReadAdvisory);
330331
("id" = Id, Path),
331332
),
332333
responses(
333-
(status = 200, description = "Matching SBOM", body = SbomSummary),
334+
(status = 204, description = "Matching SBOM as deleted"),
334335
(status = 404, description = "The SBOM could not be found"),
335336
),
336337
)]
@@ -345,15 +346,17 @@ pub async fn delete(
345346
let tx = db.begin().await?;
346347

347348
let id = Id::from_str(&id)?;
348-
match service.fetch_sbom_summary(id, &tx).await? {
349-
Some(v) => match service.delete_sbom(v.head.id, &tx).await? {
349+
match service.fetch_sbom(id, &tx).await? {
350+
Some((v, _, source_document)) => match service.delete_sbom(v.sbom_id, &tx).await? {
350351
false => Ok(HttpResponse::NotFound().finish()),
351352
true => {
352353
tx.commit().await?;
353-
if let Err(e) = delete_doc(&v.source_document, i.storage()).await {
354+
if let Err(e) =
355+
delete_doc(&SourceDocument::from_entity(&source_document), i.storage()).await
356+
{
354357
log::warn!("Ignoring {e}");
355358
}
356-
Ok(HttpResponse::Ok().json(v))
359+
Ok(HttpResponse::NoContent().finish())
357360
}
358361
},
359362
None => Ok(HttpResponse::NotFound().finish()),
@@ -386,12 +389,12 @@ pub async fn packages(
386389
let id = Id::from_str(&id).map_err(Error::IdKey)?;
387390
let tx = db.begin_read().await?;
388391

389-
let Some(sbom) = fetch.fetch_sbom_summary(id, &tx).await? else {
392+
let Some((sbom, _, _)) = fetch.fetch_sbom(id, &tx).await? else {
390393
return Ok(HttpResponse::NotFound().finish());
391394
};
392395

393396
let result = fetch
394-
.fetch_sbom_packages(sbom.head.id, search, paginated, &tx)
397+
.fetch_sbom_packages(sbom.sbom_id, search, paginated, &tx)
395398
.await?;
396399

397400
Ok(HttpResponse::Ok().json(result))
@@ -438,13 +441,13 @@ pub async fn related(
438441
let id = Id::from_str(&id).map_err(Error::IdKey)?;
439442
let tx = db.begin_read().await?;
440443

441-
let Some(sbom) = fetch.fetch_sbom_summary(id, &tx).await? else {
444+
let Some((sbom, _, _)) = fetch.fetch_sbom(id, &tx).await? else {
442445
return Ok(HttpResponse::NotFound().finish());
443446
};
444447

445448
let result = fetch
446449
.fetch_related_packages(
447-
sbom.head.id,
450+
sbom.sbom_id,
448451
search,
449452
paginated,
450453
related.which,

modules/fundamental/src/sbom/model/details.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ use trustify_common::{db::VersionMatches, memo::Memo};
2525
use trustify_cvss::cvss3::{score::Score, severity::Severity};
2626
use trustify_entity::{
2727
advisory, advisory_vulnerability, advisory_vulnerability_score, base_purl, cpe, organization,
28-
purl_status, qualified_purl, sbom, sbom_node, sbom_package, sbom_package_purl_ref, status,
29-
version_range, versioned_purl, vulnerability,
28+
purl_status, qualified_purl, sbom, sbom_node, sbom_package, sbom_package_purl_ref,
29+
source_document, status, version_range, versioned_purl, vulnerability,
3030
};
3131
use utoipa::ToSchema;
3232
use uuid::Uuid;
@@ -75,18 +75,16 @@ impl SbomDetails {
7575
/// turn an (sbom, sbom_node) row into an [`SbomDetails`], if possible
7676
#[instrument(skip(service, tx), err(level=tracing::Level::INFO))]
7777
pub async fn from_entity<C>(
78-
(sbom, node): (sbom::Model, Option<sbom_node::Model>),
78+
(sbom, node, source_document): (sbom::Model, sbom_node::Model, source_document::Model),
7979
service: &SbomService,
8080
tx: &C,
8181
statuses: Vec<String>,
8282
) -> Result<Option<SbomDetails>, Error>
8383
where
8484
C: ConnectionTrait,
8585
{
86-
let summary = match SbomSummary::from_entity((sbom.clone(), node), service, tx).await? {
87-
Some(summary) => summary,
88-
None => return Ok(None),
89-
};
86+
let summary =
87+
SbomSummary::from_entity((sbom.clone(), node, source_document), service, tx).await?;
9088

9189
let mut query = sbom
9290
.find_related(sbom_package::Entity)

modules/fundamental/src/sbom/model/mod.rs

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ impl SbomHead {
5353
)]
5454
pub async fn from_entity<C: ConnectionTrait>(
5555
sbom: &sbom::Model,
56-
sbom_node: Option<sbom_node::Model>,
56+
sbom_node: &sbom_node::Model,
5757
db: &C,
5858
) -> Result<Self, Error> {
5959
let number_of_packages = sbom.find_related(sbom_package::Entity).count(db).await?;
@@ -64,7 +64,7 @@ impl SbomHead {
6464
published: sbom.published,
6565
authors: sbom.authors.clone(),
6666
suppliers: sbom.suppliers.clone(),
67-
name: sbom_node.map(|node| node.name).unwrap_or("".to_string()),
67+
name: sbom_node.name.clone(),
6868
data_licenses: sbom.data_licenses.clone(),
6969
number_of_packages,
7070
})
@@ -85,26 +85,17 @@ pub struct SbomSummary {
8585
impl SbomSummary {
8686
#[instrument(skip(service, db), err(level=tracing::Level::INFO))]
8787
pub async fn from_entity<C: ConnectionTrait>(
88-
(sbom, node): (sbom::Model, Option<sbom_node::Model>),
88+
(sbom, node, source_document): (sbom::Model, sbom_node::Model, source_document::Model),
8989
service: &SbomService,
9090
db: &C,
91-
) -> Result<Option<SbomSummary>, Error> {
91+
) -> Result<SbomSummary, Error> {
9292
// TODO: consider improving the n-select issues here
9393
let described_by = service.describes_packages(sbom.sbom_id, (), db).await?;
9494

95-
let source_document = sbom
96-
.find_related(source_document::Entity)
97-
.one(db)
98-
.await?
99-
.ok_or_else(|| Error::NotFound("Missing source document".to_string()))?;
100-
101-
Ok(match node {
102-
Some(_) => Some(SbomSummary {
103-
head: SbomHead::from_entity(&sbom, node, db).await?,
104-
source_document: SourceDocument::from_entity(&source_document),
105-
described_by,
106-
}),
107-
None => None,
95+
Ok(SbomSummary {
96+
head: SbomHead::from_entity(&sbom, &node, db).await?,
97+
source_document: SourceDocument::from_entity(&source_document),
98+
described_by,
10899
})
109100
}
110101
}

modules/fundamental/src/sbom/service/sbom.rs

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ use trustify_entity::{
3737
license, organization, package_relates_to_package,
3838
qualified_purl::{self, CanonicalPurl},
3939
relationship::Relationship,
40-
sbom::{self, SbomNodeLink},
40+
sbom::{self},
4141
sbom_group_assignment, sbom_license_expanded, sbom_node, sbom_package, sbom_package_cpe_ref,
4242
sbom_package_license, sbom_package_purl_ref, source_document, status, versioned_purl,
4343
vulnerability,
@@ -67,20 +67,21 @@ impl FetchOptions {
6767
}
6868

6969
impl SbomService {
70+
/// Fetch an SBOM, its node, and source document
7071
#[instrument(skip(self, connection), err(level=tracing::Level::INFO))]
71-
async fn fetch_sbom<C: ConnectionTrait>(
72+
pub async fn fetch_sbom<C: ConnectionTrait>(
7273
&self,
7374
id: Id,
7475
connection: &C,
75-
) -> Result<Option<(sbom::Model, Option<sbom_node::Model>)>, Error> {
76+
) -> Result<Option<(sbom::Model, sbom_node::Model, source_document::Model)>, Error> {
7677
let select = sbom::Entity::find()
77-
.join(JoinType::LeftJoin, sbom::Relation::SourceDocument.def())
78+
.find_also_linked(sbom::SbomNodeLink)
79+
.find_also_related(source_document::Entity)
7880
.try_filter(id)?;
7981

80-
Ok(select
81-
.find_also_linked(SbomNodeLink)
82-
.one(connection)
83-
.await?)
82+
let map = |(sbom, node, source_document)| Some((sbom, node?, source_document?));
83+
84+
Ok(select.one(connection).await?.and_then(map))
8485
}
8586

8687
/// fetch one sbom
@@ -108,7 +109,7 @@ impl SbomService {
108109
connection: &C,
109110
) -> Result<Option<SbomSummary>, Error> {
110111
Ok(match self.fetch_sbom(id, connection).await? {
111-
Some(row) => SbomSummary::from_entity(row, self, connection).await?,
112+
Some(row) => Some(SbomSummary::from_entity(row, self, connection).await?),
112113
None => None,
113114
})
114115
}
@@ -269,8 +270,8 @@ impl SbomService {
269270
}
270271

271272
let limiter = query
272-
.join(JoinType::Join, sbom::Relation::SourceDocument.def())
273-
.find_also_linked(SbomNodeLink)
273+
.find_also_linked(sbom::SbomNodeLink)
274+
.find_also_related(source_document::Entity)
274275
.filtering_with(
275276
search,
276277
Columns::from_entity::<sbom::Entity>()
@@ -292,11 +293,14 @@ impl SbomService {
292293
let total = limiter.total().await?;
293294
let sboms = limiter.fetch().await?;
294295

295-
let items = stream::iter(sboms.into_iter())
296-
.then(|row| async { SbomSummary::from_entity(row, self, connection).await })
297-
.try_filter_map(futures_util::future::ok)
298-
.try_collect()
299-
.await?;
296+
let items = stream::iter(
297+
sboms
298+
.into_iter()
299+
.filter_map(|(sbom, node, source_document)| Some((sbom, node?, source_document?))),
300+
)
301+
.then(|row| async { SbomSummary::from_entity(row, self, connection).await })
302+
.try_collect()
303+
.await?;
300304

301305
Ok(PaginatedResults { total, items })
302306
}
@@ -559,12 +563,15 @@ impl SbomService {
559563
.filter(sbom_package_cpe_ref::Column::CpeId.eq(cpe.uuid())),
560564
};
561565

562-
let query = select.find_also_linked(SbomNodeLink).filtering_with(
563-
query,
564-
Columns::from_entity::<sbom::Entity>()
565-
.add_columns(sbom_node::Entity)
566-
.alias("sbom_node", "r0"),
567-
)?;
566+
let query = select
567+
.find_also_linked(sbom::SbomNodeLink)
568+
.find_also_related(source_document::Entity)
569+
.filtering_with(
570+
query,
571+
Columns::from_entity::<sbom::Entity>()
572+
.add_columns(sbom_node::Entity)
573+
.alias("sbom_node", "r0"),
574+
)?;
568575

569576
// limit and execute
570577

@@ -575,11 +582,14 @@ impl SbomService {
575582

576583
// collect results
577584

578-
let items = stream::iter(sboms.into_iter())
579-
.then(|row| async { SbomSummary::from_entity(row, self, connection).await })
580-
.try_filter_map(futures_util::future::ok)
581-
.try_collect()
582-
.await?;
585+
let items = stream::iter(
586+
sboms
587+
.into_iter()
588+
.filter_map(|(sbom, node, source_document)| Some((sbom, node?, source_document?))),
589+
)
590+
.then(|row| async { SbomSummary::from_entity(row, self, connection).await })
591+
.try_collect()
592+
.await?;
583593

584594
Ok(PaginatedResults { items, total })
585595
}

modules/fundamental/src/vulnerability/model/details/vulnerability_advisory.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -547,12 +547,7 @@ impl VulnerabilitySbomStatus {
547547
Some(existing_entry) => existing_entry,
548548
None => {
549549
let new_entry = VulnerabilitySbomStatus {
550-
head: SbomHead::from_entity(
551-
&status.sbom,
552-
Some(status.sbom_node.clone()),
553-
tx,
554-
)
555-
.await?,
550+
head: SbomHead::from_entity(&status.sbom, &status.sbom_node, tx).await?,
556551
version: status.sbom_package.version.clone(),
557552
purl_statuses: Default::default(),
558553
};

0 commit comments

Comments
 (0)