From caea5c62692b226de15e53a68eda7b21b3ac571d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Wed, 14 Jan 2026 20:33:19 +0100 Subject: [PATCH 1/6] Unify functions for updating a Build in the DB --- src/bors/build.rs | 4 +-- src/bors/build_queue.rs | 19 ++++++++++++--- src/bors/handlers/trybuild.rs | 9 ++++--- src/bors/merge_queue.rs | 12 ++++++--- src/database/client.rs | 29 +++++++++------------- src/database/mod.rs | 28 +++++++++++++++++++++ src/database/operations.rs | 46 +++++++++++++---------------------- 7 files changed, 88 insertions(+), 59 deletions(-) diff --git a/src/bors/build.rs b/src/bors/build.rs index 84b1cd5c..0de823c0 100644 --- a/src/bors/build.rs +++ b/src/bors/build.rs @@ -1,6 +1,6 @@ use crate::PgDbClient; use crate::bors::{RepositoryState, WorkflowRun}; -use crate::database::{BuildModel, BuildStatus, WorkflowModel}; +use crate::database::{BuildModel, BuildStatus, UpdateBuildParams, WorkflowModel}; use crate::github::CommitSha; use crate::github::api::client::GithubRepositoryClient; use octocrab::models::CheckRunId; @@ -50,7 +50,7 @@ pub async fn cancel_build( CancelBuildConclusion::Timeout => BuildStatus::Timeouted, CancelBuildConclusion::Cancel => BuildStatus::Cancelled, }; - db.update_build_column(build, status, None) + db.update_build(build.id, UpdateBuildParams::default().status(status)) .await .map_err(CancelBuildError::FailedToMarkBuildAsCancelled)?; diff --git a/src/bors/build_queue.rs b/src/bors/build_queue.rs index 264864f0..f6d8bfb8 100644 --- a/src/bors/build_queue.rs +++ b/src/bors/build_queue.rs @@ -21,7 +21,9 @@ use crate::bors::merge_queue::MergeQueueSender; use crate::bors::{ BuildKind, FailedWorkflowRun, RepositoryState, elapsed_time_since, hide_tagged_comments, }; -use crate::database::{BuildModel, BuildStatus, PullRequestModel, WorkflowStatus}; +use crate::database::{ + BuildModel, BuildStatus, PullRequestModel, UpdateBuildParams, WorkflowStatus, +}; use crate::github::{CommitSha, GithubRepoName, LabelTrigger}; use crate::{BorsContext, PgDbClient}; use anyhow::Context; @@ -121,8 +123,11 @@ pub async fn handle_build_queue_event( tracing::warn!( "Detected orphaned pending without a PR, marking it as timeouted: {build:?}" ); - db.update_build_column(&build, BuildStatus::Timeouted, None) - .await?; + db.update_build( + build.id, + UpdateBuildParams::default().status(BuildStatus::Timeouted), + ) + .await?; } anyhow::Ok(()) }; @@ -309,7 +314,13 @@ async fn maybe_complete_build( }), }; - db.update_build_column(build, status, running_time).await?; + db.update_build( + build.id, + UpdateBuildParams::default() + .status(status) + .duration(running_time), + ) + .await?; if let Some(trigger) = trigger { let pr = repo.client.get_pull_request(pr_num).await?; handle_label_trigger(repo, &pr, trigger).await?; diff --git a/src/bors/handlers/trybuild.rs b/src/bors/handlers/trybuild.rs index 16b00067..8e6aecec 100644 --- a/src/bors/handlers/trybuild.rs +++ b/src/bors/handlers/trybuild.rs @@ -15,7 +15,9 @@ use crate::bors::{ MergeType, RepositoryState, TRY_BRANCH_NAME, bors_commit_author, create_merge_commit_message, hide_tagged_comments, }; -use crate::database::{BuildModel, BuildStatus, ExclusiveOperationOutcome, PullRequestModel}; +use crate::database::{ + BuildModel, BuildStatus, ExclusiveOperationOutcome, PullRequestModel, UpdateBuildParams, +}; use crate::github::api::client::{CheckRunOutput, GithubRepositoryClient}; use crate::github::api::operations::ForcePush; use crate::github::{CommitSha, GithubUser, PullRequestNumber}; @@ -139,9 +141,10 @@ pub(super) async fn command_try_build( .await { Ok(check_run) => { - db.update_build_check_run_id( + db.update_build( build_id, - check_run.id.into_inner() as i64, + UpdateBuildParams::default() + .check_run_id(check_run.id.into_inner() as i64), ) .await?; } diff --git a/src/bors/merge_queue.rs b/src/bors/merge_queue.rs index 06115782..48cdc712 100644 --- a/src/bors/merge_queue.rs +++ b/src/bors/merge_queue.rs @@ -18,7 +18,7 @@ use crate::bors::mergeability_queue::{MergeabilityQueueSender, update_pr_with_kn use crate::bors::{AUTO_BRANCH_NAME, PullRequestStatus, RepositoryState}; use crate::database::{ ApprovalInfo, BuildModel, BuildStatus, ExclusiveLockProof, ExclusiveOperationOutcome, - MergeableState, PullRequestModel, QueueStatus, + MergeableState, PullRequestModel, QueueStatus, UpdateBuildParams, }; use crate::github::api::client::CheckRunOutput; use crate::github::api::operations::{BranchUpdateError, ForcePush}; @@ -268,7 +268,10 @@ async fn handle_successful_build( if let Some(error_comment) = error_comment { ctx.db - .update_build_column(auto_build, BuildStatus::Failure, None) + .update_build( + auto_build.id, + UpdateBuildParams::default().status(BuildStatus::Failure), + ) .await?; repo.client .post_comment(pr_num, error_comment, &ctx.db) @@ -593,7 +596,10 @@ async fn start_auto_build( if let Err(error) = ctx .db - .update_build_check_run_id(build_id, id.into_inner() as i64) + .update_build( + build_id, + UpdateBuildParams::default().check_run_id(id.into_inner() as i64), + ) .await { tracing::error!("Failed to update database with build check run id {id}: {error:?}",) diff --git a/src/database/client.rs b/src/database/client.rs index ad80e314..69167363 100644 --- a/src/database/client.rs +++ b/src/database/client.rs @@ -6,21 +6,23 @@ use super::operations::{ get_workflows_for_build, insert_repo_if_not_exists, record_tagged_bot_comment, set_pr_assignees, set_pr_mergeability_state, set_pr_priority, set_pr_rollup, set_pr_status, set_stale_mergeability_status_by_base_branch, unapprove_pull_request, undelegate_pull_request, - update_build_check_run_id, update_build_column, update_pr_try_build_id, update_workflow_status, - upsert_pull_request, upsert_repository, + update_build, update_pr_try_build_id, update_workflow_status, upsert_pull_request, + upsert_repository, +}; +use super::{ + ApprovalInfo, DelegatedPermission, MergeableState, PrimaryKey, RunId, UpdateBuildParams, + UpsertPullRequestParams, }; -use super::{ApprovalInfo, DelegatedPermission, MergeableState, RunId, UpsertPullRequestParams}; use crate::bors::comment::CommentTag; use crate::bors::{BuildKind, PullRequestStatus, RollupMode}; use crate::database::operations::update_pr_auto_build_id; use crate::database::{ - BuildModel, BuildStatus, CommentModel, PullRequestModel, RepoModel, TreeState, WorkflowModel, + BuildModel, CommentModel, PullRequestModel, RepoModel, TreeState, WorkflowModel, WorkflowStatus, WorkflowType, }; use crate::github::PullRequestNumber; use crate::github::{CommitSha, GithubRepoName}; use anyhow::Context; -use chrono::Duration; use itertools::Either; use sqlx::PgPool; use sqlx::postgres::PgAdvisoryLock; @@ -257,21 +259,12 @@ impl PgDbClient { get_pending_builds(&self.pool, repo).await } - pub async fn update_build_column( - &self, - build: &BuildModel, - status: BuildStatus, - duration: Option, - ) -> anyhow::Result<()> { - update_build_column(&self.pool, build.id, status, duration).await - } - - pub async fn update_build_check_run_id( + pub async fn update_build( &self, - build_id: i32, - check_run_id: i64, + build_id: PrimaryKey, + params: UpdateBuildParams, ) -> anyhow::Result<()> { - update_build_check_run_id(&self.pool, build_id, check_run_id).await + update_build(&self.pool, build_id, params).await } pub async fn create_workflow( diff --git a/src/database/mod.rs b/src/database/mod.rs index 0e90eb57..ceba445b 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -650,6 +650,34 @@ impl From for UpsertPullRequestParams { } } +/// Updates the build table with the given fields. +/// If `None`, the given column will not be updated. +/// In other words, none of those columns can be set to `None` after being set at least once. +#[derive(Debug, Default)] +pub struct UpdateBuildParams { + status: Option, + duration: Option, + check_run_id: Option, +} + +impl UpdateBuildParams { + pub fn status(self, status: BuildStatus) -> Self { + Self { + status: Some(status), + ..self + } + } + pub fn check_run_id(self, check_run_id: i64) -> Self { + Self { + check_run_id: Some(check_run_id), + ..self + } + } + pub fn duration(self, duration: Option) -> Self { + Self { duration, ..self } + } +} + /// Represents a repository configuration. pub struct RepoModel { pub id: PrimaryKey, diff --git a/src/database/operations.rs b/src/database/operations.rs index 81dee193..ef7ab99f 100644 --- a/src/database/operations.rs +++ b/src/database/operations.rs @@ -1,8 +1,7 @@ +use chrono::DateTime; use chrono::Utc; -use chrono::{DateTime, Duration}; use sqlx::postgres::PgExecutor; -use super::ApprovalInfo; use super::ApprovalStatus; use super::Assignees; use super::BuildModel; @@ -15,6 +14,7 @@ use super::TreeState; use super::UpsertPullRequestParams; use super::WorkflowStatus; use super::WorkflowType; +use super::{ApprovalInfo, PrimaryKey, UpdateBuildParams}; use crate::bors::PullRequestStatus; use crate::bors::RollupMode; use crate::bors::comment::CommentTag; @@ -646,23 +646,29 @@ WHERE repository = $1 .await } -pub(crate) async fn update_build_column( +pub(crate) async fn update_build( executor: impl PgExecutor<'_>, - build_id: i32, - status: BuildStatus, - duration: Option, + build_id: PrimaryKey, + params: UpdateBuildParams, ) -> anyhow::Result<()> { - measure_db_query("update_build_column", || async { + let UpdateBuildParams { + status, + duration, + check_run_id, + } = params; + measure_db_query("update_build", || async { sqlx::query!( r#" UPDATE build SET - status = $1, - duration = COALESCE($2, duration) -WHERE id = $3 + status = COALESCE($1, status), + duration = COALESCE($2, duration), + check_run_id = COALESCE($3, check_run_id) +WHERE id = $4 "#, - status as BuildStatus, + status as Option, duration as Option<_>, + check_run_id as Option, build_id ) .execute(executor) @@ -996,24 +1002,6 @@ pub(crate) async fn upsert_repository( .await } -pub(crate) async fn update_build_check_run_id( - executor: impl PgExecutor<'_>, - build_id: i32, - check_run_id: i64, -) -> anyhow::Result<()> { - measure_db_query("update_build_check_run_id", || async { - sqlx::query!( - "UPDATE build SET check_run_id = $1 WHERE id = $2", - check_run_id, - build_id - ) - .execute(executor) - .await?; - Ok(()) - }) - .await -} - pub(crate) async fn get_tagged_bot_comments( executor: impl PgExecutor<'_>, repo: &GithubRepoName, From 11d6ab8a3bfcebea6e68715fa470e48b337818e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Wed, 14 Jan 2026 20:50:30 +0100 Subject: [PATCH 2/6] Compute build duration from GitHub workflows Rather than from the WorkflowRunCompleted webhook. So that we can compute the duration even if the build queue finishes a build without a webhook. --- src/bors/build.rs | 6 ++++++ src/bors/build_queue.rs | 27 ++++++++++++++------------- src/bors/mod.rs | 2 ++ src/database/mod.rs | 11 +++++++---- src/github/api/client.rs | 12 ++++++++++++ 5 files changed, 41 insertions(+), 17 deletions(-) diff --git a/src/bors/build.rs b/src/bors/build.rs index 0de823c0..a441ae58 100644 --- a/src/bors/build.rs +++ b/src/bors/build.rs @@ -156,6 +156,12 @@ pub async fn load_workflow_runs( name: db_run.name, url: db_run.url, status: db_run.status, + // This is not really the time the workflow started running, but rather when we + // inserted it into the DB. But that hopefully should not matter, as the duration is + // `None` and this should never occur anyway :) + created_at: db_run.created_at, + // We currently do not store workflow duration in the DB + duration: None, }); } } diff --git a/src/bors/build_queue.rs b/src/bors/build_queue.rs index f6d8bfb8..23181685 100644 --- a/src/bors/build_queue.rs +++ b/src/bors/build_queue.rs @@ -97,16 +97,8 @@ pub async fn handle_build_queue_event( // First try to complete builds, and only then timeout then // Because if the bot was offline for some time, we want to first attempt to // actually finish the build, otherwise it might get instantly timeouted. - if !maybe_complete_build( - &repo, - db, - &build, - &pr, - &merge_queue_tx, - None, - None, - ) - .await? + if !maybe_complete_build(&repo, db, &build, &pr, &merge_queue_tx, None) + .await? { maybe_timeout_build(&repo, db, &build, &pr, timeout).await?; } @@ -161,7 +153,6 @@ pub async fn handle_build_queue_event( &pr, &merge_queue_tx, Some(CompletionTrigger { error_context }), - event.running_time, ) .await?; } @@ -233,7 +224,6 @@ async fn maybe_complete_build( pr: &PullRequestModel, merge_queue_tx: &MergeQueueSender, completion_trigger: Option, - running_time: Option, ) -> anyhow::Result { assert_eq!( build.status, @@ -314,11 +304,22 @@ async fn maybe_complete_build( }), }; + let compute_duration = || { + // Compute the time when the earliest workflow started, and when the latest workflow ended + let start = workflow_runs.iter().map(|run| run.created_at).min()?; + let end = workflow_runs + .iter() + .filter_map(|run| run.duration.map(|d| run.created_at + d)) + .max()?; + + // The build duration is the difference between those two + (end - start).to_std().ok() + }; db.update_build( build.id, UpdateBuildParams::default() .status(status) - .duration(running_time), + .duration(compute_duration()), ) .await?; if let Some(trigger) = trigger { diff --git a/src/bors/mod.rs b/src/bors/mod.rs index e462c3af..ee7d9514 100644 --- a/src/bors/mod.rs +++ b/src/bors/mod.rs @@ -185,6 +185,8 @@ pub struct WorkflowRun { pub name: String, pub url: String, pub status: WorkflowStatus, + pub created_at: DateTime, + pub duration: Option, } pub struct FailedWorkflowRun { diff --git a/src/database/mod.rs b/src/database/mod.rs index ceba445b..fb7442b8 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -10,11 +10,12 @@ use crate::{ bors::{PullRequestStatus, RollupMode}, github::{GithubRepoName, PullRequest, PullRequestNumber}, }; -use chrono::{DateTime, Duration, Utc}; +use chrono::{DateTime, Utc}; pub use client::{ExclusiveLockProof, ExclusiveOperationOutcome, PgDbClient}; pub use octocrab::models::pulls::MergeableState as OctocrabMergeableState; use sqlx::error::BoxDynError; use sqlx::{Database, Postgres, postgres::types::PgInterval}; +use std::time::Duration; mod client; pub(crate) mod operations; @@ -405,10 +406,12 @@ impl sqlx::Encode<'_, sqlx::Postgres> for PgDuration { impl sqlx::Decode<'_, sqlx::Postgres> for PgDuration { fn decode(value: sqlx::postgres::PgValueRef<'_>) -> Result { let interval = PgInterval::decode(value)?; - let days_us = interval.days as i64 * 86_400_000_000; - let total_us = interval.microseconds + days_us; + assert!(interval.days >= 0); + assert!(interval.microseconds >= 0); + let days_us = interval.days as u64 * 86_400_000_000; + let total_us = interval.microseconds as u64 + days_us; - Ok(PgDuration(Duration::microseconds(total_us))) + Ok(PgDuration(Duration::from_micros(total_us))) } } diff --git a/src/github/api/client.rs b/src/github/api/client.rs index 44c4ebe5..4657318e 100644 --- a/src/github/api/client.rs +++ b/src/github/api/client.rs @@ -374,11 +374,23 @@ impl GithubRepositoryClient { let mut stream = std::pin::pin!(response.into_stream(&self.client)); while let Some(run) = stream.try_next().await? { let status = get_status(&run); + let duration = match status { + WorkflowStatus::Pending => None, + WorkflowStatus::Success | + WorkflowStatus::Failure => { + let start = run.created_at; + let end = run.updated_at; + (end - start).to_std().ok() + } + }; + let run = WorkflowRun { id: run.id, name: run.name, url: run.html_url.to_string(), status, + created_at: run.created_at, + duration }; runs.push(run); } From 01c9484617fdae7a443c76edc57d36af24b6f8e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Wed, 14 Jan 2026 20:56:15 +0100 Subject: [PATCH 3/6] Add test for build duration --- src/bors/handlers/workflow.rs | 59 +++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/src/bors/handlers/workflow.rs b/src/bors/handlers/workflow.rs index d98931c6..66cdceba 100644 --- a/src/bors/handlers/workflow.rs +++ b/src/bors/handlers/workflow.rs @@ -221,9 +221,10 @@ fn auto_build_cancelled_msg( mod tests { use std::time::Duration; - use crate::database::WorkflowStatus; use crate::database::operations::get_all_workflows; - use crate::tests::{BorsBuilder, BorsTester, GitHub}; + use crate::database::{PgDuration, WorkflowStatus}; + use crate::github::CommitSha; + use crate::tests::{BorsBuilder, BorsTester, GitHub, default_repo_name}; use crate::tests::{WorkflowEvent, run_test}; #[sqlx::test] @@ -442,4 +443,58 @@ min_ci_time = 20 }) .await; } + + #[sqlx::test] + async fn build_success_duration(pool: sqlx::PgPool) { + run_test(pool, async |ctx: &mut BorsTester| { + ctx.post_comment("@bors try").await?; + ctx.expect_comments((), 1).await; + + let w1 = ctx.try_workflow(); + ctx.modify_workflow(w1, |w| w.set_duration(Duration::from_secs(100))); + ctx.workflow_full_success(w1).await?; + ctx.expect_comments((), 1).await; + + let build = ctx + .db() + .find_build( + &default_repo_name(), + ctx.try_branch().name(), + CommitSha(ctx.try_branch().sha()), + ) + .await? + .expect("Build not found"); + assert_eq!(build.duration, Some(PgDuration(Duration::from_secs(100)))); + + Ok(()) + }) + .await; + } + + #[sqlx::test] + async fn build_failed_duration(pool: sqlx::PgPool) { + run_test(pool, async |ctx: &mut BorsTester| { + ctx.post_comment("@bors try").await?; + ctx.expect_comments((), 1).await; + + let w1 = ctx.try_workflow(); + ctx.modify_workflow(w1, |w| w.set_duration(Duration::from_secs(100))); + ctx.workflow_full_failure(w1).await?; + ctx.expect_comments((), 1).await; + + let build = ctx + .db() + .find_build( + &default_repo_name(), + ctx.try_branch().name(), + CommitSha(ctx.try_branch().sha()), + ) + .await? + .expect("Build not found"); + assert_eq!(build.duration, Some(PgDuration(Duration::from_secs(100)))); + + Ok(()) + }) + .await; + } } From 8c16ccde3baa256440cb7c7e6e499968463441b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Wed, 14 Jan 2026 21:19:10 +0100 Subject: [PATCH 4/6] Fix wrong workflow run status deserialization --- src/github/api/client.rs | 2 +- src/tests/mock/workflow.rs | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/github/api/client.rs b/src/github/api/client.rs index 4657318e..b12d38c4 100644 --- a/src/github/api/client.rs +++ b/src/github/api/client.rs @@ -356,6 +356,7 @@ impl GithubRepositoryClient { .unwrap_or(response.items.len()), ); + // https://docs.github.com/en/webhooks/webhook-events-and-payloads#workflow_run fn get_status(run: &Run) -> WorkflowStatus { match run.status.as_str() { "completed" => match run.conclusion.as_deref() { @@ -366,7 +367,6 @@ impl GithubRepositoryClient { WorkflowStatus::Failure } }, - "failure" | "startup_failure" => WorkflowStatus::Failure, _ => WorkflowStatus::Pending } } diff --git a/src/tests/mock/workflow.rs b/src/tests/mock/workflow.rs index d7221f20..ba9f96a7 100644 --- a/src/tests/mock/workflow.rs +++ b/src/tests/mock/workflow.rs @@ -3,7 +3,7 @@ use crate::tests::Repo; use crate::tests::github::{WorkflowEventKind, WorkflowRun}; use crate::tests::mock::repository::GitHubRepository; use chrono::{DateTime, Utc}; -use octocrab::models::workflows::{Conclusion, Status}; +use octocrab::models::workflows::Conclusion; use octocrab::models::{CheckSuiteId, RunId, WorkflowId}; use serde::Serialize; use url::Url; @@ -41,7 +41,7 @@ pub struct GitHubWorkflowRun { head_sha: String, run_number: i64, event: String, - status: Status, + status: String, #[serde(skip_serializing_if = "Option::is_none")] conclusion: Option, created_at: DateTime, @@ -76,10 +76,10 @@ impl GitHubWorkflowRun { let created_at = completed_at - run.duration(); let status = match run.status() { - WorkflowStatus::Pending => Status::Pending, - WorkflowStatus::Success => Status::Completed, - WorkflowStatus::Failure => Status::Failed, - }; + WorkflowStatus::Pending => "pending", + WorkflowStatus::Success | WorkflowStatus::Failure => "completed", + } + .to_string(); let conclusion = match run.status() { WorkflowStatus::Pending => None, WorkflowStatus::Success => Some(Conclusion::Success), From 0eefe67ea6c81aeade5a3ae73aa4060cdef83993 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Wed, 14 Jan 2026 21:20:43 +0100 Subject: [PATCH 5/6] Round durations stored in the DB --- src/database/mod.rs | 59 +++++++++++++++++++++----------------- src/database/operations.rs | 1 + 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/src/database/mod.rs b/src/database/mod.rs index fb7442b8..2d4a202f 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -335,6 +335,38 @@ impl PgDuration { } } +impl sqlx::Type for PgDuration { + fn type_info() -> sqlx::postgres::PgTypeInfo { + >::type_info() + } +} + +impl sqlx::Encode<'_, sqlx::Postgres> for PgDuration { + fn encode_by_ref( + &self, + buf: &mut sqlx::postgres::PgArgumentBuffer, + ) -> Result { + let secs = self.0.as_secs(); + // The Postgres INTERVAL type does not support nanosecond precision + // Let's round the duration down to microseconds, we don't need higher precision anyway + let nanos = self.0.subsec_nanos(); + let nanos = nanos.saturating_sub(nanos % 1000); + Duration::new(secs, nanos).encode_by_ref(buf) + } +} + +impl sqlx::Decode<'_, sqlx::Postgres> for PgDuration { + fn decode(value: sqlx::postgres::PgValueRef<'_>) -> Result { + let interval = PgInterval::decode(value)?; + assert!(interval.days >= 0); + assert!(interval.microseconds >= 0); + let days_us = interval.days as u64 * 86_400_000_000; + let total_us = interval.microseconds as u64 + days_us; + + Ok(PgDuration(Duration::from_micros(total_us))) + } +} + /// Represents a single (merged) commit. #[derive(Debug, PartialEq, sqlx::Type)] #[sqlx(type_name = "build")] @@ -388,33 +420,6 @@ impl sqlx::Decode<'_, sqlx::Postgres> for BuildKind { } } -impl sqlx::Type for PgDuration { - fn type_info() -> sqlx::postgres::PgTypeInfo { - >::type_info() - } -} - -impl sqlx::Encode<'_, sqlx::Postgres> for PgDuration { - fn encode_by_ref( - &self, - buf: &mut sqlx::postgres::PgArgumentBuffer, - ) -> Result { - self.0.encode_by_ref(buf) - } -} - -impl sqlx::Decode<'_, sqlx::Postgres> for PgDuration { - fn decode(value: sqlx::postgres::PgValueRef<'_>) -> Result { - let interval = PgInterval::decode(value)?; - assert!(interval.days >= 0); - assert!(interval.microseconds >= 0); - let days_us = interval.days as u64 * 86_400_000_000; - let total_us = interval.microseconds as u64 + days_us; - - Ok(PgDuration(Duration::from_micros(total_us))) - } -} - /// Represents a pull request. #[derive(Debug)] pub struct PullRequestModel { diff --git a/src/database/operations.rs b/src/database/operations.rs index ef7ab99f..35f25d78 100644 --- a/src/database/operations.rs +++ b/src/database/operations.rs @@ -656,6 +656,7 @@ pub(crate) async fn update_build( duration, check_run_id, } = params; + let duration = duration.map(PgDuration); measure_db_query("update_build", || async { sqlx::query!( r#" From c1d4f5acb8f852b9511148e82015f976f81e3db3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Wed, 14 Jan 2026 21:31:49 +0100 Subject: [PATCH 6/6] Update .sqlx --- ...7e2c6c4fe42128cb481a234372626f35392e809.json | 15 --------------- ...be72c05104c9ead86d9d6c77ab819c716e31ee7.json | 17 +++++++++++++++++ ...cd3884efe6f48cac9e08d964ccfdc724d354bfa.json | 16 ---------------- 3 files changed, 17 insertions(+), 31 deletions(-) delete mode 100644 .sqlx/query-33cf1b803a0b658f197ab26d87e2c6c4fe42128cb481a234372626f35392e809.json create mode 100644 .sqlx/query-abd50d0c8da470a214afe0f57be72c05104c9ead86d9d6c77ab819c716e31ee7.json delete mode 100644 .sqlx/query-f2654d5dd7232196754d25991cd3884efe6f48cac9e08d964ccfdc724d354bfa.json diff --git a/.sqlx/query-33cf1b803a0b658f197ab26d87e2c6c4fe42128cb481a234372626f35392e809.json b/.sqlx/query-33cf1b803a0b658f197ab26d87e2c6c4fe42128cb481a234372626f35392e809.json deleted file mode 100644 index b05ce497..00000000 --- a/.sqlx/query-33cf1b803a0b658f197ab26d87e2c6c4fe42128cb481a234372626f35392e809.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE build SET check_run_id = $1 WHERE id = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8", - "Int4" - ] - }, - "nullable": [] - }, - "hash": "33cf1b803a0b658f197ab26d87e2c6c4fe42128cb481a234372626f35392e809" -} diff --git a/.sqlx/query-abd50d0c8da470a214afe0f57be72c05104c9ead86d9d6c77ab819c716e31ee7.json b/.sqlx/query-abd50d0c8da470a214afe0f57be72c05104c9ead86d9d6c77ab819c716e31ee7.json new file mode 100644 index 00000000..fb3ac874 --- /dev/null +++ b/.sqlx/query-abd50d0c8da470a214afe0f57be72c05104c9ead86d9d6c77ab819c716e31ee7.json @@ -0,0 +1,17 @@ +{ + "db_name": "PostgreSQL", + "query": "\nUPDATE build\nSET\n status = COALESCE($1, status),\n duration = COALESCE($2, duration),\n check_run_id = COALESCE($3, check_run_id)\nWHERE id = $4\n", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Interval", + "Int8", + "Int4" + ] + }, + "nullable": [] + }, + "hash": "abd50d0c8da470a214afe0f57be72c05104c9ead86d9d6c77ab819c716e31ee7" +} diff --git a/.sqlx/query-f2654d5dd7232196754d25991cd3884efe6f48cac9e08d964ccfdc724d354bfa.json b/.sqlx/query-f2654d5dd7232196754d25991cd3884efe6f48cac9e08d964ccfdc724d354bfa.json deleted file mode 100644 index e8abebf9..00000000 --- a/.sqlx/query-f2654d5dd7232196754d25991cd3884efe6f48cac9e08d964ccfdc724d354bfa.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\nUPDATE build\nSET\n status = $1,\n duration = COALESCE($2, duration)\nWHERE id = $3\n", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Interval", - "Int4" - ] - }, - "nullable": [] - }, - "hash": "f2654d5dd7232196754d25991cd3884efe6f48cac9e08d964ccfdc724d354bfa" -}