From 10036a39597bd6a42b2df77ab906b31e3979e262 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 17 Oct 2025 16:08:13 +0200 Subject: [PATCH 01/28] database/models: Use `HasQuery` for `Keyword` --- crates/crates_io_database/src/models/keyword.rs | 8 +++----- src/controllers/keyword.rs | 2 +- src/controllers/summary.rs | 6 ++---- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/crates/crates_io_database/src/models/keyword.rs b/crates/crates_io_database/src/models/keyword.rs index 2b2caef17dd..8324b942365 100644 --- a/crates/crates_io_database/src/models/keyword.rs +++ b/crates/crates_io_database/src/models/keyword.rs @@ -7,7 +7,7 @@ use crate::models::Crate; use crate::schema::*; use crates_io_diesel_helpers::lower; -#[derive(Clone, Identifiable, Queryable, Debug, Selectable)] +#[derive(Clone, Identifiable, HasQuery, Debug)] pub struct Keyword { pub id: i32, pub keyword: String, @@ -30,9 +30,8 @@ pub struct CrateKeyword { impl Keyword { pub async fn find_by_keyword(conn: &mut AsyncPgConnection, name: &str) -> QueryResult { - keywords::table + Keyword::query() .filter(keywords::keyword.eq(lower(name))) - .select(Keyword::as_select()) .first(conn) .await } @@ -54,9 +53,8 @@ impl Keyword { .execute(conn) .await?; - keywords::table + Keyword::query() .filter(keywords::keyword.eq_any(&lowercase_names)) - .select(Keyword::as_select()) .load(conn) .await } diff --git a/src/controllers/keyword.rs b/src/controllers/keyword.rs index a15200854d8..ad3e91b3a35 100644 --- a/src/controllers/keyword.rs +++ b/src/controllers/keyword.rs @@ -53,7 +53,7 @@ pub async fn list_keywords( ) -> AppResult> { use crate::schema::keywords; - let mut query = keywords::table.select(Keyword::as_select()).into_boxed(); + let mut query = Keyword::query().into_boxed(); query = match ¶ms.sort { Some(sort) if sort == "crates" => query.order(keywords::crates_cnt.desc()), diff --git a/src/controllers/summary.rs b/src/controllers/summary.rs index 93cb251c8e7..0f68f69642c 100644 --- a/src/controllers/summary.rs +++ b/src/controllers/summary.rs @@ -115,10 +115,9 @@ pub async fn get_summary(state: AppState) -> AppResult> { .load(&mut conn) .boxed(), Category::toplevel(&mut conn, "crates", 10, 0), - keywords::table + Keyword::query() .order(keywords::crates_cnt.desc()) .limit(10) - .select(Keyword::as_select()) .load(&mut conn) .boxed(), )?; @@ -171,10 +170,9 @@ fn encode_crates( .map(|record| record.krate.id) .collect::>(); - let future = versions::table + let future = Version::query() .filter(versions::crate_id.eq_any(crate_ids)) .filter(versions::yanked.eq(false)) - .select(Version::as_select()) .load(conn); async move { From 1ce6c330b93e25b62a29e477a2b228c6225880ce Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 17 Oct 2025 16:11:10 +0200 Subject: [PATCH 02/28] database/models: Use `HasQuery` for `Team` --- crates/crates_io_database/src/models/team.rs | 2 +- src/controllers/team.rs | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/crates_io_database/src/models/team.rs b/crates/crates_io_database/src/models/team.rs index 0d8ec050877..d1561fa58fd 100644 --- a/crates/crates_io_database/src/models/team.rs +++ b/crates/crates_io_database/src/models/team.rs @@ -7,7 +7,7 @@ use crate::schema::{crate_owners, teams}; /// For now, just a GitHub Team. Can be upgraded to other teams /// later if desirable. -#[derive(Queryable, Identifiable, serde::Serialize, serde::Deserialize, Debug, Selectable)] +#[derive(HasQuery, Identifiable, serde::Serialize, serde::Deserialize, Debug)] pub struct Team { /// Unique table id pub id: i32, diff --git a/src/controllers/team.rs b/src/controllers/team.rs index bbee96e7ef9..c297d75a30c 100644 --- a/src/controllers/team.rs +++ b/src/controllers/team.rs @@ -24,12 +24,11 @@ pub struct GetResponse { responses((status = 200, description = "Successful Response", body = inline(GetResponse))), )] pub async fn find_team(state: AppState, Path(name): Path) -> AppResult> { - use crate::schema::teams::dsl::{login, teams}; + use crate::schema::teams::dsl::login; let mut conn = state.db_read().await?; - let team: Team = teams + let team: Team = Team::query() .filter(login.eq(&name)) - .select(Team::as_select()) .first(&mut conn) .await?; let team = EncodableTeam::from(team); From cb01cdaf77eb008a7865fcaed6388e8aa45cf84c Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 17 Oct 2025 16:15:21 +0200 Subject: [PATCH 03/28] database/models: Use `HasQuery` for `GitHubConfig` --- .../crates_io_database/src/models/trustpub/github_config.rs | 4 ++-- src/controllers/trustpub/github_configs/delete/tests.rs | 6 +----- src/controllers/trustpub/github_configs/list/mod.rs | 5 ++--- src/controllers/trustpub/tokens/exchange/mod.rs | 3 +-- 4 files changed, 6 insertions(+), 12 deletions(-) diff --git a/crates/crates_io_database/src/models/trustpub/github_config.rs b/crates/crates_io_database/src/models/trustpub/github_config.rs index 969d43b8271..c5bb6120ecb 100644 --- a/crates/crates_io_database/src/models/trustpub/github_config.rs +++ b/crates/crates_io_database/src/models/trustpub/github_config.rs @@ -4,8 +4,8 @@ use diesel::prelude::*; use diesel_async::{AsyncPgConnection, RunQueryDsl}; use serde::Serialize; -#[derive(Debug, Identifiable, Queryable, Selectable, Serialize)] -#[diesel(table_name = trustpub_configs_github, check_for_backend(diesel::pg::Pg))] +#[derive(Debug, Identifiable, HasQuery, Serialize)] +#[diesel(table_name = trustpub_configs_github)] pub struct GitHubConfig { pub id: i32, pub created_at: DateTime, diff --git a/src/controllers/trustpub/github_configs/delete/tests.rs b/src/controllers/trustpub/github_configs/delete/tests.rs index 1df55361154..c38b53b211d 100644 --- a/src/controllers/trustpub/github_configs/delete/tests.rs +++ b/src/controllers/trustpub/github_configs/delete/tests.rs @@ -3,7 +3,6 @@ use crate::tests::util::{RequestHelper, TestApp}; use crates_io_database::models::Crate; use crates_io_database::models::token::{CrateScope, EndpointScope}; use crates_io_database::models::trustpub::{GitHubConfig, NewGitHubConfig}; -use crates_io_database::schema::trustpub_configs_github; use diesel::prelude::*; use diesel_async::{AsyncPgConnection, RunQueryDsl}; use insta::assert_snapshot; @@ -34,10 +33,7 @@ async fn create_config(conn: &mut AsyncPgConnection, crate_id: i32) -> QueryResu } async fn get_all_configs(conn: &mut AsyncPgConnection) -> QueryResult> { - trustpub_configs_github::table - .select(GitHubConfig::as_select()) - .load::(conn) - .await + GitHubConfig::query().load(conn).await } /// Delete the config with a valid user that is an owner of the crate. diff --git a/src/controllers/trustpub/github_configs/list/mod.rs b/src/controllers/trustpub/github_configs/list/mod.rs index 07e759a7c52..6b7df5fab0f 100644 --- a/src/controllers/trustpub/github_configs/list/mod.rs +++ b/src/controllers/trustpub/github_configs/list/mod.rs @@ -67,10 +67,9 @@ pub async fn list_trustpub_github_configs( return Err(bad_request("You are not an owner of this crate")); } - let configs = trustpub_configs_github::table + let configs = GitHubConfig::query() .filter(trustpub_configs_github::crate_id.eq(krate.id)) - .select(GitHubConfig::as_select()) - .load::(&mut conn) + .load(&mut conn) .await?; let github_configs = configs diff --git a/src/controllers/trustpub/tokens/exchange/mod.rs b/src/controllers/trustpub/tokens/exchange/mod.rs index ffbd04cca8f..a996d65c195 100644 --- a/src/controllers/trustpub/tokens/exchange/mod.rs +++ b/src/controllers/trustpub/tokens/exchange/mod.rs @@ -131,8 +131,7 @@ async fn handle_github_token_inner( return Err(bad_request(message)); }; - let mut repo_configs = trustpub_configs_github::table - .select(GitHubConfig::as_select()) + let mut repo_configs = GitHubConfig::query() .filter(lower(trustpub_configs_github::repository_owner).eq(lower(&repository_owner))) .filter(lower(trustpub_configs_github::repository_name).eq(lower(&repository_name))) .load(conn) From a1481de8c6cd188bed71ce436f90cc97445c78a4 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 17 Oct 2025 16:18:04 +0200 Subject: [PATCH 04/28] database/models: Use `HasQuery` for `GitLabConfig` --- .../src/models/trustpub/gitlab_config.rs | 9 ++++----- src/controllers/trustpub/tokens/exchange/gitlab_tests.rs | 3 +-- src/controllers/trustpub/tokens/exchange/mod.rs | 3 +-- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/crates/crates_io_database/src/models/trustpub/gitlab_config.rs b/crates/crates_io_database/src/models/trustpub/gitlab_config.rs index f7ba9377125..c100f53d2e5 100644 --- a/crates/crates_io_database/src/models/trustpub/gitlab_config.rs +++ b/crates/crates_io_database/src/models/trustpub/gitlab_config.rs @@ -4,8 +4,8 @@ use diesel::prelude::*; use diesel_async::{AsyncPgConnection, RunQueryDsl}; use serde::Serialize; -#[derive(Debug, Identifiable, Queryable, Selectable, Serialize)] -#[diesel(table_name = trustpub_configs_gitlab, check_for_backend(diesel::pg::Pg))] +#[derive(Debug, Identifiable, HasQuery, Serialize)] +#[diesel(table_name = trustpub_configs_gitlab)] pub struct GitLabConfig { pub id: i32, pub created_at: DateTime, @@ -71,10 +71,9 @@ mod tests { let inserted_config = new_config.insert(&mut conn).await.unwrap(); // Retrieve the config - let retrieved_config = trustpub_configs_gitlab::table + let retrieved_config = GitLabConfig::query() .filter(trustpub_configs_gitlab::id.eq(inserted_config.id)) - .select(GitLabConfig::as_select()) - .first::(&mut conn) + .first(&mut conn) .await .unwrap(); diff --git a/src/controllers/trustpub/tokens/exchange/gitlab_tests.rs b/src/controllers/trustpub/tokens/exchange/gitlab_tests.rs index 009f98b91b6..802699049be 100644 --- a/src/controllers/trustpub/tokens/exchange/gitlab_tests.rs +++ b/src/controllers/trustpub/tokens/exchange/gitlab_tests.rs @@ -408,10 +408,9 @@ async fn test_lazy_namespace_id_population() -> anyhow::Result<()> { assert_snapshot!(response.status(), @"200 OK"); // Check that namespace_id was populated in the database - let config: GitLabConfig = trustpub_configs_gitlab::table + let config: GitLabConfig = GitLabConfig::query() .filter(trustpub_configs_gitlab::namespace.eq(NAMESPACE)) .filter(trustpub_configs_gitlab::project.eq(PROJECT)) - .select(GitLabConfig::as_select()) .first(&mut conn) .await?; diff --git a/src/controllers/trustpub/tokens/exchange/mod.rs b/src/controllers/trustpub/tokens/exchange/mod.rs index a996d65c195..8bfe9bc6e4b 100644 --- a/src/controllers/trustpub/tokens/exchange/mod.rs +++ b/src/controllers/trustpub/tokens/exchange/mod.rs @@ -269,8 +269,7 @@ async fn handle_gitlab_token_inner( return Err(bad_request(message)); }; - let mut repo_configs = trustpub_configs_gitlab::table - .select(GitLabConfig::as_select()) + let mut repo_configs = GitLabConfig::query() .filter(lower(trustpub_configs_gitlab::namespace).eq(lower(&namespace))) .filter(lower(trustpub_configs_gitlab::project).eq(lower(&project))) .load(conn) From 00bc898bce632c2f2d964f8ab152d6a15de3af92 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 17 Oct 2025 16:20:53 +0200 Subject: [PATCH 05/28] database/models: Use `HasQuery` for `CloudFrontInvalidationQueueItem` --- .../src/models/cloudfront_invalidation_queue.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/crates_io_database/src/models/cloudfront_invalidation_queue.rs b/crates/crates_io_database/src/models/cloudfront_invalidation_queue.rs index a0ea9140fd9..7f051623ad8 100644 --- a/crates/crates_io_database/src/models/cloudfront_invalidation_queue.rs +++ b/crates/crates_io_database/src/models/cloudfront_invalidation_queue.rs @@ -2,8 +2,8 @@ use crate::schema::cloudfront_invalidation_queue; use diesel::prelude::*; use diesel_async::{AsyncPgConnection, RunQueryDsl}; -#[derive(Debug, Identifiable, Queryable, QueryableByName, Selectable)] -#[diesel(table_name = cloudfront_invalidation_queue, check_for_backend(diesel::pg::Pg))] +#[derive(Debug, Identifiable, HasQuery, QueryableByName)] +#[diesel(table_name = cloudfront_invalidation_queue)] pub struct CloudFrontInvalidationQueueItem { pub id: i64, pub path: String, @@ -36,10 +36,9 @@ impl CloudFrontInvalidationQueueItem { limit: i64, ) -> QueryResult> { // Fetch the oldest entries up to the limit - cloudfront_invalidation_queue::table + Self::query() .order(cloudfront_invalidation_queue::created_at.asc()) .limit(limit) - .select(Self::as_select()) .load(conn) .await } From b61c14d5b317d1efc30a10728abf46eeaeb3aa2a Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 17 Oct 2025 16:24:13 +0200 Subject: [PATCH 06/28] database/models: Use `HasQuery` for `VersionDownload` --- crates/crates_io_database/src/models/download.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/crates_io_database/src/models/download.rs b/crates/crates_io_database/src/models/download.rs index 2f4f0efea71..285c7b58d98 100644 --- a/crates/crates_io_database/src/models/download.rs +++ b/crates/crates_io_database/src/models/download.rs @@ -4,7 +4,7 @@ use chrono::NaiveDate; use crates_io_diesel_helpers::SemverVersion; use diesel::prelude::*; -#[derive(Queryable, Identifiable, Selectable, Associations, Debug, Clone, Copy)] +#[derive(HasQuery, Identifiable, Associations, Debug, Clone, Copy)] #[diesel( primary_key(version_id, date), belongs_to(FullVersion, foreign_key=version_id), From 79eeaa5f2e3f56734d1ec0e2e1ec738265f4dc65 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 17 Oct 2025 16:27:18 +0200 Subject: [PATCH 07/28] database/models: Use `HasQuery` for `ApiToken` --- crates/crates_io_database/src/models/token.rs | 4 +--- src/controllers/github/secret_scanning.rs | 5 ++--- src/worker/jobs/expiry_notification.rs | 9 +++------ 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/crates/crates_io_database/src/models/token.rs b/crates/crates_io_database/src/models/token.rs index 1a2ba06b673..3fffa206948 100644 --- a/crates/crates_io_database/src/models/token.rs +++ b/crates/crates_io_database/src/models/token.rs @@ -39,9 +39,7 @@ impl NewApiToken { } /// The model representing a row in the `api_tokens` database table. -#[derive( - Debug, Identifiable, Queryable, Selectable, Associations, serde::Serialize, utoipa::ToSchema, -)] +#[derive(Debug, Identifiable, HasQuery, Associations, serde::Serialize, utoipa::ToSchema)] #[diesel(belongs_to(User))] pub struct ApiToken { /// An opaque unique identifier for the token. diff --git a/src/controllers/github/secret_scanning.rs b/src/controllers/github/secret_scanning.rs index 4e8866644c4..03ffddb1645 100644 --- a/src/controllers/github/secret_scanning.rs +++ b/src/controllers/github/secret_scanning.rs @@ -176,10 +176,9 @@ async fn alert_revoke_token( let hashed_token = HashedToken::hash(&alert.token); // Not using `ApiToken::find_by_api_token()` in order to preserve `last_used_at` - let token = api_tokens::table - .select(ApiToken::as_select()) + let token = ApiToken::query() .filter(api_tokens::token.eq(hashed_token)) - .get_result::(conn) + .get_result(conn) .await .optional()?; diff --git a/src/worker/jobs/expiry_notification.rs b/src/worker/jobs/expiry_notification.rs index f89669cd815..29319c480ae 100644 --- a/src/worker/jobs/expiry_notification.rs +++ b/src/worker/jobs/expiry_notification.rs @@ -122,7 +122,7 @@ pub async fn find_expiring_tokens( conn: &mut AsyncPgConnection, before: chrono::DateTime, ) -> QueryResult> { - api_tokens::table + ApiToken::query() .filter(api_tokens::revoked.eq(false)) .filter(api_tokens::expired_at.is_not_null()) // Ignore already expired tokens @@ -133,7 +133,6 @@ pub async fn find_expiring_tokens( .lt(before.naive_utc()), ) .filter(api_tokens::expiry_notification_at.is_null()) - .select(ApiToken::as_select()) .order_by(api_tokens::expired_at.asc()) // The most urgent tokens first .limit(MAX_ROWS) .get_results(conn) @@ -215,19 +214,17 @@ mod tests { sent.1 .contains("crates.io: Your API token \"test_token\" is about to expire") ); - let updated_token = api_tokens::table + let updated_token = ApiToken::query() .filter(api_tokens::id.eq(token.id)) .filter(api_tokens::expiry_notification_at.is_not_null()) - .select(ApiToken::as_select()) .first::(&mut conn) .await?; assert_eq!(updated_token.name, "test_token".to_owned()); // Check that the token is not about to expire. - let tokens = api_tokens::table + let tokens = ApiToken::query() .filter(api_tokens::revoked.eq(false)) .filter(api_tokens::expiry_notification_at.is_null()) - .select(ApiToken::as_select()) .load::(&mut conn) .await?; assert_eq!(tokens.len(), 3); From b4f469219d4ccb77d8eb47c51ee21c57760bfe62 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 17 Oct 2025 16:33:47 +0200 Subject: [PATCH 08/28] database/models: Use `HasQuery` for `User` --- .../src/models/crate_owner_invitation.rs | 8 ++------ crates/crates_io_database/src/models/user.rs | 11 +++-------- crates/crates_io_database/src/models/version.rs | 9 ++------- src/bin/crates-admin/transfer_crates.rs | 6 ++---- src/controllers/crate_owner_invitation.rs | 3 +-- src/controllers/session.rs | 3 +-- src/controllers/user/other.rs | 5 ++--- 7 files changed, 13 insertions(+), 32 deletions(-) diff --git a/crates/crates_io_database/src/models/crate_owner_invitation.rs b/crates/crates_io_database/src/models/crate_owner_invitation.rs index 1b0773906d0..07be85e2961 100644 --- a/crates/crates_io_database/src/models/crate_owner_invitation.rs +++ b/crates/crates_io_database/src/models/crate_owner_invitation.rs @@ -5,7 +5,7 @@ use diesel_async::{AsyncConnection, AsyncPgConnection, RunQueryDsl}; use secrecy::SecretString; use crate::models::{CrateOwner, User}; -use crate::schema::{crate_owner_invitations, crates, users}; +use crate::schema::{crate_owner_invitations, crates}; #[derive(Debug)] pub enum NewCrateOwnerInvitationOutcome { @@ -106,11 +106,7 @@ impl CrateOwnerInvitation { } // Get the user and check if they have a verified email - let user: User = users::table - .find(self.invited_user_id) - .select(User::as_select()) - .first(conn) - .await?; + let user = User::query().find(self.invited_user_id).first(conn).await?; let verified_email = user.verified_email(conn).await?; if verified_email.is_none() { diff --git a/crates/crates_io_database/src/models/user.rs b/crates/crates_io_database/src/models/user.rs index 78f6ea3749e..bc4c862c017 100644 --- a/crates/crates_io_database/src/models/user.rs +++ b/crates/crates_io_database/src/models/user.rs @@ -12,7 +12,7 @@ use crate::schema::{crate_owners, emails, users}; use crates_io_diesel_helpers::lower; /// The model representing a row in the `users` database table. -#[derive(Clone, Debug, Queryable, Identifiable, Selectable, Serialize)] +#[derive(Clone, Debug, HasQuery, Identifiable, Serialize)] pub struct User { pub id: i32, pub name: Option, @@ -29,18 +29,13 @@ pub struct User { impl User { pub async fn find(conn: &mut AsyncPgConnection, id: i32) -> QueryResult { - users::table - .find(id) - .select(User::as_select()) - .first(conn) - .await + User::query().find(id).first(conn).await } pub async fn find_by_login(conn: &mut AsyncPgConnection, login: &str) -> QueryResult { - users::table + User::query() .filter(lower(users::gh_login).eq(login.to_lowercase())) .filter(users::gh_id.ne(-1)) - .select(User::as_select()) .order(users::gh_id.desc()) .first(conn) .await diff --git a/crates/crates_io_database/src/models/version.rs b/crates/crates_io_database/src/models/version.rs index 11c41d415fe..7d74a80d642 100644 --- a/crates/crates_io_database/src/models/version.rs +++ b/crates/crates_io_database/src/models/version.rs @@ -8,7 +8,7 @@ use diesel_async::{AsyncPgConnection, RunQueryDsl}; use serde::Deserialize; use crate::models::{Crate, TrustpubData, User}; -use crate::schema::{readme_renderings, users, versions}; +use crate::schema::{readme_renderings, versions}; #[derive(Clone, Identifiable, Associations, Debug, Queryable, Selectable)] #[diesel(belongs_to(Crate), belongs_to(crate::models::download::Version, foreign_key=id))] @@ -59,12 +59,7 @@ impl Version { /// Not for use when you have a group of versions you need the publishers for. pub async fn published_by(&self, conn: &mut AsyncPgConnection) -> QueryResult> { match self.published_by { - Some(pb) => users::table - .find(pb) - .select(User::as_select()) - .first(conn) - .await - .optional(), + Some(pb) => User::query().find(pb).first(conn).await.optional(), None => Ok(None), } } diff --git a/src/bin/crates-admin/transfer_crates.rs b/src/bin/crates-admin/transfer_crates.rs index 08423837fec..bfd291199cf 100644 --- a/src/bin/crates-admin/transfer_crates.rs +++ b/src/bin/crates-admin/transfer_crates.rs @@ -25,15 +25,13 @@ pub async fn run(opts: Opts) -> anyhow::Result<()> { } async fn transfer(opts: Opts, conn: &mut AsyncPgConnection) -> anyhow::Result<()> { - let from: User = users::table + let from: User = User::query() .filter(users::gh_login.eq(opts.from_user)) - .select(User::as_select()) .first(conn) .await?; - let to: User = users::table + let to: User = User::query() .filter(users::gh_login.eq(opts.to_user)) - .select(User::as_select()) .first(conn) .await?; diff --git a/src/controllers/crate_owner_invitation.rs b/src/controllers/crate_owner_invitation.rs index ac74f58255a..2aa1cd8b76e 100644 --- a/src/controllers/crate_owner_invitation.rs +++ b/src/controllers/crate_owner_invitation.rs @@ -266,9 +266,8 @@ async fn prepare_list( .filter(|id| !users.contains_key(id)) .collect::>(); if !missing_users.is_empty() { - let new_users: Vec = users::table + let new_users: Vec = User::query() .filter(users::id.eq_any(missing_users)) - .select(User::as_select()) .load(conn) .await?; for user in new_users.into_iter() { diff --git a/src/controllers/session.rs b/src/controllers/session.rs index 3aa7e76f225..bcecf3a280a 100644 --- a/src/controllers/session.rs +++ b/src/controllers/session.rs @@ -209,9 +209,8 @@ async fn create_or_update_user( } async fn find_user_by_gh_id(conn: &mut AsyncPgConnection, gh_id: i32) -> QueryResult> { - users::table + User::query() .filter(users::gh_id.eq(gh_id)) - .select(User::as_select()) .first(conn) .await .optional() diff --git a/src/controllers/user/other.rs b/src/controllers/user/other.rs index 34f179786ef..eedbc57ec8a 100644 --- a/src/controllers/user/other.rs +++ b/src/controllers/user/other.rs @@ -32,12 +32,11 @@ pub async fn find_user( ) -> AppResult> { let mut conn = state.db_read_prefer_primary().await?; - use crate::schema::users::dsl::{gh_login, id, users}; + use crate::schema::users::dsl::{gh_login, id}; let name = lower(&user_name); - let user: User = users + let user: User = User::query() .filter(lower(gh_login).eq(name)) - .select(User::as_select()) .order(id.desc()) .first(&mut conn) .await?; From 7da19258e8c42f2b4685b4e439cd0c80f204f90c Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 17 Oct 2025 16:37:31 +0200 Subject: [PATCH 09/28] database/models: Use `HasQuery` for `Email` --- crates/crates_io_database/src/models/email.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/crates_io_database/src/models/email.rs b/crates/crates_io_database/src/models/email.rs index d3a96cca132..459c208e45c 100644 --- a/crates/crates_io_database/src/models/email.rs +++ b/crates/crates_io_database/src/models/email.rs @@ -6,7 +6,7 @@ use secrecy::SecretString; use crate::models::User; use crate::schema::emails; -#[derive(Debug, Queryable, Identifiable, Selectable, Associations)] +#[derive(Debug, HasQuery, Identifiable, Associations)] #[diesel(belongs_to(User))] pub struct Email { pub id: i32, From 4d3fd013f768ddae33f3716722dbc85a1d5d3022 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 17 Oct 2025 16:47:05 +0200 Subject: [PATCH 10/28] database/models: Use `HasQuery` for `CrateOwnerInvitation` --- .../src/models/crate_owner_invitation.rs | 12 +++++------- src/controllers/crate_owner_invitation.rs | 5 ++--- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/crates/crates_io_database/src/models/crate_owner_invitation.rs b/crates/crates_io_database/src/models/crate_owner_invitation.rs index 07be85e2961..6c0c7c73645 100644 --- a/crates/crates_io_database/src/models/crate_owner_invitation.rs +++ b/crates/crates_io_database/src/models/crate_owner_invitation.rs @@ -58,7 +58,7 @@ impl NewCrateOwnerInvitation { } /// The model representing a row in the `crate_owner_invitations` database table. -#[derive(Clone, Debug, Identifiable, Queryable, Selectable)] +#[derive(Clone, Debug, Identifiable, HasQuery)] #[diesel(primary_key(invited_user_id, crate_id))] pub struct CrateOwnerInvitation { pub invited_user_id: i32, @@ -76,18 +76,16 @@ impl CrateOwnerInvitation { crate_id: i32, conn: &mut AsyncPgConnection, ) -> QueryResult { - crate_owner_invitations::table + CrateOwnerInvitation::query() .find((user_id, crate_id)) - .select(CrateOwnerInvitation::as_select()) - .first::(conn) + .first(conn) .await } pub async fn find_by_token(token: &str, conn: &mut AsyncPgConnection) -> QueryResult { - crate_owner_invitations::table + CrateOwnerInvitation::query() .filter(crate_owner_invitations::token.eq(token)) - .select(CrateOwnerInvitation::as_select()) - .first::(conn) + .first(conn) .await } diff --git a/src/controllers/crate_owner_invitation.rs b/src/controllers/crate_owner_invitation.rs index 2aa1cd8b76e..3b7079f6456 100644 --- a/src/controllers/crate_owner_invitation.rs +++ b/src/controllers/crate_owner_invitation.rs @@ -188,7 +188,7 @@ async fn prepare_list( // Load all the non-expired invitations matching the filter. let expire_cutoff = config.ownership_invitations_expiration; - let query = crate_owner_invitations::table + let query = CrateOwnerInvitation::query() .filter(sql_filter) .filter(crate_owner_invitations::created_at.gt((Utc::now() - expire_cutoff).naive_utc())) .order_by(( @@ -196,8 +196,7 @@ async fn prepare_list( crate_owner_invitations::invited_user_id, )) // We fetch one element over the page limit to then detect whether there is a next page. - .limit(pagination.per_page + 1) - .select(CrateOwnerInvitation::as_select()); + .limit(pagination.per_page + 1); // Load and paginate the results. let mut raw_invitations: Vec = match pagination.page { From bb6ad155661468173175671944516ac66ed5804c Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 17 Oct 2025 16:53:43 +0200 Subject: [PATCH 11/28] database/models: Use `HasQuery` for `VersionOwnerAction` --- crates/crates_io_database/src/models/action.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/crates_io_database/src/models/action.rs b/crates/crates_io_database/src/models/action.rs index c1b1990230f..3bb53632084 100644 --- a/crates/crates_io_database/src/models/action.rs +++ b/crates/crates_io_database/src/models/action.rs @@ -34,10 +34,9 @@ impl From for String { } } -#[derive(Debug, Clone, Copy, Queryable, Identifiable, Selectable, Associations)] +#[derive(Debug, Clone, Copy, HasQuery, Identifiable, Associations)] #[diesel( table_name = version_owner_actions, - check_for_backend(diesel::pg::Pg), belongs_to(Version), belongs_to(User, foreign_key = user_id), belongs_to(ApiToken, foreign_key = api_token_id), @@ -54,10 +53,7 @@ pub struct VersionOwnerAction { impl VersionOwnerAction { pub async fn all(conn: &mut AsyncPgConnection) -> QueryResult> { - version_owner_actions::table - .select(Self::as_select()) - .load(conn) - .await + Self::query().load(conn).await } pub fn by_version<'a>( From 86749446283d9cd57f66bfc0e2304af98f2fb0b7 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 17 Oct 2025 16:56:16 +0200 Subject: [PATCH 12/28] database/models: Use `HasQuery` for `Dependency` --- crates/crates_io_database/src/models/dependency.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/crates_io_database/src/models/dependency.rs b/crates/crates_io_database/src/models/dependency.rs index b84e722f7f9..a549eb2067a 100644 --- a/crates/crates_io_database/src/models/dependency.rs +++ b/crates/crates_io_database/src/models/dependency.rs @@ -5,10 +5,9 @@ use crates_io_index::DependencyKind as IndexDependencyKind; use diesel::prelude::*; use diesel::sql_types::{BigInt, Text}; -#[derive(Identifiable, Associations, Debug, Queryable, QueryableByName, Selectable)] +#[derive(Identifiable, Associations, Debug, HasQuery, QueryableByName)] #[diesel( table_name = dependencies, - check_for_backend(diesel::pg::Pg), belongs_to(Version), belongs_to(Crate), )] From 2469ec96ed80815d005f24c1750cea7148cafad2 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 17 Oct 2025 17:10:44 +0200 Subject: [PATCH 13/28] database/models: Use `HasQuery` for `Category` --- crates/crates_io_database/src/models/category.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/crates_io_database/src/models/category.rs b/crates/crates_io_database/src/models/category.rs index 2d4e50fe30b..7f2494e96cb 100644 --- a/crates/crates_io_database/src/models/category.rs +++ b/crates/crates_io_database/src/models/category.rs @@ -9,8 +9,8 @@ use diesel_async::{AsyncConnection, AsyncPgConnection, RunQueryDsl}; use futures_util::FutureExt; use futures_util::future::BoxFuture; -#[derive(Clone, Identifiable, Queryable, QueryableByName, Debug, Selectable)] -#[diesel(table_name = categories, check_for_backend(diesel::pg::Pg))] +#[derive(Clone, Identifiable, HasQuery, QueryableByName, Debug)] +#[diesel(table_name = categories)] pub struct Category { pub id: i32, pub category: String, @@ -53,9 +53,8 @@ impl Category { ) -> QueryResult> { conn.transaction(|conn| { async move { - let categories: Vec = categories::table + let categories: Vec = Category::query() .filter(categories::slug.eq_any(slugs)) - .select(Category::as_select()) .load(conn) .await?; From 66b9e4be477608fd7f6892cffc9584aab60eeeb2 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 17 Oct 2025 17:16:46 +0200 Subject: [PATCH 14/28] database/models: Use `HasQuery` for `Version` --- crates/crates_io_database/src/models/version.rs | 2 +- src/worker/jobs/downloads/update_metadata.rs | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/crates/crates_io_database/src/models/version.rs b/crates/crates_io_database/src/models/version.rs index 7d74a80d642..c9dbd59162e 100644 --- a/crates/crates_io_database/src/models/version.rs +++ b/crates/crates_io_database/src/models/version.rs @@ -10,7 +10,7 @@ use serde::Deserialize; use crate::models::{Crate, TrustpubData, User}; use crate::schema::{readme_renderings, versions}; -#[derive(Clone, Identifiable, Associations, Debug, Queryable, Selectable)] +#[derive(Clone, Identifiable, Associations, Debug, HasQuery)] #[diesel(belongs_to(Crate), belongs_to(crate::models::download::Version, foreign_key=id))] pub struct Version { pub id: i32, diff --git a/src/worker/jobs/downloads/update_metadata.rs b/src/worker/jobs/downloads/update_metadata.rs index 50c4d449848..a036a6050a4 100644 --- a/src/worker/jobs/downloads/update_metadata.rs +++ b/src/worker/jobs/downloads/update_metadata.rs @@ -290,9 +290,8 @@ mod tests { .await .unwrap(); - let version_before: Version = versions::table + let version_before: Version = Version::query() .find(version.id) - .select(Version::as_select()) .first(&mut conn) .await .unwrap(); @@ -304,9 +303,8 @@ mod tests { super::update(&mut conn).await.unwrap(); - let version2: Version = versions::table + let version2: Version = Version::query() .find(version.id) - .select(Version::as_select()) .first(&mut conn) .await .unwrap(); @@ -330,9 +328,8 @@ mod tests { super::update(&mut conn).await.unwrap(); - let version3: Version = versions::table + let version3: Version = Version::query() .find(version.id) - .select(Version::as_select()) .first(&mut conn) .await .unwrap(); From 1af702b4be82829f48935eb4beb906444f94f291 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 17 Oct 2025 17:25:08 +0200 Subject: [PATCH 15/28] database/models: Use `HasQuery` for `CrateName` --- crates/crates_io_database/src/models/krate.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/crates_io_database/src/models/krate.rs b/crates/crates_io_database/src/models/krate.rs index 23988ec4d20..fe27356250d 100644 --- a/crates/crates_io_database/src/models/krate.rs +++ b/crates/crates_io_database/src/models/krate.rs @@ -18,8 +18,8 @@ use tracing::instrument; use super::Team; -#[derive(Debug, Clone, Queryable, Selectable)] -#[diesel(table_name = crates, check_for_backend(diesel::pg::Pg))] +#[derive(Debug, Clone, HasQuery)] +#[diesel(table_name = crates)] pub struct CrateName { pub name: String, } From 75a55d9b9fe85676daa5ba094a97ffcb5e9b9a60 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 17 Oct 2025 17:36:01 +0200 Subject: [PATCH 16/28] database/models: Use `HasQuery` for `Version` --- crates/crates_io_database/src/models/download.rs | 2 +- src/controllers/krate/downloads.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/crates_io_database/src/models/download.rs b/crates/crates_io_database/src/models/download.rs index 285c7b58d98..d1a5b1d84f1 100644 --- a/crates/crates_io_database/src/models/download.rs +++ b/crates/crates_io_database/src/models/download.rs @@ -21,7 +21,7 @@ pub struct VersionDownload { /// This struct is used to load all versions of a crate from the database, /// without loading the additional data that is unnecessary for download version resolution. /// -#[derive(Queryable, Selectable, Identifiable)] +#[derive(HasQuery, Identifiable)] #[diesel(table_name = versions)] pub struct Version { pub id: i32, diff --git a/src/controllers/krate/downloads.rs b/src/controllers/krate/downloads.rs index 9a4a66741ab..82213582283 100644 --- a/src/controllers/krate/downloads.rs +++ b/src/controllers/krate/downloads.rs @@ -91,9 +91,8 @@ pub async fn get_crate_downloads( let crate_id: i32 = path.load_crate_id(&mut conn).await?; - let mut versions: Vec = versions::table + let mut versions: Vec = Version::query() .filter(versions::crate_id.eq(crate_id)) - .select(Version::as_select()) .load(&mut conn) .await?; From c30b1e60d42b84318d2fbdd8c1bcf7a77b7eae14 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 17 Oct 2025 17:42:37 +0200 Subject: [PATCH 17/28] database/models: Use `HasQuery` for default versions --- crates/crates_io_database/src/models/default_versions.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/crates_io_database/src/models/default_versions.rs b/crates/crates_io_database/src/models/default_versions.rs index 63ecce9f334..a52988d8e36 100644 --- a/crates/crates_io_database/src/models/default_versions.rs +++ b/crates/crates_io_database/src/models/default_versions.rs @@ -13,9 +13,8 @@ use tracing::{debug, instrument, warn}; /// It implements [Ord] in a way that sorts versions by the criteria specified /// in the [update_default_version] function documentation. The default version /// will be the "maximum" element in a sorted list of versions. -#[derive(Clone, Debug, PartialEq, Eq, Queryable, Selectable)] +#[derive(Clone, Debug, PartialEq, Eq, HasQuery)] #[diesel(table_name = versions)] -#[diesel(check_for_backend(diesel::pg::Pg))] pub struct Version { pub id: i32, #[diesel(deserialize_as = SemverVersion)] @@ -123,10 +122,9 @@ async fn calculate_default_version( use diesel::result::Error::NotFound; debug!("Loading all versions for the crate…"); - let versions = versions::table + let versions = Version::query() .filter(versions::crate_id.eq(crate_id)) - .select(Version::as_returning()) - .load::(conn) + .load(conn) .await?; debug!("Found {} versions", versions.len()); From 9b7e58f67cea0ef9fa1fcfd0ab0e07de10587c0a Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 17 Oct 2025 19:26:43 +0200 Subject: [PATCH 18/28] database/models: Use `HasQuery` for `Crate` --- crates/crates_io_database/src/models/krate.rs | 4 ++-- src/bin/crates-admin/transfer_crates.rs | 2 +- src/index.rs | 5 ++--- src/typosquat/database.rs | 2 +- src/worker/jobs/downloads/update_metadata.rs | 4 ++-- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/crates/crates_io_database/src/models/krate.rs b/crates/crates_io_database/src/models/krate.rs index fe27356250d..98a013f8881 100644 --- a/crates/crates_io_database/src/models/krate.rs +++ b/crates/crates_io_database/src/models/krate.rs @@ -24,8 +24,8 @@ pub struct CrateName { pub name: String, } -#[derive(Debug, Clone, Queryable, Identifiable, AsChangeset, Selectable, Serialize)] -#[diesel(table_name = crates, check_for_backend(diesel::pg::Pg))] +#[derive(Debug, Clone, Identifiable, AsChangeset, HasQuery, Serialize)] +#[diesel(table_name = crates)] pub struct Crate { pub id: i32, pub name: String, diff --git a/src/bin/crates-admin/transfer_crates.rs b/src/bin/crates-admin/transfer_crates.rs index bfd291199cf..a7c7d4f6d5a 100644 --- a/src/bin/crates-admin/transfer_crates.rs +++ b/src/bin/crates-admin/transfer_crates.rs @@ -60,7 +60,7 @@ async fn transfer(opts: Opts, conn: &mut AsyncPgConnection) -> anyhow::Result<() let crate_owners = crate_owners::table .filter(crate_owners::owner_id.eq(from.id)) .filter(crate_owners::owner_kind.eq(OwnerKind::User)); - let crates: Vec = Crate::all() + let crates: Vec = Crate::query() .filter(crates::id.eq_any(crate_owners.select(crate_owners::crate_id))) .load(conn) .await?; diff --git a/src/index.rs b/src/index.rs index 790c41438f7..ccc7da31edb 100644 --- a/src/index.rs +++ b/src/index.rs @@ -17,10 +17,9 @@ pub async fn get_index_data( conn: &mut AsyncPgConnection, ) -> anyhow::Result> { debug!("Looking up crate by name"); - let krate = crates::table - .select(Crate::as_select()) + let krate = Crate::query() .filter(crates::name.eq(name)) - .first::(conn) + .first(conn) .await .optional(); diff --git a/src/typosquat/database.rs b/src/typosquat/database.rs index ffb00a82e94..be0b7ba0807 100644 --- a/src/typosquat/database.rs +++ b/src/typosquat/database.rs @@ -39,7 +39,7 @@ impl TopCrates { // data structure. let crates: BTreeMap = BTreeMap::new(); - let crates = models::Crate::all() + let crates = models::Crate::query() .inner_join(crate_downloads::table) .order(crate_downloads::downloads.desc()) .limit(num) diff --git a/src/worker/jobs/downloads/update_metadata.rs b/src/worker/jobs/downloads/update_metadata.rs index a036a6050a4..c625701ed84 100644 --- a/src/worker/jobs/downloads/update_metadata.rs +++ b/src/worker/jobs/downloads/update_metadata.rs @@ -295,7 +295,7 @@ mod tests { .first(&mut conn) .await .unwrap(); - let krate_before: Crate = Crate::all() + let krate_before: Crate = Crate::query() .filter(crates::id.eq(krate.id)) .first(&mut conn) .await @@ -311,7 +311,7 @@ mod tests { assert_eq!(version2.downloads, 2); assert_eq!(version2.updated_at, version_before.updated_at); - let krate2: Crate = Crate::all() + let krate2: Crate = Crate::query() .filter(crates::id.eq(krate.id)) .first(&mut conn) .await From 67ecdf33c100087cb83811a5b6866600625b2b3c Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 17 Oct 2025 18:14:51 +0200 Subject: [PATCH 19/28] controllers/summary: Use `HasQuery` for `Record` --- src/controllers/summary.rs | 39 +++++++++++++------------------------- 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/src/controllers/summary.rs b/src/controllers/summary.rs index 0f68f69642c..fb14a898c1a 100644 --- a/src/controllers/summary.rs +++ b/src/controllers/summary.rs @@ -71,46 +71,27 @@ pub async fn get_summary(state: AppState) -> AppResult> { .select(metadata::total_downloads) .get_result(&mut conn) .boxed(), - crates::table - .inner_join(crate_downloads::table) - .left_join(recent_crate_downloads::table) - .left_join(default_versions::table) - .left_join(versions::table.on(default_versions::version_id.eq(versions::id))) + Record::query() .order(crates::created_at.desc()) - .select(Record::as_select()) .limit(10) .load(&mut conn) .boxed(), - crates::table - .inner_join(crate_downloads::table) - .left_join(recent_crate_downloads::table) - .left_join(default_versions::table) - .left_join(versions::table.on(default_versions::version_id.eq(versions::id))) + Record::query() .filter(crates::updated_at.ne(crates::created_at)) .order(crates::updated_at.desc()) - .select(Record::as_select()) .limit(10) .load(&mut conn) .boxed(), - crates::table - .inner_join(crate_downloads::table) - .left_join(recent_crate_downloads::table) - .left_join(default_versions::table) - .left_join(versions::table.on(default_versions::version_id.eq(versions::id))) + Record::query() .filter(crates::name.ne_all(&config.excluded_crate_names)) .then_order_by(crate_downloads::downloads.desc()) - .select(Record::as_select()) .limit(10) .load(&mut conn) .boxed(), - crates::table - .inner_join(crate_downloads::table) - .inner_join(recent_crate_downloads::table) - .left_join(default_versions::table) - .left_join(versions::table.on(default_versions::version_id.eq(versions::id))) + Record::query() .filter(crates::name.ne_all(&config.excluded_crate_names)) + .filter(recent_crate_downloads::downloads.is_not_null()) .then_order_by(recent_crate_downloads::downloads.desc()) - .select(Record::as_select()) .limit(10) .load(&mut conn) .boxed(), @@ -144,8 +125,14 @@ pub async fn get_summary(state: AppState) -> AppResult> { })) } -#[derive(Debug, Queryable, Selectable)] -#[diesel(check_for_backend(diesel::pg::Pg))] +#[derive(Debug, HasQuery)] +#[diesel( + base_query = crates::table + .inner_join(crate_downloads::table) + .left_join(recent_crate_downloads::table) + .left_join(default_versions::table) + .left_join(versions::table.on(default_versions::version_id.eq(versions::id))) +)] struct Record { #[diesel(embed)] krate: Crate, From c54507820c1efa6a2772bc94b3da6002625b2cc8 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 17 Oct 2025 18:20:48 +0200 Subject: [PATCH 20/28] controllers/admin: Use `HasQuery` for `DatabaseCrateInfo` --- src/controllers/admin.rs | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/controllers/admin.rs b/src/controllers/admin.rs index f75e350ccec..f3637bda537 100644 --- a/src/controllers/admin.rs +++ b/src/controllers/admin.rs @@ -1,7 +1,7 @@ use crate::{ app::AppState, auth::AuthCheck, - models::{CrateOwner, OwnerKind, User}, + models::{OwnerKind, User}, schema::*, util::errors::{AppResult, custom}, }; @@ -12,8 +12,17 @@ use diesel_async::RunQueryDsl; use http::{StatusCode, request::Parts}; use serde::Serialize; -#[derive(Debug, Queryable, Selectable)] -#[diesel(check_for_backend(diesel::pg::Pg))] +#[derive(Debug, HasQuery)] +#[diesel( + base_query = crate_owners::table + .inner_join(crates::table) + .left_join(crate_downloads::table.on(crates::id.eq(crate_downloads::crate_id))) + .left_join( + recent_crate_downloads::table.on(crates::id.eq(recent_crate_downloads::crate_id)), + ) + .inner_join(default_versions::table.on(crates::id.eq(default_versions::crate_id))) + .inner_join(versions::table.on(default_versions::version_id.eq(versions::id))) +)] struct DatabaseCrateInfo { #[diesel(select_expression = crates::columns::name)] name: String, @@ -78,16 +87,10 @@ pub async fn list( .first::<(User, Option, Option)>(&mut conn) .await?; - let crates: Vec = CrateOwner::by_owner_kind(OwnerKind::User) - .inner_join(crates::table) - .left_join(crate_downloads::table.on(crates::id.eq(crate_downloads::crate_id))) - .left_join( - recent_crate_downloads::table.on(crates::id.eq(recent_crate_downloads::crate_id)), - ) - .inner_join(default_versions::table.on(crates::id.eq(default_versions::crate_id))) - .inner_join(versions::table.on(default_versions::version_id.eq(versions::id))) + let crates: Vec = DatabaseCrateInfo::query() + .filter(crate_owners::deleted.eq(false)) + .filter(crate_owners::owner_kind.eq(OwnerKind::User)) .filter(crate_owners::owner_id.eq(user.id)) - .select(DatabaseCrateInfo::as_select()) .order(crates::name.asc()) .load(&mut conn) .await?; From 2d186385684612d09fdd025fa88588418e7e3e9b Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 17 Oct 2025 17:46:35 +0200 Subject: [PATCH 21/28] jobs/storage: Use `HasQuery` for `BackgroundJob` --- crates/crates_io_worker/src/storage.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/crates_io_worker/src/storage.rs b/crates/crates_io_worker/src/storage.rs index 2e927dfed81..0df60d9db2e 100644 --- a/crates/crates_io_worker/src/storage.rs +++ b/crates/crates_io_worker/src/storage.rs @@ -6,7 +6,7 @@ use diesel::sql_types::{Bool, Integer, Interval}; use diesel::{delete, update}; use diesel_async::{AsyncPgConnection, RunQueryDsl}; -#[derive(Queryable, Selectable, Identifiable, Debug, Clone)] +#[derive(HasQuery, Identifiable, Debug, Clone)] pub(super) struct BackgroundJob { pub(super) id: i64, pub(super) job_type: String, @@ -30,14 +30,13 @@ pub(super) async fn find_next_unlocked_job( conn: &mut AsyncPgConnection, job_types: &[String], ) -> QueryResult { - background_jobs::table - .select(BackgroundJob::as_select()) + BackgroundJob::query() .filter(background_jobs::job_type.eq_any(job_types)) .filter(retriable()) .order((background_jobs::priority.desc(), background_jobs::id)) .for_update() .skip_locked() - .first::(conn) + .first(conn) .await } From 80c05a6f378e3786ba905353edc2fbea5726a030 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 17 Oct 2025 17:52:59 +0200 Subject: [PATCH 22/28] jobs/send_publish_notifications: Use `HasQuery` for `PublishDetails` --- src/worker/jobs/send_publish_notifications.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/worker/jobs/send_publish_notifications.rs b/src/worker/jobs/send_publish_notifications.rs index b945456e7dd..689d59f9b4e 100644 --- a/src/worker/jobs/send_publish_notifications.rs +++ b/src/worker/jobs/send_publish_notifications.rs @@ -156,7 +156,12 @@ impl BackgroundJob for SendPublishNotificationsJob { } } -#[derive(Debug, Queryable, Selectable)] +#[derive(Debug, HasQuery)] +#[diesel( + base_query = versions::table + .inner_join(crates::table) + .left_join(users::table), +)] struct PublishDetails { #[diesel(select_expression = crates::columns::id)] crate_id: i32, @@ -177,11 +182,8 @@ impl PublishDetails { version_id: i32, conn: &mut AsyncPgConnection, ) -> QueryResult> { - versions::table - .find(version_id) - .inner_join(crates::table) - .left_join(users::table) - .select(PublishDetails::as_select()) + PublishDetails::query() + .filter(versions::id.eq(version_id)) .first(conn) .await .optional() From 21ddf71c1a7f2faf3e8b09fcc509fb1b56832a61 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 17 Oct 2025 17:57:43 +0200 Subject: [PATCH 23/28] jobs/generate_og_image: Use `HasQuery` for `QueryRow` --- src/worker/jobs/generate_og_image.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/worker/jobs/generate_og_image.rs b/src/worker/jobs/generate_og_image.rs index d82876f9420..b93df2976b1 100644 --- a/src/worker/jobs/generate_og_image.rs +++ b/src/worker/jobs/generate_og_image.rs @@ -133,8 +133,12 @@ impl BackgroundJob for GenerateOgImage { } } -#[derive(Queryable, Selectable)] -#[diesel(check_for_backend(diesel::pg::Pg))] +#[derive(HasQuery)] +#[diesel( + base_query = crates::table + .inner_join(default_versions::table) + .inner_join(versions::table.on(default_versions::version_id.eq(versions::id))) +)] struct QueryRow { #[diesel(select_expression = crates::id)] _crate_id: i32, @@ -171,11 +175,8 @@ async fn fetch_crate_data( crate_name: &str, conn: &mut AsyncPgConnection, ) -> QueryResult> { - crates::table - .inner_join(default_versions::table) - .inner_join(versions::table.on(default_versions::version_id.eq(versions::id))) + QueryRow::query() .filter(crates::name.eq(crate_name)) - .select(QueryRow::as_select()) .first(conn) .await .optional() From 8b9c76b58e130a988e95db9106605060fb55048b Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 17 Oct 2025 18:01:19 +0200 Subject: [PATCH 24/28] jobs/rss: Use `HasQuery` for `VersionUpdate` --- src/worker/jobs/rss/sync_updates_feed.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/worker/jobs/rss/sync_updates_feed.rs b/src/worker/jobs/rss/sync_updates_feed.rs index a63f055209a..2ec9c1bb40f 100644 --- a/src/worker/jobs/rss/sync_updates_feed.rs +++ b/src/worker/jobs/rss/sync_updates_feed.rs @@ -85,11 +85,9 @@ impl BackgroundJob for SyncUpdatesFeed { async fn load_version_updates(conn: &mut AsyncPgConnection) -> QueryResult> { let threshold_dt = chrono::Utc::now().naive_utc() - ALWAYS_INCLUDE_AGE; - let updates = versions::table - .inner_join(crates::table) + let updates = VersionUpdate::query() .filter(versions::created_at.gt(threshold_dt)) .order(versions::created_at.desc()) - .select(VersionUpdate::as_select()) .load(conn) .await?; @@ -98,17 +96,15 @@ async fn load_version_updates(conn: &mut AsyncPgConnection) -> QueryResult Date: Fri, 17 Oct 2025 18:04:59 +0200 Subject: [PATCH 25/28] jobs/rss: Use `HasQuery` for `NewCrate` --- src/worker/jobs/rss/sync_crates_feed.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/worker/jobs/rss/sync_crates_feed.rs b/src/worker/jobs/rss/sync_crates_feed.rs index 92a377f7147..32304b4a96b 100644 --- a/src/worker/jobs/rss/sync_crates_feed.rs +++ b/src/worker/jobs/rss/sync_crates_feed.rs @@ -85,10 +85,9 @@ impl BackgroundJob for SyncCratesFeed { async fn load_new_crates(conn: &mut AsyncPgConnection) -> QueryResult> { let threshold_dt = chrono::Utc::now().naive_utc() - ALWAYS_INCLUDE_AGE; - let new_crates = crates::table + let new_crates = NewCrate::query() .filter(crates::created_at.gt(threshold_dt)) .order(crates::created_at.desc()) - .select(NewCrate::as_select()) .load(conn) .await?; @@ -97,16 +96,15 @@ async fn load_new_crates(conn: &mut AsyncPgConnection) -> QueryResult Date: Fri, 17 Oct 2025 18:10:35 +0200 Subject: [PATCH 26/28] jobs/rss: Use `HasQuery` for `VersionUpdate` --- src/worker/jobs/rss/sync_crate_feed.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/worker/jobs/rss/sync_crate_feed.rs b/src/worker/jobs/rss/sync_crate_feed.rs index 8d06df6a470..6e072de1645 100644 --- a/src/worker/jobs/rss/sync_crate_feed.rs +++ b/src/worker/jobs/rss/sync_crate_feed.rs @@ -101,12 +101,10 @@ async fn load_version_updates( ) -> QueryResult> { let threshold_dt = chrono::Utc::now().naive_utc() - ALWAYS_INCLUDE_AGE; - let updates = versions::table - .inner_join(crates::table) + let updates = VersionUpdate::query() .filter(crates::name.eq(name)) .filter(versions::created_at.gt(threshold_dt)) .order(versions::created_at.desc()) - .select(VersionUpdate::as_select()) .load(conn) .await?; @@ -115,18 +113,16 @@ async fn load_version_updates( return Ok(updates); } - versions::table - .inner_join(crates::table) + VersionUpdate::query() .filter(crates::name.eq(name)) .order(versions::created_at.desc()) - .select(VersionUpdate::as_select()) .limit(NUM_ITEMS) .load(conn) .await } -#[derive(Debug, Queryable, Selectable)] -#[diesel(check_for_backend(diesel::pg::Pg))] +#[derive(Debug, HasQuery)] +#[diesel(base_query = versions::table.inner_join(crates::table))] struct VersionUpdate { #[diesel(select_expression = versions::columns::num)] version: String, From b960683e080f4da92faafac119859f12d4557e86 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 17 Oct 2025 18:24:00 +0200 Subject: [PATCH 27/28] crates-admin: Use `HasQuery` for `CrateInfo` --- src/bin/crates-admin/delete_crate.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/bin/crates-admin/delete_crate.rs b/src/bin/crates-admin/delete_crate.rs index 1a1c89a5dc6..f5a28b12453 100644 --- a/src/bin/crates-admin/delete_crate.rs +++ b/src/bin/crates-admin/delete_crate.rs @@ -54,11 +54,9 @@ pub async fn run(opts: Opts) -> anyhow::Result<()> { let mut crate_names = opts.crate_names; crate_names.sort(); - let existing_crates = crates::table - .inner_join(crate_downloads::table) + let existing_crates = CrateInfo::query() .filter(crates::name.eq_any(&crate_names)) - .select(CrateInfo::as_select()) - .load::(&mut conn) + .load(&mut conn) .await .context("Failed to look up crate name from the database")?; @@ -141,8 +139,11 @@ async fn delete_from_database( Ok(()) } -#[derive(Debug, Clone, Queryable, Selectable)] -#[diesel(check_for_backend(diesel::pg::Pg))] +#[derive(Debug, Clone, HasQuery)] +#[diesel( + base_query = crates::table + .inner_join(crate_downloads::table) +)] struct CrateInfo { #[diesel(select_expression = crates::columns::name)] name: String, From a787c181995eef8fd25aff8fdb3073169cb8e1ed Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Fri, 17 Oct 2025 18:28:28 +0200 Subject: [PATCH 28/28] rate_limiter: Use `HasQuery` for `Bucket` --- src/rate_limiter.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rate_limiter.rs b/src/rate_limiter.rs index fec89261808..b00f1331eef 100644 --- a/src/rate_limiter.rs +++ b/src/rate_limiter.rs @@ -154,7 +154,7 @@ impl RateLimiter { publish_limit_buckets::last_refill.eq(publish_limit_buckets::last_refill + refill_rate.into_sql::() * tokens_to_add), )) - .returning(Bucket::as_select()) + .returning(Bucket::as_returning()) .get_result(conn) .await } @@ -171,8 +171,8 @@ impl RateLimiter { } } -#[derive(Queryable, Insertable, Selectable, Debug, PartialEq, Clone, Copy)] -#[diesel(table_name = publish_limit_buckets, check_for_backend(diesel::pg::Pg))] +#[derive(HasQuery, Insertable, Debug, PartialEq, Clone, Copy)] +#[diesel(table_name = publish_limit_buckets)] #[allow(dead_code)] // Most fields only read in tests struct Bucket { user_id: i32,