diff --git a/.sqlx/query-33fd4ac8d3abdd7d240187e6a834ab8f2384521b036e24ba01501eb7d499e65d.json b/.sqlx/query-33fd4ac8d3abdd7d240187e6a834ab8f2384521b036e24ba01501eb7d499e65d.json new file mode 100644 index 000000000..5590d1ee1 --- /dev/null +++ b/.sqlx/query-33fd4ac8d3abdd7d240187e6a834ab8f2384521b036e24ba01501eb7d499e65d.json @@ -0,0 +1,34 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT crates.name,\n releases.target_name,\n MAX(releases.release_time) as release_time\n FROM crates\n INNER JOIN releases ON releases.crate_id = crates.id\n WHERE\n rustdoc_status = true AND\n crates.name ILIKE $1\n GROUP BY crates.name, releases.target_name\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "name", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "target_name", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "release_time", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + null + ] + }, + "hash": "33fd4ac8d3abdd7d240187e6a834ab8f2384521b036e24ba01501eb7d499e65d" +} diff --git a/.sqlx/query-5d8e187d604de870d347be77abae3a272114732644975e9dbf396f5ffe689f6b.json b/.sqlx/query-5d8e187d604de870d347be77abae3a272114732644975e9dbf396f5ffe689f6b.json new file mode 100644 index 000000000..9e4817ed6 --- /dev/null +++ b/.sqlx/query-5d8e187d604de870d347be77abae3a272114732644975e9dbf396f5ffe689f6b.json @@ -0,0 +1,71 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT\n crates.name,\n releases.version,\n releases.description,\n releases.target_name,\n releases.rustdoc_status,\n releases.default_target,\n releases.doc_targets,\n releases.yanked,\n releases.doc_rustc_version\n FROM releases\n INNER JOIN crates ON crates.id = releases.crate_id\n WHERE crates.name = $1 AND releases.version = $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "name", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "version", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "description", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "target_name", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "rustdoc_status", + "type_info": "Bool" + }, + { + "ordinal": 5, + "name": "default_target", + "type_info": "Varchar" + }, + { + "ordinal": 6, + "name": "doc_targets", + "type_info": "Json" + }, + { + "ordinal": 7, + "name": "yanked", + "type_info": "Bool" + }, + { + "ordinal": 8, + "name": "doc_rustc_version", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false, + false, + true, + false, + false, + false, + false, + false, + false + ] + }, + "hash": "5d8e187d604de870d347be77abae3a272114732644975e9dbf396f5ffe689f6b" +} diff --git a/.sqlx/query-908827d37c731be4a6611eacd2e53aa03ae0b38047e0e58c034a3d34f8e29631.json b/.sqlx/query-908827d37c731be4a6611eacd2e53aa03ae0b38047e0e58c034a3d34f8e29631.json new file mode 100644 index 000000000..eb2800d85 --- /dev/null +++ b/.sqlx/query-908827d37c731be4a6611eacd2e53aa03ae0b38047e0e58c034a3d34f8e29631.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT releases.rustdoc_status\n FROM releases\n WHERE releases.id = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "rustdoc_status", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Int4" + ] + }, + "nullable": [ + false + ] + }, + "hash": "908827d37c731be4a6611eacd2e53aa03ae0b38047e0e58c034a3d34f8e29631" +} diff --git a/.sqlx/query-96817014a0af7e5946ecd00000ff08b5deaa12d85e1e0bb7bd845beafb0f2702.json b/.sqlx/query-96817014a0af7e5946ecd00000ff08b5deaa12d85e1e0bb7bd845beafb0f2702.json new file mode 100644 index 000000000..6cfb1e1a8 --- /dev/null +++ b/.sqlx/query-96817014a0af7e5946ecd00000ff08b5deaa12d85e1e0bb7bd845beafb0f2702.json @@ -0,0 +1,47 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT\n builds.id,\n builds.rustc_version,\n builds.docsrs_version,\n builds.build_status,\n builds.build_time\n FROM builds\n INNER JOIN releases ON releases.id = builds.rid\n INNER JOIN crates ON releases.crate_id = crates.id\n WHERE crates.name = $1 AND releases.version = $2\n ORDER BY id DESC", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + }, + { + "ordinal": 1, + "name": "rustc_version", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "docsrs_version", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "build_status", + "type_info": "Bool" + }, + { + "ordinal": 4, + "name": "build_time", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false + ] + }, + "hash": "96817014a0af7e5946ecd00000ff08b5deaa12d85e1e0bb7bd845beafb0f2702" +} diff --git a/.sqlx/query-a7cbfecb1e270232061de05fa4a53f926d9e289dcb4b03cba3e3eeebce74dfe0.json b/.sqlx/query-a7cbfecb1e270232061de05fa4a53f926d9e289dcb4b03cba3e3eeebce74dfe0.json new file mode 100644 index 000000000..469fc6e1e --- /dev/null +++ b/.sqlx/query-a7cbfecb1e270232061de05fa4a53f926d9e289dcb4b03cba3e3eeebce74dfe0.json @@ -0,0 +1,54 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT\n builds.rustc_version,\n builds.docsrs_version,\n builds.build_status,\n builds.build_time,\n builds.output,\n releases.default_target\n FROM builds\n INNER JOIN releases ON releases.id = builds.rid\n INNER JOIN crates ON releases.crate_id = crates.id\n WHERE builds.id = $1 AND crates.name = $2 AND releases.version = $3", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "rustc_version", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "docsrs_version", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "build_status", + "type_info": "Bool" + }, + { + "ordinal": 3, + "name": "build_time", + "type_info": "Timestamptz" + }, + { + "ordinal": 4, + "name": "output", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "default_target", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Int4", + "Text", + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + true, + false + ] + }, + "hash": "a7cbfecb1e270232061de05fa4a53f926d9e289dcb4b03cba3e3eeebce74dfe0" +} diff --git a/src/db/types.rs b/src/db/types.rs index 74d5f1e12..7936ce807 100644 --- a/src/db/types.rs +++ b/src/db/types.rs @@ -1,7 +1,7 @@ use postgres_types::{FromSql, ToSql}; use serde::Serialize; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, FromSql, ToSql)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, FromSql, ToSql, sqlx::Type)] #[postgres(name = "feature")] pub struct Feature { pub(crate) name: String, @@ -17,3 +17,9 @@ impl Feature { self.name.starts_with('_') } } + +impl sqlx::postgres::PgHasArrayType for Feature { + fn array_type_info() -> sqlx::postgres::PgTypeInfo { + sqlx::postgres::PgTypeInfo::with_name("_feature") + } +} diff --git a/src/web/build_details.rs b/src/web/build_details.rs index 7d3c4b3a5..fd627064f 100644 --- a/src/web/build_details.rs +++ b/src/web/build_details.rs @@ -1,7 +1,6 @@ use crate::{ db::Pool, impl_axum_webpage, - utils::spawn_blocking, web::{ error::{AxumNope, AxumResult}, file::File, @@ -47,52 +46,44 @@ pub(crate) async fn build_details_handler( ) -> AxumResult { let id: i32 = id.parse().map_err(|_| AxumNope::BuildNotFound)?; - let (row, output, metadata) = spawn_blocking(move || { - let mut conn = pool.get()?; - let row = conn - .query_opt( - "SELECT - builds.rustc_version, - builds.docsrs_version, - builds.build_status, - builds.build_time, - builds.output, - releases.default_target - FROM builds - INNER JOIN releases ON releases.id = builds.rid - INNER JOIN crates ON releases.crate_id = crates.id - WHERE builds.id = $1 AND crates.name = $2 AND releases.version = $3", - &[&id, &name, &version], - )? - .ok_or(AxumNope::BuildNotFound)?; - - let output: Option = row.get("output"); - - Ok(( - row, - output, - MetaData::from_crate(&mut conn, &name, &version, &version)?, - )) - }) - .await?; - - let output = if let Some(output) = output { + let mut conn = pool.get_async().await?; + + let row = sqlx::query!( + "SELECT + builds.rustc_version, + builds.docsrs_version, + builds.build_status, + builds.build_time, + builds.output, + releases.default_target + FROM builds + INNER JOIN releases ON releases.id = builds.rid + INNER JOIN crates ON releases.crate_id = crates.id + WHERE builds.id = $1 AND crates.name = $2 AND releases.version = $3", + id, + name, + version, + ) + .fetch_optional(&mut *conn) + .await? + .ok_or(AxumNope::BuildNotFound)?; + + let output = if let Some(output) = row.output { output } else { - let target: String = row.get("default_target"); - let path = format!("build-logs/{id}/{target}.txt"); + let path = format!("build-logs/{id}/{}.txt", row.default_target); let file = File::from_path(&storage, &path, &config).await?; String::from_utf8(file.0.content).context("non utf8")? }; Ok(BuildDetailsPage { - metadata, + metadata: MetaData::from_crate(&mut conn, &name, &version, &version).await?, build_details: BuildDetails { id, - rustc_version: row.get("rustc_version"), - docsrs_version: row.get("docsrs_version"), - build_status: row.get("build_status"), - build_time: row.get("build_time"), + rustc_version: row.rustc_version, + docsrs_version: row.docsrs_version, + build_status: row.build_status, + build_time: row.build_time, output, }, use_direct_platform_links: true, diff --git a/src/web/builds.rs b/src/web/builds.rs index 79c0ae836..aeb670f9d 100644 --- a/src/web/builds.rs +++ b/src/web/builds.rs @@ -61,22 +61,18 @@ pub(crate) async fn build_list_handler( } }; - let (limits, builds, metadata) = spawn_blocking({ + let limits = spawn_blocking({ let name = name.clone(); - move || { - let mut conn = pool.get()?; - Ok(( - Limits::for_crate(&config, &mut conn, &name)?, - get_builds(&mut conn, &name, &version)?, - MetaData::from_crate(&mut conn, &name, &version, &version_or_latest)?, - )) - } + let mut conn = pool.get()?; + move || Limits::for_crate(&config, &mut conn, &name) }) .await?; + let mut conn = pool.get_async().await?; + Ok(BuildsPage { - metadata, - builds, + metadata: MetaData::from_crate(&mut conn, &name, &version, &version_or_latest).await?, + builds: get_builds(&mut conn, &name, &version).await?, limits, canonical_url: CanonicalUrl::from_path(format!("/crate/{name}/latest/builds")), use_direct_platform_links: true, @@ -102,51 +98,39 @@ pub(crate) async fn build_list_json_handler( } }; - let builds = spawn_blocking({ - move || { - let mut conn = pool.get()?; - get_builds(&mut conn, &name, &version) - } - }) - .await?; + let mut conn = pool.get_async().await?; Ok(( Extension(CachePolicy::NoStoreMustRevalidate), [(ACCESS_CONTROL_ALLOW_ORIGIN, "*")], - Json(builds), + Json(get_builds(&mut conn, &name, &version).await?), ) .into_response()) } -fn get_builds(conn: &mut postgres::Client, name: &str, version: &str) -> Result> { - Ok(conn - .query( - "SELECT crates.name, - releases.version, - releases.description, - releases.rustdoc_status, - releases.target_name, - builds.id, - builds.rustc_version, - builds.docsrs_version, - builds.build_status, - builds.build_time - FROM builds - INNER JOIN releases ON releases.id = builds.rid - INNER JOIN crates ON releases.crate_id = crates.id - WHERE crates.name = $1 AND releases.version = $2 - ORDER BY id DESC", - &[&name, &version], - )? - .iter() - .map(|row| Build { - id: row.get("id"), - rustc_version: row.get("rustc_version"), - docsrs_version: row.get("docsrs_version"), - build_status: row.get("build_status"), - build_time: row.get("build_time"), - }) - .collect()) +async fn get_builds( + conn: &mut sqlx::PgConnection, + name: &str, + version: &str, +) -> Result> { + Ok(sqlx::query_as!( + Build, + "SELECT + builds.id, + builds.rustc_version, + builds.docsrs_version, + builds.build_status, + builds.build_time + FROM builds + INNER JOIN releases ON releases.id = builds.rid + INNER JOIN crates ON releases.crate_id = crates.id + WHERE crates.name = $1 AND releases.version = $2 + ORDER BY id DESC", + name, + version, + ) + .fetch_all(&mut *conn) + .await?) } #[cfg(test)] diff --git a/src/web/error.rs b/src/web/error.rs index 47f08ee23..51f73b437 100644 --- a/src/web/error.rs +++ b/src/web/error.rs @@ -1,13 +1,14 @@ -use std::borrow::Cow; - use crate::{ + db::PoolError, storage::PathNotFoundError, web::{releases::Search, AxumErrorPage}, }; +use anyhow::anyhow; use axum::{ http::StatusCode, response::{IntoResponse, Response as AxumResponse}, }; +use std::borrow::Cow; #[derive(Debug, thiserror::Error)] #[allow(dead_code)] // FIXME: remove after iron is gone @@ -131,6 +132,18 @@ impl From for AxumNope { } } +impl From for AxumNope { + fn from(err: sqlx::Error) -> Self { + AxumNope::InternalError(anyhow!(err)) + } +} + +impl From for AxumNope { + fn from(err: PoolError) -> Self { + AxumNope::InternalError(anyhow!(err)) + } +} + pub(crate) type AxumResult = Result; #[cfg(test)] diff --git a/src/web/features.rs b/src/web/features.rs index 7800dbeb3..dbe8af1c8 100644 --- a/src/web/features.rs +++ b/src/web/features.rs @@ -1,10 +1,9 @@ use super::headers::CanonicalUrl; use super::MatchSemver; -use crate::db::types::Feature; use crate::{ + db::types::Feature, db::Pool, impl_axum_webpage, - utils::spawn_blocking, web::{cache::CachePolicy, error::AxumResult, match_version_axum, MetaData}, }; use anyhow::anyhow; @@ -13,6 +12,7 @@ use axum::{ response::IntoResponse, }; use serde::Serialize; +use sqlx::Row as _; use std::collections::{HashMap, VecDeque}; const DEFAULT_NAME: &str = "default"; @@ -57,23 +57,20 @@ pub(crate) async fn build_features_handler( } }; - let (row, metadata) = spawn_blocking({ - let name = name.clone(); - move || { - let mut conn = pool.get()?; - Ok(( - conn.query_opt( - "SELECT releases.features FROM releases - INNER JOIN crates ON crates.id = releases.crate_id - WHERE crates.name = $1 AND releases.version = $2", - &[&name, &version], - )? - .ok_or_else(|| anyhow!("missing release"))?, - MetaData::from_crate(&mut conn, &name, &version, &version_or_latest)?, - )) - } - }) - .await?; + let mut conn = pool.get_async().await?; + + let metadata = MetaData::from_crate(&mut conn, &name, &version, &version_or_latest).await?; + + let row = sqlx::query( + "SELECT releases.features FROM releases + INNER JOIN crates ON crates.id = releases.crate_id + WHERE crates.name = $1 AND releases.version = $2", + ) + .bind(&name) + .bind(&version) + .fetch_optional(&mut *conn) + .await? + .ok_or_else(|| anyhow!("missing release"))?; let mut features = None; let mut default_len = 0; diff --git a/src/web/mod.rs b/src/web/mod.rs index 6448fd99a..92df48cc6 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -512,38 +512,43 @@ pub(crate) struct MetaData { } impl MetaData { - fn from_crate( - conn: &mut Client, + async fn from_crate( + conn: &mut sqlx::PgConnection, name: &str, version: &str, version_or_latest: &str, ) -> Result { - conn.query_opt( - "SELECT crates.name, - releases.version, - releases.description, - releases.target_name, - releases.rustdoc_status, - releases.default_target, - releases.doc_targets, - releases.yanked, - releases.doc_rustc_version - FROM releases - INNER JOIN crates ON crates.id = releases.crate_id - WHERE crates.name = $1 AND releases.version = $2", - &[&name, &version], - )? + sqlx::query!( + "SELECT + crates.name, + releases.version, + releases.description, + releases.target_name, + releases.rustdoc_status, + releases.default_target, + releases.doc_targets, + releases.yanked, + releases.doc_rustc_version + FROM releases + INNER JOIN crates ON crates.id = releases.crate_id + WHERE crates.name = $1 AND releases.version = $2", + name, + version + ) + .fetch_optional(&mut *conn) + .await + .context("error fetching crate metadata")? .map(|row| MetaData { - name: row.get(0), - version: row.get(1), + name: row.name, + version: row.version, version_or_latest: version_or_latest.to_string(), - description: row.get(2), - target_name: row.get(3), - rustdoc_status: row.get(4), - default_target: row.get(5), - doc_targets: MetaData::parse_doc_targets(row.get(6)), - yanked: row.get(7), - rustdoc_css_file: get_correct_docsrs_style_file(row.get(8)).unwrap(), + description: row.description, + target_name: Some(row.target_name), + rustdoc_status: row.rustdoc_status, + default_target: row.default_target, + doc_targets: MetaData::parse_doc_targets(row.doc_targets), + yanked: row.yanked, + rustdoc_css_file: get_correct_docsrs_style_file(&row.doc_rustc_version).unwrap(), }) .ok_or_else(|| anyhow!("missing metadata for {} {}", name, version)) } @@ -1001,8 +1006,10 @@ mod test { fn metadata_from_crate() { wrapper(|env| { release("0.1.0", env); - let mut conn = env.db().conn(); - let metadata = MetaData::from_crate(&mut conn, "foo", "0.1.0", "latest"); + let metadata = env.runtime().block_on(async move { + let mut conn = env.db().async_conn().await; + MetaData::from_crate(&mut conn, "foo", "0.1.0", "latest").await + }); assert_eq!( metadata.unwrap(), MetaData { diff --git a/src/web/sitemap.rs b/src/web/sitemap.rs index 37b01b0ff..dc954a04f 100644 --- a/src/web/sitemap.rs +++ b/src/web/sitemap.rs @@ -14,7 +14,8 @@ use axum::{ http::StatusCode, response::IntoResponse, }; -use chrono::{DateTime, TimeZone, Utc}; +use chrono::{TimeZone, Utc}; +use futures_util::stream::TryStreamExt; use serde::Serialize; use std::sync::Arc; @@ -64,37 +65,39 @@ pub(crate) async fn sitemap_handler( return Err(AxumNope::ResourceNotFound); } } - let releases = spawn_blocking(move || { - let mut conn = pool.get()?; - let query = conn.query( - "SELECT crates.name, - releases.target_name, - MAX(releases.release_time) as release_time - FROM crates - INNER JOIN releases ON releases.crate_id = crates.id - WHERE - rustdoc_status = true AND - crates.name ILIKE $1 - GROUP BY crates.name, releases.target_name - ", - &[&format!("{letter}%")], - )?; - - Ok(query - .into_iter() - .map(|row| SitemapRow { - crate_name: row.get("name"), - target_name: row.get("target_name"), - last_modified: row - .get::<_, DateTime>("release_time") - // On Aug 27 2022 we added `` to all pages, - // so they should all get recrawled if they haven't been since then. + + let mut conn = pool.get_async().await?; + + let releases: Vec<_> = sqlx::query!( + "SELECT crates.name, + releases.target_name, + MAX(releases.release_time) as release_time + FROM crates + INNER JOIN releases ON releases.crate_id = crates.id + WHERE + rustdoc_status = true AND + crates.name ILIKE $1 + GROUP BY crates.name, releases.target_name + ", + format!("{letter}%"), + ) + .fetch(&mut *conn) + .map_ok(|row| SitemapRow { + crate_name: row.name, + target_name: row.target_name, + last_modified: row + .release_time + .map(|release_time| { + // On Aug 27 2022 we added `` to all pages, + // so they should all get recrawled if they haven't been since then. + release_time .max(Utc.with_ymd_and_hms(2022, 8, 28, 0, 0, 0).unwrap()) .format("%+") - .to_string(), + .to_string() }) - .collect()) + .unwrap_or_default(), }) + .try_collect() .await?; Ok(SitemapXml { releases }) diff --git a/src/web/status.rs b/src/web/status.rs index b84274304..039fe4d1f 100644 --- a/src/web/status.rs +++ b/src/web/status.rs @@ -1,7 +1,6 @@ use super::cache::CachePolicy; use crate::{ db::Pool, - utils::spawn_blocking, web::{axum_redirect, error::AxumResult, match_version_axum, MatchSemver}, }; use axum::{ @@ -34,20 +33,16 @@ pub(crate) async fn status_handler( } }; - let rustdoc_status: bool = spawn_blocking({ - move || { - Ok(pool - .get()? - .query_one( - "SELECT releases.rustdoc_status - FROM releases - WHERE releases.id = $1 - ", - &[&id], - )? - .get("rustdoc_status")) - } - }) + let mut conn = pool.get_async().await?; + + let rustdoc_status: bool = sqlx::query_scalar!( + "SELECT releases.rustdoc_status + FROM releases + WHERE releases.id = $1 + ", + id + ) + .fetch_one(&mut *conn) .await?; let json = Json(serde_json::json!({