From 4efbb952043ccde0408dfe5535ae98261e87d8c9 Mon Sep 17 00:00:00 2001 From: Joshua Coles Date: Sat, 27 Jul 2024 20:28:39 +0100 Subject: [PATCH] SOA for batch inserts using UNNEST as per docs --- Cargo.lock | 21 +++++++++++++++++++ Cargo.toml | 1 + src/main.rs | 54 +++++++++++++++++++++++++----------------------- src/toggl/mod.rs | 5 +++-- 4 files changed, 53 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c61002b..ed0b505 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1912,6 +1912,26 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "soa-rs" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c76685dff09a60d416b82a23e192e14cd47d147858b9aca2ce5ca05877acd93" +dependencies = [ + "soa-rs-derive", +] + +[[package]] +name = "soa-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4281fd25ca7e6cc6e84cadaf296ffabac12719e830e1af160cd243dca831577" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + [[package]] name = "socket2" version = "0.5.7" @@ -2343,6 +2363,7 @@ dependencies = [ "serde_json", "serde_json_path_to_error", "serde_with", + "soa-rs", "sqlx", "thiserror", "tokio", diff --git a/Cargo.toml b/Cargo.toml index e803cc4..66ef1d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,3 +25,4 @@ futures = "0.3.30" tracing = "0.1.40" tracing-subscriber = "0.3.18" itertools = "0.13.0" +soa-rs = "0.6.1" diff --git a/src/main.rs b/src/main.rs index fcdccd5..09cb69c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,9 @@ use chrono::{DateTime, DurationRound, NaiveDate, TimeDelta, Utc}; use sqlx::{Connection, PgConnection, Postgres, QueryBuilder}; use toggl::TogglApi; -use crate::toggl::types::{TimeEntry, TogglReportFilters}; +use crate::toggl::types::{Tag, TimeEntry, TogglReportFilters}; use itertools::Itertools; +use soa_rs::Soa; mod toggl; mod sensitive; @@ -256,31 +257,32 @@ impl Worker { async fn update_tags(&mut self) -> Result<(), AppError> { let tags = self.toggl_api.get_tags().await?; + let tags: Soa = Soa::from(tags.as_slice()); - for tag in tags { - sqlx::query!( - r#" - INSERT INTO tags (id, name, workspace_id, creator_id, updated_at, deleted_at, permissions) - VALUES ($1, $2, $3, $4, $5, $6, $7) - ON CONFLICT (id) DO UPDATE SET - name = excluded.name, - workspace_id = excluded.workspace_id, - creator_id = excluded.creator_id, - updated_at = excluded.updated_at, - deleted_at = excluded.deleted_at, - permissions = excluded.permissions - "#, - tag.id as i64, - tag.name, - tag.workspace_id as i64, - tag.creator_id as i64, - tag.updated_at, - tag.deleted_at, - tag.permissions, - ) - .execute(&mut self.db) - .await?; - } + sqlx::query!( + r#" + INSERT INTO tags (id, name, workspace_id, creator_id, updated_at, deleted_at, permissions) + SELECT * FROM UNNEST($1::bigint[], $2::text[], $3::bigint[], $4::bigint[], $5::timestamptz[], $6::timestamptz[], $7::text[]) + ON CONFLICT (id) DO UPDATE SET + name = excluded.name, + workspace_id = excluded.workspace_id, + creator_id = excluded.creator_id, + updated_at = excluded.updated_at, + deleted_at = excluded.deleted_at, + permissions = excluded.permissions + "#, + &tags.id().iter().map(|id| *id as i64).collect::>()[..], + tags.name(), + &tags.workspace_id().iter().map(|id| *id as i64).collect::>()[..], + &tags.creator_id().iter().map(|id| *id as i64).collect::>()[..], + tags.updated_at(), + // Nullable fields fail to type check with UNNEST batch inserts so we silence the + // errors using `: _`. + tags.deleted_at(): _, + tags.permissions(): _, + ) + .execute(&mut self.db) + .await?; Ok(()) } @@ -349,7 +351,7 @@ async fn main() { toggl_api: api, }; - worker.update_clients() + worker.update_tags() .await .unwrap(); diff --git a/src/toggl/mod.rs b/src/toggl/mod.rs index 6e3820b..e8f7794 100644 --- a/src/toggl/mod.rs +++ b/src/toggl/mod.rs @@ -349,7 +349,7 @@ pub mod types { } } - #[derive(Serialize, Deserialize, Debug, Clone)] + #[derive(Soars, Serialize, Deserialize, Debug, Clone)] pub struct TrackingClient { /// The unique identifier for the client. pub id: i64, @@ -378,7 +378,7 @@ pub mod types { pub permissions: Option, } - #[derive(Serialize, Deserialize, Debug, Clone)] + #[derive(Soars, Serialize, Deserialize, Debug, Clone)] pub struct Tag { pub id: u64, pub name: String, @@ -493,6 +493,7 @@ pub mod types { } use std::fmt; + use soa_rs::Soars; impl fmt::Debug for TogglReportFilters { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {