mirror of
https://github.com/Phundrak/georm.git
synced 2025-11-30 19:03:59 +00:00
fix: simple ORM for one struct and foreign references work
Currently, all methods declared in the Georm trait are available. If a struct has an ID pointing towards another entity, the user can create a get method to get the entity pointed at from the database too (local one-to-one relationship). I still need to implement remote one-to-one relationships (one-to-one relationships when the ID of the remote object is not available locally). I still need to also test and debug one-to-many relationships (ID of the remote entiies not available locally) and many-to-many relationships (declared in a dedicated table). For now, IDs in all cases are simple types recognized by SQLx that are not arrays. Options are only supported when explicitely specified for one-to-one relationships.
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
use quote::quote;
|
||||
use std::fmt::{self, Display};
|
||||
|
||||
#[derive(deluxe::ExtractAttributes)]
|
||||
#[deluxe(attributes(georm))]
|
||||
@@ -32,7 +31,7 @@ impl From<&O2MRelationship> for proc_macro2::TokenStream {
|
||||
);
|
||||
quote! {
|
||||
pub async fn #function(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<Vec<#entity>> {
|
||||
query_as!(#entity, #query, self.get_id()).fetch_all(pool).await
|
||||
::sqlx::query_as!(#entity, #query, self.get_id()).fetch_all(pool).await
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,7 +121,7 @@ WHERE local.{} = $1
|
||||
);
|
||||
quote! {
|
||||
pub async fn #function(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<Vec<#entity>> {
|
||||
query_as!(#entity, #query, self.get_id()).fetch_all(pool).await
|
||||
::sqlx::query_as!(#entity, #query, self.get_id()).fetch_all(pool).await
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,16 +133,11 @@ struct GeormFieldAttributes {
|
||||
#[deluxe(default = false)]
|
||||
pub id: bool,
|
||||
#[deluxe(default = None)]
|
||||
pub column: Option<String>,
|
||||
#[deluxe(default = None)]
|
||||
pub relation: Option<O2ORelationship>,
|
||||
}
|
||||
|
||||
// #[georm(
|
||||
// table = "profileId",
|
||||
// one_to_one = { name = profile, id = "id", entity = Profile, nullable }
|
||||
// )]
|
||||
#[derive(deluxe::ParseMetaItem, Clone)]
|
||||
// #[georm(relation = { name = profile, id = "id", entity = Profile, nullable })]
|
||||
#[derive(deluxe::ParseMetaItem, Clone, Debug)]
|
||||
pub struct O2ORelationship {
|
||||
pub entity: syn::Type,
|
||||
pub table: String,
|
||||
@@ -154,12 +148,11 @@ pub struct O2ORelationship {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GeormField {
|
||||
pub ident: syn::Ident,
|
||||
pub field: syn::Field,
|
||||
pub ty: syn::Type,
|
||||
pub column: Option<String>,
|
||||
pub id: bool,
|
||||
pub relation: Option<O2ORelationship>,
|
||||
}
|
||||
@@ -170,40 +163,22 @@ impl GeormField {
|
||||
let ty = field.clone().ty;
|
||||
let attrs: GeormFieldAttributes =
|
||||
deluxe::extract_attributes(field).expect("Could not extract attributes from field");
|
||||
let GeormFieldAttributes {
|
||||
id,
|
||||
column,
|
||||
relation,
|
||||
} = attrs;
|
||||
let GeormFieldAttributes { id, relation } = attrs;
|
||||
Self {
|
||||
ident,
|
||||
field: field.to_owned(),
|
||||
id,
|
||||
ty,
|
||||
relation,
|
||||
column,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for GeormField {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
self.column
|
||||
.clone()
|
||||
.unwrap_or_else(|| self.ident.to_string())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&GeormField> for proc_macro2::TokenStream {
|
||||
fn from(value: &GeormField) -> Self {
|
||||
let Some(relation) = value.relation.clone() else {
|
||||
return quote! {};
|
||||
};
|
||||
|
||||
let function = syn::Ident::new(
|
||||
&format!("get_{}", relation.name),
|
||||
proc_macro2::Span::call_site(),
|
||||
@@ -225,8 +200,8 @@ impl From<&GeormField> for proc_macro2::TokenStream {
|
||||
quote! { fetch_one }
|
||||
};
|
||||
quote! {
|
||||
pub async fn #function(&value, pool: &::sqlx::PgPool) -> ::sqlx::Result<#return_type> {
|
||||
query_as!(#entity, #query, value.#local_ident).#fetch(pool).await
|
||||
pub async fn #function(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<#return_type> {
|
||||
::sqlx::query_as!(#entity, #query, self.#local_ident).#fetch(pool).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,12 +49,11 @@ pub fn georm_derive_macro2(
|
||||
let struct_attrs: ir::GeormStructAttributes =
|
||||
deluxe::extract_attributes(&mut ast).expect("Could not extract attributes from struct");
|
||||
let (fields, id) = extract_georm_field_attrs(&mut ast)?;
|
||||
let trait_impl = trait_implementation::derive_trait(&ast, &struct_attrs.table, &fields, &id);
|
||||
let relationships = relationships::derive_relationships(&ast, &struct_attrs, &fields, &id);
|
||||
let trait_impl = trait_implementation::derive_trait(&ast, &struct_attrs.table, &fields, &id);
|
||||
let code = quote! {
|
||||
#trait_impl
|
||||
#relationships
|
||||
#trait_impl
|
||||
};
|
||||
println!("{code}");
|
||||
Ok(code)
|
||||
}
|
||||
|
||||
@@ -35,12 +35,12 @@ pub fn derive_relationships(
|
||||
id: &GeormField,
|
||||
) -> TokenStream {
|
||||
let struct_name = &ast.ident;
|
||||
let one_to_one = derive(fields, |field| field.relation.is_none());
|
||||
let one_to_one = derive(fields, |field| field.relation.is_some());
|
||||
let one_to_many = derive(&struct_attrs.one_to_many, |_| true);
|
||||
let many_to_many: Vec<M2MRelationshipComplete> = struct_attrs
|
||||
.many_to_many
|
||||
.iter()
|
||||
.map(|v| M2MRelationshipComplete::new(v, &struct_attrs.table, id.to_string()))
|
||||
.map(|v| M2MRelationshipComplete::new(v, &struct_attrs.table, id.ident.to_string()))
|
||||
.collect();
|
||||
let many_to_many = derive(&many_to_many, |_| true);
|
||||
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
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: &GeormField) -> proc_macro2::TokenStream {
|
||||
let find_string = format!("SELECT * FROM {table} WHERE {id} = $1",);
|
||||
let find_string = format!("SELECT * FROM {table} WHERE {} = $1", id.ident);
|
||||
let ty = &id.ty;
|
||||
quote! {
|
||||
async fn find(pool: &::sqlx::PgPool, id: &#ty) -> ::sqlx::Result<Option<Self>> {
|
||||
@@ -19,7 +28,7 @@ fn generate_create_query(table: &str, fields: &[GeormField]) -> proc_macro2::Tok
|
||||
"INSERT INTO {table} ({}) VALUES ({}) RETURNING *",
|
||||
fields
|
||||
.iter()
|
||||
.map(std::string::ToString::to_string)
|
||||
.map(|f| f.ident.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", "),
|
||||
inputs.join(", ")
|
||||
@@ -47,11 +56,12 @@ fn generate_update_query(
|
||||
let update_columns = fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, &field)| format!("{field} = ${}", i + 1))
|
||||
.map(|(i, &field)| format!("{} = ${}", field.ident, i + 1))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
let update_string = format!(
|
||||
"UPDATE {table} SET {update_columns} WHERE {id} = ${} RETURNING *",
|
||||
"UPDATE {table} SET {update_columns} WHERE {} = ${} RETURNING *",
|
||||
id.ident,
|
||||
fields.len() + 1
|
||||
);
|
||||
fields.push(id);
|
||||
@@ -70,7 +80,7 @@ fn generate_update_query(
|
||||
}
|
||||
|
||||
fn generate_delete_query(table: &str, id: &GeormField) -> proc_macro2::TokenStream {
|
||||
let delete_string = format!("DELETE FROM {table} WHERE {id} = $1");
|
||||
let delete_string = format!("DELETE FROM {table} WHERE {} = $1", id.ident);
|
||||
let ty = &id.ty;
|
||||
quote! {
|
||||
async fn delete_by_id(pool: &::sqlx::PgPool, id: &#ty) -> ::sqlx::Result<u64> {
|
||||
@@ -104,13 +114,13 @@ pub fn derive_trait(
|
||||
id: &GeormField,
|
||||
) -> proc_macro2::TokenStream {
|
||||
let ty = &id.ty;
|
||||
let id_ident = &id.ident;
|
||||
|
||||
// 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);
|
||||
@@ -118,19 +128,11 @@ pub fn derive_trait(
|
||||
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
|
||||
|
||||
async fn create_or_update(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<Self> {
|
||||
if Self::find(pool, &self.#id_ident).await?.is_some() {
|
||||
self.update(pool).await
|
||||
} else {
|
||||
self.create(pool).await
|
||||
}
|
||||
}
|
||||
|
||||
#delete_query
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user