From 50aa49a4b1f479cece03a463294c2abfb664b4e7 Mon Sep 17 00:00:00 2001 From: boxbeam Date: Wed, 13 Mar 2024 16:04:18 -0400 Subject: [PATCH] refactor(db): Convert job run DAO to be usable with query_paged_as --- ee/tabby-db-macros/src/lib.rs | 62 +++++++++++++------------ ee/tabby-db/src/invitations.rs | 4 +- ee/tabby-db/src/job_runs.rs | 36 ++++++++------- ee/tabby-db/src/lib.rs | 66 ++++++++++++++++++++++++++- ee/tabby-webserver/src/service/dao.rs | 8 ++-- 5 files changed, 121 insertions(+), 55 deletions(-) diff --git a/ee/tabby-db-macros/src/lib.rs b/ee/tabby-db-macros/src/lib.rs index 88ebaa292520..93efcb07e301 100644 --- a/ee/tabby-db-macros/src/lib.rs +++ b/ee/tabby-db-macros/src/lib.rs @@ -1,21 +1,31 @@ use proc_macro::TokenStream; use quote::quote; -use syn::{bracketed, parse::Parse, parse_macro_input, Ident, LitStr, Token, Type}; +use syn::{bracketed, parse::Parse, parse_macro_input, Expr, Ident, LitStr, Token, Type}; #[derive(Clone)] struct Column { name: LitStr, non_null: bool, + rename: LitStr, } impl Parse for Column { fn parse(input: syn::parse::ParseStream) -> syn::Result { - let name = input.parse()?; + let name: LitStr = input.parse()?; let non_null = input.peek(Token![!]); if non_null { input.parse::()?; } - Ok(Column { name, non_null }) + let mut rename = None; + if input.peek(kw::AS) { + input.parse::()?; + rename = Some(input.parse()?); + } + Ok(Column { + rename: rename.unwrap_or(name.clone()), + name, + non_null, + }) } } @@ -23,7 +33,7 @@ struct PaginationQueryInput { pub typ: Type, pub table_name: LitStr, pub columns: Vec, - pub condition: Option, + pub condition: Option, pub limit: Ident, pub skip_id: Ident, pub backwards: Ident, @@ -32,8 +42,7 @@ struct PaginationQueryInput { mod kw { use syn::custom_keyword; - custom_keyword!(FROM); - custom_keyword!(WHERE); + custom_keyword!(AS); } impl Parse for PaginationQueryInput { @@ -54,12 +63,6 @@ impl Parse for PaginationQueryInput { columns.push(inner.parse()?); } - let mut condition = None; - if input.peek(kw::WHERE) { - input.parse::()?; - condition = Some(input.parse()?); - } - input.parse::()?; let limit = input.parse()?; input.parse::()?; @@ -67,6 +70,12 @@ impl Parse for PaginationQueryInput { input.parse::()?; let backwards = input.parse()?; + let mut condition = None; + if input.peek(Token![,]) { + input.parse::()?; + condition = Some(input.parse()?); + } + Ok(PaginationQueryInput { typ, table_name, @@ -90,32 +99,25 @@ pub fn query_paged_as(input: TokenStream) -> TokenStream { .iter() .map(|col| { let name = col.name.value(); - if col.non_null { - format!("{name} as \"{name}!\"") - } else { - name - } + let rename = col.rename.value(); + let non_null = col.non_null.then_some("!").unwrap_or_default(); + format!("{name} AS '{rename}{non_null}'") }) .collect::>() .join(", "); let column_args: Vec = input.columns.iter().map(|col| col.name.value()).collect(); - let where_clause = input - .condition - .clone() - .map(|cond| format!("WHERE {}", cond.value())) - .unwrap_or_default(); + let limit = input.limit; let condition = match input.condition { - Some(cond) => quote! {Some(#cond.into())}, + Some(cond) => quote! {#cond}, None => quote! {None}, }; - - let limit = input.limit; let skip_id = input.skip_id; let backwards = input.backwards; quote! { - sqlx::query_as(&crate::make_pagination_query_with_condition({ - let _ = sqlx::query_as!(#typ, "SELECT " + #columns + " FROM " + #table_name + #where_clause); - &#table_name - }, &[ #(#column_args),* ], #limit, #skip_id, #backwards, #condition)) - }.into() + sqlx::query_as(&crate::make_pagination_query_with_condition({ + let _ = sqlx::query_as!(#typ, "SELECT " + #columns + " FROM " + #table_name); + &#table_name + }, &[ #(#column_args),* ], #limit, #skip_id, #backwards, #condition)) + } + .into() } diff --git a/ee/tabby-db/src/invitations.rs b/ee/tabby-db/src/invitations.rs index dcfc5fc7aaff..b2eaba127e49 100644 --- a/ee/tabby-db/src/invitations.rs +++ b/ee/tabby-db/src/invitations.rs @@ -24,8 +24,8 @@ impl DbConn { backwards: bool, ) -> Result> { let invitations = query_paged_as!(InvitationDAO, "invitations", ["id", "email", "code", "created_at"!], limit, skip_id, backwards) - .fetch_all(&self.pool) - .await?; + .fetch_all(&self.pool) + .await?; Ok(invitations) } diff --git a/ee/tabby-db/src/job_runs.rs b/ee/tabby-db/src/job_runs.rs index d3c3d7ed91f1..a6a8b4994e99 100644 --- a/ee/tabby-db/src/job_runs.rs +++ b/ee/tabby-db/src/job_runs.rs @@ -1,23 +1,23 @@ use anyhow::Result; -use chrono::{DateTime, Utc}; use sqlx::{query, FromRow}; +use tabby_db_macros::query_paged_as; use super::DbConn; -use crate::make_pagination_query_with_condition; +use crate::{DateTimeUtc, DbOption}; #[derive(Default, Clone, FromRow)] pub struct JobRunDAO { - pub id: i32, + pub id: i64, #[sqlx(rename = "job")] pub name: String, - pub exit_code: Option, + pub exit_code: Option, pub stdout: String, pub stderr: String, - pub created_at: DateTime, - pub updated_at: DateTime, + pub created_at: DateTimeUtc, + pub updated_at: DateTimeUtc, #[sqlx(rename = "end_ts")] - pub finished_at: Option>, + pub finished_at: DbOption, } /// db read/write operations for `job_runs` table @@ -72,26 +72,28 @@ impl DbConn { } else { None }; - let query = make_pagination_query_with_condition( + let job_runs: Vec = query_paged_as!( + JobRunDAO, "job_runs", - &[ + [ "id", - "job", + "job" AS "name", "exit_code", "stdout", "stderr", - "created_at", - "updated_at", - "end_ts", + "created_at"!, + "updated_at"!, + "end_ts" AS "finished_at" ], limit, skip_id, backwards, - condition, - ); + condition + ) + .fetch_all(&self.pool) + .await?; - let runs = sqlx::query_as(&query).fetch_all(&self.pool).await?; - Ok(runs) + Ok(job_runs) } pub async fn cleanup_stale_job_runs(&self) -> Result<()> { diff --git a/ee/tabby-db/src/lib.rs b/ee/tabby-db/src/lib.rs index 0052493c3923..014ad087dfa8 100644 --- a/ee/tabby-db/src/lib.rs +++ b/ee/tabby-db/src/lib.rs @@ -10,7 +10,8 @@ pub use oauth_credential::OAuthCredentialDAO; pub use repositories::RepositoryDAO; pub use server_setting::ServerSettingDAO; use sqlx::{ - query, query_scalar, sqlite::SqliteQueryResult, Pool, Sqlite, SqlitePool, Type, Value, ValueRef, + database::HasValueRef, query, query_scalar, sqlite::SqliteQueryResult, Pool, Sqlite, + SqlitePool, Type, Value, ValueRef, }; pub use users::UserDAO; @@ -187,6 +188,61 @@ impl DbConn { } } +#[derive(Default)] +pub struct DbOption(Option); + +impl Type for DbOption +where + T: Type, +{ + fn type_info() -> ::TypeInfo { + T::type_info() + } +} + +impl<'a, T> sqlx::Decode<'a, Sqlite> for DbOption +where + T: sqlx::Decode<'a, Sqlite>, +{ + fn decode( + value: >::ValueRef, + ) -> std::prelude::v1::Result { + if value.is_null() { + Ok(Self(None)) + } else { + Ok(Self(Some(T::decode(value)?))) + } + } +} + +impl From> for DbOption +where + T: From, +{ + fn from(value: Option) -> Self { + DbOption(value.map(|v| T::from(v))) + } +} + +impl DbOption { + pub fn into_option(self) -> Option + where + T: Into, + { + self.0.map(Into::into) + } +} + +impl Clone for DbOption +where + T: Clone, +{ + fn clone(&self) -> Self { + self.0.clone().into() + } +} + +#[derive(Default, Clone)] pub struct DateTimeUtc(DateTime); impl From> for DateTimeUtc { @@ -195,9 +251,15 @@ impl From> for DateTimeUtc { } } +impl Into> for DateTimeUtc { + fn into(self) -> DateTime { + *self + } +} + impl<'a> sqlx::Decode<'a, Sqlite> for DateTimeUtc { fn decode( - value: >::ValueRef, + value: >::ValueRef, ) -> std::prelude::v1::Result { let time: NaiveDateTime = value.to_owned().decode(); Ok(time.into()) diff --git a/ee/tabby-webserver/src/service/dao.rs b/ee/tabby-webserver/src/service/dao.rs index d1d2c0e28e3d..ad0113713153 100644 --- a/ee/tabby-webserver/src/service/dao.rs +++ b/ee/tabby-webserver/src/service/dao.rs @@ -31,10 +31,10 @@ impl From for job::JobRun { Self { id: run.id.as_id(), job: run.name, - created_at: run.created_at, - updated_at: run.updated_at, - finished_at: run.finished_at, - exit_code: run.exit_code, + created_at: *run.created_at, + updated_at: *run.updated_at, + finished_at: run.finished_at.into_option(), + exit_code: run.exit_code.map(|i| i as i32), stdout: run.stdout, stderr: run.stderr, }