mirror of
https://github.com/Phundrak/georm.git
synced 2025-06-25 17:34:57 +00:00
refactor(macros): split trait implementations into modular files
Move trait implementation code from single monolithic file into separate modules organised by operation type (create, delete, find, update, upsert) for better code organisation and maintainability.
This commit is contained in:
parent
a7696270da
commit
7e7a3ccd29
@ -1,11 +1,12 @@
|
|||||||
use ir::GeormField;
|
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
|
||||||
mod composite_keys;
|
mod composite_keys;
|
||||||
mod defaultable_struct;
|
mod defaultable_struct;
|
||||||
mod ir;
|
mod ir;
|
||||||
|
pub(crate) use ir::GeormField;
|
||||||
mod relationships;
|
mod relationships;
|
||||||
mod trait_implementation;
|
mod traits;
|
||||||
|
pub(crate) use composite_keys::IdType;
|
||||||
|
|
||||||
fn extract_georm_field_attrs(ast: &mut syn::DeriveInput) -> deluxe::Result<Vec<GeormField>> {
|
fn extract_georm_field_attrs(ast: &mut syn::DeriveInput) -> deluxe::Result<Vec<GeormField>> {
|
||||||
let syn::Data::Struct(s) = &mut ast.data else {
|
let syn::Data::Struct(s) = &mut ast.data else {
|
||||||
@ -50,8 +51,7 @@ pub fn georm_derive_macro2(
|
|||||||
|
|
||||||
let relationships =
|
let relationships =
|
||||||
relationships::derive_relationships(&ast, &struct_attrs, &fields, &identifier);
|
relationships::derive_relationships(&ast, &struct_attrs, &fields, &identifier);
|
||||||
let trait_impl =
|
let trait_impl = traits::derive_trait(&ast, &struct_attrs.table, &fields, &identifier);
|
||||||
trait_implementation::derive_trait(&ast, &struct_attrs.table, &fields, &identifier);
|
|
||||||
|
|
||||||
let code = quote! {
|
let code = quote! {
|
||||||
#id_struct
|
#id_struct
|
||||||
|
@ -1,268 +0,0 @@
|
|||||||
use super::composite_keys::IdType;
|
|
||||||
use super::ir::GeormField;
|
|
||||||
use quote::quote;
|
|
||||||
|
|
||||||
fn generate_find_all_query(table: &str) -> proc_macro2::TokenStream {
|
|
||||||
let find_string = format!("SELECT * FROM {table}");
|
|
||||||
quote! {
|
|
||||||
async fn find_all(pool: &::sqlx::PgPool) -> ::sqlx::Result<Vec<Self>> {
|
|
||||||
::sqlx::query_as!(Self, #find_string).fetch_all(pool).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_find_query(table: &str, id: &IdType) -> proc_macro2::TokenStream {
|
|
||||||
match id {
|
|
||||||
IdType::Simple {
|
|
||||||
field_name,
|
|
||||||
field_type,
|
|
||||||
} => {
|
|
||||||
let find_string = format!("SELECT * FROM {table} WHERE {} = $1", field_name);
|
|
||||||
quote! {
|
|
||||||
async fn find(pool: &::sqlx::PgPool, id: &#field_type) -> ::sqlx::Result<Option<Self>> {
|
|
||||||
::sqlx::query_as!(Self, #find_string, id)
|
|
||||||
.fetch_optional(pool)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
IdType::Composite { fields, field_type } => {
|
|
||||||
let id_match_string = fields
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, field)| format!("{} = ${}", field.name, i + 1))
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join(" AND ");
|
|
||||||
let id_members: Vec<syn::Ident> =
|
|
||||||
fields.iter().map(|field| field.name.clone()).collect();
|
|
||||||
let find_string = format!("SELECT * FROM {table} WHERE {id_match_string}");
|
|
||||||
quote! {
|
|
||||||
async fn find(pool: &::sqlx::PgPool, id: &#field_type) -> ::sqlx::Result<Option<Self>> {
|
|
||||||
::sqlx::query_as!(Self, #find_string, #(id.#id_members),*)
|
|
||||||
.fetch_optional(pool)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_create_query(table: &str, fields: &[GeormField]) -> proc_macro2::TokenStream {
|
|
||||||
let inputs: Vec<String> = (1..=fields.len()).map(|num| format!("${num}")).collect();
|
|
||||||
let create_string = format!(
|
|
||||||
"INSERT INTO {table} ({}) VALUES ({}) RETURNING *",
|
|
||||||
fields
|
|
||||||
.iter()
|
|
||||||
.map(|f| f.ident.to_string())
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join(", "),
|
|
||||||
inputs.join(", ")
|
|
||||||
);
|
|
||||||
let field_idents: Vec<syn::Ident> = fields.iter().map(|f| f.ident.clone()).collect();
|
|
||||||
quote! {
|
|
||||||
async fn create(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<Self> {
|
|
||||||
::sqlx::query_as!(
|
|
||||||
Self,
|
|
||||||
#create_string,
|
|
||||||
#(self.#field_idents),*
|
|
||||||
)
|
|
||||||
.fetch_one(pool)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_update_query(
|
|
||||||
table: &str,
|
|
||||||
fields: &[GeormField],
|
|
||||||
id: &IdType,
|
|
||||||
) -> proc_macro2::TokenStream {
|
|
||||||
let non_id_fields: Vec<syn::Ident> = fields
|
|
||||||
.iter()
|
|
||||||
.filter_map(|f| if f.id { None } else { Some(f.ident.clone()) })
|
|
||||||
.collect();
|
|
||||||
let update_columns = non_id_fields
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, field)| format!("{} = ${}", field, i + 1))
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join(", ");
|
|
||||||
let mut all_fields = non_id_fields.clone();
|
|
||||||
let where_clause = match id {
|
|
||||||
IdType::Simple { field_name, .. } => {
|
|
||||||
let where_clause = format!("{} = ${}", field_name, non_id_fields.len() + 1);
|
|
||||||
all_fields.push(field_name.clone());
|
|
||||||
where_clause
|
|
||||||
}
|
|
||||||
IdType::Composite { fields, .. } => fields
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, field)| {
|
|
||||||
let where_clause = format!("{} = ${}", field.name, non_id_fields.len() + i + 1);
|
|
||||||
all_fields.push(field.name.clone());
|
|
||||||
where_clause
|
|
||||||
})
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join(" AND "),
|
|
||||||
};
|
|
||||||
let update_string =
|
|
||||||
format!("UPDATE {table} SET {update_columns} WHERE {where_clause} RETURNING *");
|
|
||||||
quote! {
|
|
||||||
async fn update(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<Self> {
|
|
||||||
::sqlx::query_as!(
|
|
||||||
Self, #update_string, #(self.#all_fields),*
|
|
||||||
)
|
|
||||||
.fetch_one(pool)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_delete_query(table: &str, id: &IdType) -> proc_macro2::TokenStream {
|
|
||||||
let where_clause = match id {
|
|
||||||
IdType::Simple { field_name, .. } => format!("{} = $1", field_name),
|
|
||||||
IdType::Composite { fields, .. } => fields
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, field)| format!("{} = ${}", field.name, i + 1))
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join(" AND "),
|
|
||||||
};
|
|
||||||
let query_args = match id {
|
|
||||||
IdType::Simple { .. } => quote! { id },
|
|
||||||
IdType::Composite { fields, .. } => {
|
|
||||||
let fields: Vec<syn::Ident> = fields.iter().map(|f| f.name.clone()).collect();
|
|
||||||
quote! { #(id.#fields), * }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let id_type = match id {
|
|
||||||
IdType::Simple { field_type, .. } => quote! { #field_type },
|
|
||||||
IdType::Composite { field_type, .. } => quote! { #field_type },
|
|
||||||
};
|
|
||||||
let delete_string = format!("DELETE FROM {table} WHERE {where_clause}");
|
|
||||||
quote! {
|
|
||||||
async fn delete_by_id(pool: &::sqlx::PgPool, id: &#id_type) -> ::sqlx::Result<u64> {
|
|
||||||
let rows_affected = ::sqlx::query!(#delete_string, #query_args)
|
|
||||||
.execute(pool)
|
|
||||||
.await?
|
|
||||||
.rows_affected();
|
|
||||||
Ok(rows_affected)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn delete(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<u64> {
|
|
||||||
Self::delete_by_id(pool, &self.get_id()).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_upsert_query(
|
|
||||||
table: &str,
|
|
||||||
fields: &[GeormField],
|
|
||||||
id: &IdType,
|
|
||||||
) -> proc_macro2::TokenStream {
|
|
||||||
let inputs: Vec<String> = (1..=fields.len()).map(|num| format!("${num}")).collect();
|
|
||||||
let columns = fields
|
|
||||||
.iter()
|
|
||||||
.map(|f| f.ident.to_string())
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join(", ");
|
|
||||||
|
|
||||||
let primary_key: proc_macro2::TokenStream = match id {
|
|
||||||
IdType::Simple { field_name, .. } => quote! {#field_name},
|
|
||||||
IdType::Composite { fields, .. } => {
|
|
||||||
let field_names: Vec<syn::Ident> = fields.iter().map(|f| f.name.clone()).collect();
|
|
||||||
quote! {
|
|
||||||
#(#field_names),*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// For ON CONFLICT DO UPDATE, exclude the ID field from updates
|
|
||||||
let update_assignments = fields
|
|
||||||
.iter()
|
|
||||||
.filter(|f| !f.id)
|
|
||||||
.map(|f| format!("{} = EXCLUDED.{}", f.ident, f.ident))
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join(", ");
|
|
||||||
|
|
||||||
let upsert_string = format!(
|
|
||||||
"INSERT INTO {table} ({columns}) VALUES ({}) ON CONFLICT ({}) DO UPDATE SET {update_assignments} RETURNING *",
|
|
||||||
inputs.join(", "),
|
|
||||||
primary_key
|
|
||||||
);
|
|
||||||
|
|
||||||
let field_idents: Vec<syn::Ident> = fields.iter().map(|f| f.ident.clone()).collect();
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
async fn create_or_update(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<Self> {
|
|
||||||
::sqlx::query_as!(
|
|
||||||
Self,
|
|
||||||
#upsert_string,
|
|
||||||
#(self.#field_idents),*
|
|
||||||
)
|
|
||||||
.fetch_one(pool)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_get_id(id: &IdType) -> proc_macro2::TokenStream {
|
|
||||||
match id {
|
|
||||||
IdType::Simple {
|
|
||||||
field_name,
|
|
||||||
field_type,
|
|
||||||
} => {
|
|
||||||
quote! {
|
|
||||||
fn get_id(&self) -> #field_type {
|
|
||||||
self.#field_name.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
IdType::Composite { fields, field_type } => {
|
|
||||||
let field_names: Vec<syn::Ident> = fields.iter().map(|f| f.name.clone()).collect();
|
|
||||||
quote! {
|
|
||||||
fn get_id(&self) -> #field_type {
|
|
||||||
#field_type {
|
|
||||||
#(#field_names: self.#field_names),*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn derive_trait(
|
|
||||||
ast: &syn::DeriveInput,
|
|
||||||
table: &str,
|
|
||||||
fields: &[GeormField],
|
|
||||||
id: &IdType,
|
|
||||||
) -> proc_macro2::TokenStream {
|
|
||||||
let ty = match id {
|
|
||||||
IdType::Simple { field_type, .. } => quote! {#field_type},
|
|
||||||
IdType::Composite { field_type, .. } => quote! {#field_type},
|
|
||||||
};
|
|
||||||
|
|
||||||
// define impl variables
|
|
||||||
let ident = &ast.ident;
|
|
||||||
let (impl_generics, type_generics, where_clause) = ast.generics.split_for_impl();
|
|
||||||
|
|
||||||
// generate
|
|
||||||
let get_all = generate_find_all_query(table);
|
|
||||||
let get_id = generate_get_id(id);
|
|
||||||
let find_query = generate_find_query(table, id);
|
|
||||||
let create_query = generate_create_query(table, fields);
|
|
||||||
let update_query = generate_update_query(table, fields, id);
|
|
||||||
let upsert_query = generate_upsert_query(table, fields, id);
|
|
||||||
let delete_query = generate_delete_query(table, id);
|
|
||||||
quote! {
|
|
||||||
impl #impl_generics Georm<#ty> for #ident #type_generics #where_clause {
|
|
||||||
#get_all
|
|
||||||
#get_id
|
|
||||||
#find_query
|
|
||||||
#create_query
|
|
||||||
#update_query
|
|
||||||
#upsert_query
|
|
||||||
#delete_query
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
27
georm-macros/src/georm/traits/create.rs
Normal file
27
georm-macros/src/georm/traits/create.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
use crate::georm::GeormField;
|
||||||
|
use quote::quote;
|
||||||
|
|
||||||
|
pub fn generate_create_query(table: &str, fields: &[GeormField]) -> proc_macro2::TokenStream {
|
||||||
|
let inputs: Vec<String> = (1..=fields.len()).map(|num| format!("${num}")).collect();
|
||||||
|
let create_string = format!(
|
||||||
|
"INSERT INTO {table} ({}) VALUES ({}) RETURNING *",
|
||||||
|
fields
|
||||||
|
.iter()
|
||||||
|
.map(|f| f.ident.to_string())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", "),
|
||||||
|
inputs.join(", ")
|
||||||
|
);
|
||||||
|
let field_idents: Vec<syn::Ident> = fields.iter().map(|f| f.ident.clone()).collect();
|
||||||
|
quote! {
|
||||||
|
async fn create(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<Self> {
|
||||||
|
::sqlx::query_as!(
|
||||||
|
Self,
|
||||||
|
#create_string,
|
||||||
|
#(self.#field_idents),*
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
39
georm-macros/src/georm/traits/delete.rs
Normal file
39
georm-macros/src/georm/traits/delete.rs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
use crate::georm::IdType;
|
||||||
|
use quote::quote;
|
||||||
|
|
||||||
|
pub fn generate_delete_query(table: &str, id: &IdType) -> proc_macro2::TokenStream {
|
||||||
|
let where_clause = match id {
|
||||||
|
IdType::Simple { field_name, .. } => format!("{} = $1", field_name),
|
||||||
|
IdType::Composite { fields, .. } => fields
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, field)| format!("{} = ${}", field.name, i + 1))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(" AND "),
|
||||||
|
};
|
||||||
|
let query_args = match id {
|
||||||
|
IdType::Simple { .. } => quote! { id },
|
||||||
|
IdType::Composite { fields, .. } => {
|
||||||
|
let fields: Vec<syn::Ident> = fields.iter().map(|f| f.name.clone()).collect();
|
||||||
|
quote! { #(id.#fields), * }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let id_type = match id {
|
||||||
|
IdType::Simple { field_type, .. } => quote! { #field_type },
|
||||||
|
IdType::Composite { field_type, .. } => quote! { #field_type },
|
||||||
|
};
|
||||||
|
let delete_string = format!("DELETE FROM {table} WHERE {where_clause}");
|
||||||
|
quote! {
|
||||||
|
async fn delete_by_id(pool: &::sqlx::PgPool, id: &#id_type) -> ::sqlx::Result<u64> {
|
||||||
|
let rows_affected = ::sqlx::query!(#delete_string, #query_args)
|
||||||
|
.execute(pool)
|
||||||
|
.await?
|
||||||
|
.rows_affected();
|
||||||
|
Ok(rows_affected)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<u64> {
|
||||||
|
Self::delete_by_id(pool, &self.get_id()).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
47
georm-macros/src/georm/traits/find.rs
Normal file
47
georm-macros/src/georm/traits/find.rs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
use crate::georm::IdType;
|
||||||
|
use quote::quote;
|
||||||
|
|
||||||
|
pub fn generate_find_all_query(table: &str) -> proc_macro2::TokenStream {
|
||||||
|
let find_string = format!("SELECT * FROM {table}");
|
||||||
|
quote! {
|
||||||
|
async fn find_all(pool: &::sqlx::PgPool) -> ::sqlx::Result<Vec<Self>> {
|
||||||
|
::sqlx::query_as!(Self, #find_string).fetch_all(pool).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_find_query(table: &str, id: &IdType) -> proc_macro2::TokenStream {
|
||||||
|
match id {
|
||||||
|
IdType::Simple {
|
||||||
|
field_name,
|
||||||
|
field_type,
|
||||||
|
} => {
|
||||||
|
let find_string = format!("SELECT * FROM {table} WHERE {} = $1", field_name);
|
||||||
|
quote! {
|
||||||
|
async fn find(pool: &::sqlx::PgPool, id: &#field_type) -> ::sqlx::Result<Option<Self>> {
|
||||||
|
::sqlx::query_as!(Self, #find_string, id)
|
||||||
|
.fetch_optional(pool)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IdType::Composite { fields, field_type } => {
|
||||||
|
let id_match_string = fields
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, field)| format!("{} = ${}", field.name, i + 1))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(" AND ");
|
||||||
|
let id_members: Vec<syn::Ident> =
|
||||||
|
fields.iter().map(|field| field.name.clone()).collect();
|
||||||
|
let find_string = format!("SELECT * FROM {table} WHERE {id_match_string}");
|
||||||
|
quote! {
|
||||||
|
async fn find(pool: &::sqlx::PgPool, id: &#field_type) -> ::sqlx::Result<Option<Self>> {
|
||||||
|
::sqlx::query_as!(Self, #find_string, #(id.#id_members),*)
|
||||||
|
.fetch_optional(pool)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
70
georm-macros/src/georm/traits/mod.rs
Normal file
70
georm-macros/src/georm/traits/mod.rs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
use super::composite_keys::IdType;
|
||||||
|
use super::ir::GeormField;
|
||||||
|
use quote::quote;
|
||||||
|
|
||||||
|
mod create;
|
||||||
|
mod delete;
|
||||||
|
mod find;
|
||||||
|
mod update;
|
||||||
|
mod upsert;
|
||||||
|
|
||||||
|
fn generate_get_id(id: &IdType) -> proc_macro2::TokenStream {
|
||||||
|
match id {
|
||||||
|
IdType::Simple {
|
||||||
|
field_name,
|
||||||
|
field_type,
|
||||||
|
} => {
|
||||||
|
quote! {
|
||||||
|
fn get_id(&self) -> #field_type {
|
||||||
|
self.#field_name.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IdType::Composite { fields, field_type } => {
|
||||||
|
let field_names: Vec<syn::Ident> = fields.iter().map(|f| f.name.clone()).collect();
|
||||||
|
quote! {
|
||||||
|
fn get_id(&self) -> #field_type {
|
||||||
|
#field_type {
|
||||||
|
#(#field_names: self.#field_names),*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn derive_trait(
|
||||||
|
ast: &syn::DeriveInput,
|
||||||
|
table: &str,
|
||||||
|
fields: &[GeormField],
|
||||||
|
id: &IdType,
|
||||||
|
) -> proc_macro2::TokenStream {
|
||||||
|
let ty = match id {
|
||||||
|
IdType::Simple { field_type, .. } => quote! {#field_type},
|
||||||
|
IdType::Composite { field_type, .. } => quote! {#field_type},
|
||||||
|
};
|
||||||
|
|
||||||
|
// define impl variables
|
||||||
|
let ident = &ast.ident;
|
||||||
|
let (impl_generics, type_generics, where_clause) = ast.generics.split_for_impl();
|
||||||
|
|
||||||
|
// generate
|
||||||
|
let get_id = generate_get_id(id);
|
||||||
|
let get_all = find::generate_find_all_query(table);
|
||||||
|
let find_query = find::generate_find_query(table, id);
|
||||||
|
let create_query = create::generate_create_query(table, fields);
|
||||||
|
let update_query = update::generate_update_query(table, fields, id);
|
||||||
|
let upsert_query = upsert::generate_upsert_query(table, fields, id);
|
||||||
|
let delete_query = delete::generate_delete_query(table, id);
|
||||||
|
quote! {
|
||||||
|
impl #impl_generics Georm<#ty> for #ident #type_generics #where_clause {
|
||||||
|
#get_all
|
||||||
|
#get_id
|
||||||
|
#find_query
|
||||||
|
#create_query
|
||||||
|
#update_query
|
||||||
|
#upsert_query
|
||||||
|
#delete_query
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
georm-macros/src/georm/traits/update.rs
Normal file
48
georm-macros/src/georm/traits/update.rs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
use crate::georm::{GeormField, IdType};
|
||||||
|
use quote::quote;
|
||||||
|
|
||||||
|
pub fn generate_update_query(
|
||||||
|
table: &str,
|
||||||
|
fields: &[GeormField],
|
||||||
|
id: &IdType,
|
||||||
|
) -> proc_macro2::TokenStream {
|
||||||
|
let non_id_fields: Vec<syn::Ident> = fields
|
||||||
|
.iter()
|
||||||
|
.filter_map(|f| if f.id { None } else { Some(f.ident.clone()) })
|
||||||
|
.collect();
|
||||||
|
let update_columns = non_id_fields
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, field)| format!("{} = ${}", field, i + 1))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ");
|
||||||
|
let mut all_fields = non_id_fields.clone();
|
||||||
|
let where_clause = match id {
|
||||||
|
IdType::Simple { field_name, .. } => {
|
||||||
|
let where_clause = format!("{} = ${}", field_name, non_id_fields.len() + 1);
|
||||||
|
all_fields.push(field_name.clone());
|
||||||
|
where_clause
|
||||||
|
}
|
||||||
|
IdType::Composite { fields, .. } => fields
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, field)| {
|
||||||
|
let where_clause = format!("{} = ${}", field.name, non_id_fields.len() + i + 1);
|
||||||
|
all_fields.push(field.name.clone());
|
||||||
|
where_clause
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(" AND "),
|
||||||
|
};
|
||||||
|
let update_string =
|
||||||
|
format!("UPDATE {table} SET {update_columns} WHERE {where_clause} RETURNING *");
|
||||||
|
quote! {
|
||||||
|
async fn update(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<Self> {
|
||||||
|
::sqlx::query_as!(
|
||||||
|
Self, #update_string, #(self.#all_fields),*
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
georm-macros/src/georm/traits/upsert.rs
Normal file
53
georm-macros/src/georm/traits/upsert.rs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
use crate::georm::{GeormField, IdType};
|
||||||
|
use quote::quote;
|
||||||
|
|
||||||
|
pub fn generate_upsert_query(
|
||||||
|
table: &str,
|
||||||
|
fields: &[GeormField],
|
||||||
|
id: &IdType,
|
||||||
|
) -> proc_macro2::TokenStream {
|
||||||
|
let inputs: Vec<String> = (1..=fields.len()).map(|num| format!("${num}")).collect();
|
||||||
|
let columns = fields
|
||||||
|
.iter()
|
||||||
|
.map(|f| f.ident.to_string())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
|
let primary_key: proc_macro2::TokenStream = match id {
|
||||||
|
IdType::Simple { field_name, .. } => quote! {#field_name},
|
||||||
|
IdType::Composite { fields, .. } => {
|
||||||
|
let field_names: Vec<syn::Ident> = fields.iter().map(|f| f.name.clone()).collect();
|
||||||
|
quote! {
|
||||||
|
#(#field_names),*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// For ON CONFLICT DO UPDATE, exclude the ID field from updates
|
||||||
|
let update_assignments = fields
|
||||||
|
.iter()
|
||||||
|
.filter(|f| !f.id)
|
||||||
|
.map(|f| format!("{} = EXCLUDED.{}", f.ident, f.ident))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
|
let upsert_string = format!(
|
||||||
|
"INSERT INTO {table} ({columns}) VALUES ({}) ON CONFLICT ({}) DO UPDATE SET {update_assignments} RETURNING *",
|
||||||
|
inputs.join(", "),
|
||||||
|
primary_key
|
||||||
|
);
|
||||||
|
|
||||||
|
let field_idents: Vec<syn::Ident> = fields.iter().map(|f| f.ident.clone()).collect();
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
async fn create_or_update(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<Self> {
|
||||||
|
::sqlx::query_as!(
|
||||||
|
Self,
|
||||||
|
#upsert_string,
|
||||||
|
#(self.#field_idents),*
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user