diff --git a/ee/tabby-db-macros/src/lib.rs b/ee/tabby-db-macros/src/lib.rs index 88ebaa292520..f726551b5b1d 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(Token![as]) { + input.parse::()?; + rename = Some(input.parse()?); + } + Ok(Column { + rename: rename.unwrap_or(name.clone()), + name, + non_null, + }) } } @@ -23,19 +33,12 @@ 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, } -mod kw { - use syn::custom_keyword; - - custom_keyword!(FROM); - custom_keyword!(WHERE); -} - impl Parse for PaginationQueryInput { fn parse(input: syn::parse::ParseStream) -> syn::Result { let typ = input.parse()?; @@ -54,12 +57,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 +64,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 +93,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/job_runs.rs b/ee/tabby-db/src/job_runs.rs index d3c3d7ed91f1..ca6cd91d2908 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 0d8b7dea8ae7..728a8aed77fc 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, Decode, Encode, Pool, + Sqlite, SqlitePool, Type, Value, ValueRef, }; pub use users::UserDAO; @@ -187,6 +188,72 @@ impl DbConn { } } +pub trait DbNullable: + for<'a> Decode<'a, Sqlite> + for<'a> Encode<'a, Sqlite> + Type +{ +} +impl DbNullable for DateTimeUtc {} + +#[derive(Default)] +pub struct DbOption(Option) +where + T: DbNullable; + +impl Type for DbOption +where + T: Type + DbNullable, +{ + fn type_info() -> ::TypeInfo { + T::type_info() + } +} + +impl<'a, T> Decode<'a, Sqlite> for DbOption +where + T: DbNullable, +{ + 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 + DbNullable, +{ + fn from(value: Option) -> Self { + DbOption(value.map(|v| T::from(v))) + } +} + +impl DbOption +where + T: DbNullable, +{ + pub fn into_option(self) -> Option + where + T: Into, + { + self.0.map(Into::into) + } +} + +impl Clone for DbOption +where + T: Clone + DbNullable, +{ + fn clone(&self) -> Self { + self.0.clone().into() + } +} + +#[derive(Default, Clone)] pub struct DateTimeUtc(DateTime); impl From> for DateTimeUtc { @@ -195,9 +262,15 @@ impl From> for DateTimeUtc { } } -impl<'a> sqlx::Decode<'a, Sqlite> for DateTimeUtc { +impl From for DateTime { + fn from(val: DateTimeUtc) -> Self { + *val + } +} + +impl<'a> 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()) @@ -210,7 +283,7 @@ impl Type for DateTimeUtc { } } -impl<'a> sqlx::Encode<'a, Sqlite> for DateTimeUtc { +impl<'a> Encode<'a, Sqlite> for DateTimeUtc { fn encode_by_ref( &self, buf: &mut >::ArgumentBuffer, diff --git a/ee/tabby-webserver/src/service/dao.rs b/ee/tabby-webserver/src/service/dao.rs index f9748f37a9bb..28923cc9f645 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, }