mirror of
https://github.com/Phundrak/georm.git
synced 2025-11-30 19:03:59 +00:00
feat: add foreign one_to_one relationships
This commit is contained in:
@@ -1,195 +0,0 @@
|
||||
use quote::quote;
|
||||
|
||||
#[derive(deluxe::ExtractAttributes)]
|
||||
#[deluxe(attributes(georm))]
|
||||
pub struct GeormStructAttributes {
|
||||
pub table: String,
|
||||
#[deluxe(default = Vec::new())]
|
||||
pub one_to_many: Vec<O2MRelationship>,
|
||||
#[deluxe(default = Vec::new())]
|
||||
pub many_to_many: Vec<M2MRelationship>,
|
||||
}
|
||||
|
||||
#[derive(deluxe::ParseMetaItem)]
|
||||
pub struct O2MRelationship {
|
||||
pub name: String,
|
||||
pub remote_id: String,
|
||||
pub table: String,
|
||||
pub entity: syn::Type,
|
||||
}
|
||||
|
||||
impl From<&O2MRelationship> for proc_macro2::TokenStream {
|
||||
fn from(value: &O2MRelationship) -> Self {
|
||||
let query = format!(
|
||||
"SELECT * FROM {} WHERE {} = $1",
|
||||
value.table, value.remote_id
|
||||
);
|
||||
let entity = &value.entity;
|
||||
let function = syn::Ident::new(
|
||||
&format!("get_{}", value.name),
|
||||
proc_macro2::Span::call_site(),
|
||||
);
|
||||
quote! {
|
||||
pub async fn #function(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<Vec<#entity>> {
|
||||
::sqlx::query_as!(#entity, #query, self.get_id()).fetch_all(pool).await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(deluxe::ParseMetaItem, Clone)]
|
||||
pub struct M2MLink {
|
||||
pub table: String,
|
||||
pub from: String,
|
||||
pub to: String,
|
||||
}
|
||||
|
||||
#[derive(deluxe::ParseMetaItem)]
|
||||
pub struct M2MRelationship {
|
||||
pub name: String,
|
||||
pub entity: syn::Type,
|
||||
pub table: String,
|
||||
#[deluxe(default = String::from("id"))]
|
||||
pub remote_id: String,
|
||||
pub link: M2MLink,
|
||||
}
|
||||
|
||||
pub struct Identifier {
|
||||
pub table: String,
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
pub struct M2MRelationshipComplete {
|
||||
pub name: String,
|
||||
pub entity: syn::Type,
|
||||
pub local: Identifier,
|
||||
pub remote: Identifier,
|
||||
pub link: M2MLink,
|
||||
}
|
||||
|
||||
impl M2MRelationshipComplete {
|
||||
pub fn new(other: &M2MRelationship, local_table: &String, local_id: String) -> Self {
|
||||
Self {
|
||||
name: other.name.clone(),
|
||||
entity: other.entity.clone(),
|
||||
link: other.link.clone(),
|
||||
local: Identifier {
|
||||
table: local_table.to_string(),
|
||||
id: local_id,
|
||||
},
|
||||
remote: Identifier {
|
||||
table: other.table.clone(),
|
||||
id: other.remote_id.clone(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&M2MRelationshipComplete> for proc_macro2::TokenStream {
|
||||
fn from(value: &M2MRelationshipComplete) -> Self {
|
||||
let function = syn::Ident::new(
|
||||
&format!("get_{}", value.name),
|
||||
proc_macro2::Span::call_site(),
|
||||
);
|
||||
let entity = &value.entity;
|
||||
let query = format!(
|
||||
"SELECT remote.*
|
||||
FROM {} local
|
||||
JOIN {} link ON link.{} = local.{}
|
||||
JOIN {} remote ON link.{} = remote.{}
|
||||
WHERE local.{} = $1",
|
||||
value.local.table,
|
||||
value.link.table,
|
||||
value.link.from,
|
||||
value.local.id,
|
||||
value.remote.table,
|
||||
value.link.to,
|
||||
value.remote.id,
|
||||
value.local.id
|
||||
);
|
||||
quote! {
|
||||
pub async fn #function(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<Vec<#entity>> {
|
||||
::sqlx::query_as!(#entity, #query, self.get_id()).fetch_all(pool).await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(deluxe::ExtractAttributes, Clone)]
|
||||
#[deluxe(attributes(georm))]
|
||||
struct GeormFieldAttributes {
|
||||
#[deluxe(default = false)]
|
||||
pub id: bool,
|
||||
#[deluxe(default = None)]
|
||||
pub relation: Option<O2ORelationship>,
|
||||
}
|
||||
|
||||
#[derive(deluxe::ParseMetaItem, Clone, Debug)]
|
||||
pub struct O2ORelationship {
|
||||
pub entity: syn::Type,
|
||||
pub table: String,
|
||||
#[deluxe(default = String::from("id"))]
|
||||
pub remote_id: String,
|
||||
#[deluxe(default = false)]
|
||||
pub nullable: bool,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GeormField {
|
||||
pub ident: syn::Ident,
|
||||
pub field: syn::Field,
|
||||
pub ty: syn::Type,
|
||||
pub id: bool,
|
||||
pub relation: Option<O2ORelationship>,
|
||||
}
|
||||
|
||||
impl GeormField {
|
||||
pub fn new(field: &mut syn::Field) -> Self {
|
||||
let ident = field.clone().ident.unwrap();
|
||||
let ty = field.clone().ty;
|
||||
let attrs: GeormFieldAttributes =
|
||||
deluxe::extract_attributes(field).expect("Could not extract attributes from field");
|
||||
let GeormFieldAttributes { id, relation } = attrs;
|
||||
Self {
|
||||
ident,
|
||||
field: field.to_owned(),
|
||||
id,
|
||||
ty,
|
||||
relation,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
);
|
||||
let entity = &relation.entity;
|
||||
let return_type = if relation.nullable {
|
||||
quote! { Option<#entity> }
|
||||
} else {
|
||||
quote! { #entity }
|
||||
};
|
||||
let query = format!(
|
||||
"SELECT * FROM {} WHERE {} = $1",
|
||||
relation.table, relation.remote_id
|
||||
);
|
||||
let local_ident = &value.field.ident;
|
||||
let fetch = if relation.nullable {
|
||||
quote! { fetch_optional }
|
||||
} else {
|
||||
quote! { fetch_one }
|
||||
};
|
||||
quote! {
|
||||
pub async fn #function(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<#return_type> {
|
||||
::sqlx::query_as!(#entity, #query, self.#local_ident).#fetch(pool).await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
79
georm-macros/src/georm/ir/m2m_relationship.rs
Normal file
79
georm-macros/src/georm/ir/m2m_relationship.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
use quote::quote;
|
||||
|
||||
#[derive(deluxe::ParseMetaItem, Clone)]
|
||||
pub struct M2MLink {
|
||||
pub table: String,
|
||||
pub from: String,
|
||||
pub to: String,
|
||||
}
|
||||
|
||||
#[derive(deluxe::ParseMetaItem)]
|
||||
pub struct M2MRelationship {
|
||||
pub name: String,
|
||||
pub entity: syn::Type,
|
||||
pub table: String,
|
||||
#[deluxe(default = String::from("id"))]
|
||||
pub remote_id: String,
|
||||
pub link: M2MLink,
|
||||
}
|
||||
|
||||
pub struct Identifier {
|
||||
pub table: String,
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
pub struct M2MRelationshipComplete {
|
||||
pub name: String,
|
||||
pub entity: syn::Type,
|
||||
pub local: Identifier,
|
||||
pub remote: Identifier,
|
||||
pub link: M2MLink,
|
||||
}
|
||||
|
||||
impl M2MRelationshipComplete {
|
||||
pub fn new(other: &M2MRelationship, local_table: &String, local_id: String) -> Self {
|
||||
Self {
|
||||
name: other.name.clone(),
|
||||
entity: other.entity.clone(),
|
||||
link: other.link.clone(),
|
||||
local: Identifier {
|
||||
table: local_table.to_string(),
|
||||
id: local_id,
|
||||
},
|
||||
remote: Identifier {
|
||||
table: other.table.clone(),
|
||||
id: other.remote_id.clone(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&M2MRelationshipComplete> for proc_macro2::TokenStream {
|
||||
fn from(value: &M2MRelationshipComplete) -> Self {
|
||||
let function = syn::Ident::new(
|
||||
&format!("get_{}", value.name),
|
||||
proc_macro2::Span::call_site(),
|
||||
);
|
||||
let entity = &value.entity;
|
||||
let query = format!(
|
||||
"SELECT remote.*
|
||||
FROM {} local
|
||||
JOIN {} link ON link.{} = local.{}
|
||||
JOIN {} remote ON link.{} = remote.{}
|
||||
WHERE local.{} = $1",
|
||||
value.local.table,
|
||||
value.link.table,
|
||||
value.link.from,
|
||||
value.local.id,
|
||||
value.remote.table,
|
||||
value.link.to,
|
||||
value.remote.id,
|
||||
value.local.id
|
||||
);
|
||||
quote! {
|
||||
pub async fn #function(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<Vec<#entity>> {
|
||||
::sqlx::query_as!(#entity, #query, self.get_id()).fetch_all(pool).await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
98
georm-macros/src/georm/ir/mod.rs
Normal file
98
georm-macros/src/georm/ir/mod.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
use quote::quote;
|
||||
|
||||
pub mod simple_relationship;
|
||||
use simple_relationship::{OneToMany, OneToOne, SimpleRelationship};
|
||||
|
||||
pub mod m2m_relationship;
|
||||
use m2m_relationship::M2MRelationship;
|
||||
|
||||
#[derive(deluxe::ExtractAttributes)]
|
||||
#[deluxe(attributes(georm))]
|
||||
pub struct GeormStructAttributes {
|
||||
pub table: String,
|
||||
#[deluxe(default = Vec::new())]
|
||||
pub one_to_one: Vec<SimpleRelationship<OneToOne>>,
|
||||
#[deluxe(default = Vec::new())]
|
||||
pub one_to_many: Vec<SimpleRelationship<OneToMany>>,
|
||||
#[deluxe(default = Vec::new())]
|
||||
pub many_to_many: Vec<M2MRelationship>,
|
||||
}
|
||||
|
||||
#[derive(deluxe::ExtractAttributes, Clone)]
|
||||
#[deluxe(attributes(georm))]
|
||||
struct GeormFieldAttributes {
|
||||
#[deluxe(default = false)]
|
||||
pub id: bool,
|
||||
#[deluxe(default = None)]
|
||||
pub relation: Option<O2ORelationship>,
|
||||
}
|
||||
|
||||
#[derive(deluxe::ParseMetaItem, Clone, Debug)]
|
||||
pub struct O2ORelationship {
|
||||
pub entity: syn::Type,
|
||||
pub table: String,
|
||||
#[deluxe(default = String::from("id"))]
|
||||
pub remote_id: String,
|
||||
#[deluxe(default = false)]
|
||||
pub nullable: bool,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GeormField {
|
||||
pub ident: syn::Ident,
|
||||
pub field: syn::Field,
|
||||
pub ty: syn::Type,
|
||||
pub id: bool,
|
||||
pub relation: Option<O2ORelationship>,
|
||||
}
|
||||
|
||||
impl GeormField {
|
||||
pub fn new(field: &mut syn::Field) -> Self {
|
||||
let ident = field.clone().ident.unwrap();
|
||||
let ty = field.clone().ty;
|
||||
let attrs: GeormFieldAttributes =
|
||||
deluxe::extract_attributes(field).expect("Could not extract attributes from field");
|
||||
let GeormFieldAttributes { id, relation } = attrs;
|
||||
Self {
|
||||
ident,
|
||||
field: field.to_owned(),
|
||||
id,
|
||||
ty,
|
||||
relation,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
);
|
||||
let entity = &relation.entity;
|
||||
let return_type = if relation.nullable {
|
||||
quote! { Option<#entity> }
|
||||
} else {
|
||||
quote! { #entity }
|
||||
};
|
||||
let query = format!(
|
||||
"SELECT * FROM {} WHERE {} = $1",
|
||||
relation.table, relation.remote_id
|
||||
);
|
||||
let local_ident = &value.field.ident;
|
||||
let fetch = if relation.nullable {
|
||||
quote! { fetch_optional }
|
||||
} else {
|
||||
quote! { fetch_one }
|
||||
};
|
||||
quote! {
|
||||
pub async fn #function(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<#return_type> {
|
||||
::sqlx::query_as!(#entity, #query, self.#local_ident).#fetch(pool).await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
66
georm-macros/src/georm/ir/simple_relationship.rs
Normal file
66
georm-macros/src/georm/ir/simple_relationship.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
use quote::quote;
|
||||
|
||||
pub trait SimpleRelationshipType {}
|
||||
|
||||
#[derive(deluxe::ParseMetaItem, Default)]
|
||||
pub struct OneToOne;
|
||||
impl SimpleRelationshipType for OneToOne {}
|
||||
|
||||
#[derive(deluxe::ParseMetaItem, Default)]
|
||||
pub struct OneToMany;
|
||||
impl SimpleRelationshipType for OneToMany {}
|
||||
|
||||
#[derive(deluxe::ParseMetaItem)]
|
||||
pub struct SimpleRelationship<T>
|
||||
where
|
||||
T: SimpleRelationshipType + deluxe::ParseMetaItem + Default,
|
||||
{
|
||||
pub name: String,
|
||||
pub remote_id: String,
|
||||
pub table: String,
|
||||
pub entity: syn::Type,
|
||||
#[deluxe(default = T::default())]
|
||||
_phantom: T,
|
||||
}
|
||||
|
||||
impl<T> SimpleRelationship<T>
|
||||
where
|
||||
T: SimpleRelationshipType + deluxe::ParseMetaItem + Default,
|
||||
{
|
||||
pub fn make_query(&self) -> String {
|
||||
format!("SELECT * FROM {} WHERE {} = $1", self.table, self.remote_id)
|
||||
}
|
||||
|
||||
pub fn make_function_name(&self) -> syn::Ident {
|
||||
syn::Ident::new(
|
||||
&format!("get_{}", self.name),
|
||||
proc_macro2::Span::call_site(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SimpleRelationship<OneToOne>> for proc_macro2::TokenStream {
|
||||
fn from(value: &SimpleRelationship<OneToOne>) -> Self {
|
||||
let query = value.make_query();
|
||||
let entity = &value.entity;
|
||||
let function = value.make_function_name();
|
||||
quote! {
|
||||
pub async fn #function(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<Option<#entity>> {
|
||||
::sqlx::query_as!(#entity, #query, self.get_id()).fetch_optional(pool).await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SimpleRelationship<OneToMany>> for proc_macro2::TokenStream {
|
||||
fn from(value: &SimpleRelationship<OneToMany>) -> Self {
|
||||
let query = value.make_query();
|
||||
let entity = &value.entity;
|
||||
let function = value.make_function_name();
|
||||
quote! {
|
||||
pub async fn #function(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<Vec<#entity>> {
|
||||
::sqlx::query_as!(#entity, #query, self.get_id()).fetch_all(pool).await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::georm::ir::M2MRelationshipComplete;
|
||||
use crate::georm::ir::m2m_relationship::M2MRelationshipComplete;
|
||||
|
||||
use super::ir::GeormField;
|
||||
use proc_macro2::TokenStream;
|
||||
@@ -15,16 +15,12 @@ fn join_token_streams(token_streams: &[TokenStream]) -> TokenStream {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn derive<T, P>(relationships: &[T], condition: P) -> TokenStream
|
||||
fn derive<T>(relationships: &[T]) -> TokenStream
|
||||
where
|
||||
for<'a> &'a T: Into<TokenStream>,
|
||||
P: FnMut(&&T) -> bool,
|
||||
{
|
||||
let implementations: Vec<TokenStream> = relationships
|
||||
.iter()
|
||||
.filter(condition)
|
||||
.map(std::convert::Into::into)
|
||||
.collect();
|
||||
let implementations: Vec<TokenStream> =
|
||||
relationships.iter().map(std::convert::Into::into).collect();
|
||||
join_token_streams(&implementations)
|
||||
}
|
||||
|
||||
@@ -35,18 +31,20 @@ pub fn derive_relationships(
|
||||
id: &GeormField,
|
||||
) -> TokenStream {
|
||||
let struct_name = &ast.ident;
|
||||
let one_to_one = derive(fields, |field| field.relation.is_some());
|
||||
let one_to_many = derive(&struct_attrs.one_to_many, |_| true);
|
||||
let one_to_one_local = derive(fields);
|
||||
let one_to_one_remote = derive(&struct_attrs.one_to_one);
|
||||
let one_to_many = derive(&struct_attrs.one_to_many);
|
||||
let many_to_many: Vec<M2MRelationshipComplete> = struct_attrs
|
||||
.many_to_many
|
||||
.iter()
|
||||
.map(|v| M2MRelationshipComplete::new(v, &struct_attrs.table, id.ident.to_string()))
|
||||
.collect();
|
||||
let many_to_many = derive(&many_to_many, |_| true);
|
||||
let many_to_many = derive(&many_to_many);
|
||||
|
||||
quote! {
|
||||
impl #struct_name {
|
||||
#one_to_one
|
||||
#one_to_one_local
|
||||
#one_to_one_remote
|
||||
#one_to_many
|
||||
#many_to_many
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user