mirror of
				https://github.com/Phundrak/georm.git
				synced 2025-11-04 01:11:10 +00:00 
			
		
		
		
	chore: migrate source code from old repo to this repo
This commit is contained in:
		
							parent
							
								
									39f757991a
								
							
						
					
					
						commit
						96ac2aa979
					
				
							
								
								
									
										1886
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										1886
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										17
									
								
								georm-macros/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								georm-macros/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					[package]
 | 
				
			||||||
 | 
					name = "georm-macros"
 | 
				
			||||||
 | 
					description = "Macro support for Georm. Not intended to be used directly."
 | 
				
			||||||
 | 
					version.workspace = true
 | 
				
			||||||
 | 
					license.workspace = true
 | 
				
			||||||
 | 
					edition.workspace = true
 | 
				
			||||||
 | 
					authors.workspace = true
 | 
				
			||||||
 | 
					repository.workspace = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[lib]
 | 
				
			||||||
 | 
					proc-macro = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[dependencies]
 | 
				
			||||||
 | 
					deluxe = "0.5.0"
 | 
				
			||||||
 | 
					proc-macro2 = "1.0.93"
 | 
				
			||||||
 | 
					quote = "1.0.38"
 | 
				
			||||||
 | 
					syn = "2.0.96"
 | 
				
			||||||
							
								
								
									
										233
									
								
								georm-macros/src/georm/ir.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										233
									
								
								georm-macros/src/georm/ir.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,233 @@
 | 
				
			|||||||
 | 
					use quote::quote;
 | 
				
			||||||
 | 
					use std::fmt::{self, Display};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[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>> {
 | 
				
			||||||
 | 
					                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,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//#[georm(
 | 
				
			||||||
 | 
					//    table = "users",
 | 
				
			||||||
 | 
					//    many_to_many = [
 | 
				
			||||||
 | 
					//        {
 | 
				
			||||||
 | 
					//            name = friends,
 | 
				
			||||||
 | 
					//            entity: User,
 | 
				
			||||||
 | 
					//            link = { table = "user_friendships", from: "user1", to "user2" }
 | 
				
			||||||
 | 
					//        }
 | 
				
			||||||
 | 
					//    ]
 | 
				
			||||||
 | 
					//)]
 | 
				
			||||||
 | 
					#[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>> {
 | 
				
			||||||
 | 
					                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 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)]
 | 
				
			||||||
 | 
					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)]
 | 
				
			||||||
 | 
					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>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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,
 | 
				
			||||||
 | 
					            column,
 | 
				
			||||||
 | 
					            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(),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        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(&value, pool: &::sqlx::PgPool) -> ::sqlx::Result<#return_type> {
 | 
				
			||||||
 | 
					                query_as!(#entity, #query, value.#local_ident).#fetch(pool).await
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										60
									
								
								georm-macros/src/georm/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								georm-macros/src/georm/mod.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,60 @@
 | 
				
			|||||||
 | 
					use ir::GeormField;
 | 
				
			||||||
 | 
					use quote::quote;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mod ir;
 | 
				
			||||||
 | 
					mod relationships;
 | 
				
			||||||
 | 
					mod trait_implementation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn extract_georm_field_attrs(
 | 
				
			||||||
 | 
					    ast: &mut syn::DeriveInput,
 | 
				
			||||||
 | 
					) -> deluxe::Result<(Vec<GeormField>, GeormField)> {
 | 
				
			||||||
 | 
					    let syn::Data::Struct(s) = &mut ast.data else {
 | 
				
			||||||
 | 
					        return Err(syn::Error::new_spanned(
 | 
				
			||||||
 | 
					            ast,
 | 
				
			||||||
 | 
					            "Cannot apply to something other than a struct",
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    let fields = s
 | 
				
			||||||
 | 
					        .fields
 | 
				
			||||||
 | 
					        .clone()
 | 
				
			||||||
 | 
					        .into_iter()
 | 
				
			||||||
 | 
					        .map(|mut field| GeormField::new(&mut field))
 | 
				
			||||||
 | 
					        .collect::<Vec<GeormField>>();
 | 
				
			||||||
 | 
					    let identifiers: Vec<GeormField> = fields
 | 
				
			||||||
 | 
					        .clone()
 | 
				
			||||||
 | 
					        .into_iter()
 | 
				
			||||||
 | 
					        .filter(|field| field.id)
 | 
				
			||||||
 | 
					        .collect();
 | 
				
			||||||
 | 
					    match identifiers.len() {
 | 
				
			||||||
 | 
					        0 => Err(syn::Error::new_spanned(
 | 
				
			||||||
 | 
					            ast,
 | 
				
			||||||
 | 
					            "Struct {name} must have one identifier",
 | 
				
			||||||
 | 
					        )),
 | 
				
			||||||
 | 
					        1 => Ok((fields, identifiers.first().unwrap().clone())),
 | 
				
			||||||
 | 
					        _ => {
 | 
				
			||||||
 | 
					            let id1 = identifiers.first().unwrap();
 | 
				
			||||||
 | 
					            let id2 = identifiers.get(1).unwrap();
 | 
				
			||||||
 | 
					            Err(syn::Error::new_spanned(id2.field.clone(), format!(
 | 
				
			||||||
 | 
					                "Field {} cannot be an identifier, {} already is one.\nOnly one identifier is supported.",
 | 
				
			||||||
 | 
					                id1.ident, id2.ident
 | 
				
			||||||
 | 
					            )))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn georm_derive_macro2(
 | 
				
			||||||
 | 
					    item: proc_macro2::TokenStream,
 | 
				
			||||||
 | 
					) -> deluxe::Result<proc_macro2::TokenStream> {
 | 
				
			||||||
 | 
					    let mut ast: syn::DeriveInput = syn::parse2(item).expect("Failed to parse input");
 | 
				
			||||||
 | 
					    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 code = quote! {
 | 
				
			||||||
 | 
					        #trait_impl
 | 
				
			||||||
 | 
					        #relationships
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    println!("{code}");
 | 
				
			||||||
 | 
					    Ok(code)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										54
									
								
								georm-macros/src/georm/relationships.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								georm-macros/src/georm/relationships.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,54 @@
 | 
				
			|||||||
 | 
					use std::str::FromStr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::georm::ir::M2MRelationshipComplete;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::ir::GeormField;
 | 
				
			||||||
 | 
					use proc_macro2::TokenStream;
 | 
				
			||||||
 | 
					use quote::quote;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn join_token_streams(token_streams: &[TokenStream]) -> TokenStream {
 | 
				
			||||||
 | 
					    let newline = TokenStream::from_str("\n").unwrap();
 | 
				
			||||||
 | 
					    token_streams
 | 
				
			||||||
 | 
					        .iter()
 | 
				
			||||||
 | 
					        .cloned()
 | 
				
			||||||
 | 
					        .flat_map(|ts| std::iter::once(ts).chain(std::iter::once(newline.clone())))
 | 
				
			||||||
 | 
					        .collect()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn derive<T, P>(relationships: &[T], condition: P) -> 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();
 | 
				
			||||||
 | 
					    join_token_streams(&implementations)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn derive_relationships(
 | 
				
			||||||
 | 
					    ast: &syn::DeriveInput,
 | 
				
			||||||
 | 
					    struct_attrs: &super::ir::GeormStructAttributes,
 | 
				
			||||||
 | 
					    fields: &[GeormField],
 | 
				
			||||||
 | 
					    id: &GeormField,
 | 
				
			||||||
 | 
					) -> TokenStream {
 | 
				
			||||||
 | 
					    let struct_name = &ast.ident;
 | 
				
			||||||
 | 
					    let one_to_one = derive(fields, |field| field.relation.is_none());
 | 
				
			||||||
 | 
					    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()))
 | 
				
			||||||
 | 
					        .collect();
 | 
				
			||||||
 | 
					    let many_to_many = derive(&many_to_many, |_| true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    quote! {
 | 
				
			||||||
 | 
					        impl #struct_name {
 | 
				
			||||||
 | 
					            #one_to_one
 | 
				
			||||||
 | 
					            #one_to_many
 | 
				
			||||||
 | 
					            #many_to_many
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										137
									
								
								georm-macros/src/georm/trait_implementation.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								georm-macros/src/georm/trait_implementation.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,137 @@
 | 
				
			|||||||
 | 
					use super::ir::GeormField;
 | 
				
			||||||
 | 
					use quote::quote;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn generate_find_query(table: &str, id: &GeormField) -> proc_macro2::TokenStream {
 | 
				
			||||||
 | 
					    let find_string = format!("SELECT * FROM {table} WHERE {id} = $1",);
 | 
				
			||||||
 | 
					    let ty = &id.ty;
 | 
				
			||||||
 | 
					    quote! {
 | 
				
			||||||
 | 
					        async fn find(pool: &::sqlx::PgPool, id: &#ty) -> ::sqlx::Result<Option<Self>> {
 | 
				
			||||||
 | 
					            ::sqlx::query_as!(Self, #find_string, id)
 | 
				
			||||||
 | 
					                .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(std::string::ToString::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: &GeormField,
 | 
				
			||||||
 | 
					) -> proc_macro2::TokenStream {
 | 
				
			||||||
 | 
					    let mut fields: Vec<&GeormField> = fields.iter().filter(|f| !f.id).collect();
 | 
				
			||||||
 | 
					    let update_columns = fields
 | 
				
			||||||
 | 
					        .iter()
 | 
				
			||||||
 | 
					        .enumerate()
 | 
				
			||||||
 | 
					        .map(|(i, &field)| format!("{field} = ${}", i + 1))
 | 
				
			||||||
 | 
					        .collect::<Vec<String>>()
 | 
				
			||||||
 | 
					        .join(", ");
 | 
				
			||||||
 | 
					    let update_string = format!(
 | 
				
			||||||
 | 
					        "UPDATE {table} SET {update_columns} WHERE {id} = ${} RETURNING *",
 | 
				
			||||||
 | 
					        fields.len() + 1
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    fields.push(id);
 | 
				
			||||||
 | 
					    let field_idents: Vec<_> = fields.iter().map(|f| f.ident.clone()).collect();
 | 
				
			||||||
 | 
					    quote! {
 | 
				
			||||||
 | 
					        async fn update(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result<Self> {
 | 
				
			||||||
 | 
					            ::sqlx::query_as!(
 | 
				
			||||||
 | 
					                Self,
 | 
				
			||||||
 | 
					                #update_string,
 | 
				
			||||||
 | 
					                #(self.#field_idents),*
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .fetch_one(pool)
 | 
				
			||||||
 | 
					            .await
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn generate_delete_query(table: &str, id: &GeormField) -> proc_macro2::TokenStream {
 | 
				
			||||||
 | 
					    let delete_string = format!("DELETE FROM {table} WHERE {id} = $1");
 | 
				
			||||||
 | 
					    let ty = &id.ty;
 | 
				
			||||||
 | 
					    quote! {
 | 
				
			||||||
 | 
					        async fn delete_by_id(pool: &::sqlx::PgPool, id: &#ty) -> ::sqlx::Result<u64> {
 | 
				
			||||||
 | 
					            let rows_affected = ::sqlx::query!(#delete_string, id)
 | 
				
			||||||
 | 
					                .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_get_id(id: &GeormField) -> proc_macro2::TokenStream {
 | 
				
			||||||
 | 
					    let ident = &id.ident;
 | 
				
			||||||
 | 
					    let ty = &id.ty;
 | 
				
			||||||
 | 
					    quote! {
 | 
				
			||||||
 | 
					        fn get_id(&self) -> &#ty {
 | 
				
			||||||
 | 
					            &self.#ident
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn derive_trait(
 | 
				
			||||||
 | 
					    ast: &syn::DeriveInput,
 | 
				
			||||||
 | 
					    table: &str,
 | 
				
			||||||
 | 
					    fields: &[GeormField],
 | 
				
			||||||
 | 
					    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_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 delete_query = generate_delete_query(table, id);
 | 
				
			||||||
 | 
					    quote! {
 | 
				
			||||||
 | 
					        impl #impl_generics Georm<#ty> for #ident #type_generics #where_clause {
 | 
				
			||||||
 | 
					            #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
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										82
									
								
								georm-macros/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								georm-macros/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,82 @@
 | 
				
			|||||||
 | 
					//! Creates ORM functionality for ``SQLx`` with `PostgreSQL`.
 | 
				
			||||||
 | 
					//!
 | 
				
			||||||
 | 
					//! This crate provides the trait implementation `Georm` which
 | 
				
			||||||
 | 
					//! generates the following ``SQLx`` queries:
 | 
				
			||||||
 | 
					//! - find an entity by id
 | 
				
			||||||
 | 
					//!
 | 
				
			||||||
 | 
					//!   SQL query: `SELECT * FROM ... WHERE <id> = ...`
 | 
				
			||||||
 | 
					//! - insert an entity into the database
 | 
				
			||||||
 | 
					//!
 | 
				
			||||||
 | 
					//!   SQL query: `INSERT INTO ... (...) VALUES (...) RETURNING *`
 | 
				
			||||||
 | 
					//! - update an entity in the database
 | 
				
			||||||
 | 
					//!
 | 
				
			||||||
 | 
					//!   SQL query: `UPDATE ... SET ... WHERE <id> = ... RETURNING *`
 | 
				
			||||||
 | 
					//! - delete an entity from the database using its id or an id
 | 
				
			||||||
 | 
					//!   provided by the interface’s user
 | 
				
			||||||
 | 
					//!
 | 
				
			||||||
 | 
					//!   SQL query: `DELETE FROM ... WHERE <id> = ...`
 | 
				
			||||||
 | 
					//! - update an entity or create it if it does not already exist in
 | 
				
			||||||
 | 
					//!   the database
 | 
				
			||||||
 | 
					//!
 | 
				
			||||||
 | 
					//! This macro relies on the trait `Georm` found in the `georm`
 | 
				
			||||||
 | 
					//! crate.
 | 
				
			||||||
 | 
					//!
 | 
				
			||||||
 | 
					//! To use this macro, you need to add it to the derives of the
 | 
				
			||||||
 | 
					//! struct. You will also need to define its identifier
 | 
				
			||||||
 | 
					//!
 | 
				
			||||||
 | 
					//! # Usage
 | 
				
			||||||
 | 
					//!
 | 
				
			||||||
 | 
					//! Add `#[georm(table = "my_table_name")]` atop of the structure,
 | 
				
			||||||
 | 
					//! after the `Georm` derive.
 | 
				
			||||||
 | 
					//!
 | 
				
			||||||
 | 
					//! ## Entity Identifier
 | 
				
			||||||
 | 
					//! You will also need to add `#[georm(id)]` atop of the field of your
 | 
				
			||||||
 | 
					//! struct that will be used as the identifier of your entity.
 | 
				
			||||||
 | 
					//!
 | 
				
			||||||
 | 
					//! ## Column Name
 | 
				
			||||||
 | 
					//! If the name of a field does not match the name of its related
 | 
				
			||||||
 | 
					//! column, you can use `#[georm(column = "...")]` to specify the
 | 
				
			||||||
 | 
					//! correct value.
 | 
				
			||||||
 | 
					//!
 | 
				
			||||||
 | 
					//! ```ignore
 | 
				
			||||||
 | 
					//! #[derive(Georm)]
 | 
				
			||||||
 | 
					//! #[georm(table = "users")]
 | 
				
			||||||
 | 
					//! pub struct User {
 | 
				
			||||||
 | 
					//!     #[georm(id)]
 | 
				
			||||||
 | 
					//!     id: String,
 | 
				
			||||||
 | 
					//!     #[georm(column = "name")]
 | 
				
			||||||
 | 
					//!     username: String,
 | 
				
			||||||
 | 
					//!     created_at: Timestampz,
 | 
				
			||||||
 | 
					//!     last_updated: Timestampz,
 | 
				
			||||||
 | 
					//! }
 | 
				
			||||||
 | 
					//! ```
 | 
				
			||||||
 | 
					//!
 | 
				
			||||||
 | 
					//! With the example of the `User` struct, this links it to the
 | 
				
			||||||
 | 
					//! `users` table of the connected database. It will use `Users.id` to
 | 
				
			||||||
 | 
					//! uniquely identify a user entity.
 | 
				
			||||||
 | 
					//!
 | 
				
			||||||
 | 
					//! # Limitations
 | 
				
			||||||
 | 
					//! ## ID
 | 
				
			||||||
 | 
					//! For now, only one identifier is supported. It does not have to be
 | 
				
			||||||
 | 
					//! a primary key, but it is strongly encouraged to use Georm ID on a
 | 
				
			||||||
 | 
					//! unique and non-null column of your database schema.
 | 
				
			||||||
 | 
					//!
 | 
				
			||||||
 | 
					//! ## Database type
 | 
				
			||||||
 | 
					//!
 | 
				
			||||||
 | 
					//! For now, only the ``PostgreSQL`` syntax is supported. If you use
 | 
				
			||||||
 | 
					//! another database that uses the same syntax, you’re in luck!
 | 
				
			||||||
 | 
					//! Otherwise, pull requests to add additional syntaxes are most
 | 
				
			||||||
 | 
					//! welcome.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mod georm;
 | 
				
			||||||
 | 
					use georm::georm_derive_macro2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Generates GEORM code for Sqlx for a struct.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// # Panics
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// May panic if errors arise while parsing and generating code.
 | 
				
			||||||
 | 
					#[proc_macro_derive(Georm, attributes(georm))]
 | 
				
			||||||
 | 
					pub fn georm_derive_macro(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
 | 
				
			||||||
 | 
					    georm_derive_macro2(item.into()).unwrap().into()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										75
									
								
								src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								src/lib.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,75 @@
 | 
				
			|||||||
 | 
					pub use georm_macros::Georm;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub trait Georm<Id> {
 | 
				
			||||||
 | 
					    /// Find the entiy in the database based on its identifier.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// # Errors
 | 
				
			||||||
 | 
					    /// Returns any error Postgres may have encountered
 | 
				
			||||||
 | 
					    fn find(
 | 
				
			||||||
 | 
					        pool: &sqlx::PgPool,
 | 
				
			||||||
 | 
					        id: &Id,
 | 
				
			||||||
 | 
					    ) -> impl std::future::Future<Output = sqlx::Result<Option<Self>>> + Send
 | 
				
			||||||
 | 
					    where
 | 
				
			||||||
 | 
					        Self: Sized;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Create the entity in the database.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// # Errors
 | 
				
			||||||
 | 
					    /// Returns any error Postgres may have encountered
 | 
				
			||||||
 | 
					    fn create(
 | 
				
			||||||
 | 
					        &self,
 | 
				
			||||||
 | 
					        pool: &sqlx::PgPool,
 | 
				
			||||||
 | 
					    ) -> impl std::future::Future<Output = sqlx::Result<Self>> + Send
 | 
				
			||||||
 | 
					    where
 | 
				
			||||||
 | 
					        Self: Sized;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Update an entity with a matching identifier in the database.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// # Errors
 | 
				
			||||||
 | 
					    /// Returns any error Postgres may have encountered
 | 
				
			||||||
 | 
					    fn update(
 | 
				
			||||||
 | 
					        &self,
 | 
				
			||||||
 | 
					        pool: &sqlx::PgPool,
 | 
				
			||||||
 | 
					    ) -> impl std::future::Future<Output = sqlx::Result<Self>> + Send
 | 
				
			||||||
 | 
					    where
 | 
				
			||||||
 | 
					        Self: Sized;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Update an entity with a matching identifier in the database if
 | 
				
			||||||
 | 
					    /// it exists, create it otherwise.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// # Errors
 | 
				
			||||||
 | 
					    /// Returns any error Postgres may have encountered
 | 
				
			||||||
 | 
					    fn create_or_update(
 | 
				
			||||||
 | 
					        &self,
 | 
				
			||||||
 | 
					        pool: &sqlx::PgPool,
 | 
				
			||||||
 | 
					    ) -> impl std::future::Future<Output = sqlx::Result<Self>> + Send
 | 
				
			||||||
 | 
					    where
 | 
				
			||||||
 | 
					        Self: Sized;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Delete the entity from the database if it exists.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// # Returns
 | 
				
			||||||
 | 
					    /// Returns the amount of rows affected by the deletion.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// # Errors
 | 
				
			||||||
 | 
					    /// Returns any error Postgres may have encountered
 | 
				
			||||||
 | 
					    fn delete(
 | 
				
			||||||
 | 
					        &self,
 | 
				
			||||||
 | 
					        pool: &sqlx::PgPool,
 | 
				
			||||||
 | 
					    ) -> impl std::future::Future<Output = sqlx::Result<u64>> + Send;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Delete any entity with the identifier `id`.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// # Returns
 | 
				
			||||||
 | 
					    /// Returns the amount of rows affected by the deletion.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// # Errors
 | 
				
			||||||
 | 
					    /// Returns any error Postgres may have encountered
 | 
				
			||||||
 | 
					    fn delete_by_id(
 | 
				
			||||||
 | 
					        pool: &sqlx::PgPool,
 | 
				
			||||||
 | 
					        id: &Id,
 | 
				
			||||||
 | 
					    ) -> impl std::future::Future<Output = sqlx::Result<u64>> + Send;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Returns the identifier of the entity.
 | 
				
			||||||
 | 
					    fn get_id(&self) -> &Id;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user