Skip to content

Commit d7ec40a

Browse files
committed
fetch CrateDetails via MatchedRelease to save one SQL query per request
1 parent 2df412e commit d7ec40a

4 files changed

+78
-106
lines changed

.sqlx/query-908827d37c731be4a6611eacd2e53aa03ae0b38047e0e58c034a3d34f8e29631.json

-22
This file was deleted.

src/web/crate_details.rs

+59-59
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::{
99
encode_url_path,
1010
error::{AxumNope, AxumResult},
1111
extractors::{DbConnection, Path},
12-
ReqVersion,
12+
MatchedRelease, ReqVersion,
1313
},
1414
AsyncStorage,
1515
};
@@ -98,11 +98,27 @@ pub struct Release {
9898

9999
impl CrateDetails {
100100
#[tracing::instrument(skip(conn))]
101-
pub async fn new(
101+
pub(crate) async fn from_matched_release(
102+
conn: &mut sqlx::PgConnection,
103+
release: &MatchedRelease,
104+
) -> Result<Self> {
105+
Ok(Self::new(
106+
conn,
107+
&release.name,
108+
release.version(),
109+
Some(release.req_version.clone()),
110+
release.all_releases.clone(),
111+
)
112+
.await?
113+
.unwrap())
114+
}
115+
116+
async fn new(
102117
conn: &mut sqlx::PgConnection,
103118
name: &str,
104119
version: &Version,
105120
req_version: Option<ReqVersion>,
121+
prefetched_releases: Vec<Release>,
106122
) -> Result<Option<CrateDetails>, anyhow::Error> {
107123
let krate = match sqlx::query!(
108124
r#"SELECT
@@ -163,9 +179,6 @@ impl CrateDetails {
163179
None => return Ok(None),
164180
};
165181

166-
// get releases, sorted by semver
167-
let releases = releases_for_crate(conn, krate.crate_id).await?;
168-
169182
let repository_metadata = krate.repo_host.map(|_| RepositoryMetadata {
170183
issues: krate.repo_issues.unwrap(),
171184
stars: krate.repo_stars.unwrap(),
@@ -205,7 +218,7 @@ impl CrateDetails {
205218
keywords: krate.keywords,
206219
have_examples: krate.have_examples,
207220
target_name: krate.target_name,
208-
releases,
221+
releases: prefetched_releases,
209222
repository_metadata,
210223
metadata,
211224
is_library: krate.is_library,
@@ -399,21 +412,17 @@ pub(crate) async fn crate_details_handler(
399412
)
400413
})?;
401414

402-
let version = match_version(&mut conn, &params.name, &req_version)
415+
let matched_release = match_version(&mut conn, &params.name, &req_version)
403416
.await?
404417
.assume_exact_name()?
405418
.into_canonical_req_version_or_else(|version| {
406419
AxumNope::Redirect(
407420
format!("/crate/{}/{}", &params.name, version),
408421
CachePolicy::ForeverInCdn,
409422
)
410-
})?
411-
.into_version();
423+
})?;
412424

413-
let mut details =
414-
CrateDetails::new(&mut conn, &params.name, &version, Some(req_version.clone()))
415-
.await?
416-
.ok_or(AxumNope::VersionNotFound)?;
425+
let mut details = CrateDetails::from_matched_release(&mut conn, &matched_release).await?;
417426

418427
match details.fetch_readme(&storage).await {
419428
Ok(readme) => details.readme = readme.or(details.readme),
@@ -670,23 +679,44 @@ mod tests {
670679
assert_cache_control, assert_redirect, assert_redirect_cached, async_wrapper, wrapper,
671680
TestDatabase, TestEnvironment,
672681
};
673-
use anyhow::{Context, Error};
682+
use anyhow::Error;
674683
use kuchikiki::traits::TendrilSink;
675684
use semver::Version;
676685
use std::collections::HashMap;
677686

687+
async fn crate_details(
688+
conn: &mut sqlx::PgConnection,
689+
name: &str,
690+
version: &str,
691+
req_version: Option<ReqVersion>,
692+
) -> CrateDetails {
693+
let crate_id: i32 = sqlx::query_scalar!("SELECT id FROM crates WHERE name = $1", name)
694+
.fetch_one(&mut *conn)
695+
.await
696+
.unwrap();
697+
698+
let releases = releases_for_crate(&mut *conn, crate_id).await.unwrap();
699+
700+
CrateDetails::new(
701+
&mut *conn,
702+
name,
703+
&Version::parse(version).unwrap(),
704+
req_version,
705+
releases,
706+
)
707+
.await
708+
.unwrap()
709+
.unwrap()
710+
}
711+
678712
async fn assert_last_successful_build_equals(
679713
db: &TestDatabase,
680714
package: &str,
681715
version: &str,
682716
expected_last_successful_build: Option<&str>,
683717
) -> Result<(), Error> {
684718
let mut conn = db.async_conn().await;
685-
let details =
686-
CrateDetails::new(&mut conn, package, &Version::parse(version).unwrap(), None)
687-
.await
688-
.with_context(|| anyhow::anyhow!("could not fetch crate details"))?
689-
.unwrap();
719+
let details = crate_details(&mut conn, package, version, None).await;
690720

691721
assert_eq!(
692722
details.last_successful_build,
@@ -851,10 +881,7 @@ mod tests {
851881

852882
let details = env.runtime().block_on(async move {
853883
let mut conn = db.async_conn().await;
854-
CrateDetails::new(&mut conn, "foo", &"0.2.0".parse().unwrap(), None)
855-
.await
856-
.unwrap()
857-
.unwrap()
884+
crate_details(&mut conn, "foo", "0.2.0", None).await
858885
});
859886

860887
assert_eq!(
@@ -972,10 +999,7 @@ mod tests {
972999
for version in &["0.0.1", "0.0.2", "0.0.3"] {
9731000
let details = env.runtime().block_on(async move {
9741001
let mut conn = db.async_conn().await;
975-
CrateDetails::new(&mut conn, "foo", &version.parse().unwrap(), None)
976-
.await
977-
.unwrap()
978-
.unwrap()
1002+
crate_details(&mut conn, "foo", version, None).await
9791003
});
9801004
assert_eq!(
9811005
details.latest_release().unwrap().version,
@@ -1002,10 +1026,7 @@ mod tests {
10021026
for version in &["0.0.1", "0.0.2", "0.0.3-pre.1"] {
10031027
let details = env.runtime().block_on(async move {
10041028
let mut conn = db.async_conn().await;
1005-
CrateDetails::new(&mut conn, "foo", &version.parse().unwrap(), None)
1006-
.await
1007-
.unwrap()
1008-
.unwrap()
1029+
crate_details(&mut conn, "foo", version, None).await
10091030
});
10101031
assert_eq!(
10111032
details.latest_release().unwrap().version,
@@ -1033,10 +1054,7 @@ mod tests {
10331054
for version in &["0.0.1", "0.0.2", "0.0.3"] {
10341055
let details = env.runtime().block_on(async move {
10351056
let mut conn = db.async_conn().await;
1036-
CrateDetails::new(&mut conn, "foo", &(version.parse().unwrap()), None)
1037-
.await
1038-
.unwrap()
1039-
.unwrap()
1057+
crate_details(&mut conn, "foo", version, None).await
10401058
});
10411059
assert_eq!(
10421060
details.latest_release().unwrap().version,
@@ -1072,10 +1090,7 @@ mod tests {
10721090
for version in &["0.0.1", "0.0.2", "0.0.3"] {
10731091
let details = env.runtime().block_on(async move {
10741092
let mut conn = db.async_conn().await;
1075-
CrateDetails::new(&mut conn, "foo", &version.parse().unwrap(), None)
1076-
.await
1077-
.unwrap()
1078-
.unwrap()
1093+
crate_details(&mut conn, "foo", version, None).await
10791094
});
10801095
assert_eq!(
10811096
details.latest_release().unwrap().version,
@@ -1132,10 +1147,7 @@ mod tests {
11321147

11331148
let details = env.runtime().block_on(async move {
11341149
let mut conn = db.async_conn().await;
1135-
CrateDetails::new(&mut conn, "foo", &"0.0.1".parse().unwrap(), None)
1136-
.await
1137-
.unwrap()
1138-
.unwrap()
1150+
crate_details(&mut conn, "foo", "0.0.1", None).await
11391151
});
11401152
assert_eq!(
11411153
details.owners,
@@ -1158,10 +1170,7 @@ mod tests {
11581170

11591171
let details = env.runtime().block_on(async move {
11601172
let mut conn = db.async_conn().await;
1161-
CrateDetails::new(&mut conn, "foo", &"0.0.1".parse().unwrap(), None)
1162-
.await
1163-
.unwrap()
1164-
.unwrap()
1173+
crate_details(&mut conn, "foo", "0.0.1", None).await
11651174
});
11661175
let mut owners = details.owners;
11671176
owners.sort();
@@ -1185,10 +1194,7 @@ mod tests {
11851194

11861195
let details = env.runtime().block_on(async move {
11871196
let mut conn = db.async_conn().await;
1188-
CrateDetails::new(&mut conn, "foo", &"0.0.1".parse().unwrap(), None)
1189-
.await
1190-
.unwrap()
1191-
.unwrap()
1197+
crate_details(&mut conn, "foo", "0.0.1", None).await
11921198
});
11931199
assert_eq!(
11941200
details.owners,
@@ -1207,10 +1213,7 @@ mod tests {
12071213

12081214
let details = env.runtime().block_on(async move {
12091215
let mut conn = db.async_conn().await;
1210-
CrateDetails::new(&mut conn, "foo", &"0.0.1".parse().unwrap(), None)
1211-
.await
1212-
.unwrap()
1213-
.unwrap()
1216+
crate_details(&mut conn, "foo", "0.0.1", None).await
12141217
});
12151218
assert_eq!(
12161219
details.owners,
@@ -1654,10 +1657,7 @@ mod tests {
16541657

16551658
let details = env.runtime().block_on(async move {
16561659
let mut conn = env.async_db().await.async_conn().await;
1657-
CrateDetails::new(&mut conn, "dummy", &"0.5.0".parse().unwrap(), None)
1658-
.await
1659-
.unwrap()
1660-
.unwrap()
1660+
crate_details(&mut conn, "dummy", "0.5.0", None).await
16611661
});
16621662
assert!(matches!(
16631663
env.runtime()

src/web/mod.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ impl FromStr for ReqVersion {
111111
}
112112

113113
#[derive(Debug)]
114-
struct MatchedRelease {
114+
pub(crate) struct MatchedRelease {
115115
/// crate name
116116
pub name: String,
117117

@@ -125,6 +125,9 @@ struct MatchedRelease {
125125

126126
/// the matched release
127127
pub release: crate_details::Release,
128+
129+
/// all releases since we have them anyways and so we can pass them to CrateDetails
130+
all_releases: Vec<crate_details::Release>,
128131
}
129132

130133
impl MatchedRelease {
@@ -274,6 +277,7 @@ async fn match_version(
274277
corrected_name,
275278
req_version: input_version.clone(),
276279
release: release.clone(),
280+
all_releases: releases,
277281
});
278282
}
279283

@@ -302,6 +306,7 @@ async fn match_version(
302306
corrected_name,
303307
req_version: input_version.clone(),
304308
release: release.clone(),
309+
all_releases: releases,
305310
});
306311
}
307312

@@ -311,11 +316,13 @@ async fn match_version(
311316
if req_semver == VersionReq::STAR {
312317
return releases
313318
.first()
319+
.cloned()
314320
.map(|release| MatchedRelease {
315321
name: name.to_owned(),
316322
corrected_name: corrected_name.clone(),
317323
req_version: input_version.clone(),
318-
release: release.clone(),
324+
release,
325+
all_releases: releases,
319326
})
320327
.ok_or(AxumNope::VersionNotFound);
321328
}

src/web/rustdoc.rs

+10-23
Original file line numberDiff line numberDiff line change
@@ -180,12 +180,9 @@ pub(crate) async fn rustdoc_redirector_handler(
180180
rendering_time.step("serve JS for crate");
181181

182182
return async {
183-
let version = matched_release.into_version();
183+
let krate = CrateDetails::from_matched_release(&mut conn, &matched_release).await?;
184184

185-
let krate =
186-
CrateDetails::new(&mut conn, &crate_name, &version, params.version.clone())
187-
.await?
188-
.ok_or(AxumNope::ResourceNotFound)?;
185+
let version = matched_release.into_version();
189186

190187
rendering_time.step("fetch from storage");
191188

@@ -409,7 +406,7 @@ pub(crate) async fn rustdoc_html_server_handler(
409406
// * If both the name and the version are an exact match, return the version of the crate.
410407
// * If there is an exact match, but the requested crate name was corrected (dashes vs. underscores), redirect to the corrected name.
411408
// * If there is a semver (but not exact) match, redirect to the exact version.
412-
let version = match_version(&mut conn, &params.name, &params.version)
409+
let matched_release = match_version(&mut conn, &params.name, &params.version)
413410
.await?
414411
.into_exactly_named_or_else(|corrected_name, req_version| {
415412
AxumNope::Redirect(
@@ -432,22 +429,14 @@ pub(crate) async fn rustdoc_html_server_handler(
432429
)),
433430
CachePolicy::ForeverInCdn,
434431
)
435-
})?
436-
.into_version();
432+
})?;
437433

438434
trace!("crate details");
439435
rendering_time.step("crate details");
440436

441437
// Get the crate's details from the database
442-
// NOTE: we know this crate must exist because we just checked it above (or else `match_version` is buggy)
443-
let krate = CrateDetails::new(
444-
&mut conn,
445-
&params.name,
446-
&version,
447-
Some(params.version.clone()),
448-
)
449-
.await?
450-
.ok_or(AxumNope::ResourceNotFound)?;
438+
let krate = CrateDetails::from_matched_release(&mut conn, &matched_release).await?;
439+
let version = matched_release.into_version();
451440

452441
if !krate.rustdoc_status {
453442
rendering_time.step("redirect to crate");
@@ -733,14 +722,12 @@ pub(crate) async fn target_redirect_handler(
733722
mut conn: DbConnection,
734723
Extension(storage): Extension<Arc<AsyncStorage>>,
735724
) -> AxumResult<impl IntoResponse> {
736-
let version = match_version(&mut conn, &name, &req_version)
725+
let matched_release = match_version(&mut conn, &name, &req_version)
737726
.await?
738-
.into_canonical_req_version_or_else(|_| AxumNope::VersionNotFound)?
739-
.into_version();
727+
.into_canonical_req_version_or_else(|_| AxumNope::VersionNotFound)?;
740728

741-
let crate_details = CrateDetails::new(&mut conn, &name, &version, Some(req_version.clone()))
742-
.await?
743-
.ok_or(AxumNope::VersionNotFound)?;
729+
let crate_details = CrateDetails::from_matched_release(&mut conn, &matched_release).await?;
730+
let version = matched_release.into_version();
744731

745732
// We're trying to find the storage location
746733
// for the requested path in the target-redirect.

0 commit comments

Comments
 (0)