diff --git a/Cargo.lock b/Cargo.lock index 2d159b8..d5c9a78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -965,7 +965,7 @@ name = "gejdr-core" version = "0.1.0" dependencies = [ "chrono", - "gejdr-macros", + "georm", "serde", "sqlx", "tracing", @@ -973,17 +973,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "gejdr-macros" -version = "0.1.0" -dependencies = [ - "deluxe", - "proc-macro2", - "quote", - "sqlx", - "syn 2.0.96", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -994,6 +983,24 @@ dependencies = [ "version_check", ] +[[package]] +name = "georm" +version = "0.1.0" +dependencies = [ + "georm-macros", + "sqlx", +] + +[[package]] +name = "georm-macros" +version = "0.1.0" +dependencies = [ + "deluxe", + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "getrandom" version = "0.2.15" diff --git a/Cargo.toml b/Cargo.toml index 1950193..fc2f6cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "gejdr-core", "gejdr-bot", "gejdr-backend", - "gejdr-macros" + "georm-macros", + "georm" ] resolver = "2" diff --git a/gejdr-core/Cargo.toml b/gejdr-core/Cargo.toml index 60756f7..a9f6ac9 100644 --- a/gejdr-core/Cargo.toml +++ b/gejdr-core/Cargo.toml @@ -9,7 +9,7 @@ serde = "1.0.215" tracing = "0.1.40" tracing-subscriber = { version = "0.3.18", features = ["fmt", "std", "env-filter", "registry", "json", "tracing-log"] } uuid = { version = "1.11.0", features = ["v4", "serde"] } -gejdr-macros = { path = "../gejdr-macros" } +georm = { path = "../georm" } [dependencies.sqlx] version = "0.8.3" diff --git a/gejdr-core/migrations/20250125193213_macro-test-tables.down.sql b/gejdr-core/migrations/20250125193213_macro-test-tables.down.sql new file mode 100644 index 0000000..8cd9335 --- /dev/null +++ b/gejdr-core/migrations/20250125193213_macro-test-tables.down.sql @@ -0,0 +1,5 @@ +DROP TABLE IF EXISTS book_genres; +DROP TABLE IF EXISTS books; +DROP TABLE IF EXISTS genres; +DROP TABLE IF EXISTS authors; +DROP SCHEMA IF EXISTS tests; diff --git a/gejdr-core/migrations/20250125193213_macro-test-tables.up.sql b/gejdr-core/migrations/20250125193213_macro-test-tables.up.sql new file mode 100644 index 0000000..4c4c19d --- /dev/null +++ b/gejdr-core/migrations/20250125193213_macro-test-tables.up.sql @@ -0,0 +1,26 @@ +CREATE SCHEMA IF NOT EXISTS tests; + +CREATE TABLE tests.authors ( + author_id SERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL +); + +CREATE TABLE tests.books ( + id SERIAL PRIMARY KEY, + title VARCHAR(100) NOT NULL, + author_id INT NOT NULL, + FOREIGN KEY (author_id) REFERENCES tests.authors(author_id) ON DELETE CASCADE +); + +CREATE TABLE tests.genres ( + genre_id SERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL +); + +CREATE TABLE tests.book_genres ( + book_id INT NOT NULL, + genre_id INT NOT NULL, + PRIMARY KEY (book_id, genre_id), + FOREIGN KEY (book_id) REFERENCES tests.books(id) ON DELETE CASCADE, + FOREIGN KEY (genre_id) REFERENCES tests.genres(genre_id) ON DELETE CASCADE +); diff --git a/gejdr-core/src/models/accounts.rs b/gejdr-core/src/models/accounts.rs index 5096d90..71db1dd 100644 --- a/gejdr-core/src/models/accounts.rs +++ b/gejdr-core/src/models/accounts.rs @@ -1,4 +1,4 @@ -use super::Crud; +use georm::Georm; use sqlx::PgPool; type Timestampz = chrono::DateTime; @@ -23,10 +23,10 @@ impl RemoteUser { } } -#[derive(serde::Deserialize, serde::Serialize, Debug, PartialEq, Eq, Default, Clone, Crud)] -#[crud(table = "users")] +#[derive(serde::Deserialize, serde::Serialize, Debug, PartialEq, Eq, Default, Clone, Georm)] +#[georm(table = "users")] pub struct User { - #[crud(id)] + #[georm(id)] pub id: String, pub username: String, pub email: Option, diff --git a/gejdr-core/src/models/mod.rs b/gejdr-core/src/models/mod.rs index cd80f50..9bb4894 100644 --- a/gejdr-core/src/models/mod.rs +++ b/gejdr-core/src/models/mod.rs @@ -1,76 +1 @@ pub mod accounts; -pub use gejdr_macros::Crud; - -pub trait Crud { - /// 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>> + 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> + 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> + 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> + 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> + 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> + Send; - - /// Returns the identifier of the entity. - fn get_id(&self) -> &Id; -} diff --git a/gejdr-macros/Cargo.toml b/gejdr-macros/Cargo.toml deleted file mode 100644 index 5c4fdeb..0000000 --- a/gejdr-macros/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "gejdr-macros" -version = "0.1.0" -edition = "2021" - -[lib] -proc-macro = true - -[dependencies] -deluxe = "0.5.0" -proc-macro2 = "1.0.93" -quote = "1.0.38" -syn = "2.0.96" - -[dependencies.sqlx] -version = "0.8.3" -default-features = false -features = ["postgres", "uuid", "chrono", "migrate", "runtime-tokio", "macros"] diff --git a/georm-macros/Cargo.toml b/georm-macros/Cargo.toml new file mode 100644 index 0000000..16cde4d --- /dev/null +++ b/georm-macros/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "georm-macros" +version = "0.1.0" +edition = "2021" +authors = ["Lucien Cartier-Tilet "] +description = "Macros for Georm, the small, opiniated SQLx ORM for PostgreSQL. Not intended to be used directly." +homepage = "https://labs.phundrak.com/phundrak/gejdr-rs" +repository = "https://labs.phundrak.com/phundrak/gejdr-rs" +license = "MIT OR GPL-3.0-or-later" +keywords = ["sqlx", "orm", "postgres", "postgresql", "database", "async"] +categories = ["database"] + +[lib] +proc-macro = true + +[dependencies] +deluxe = "0.5.0" +proc-macro2 = "1.0.93" +quote = "1.0.38" +syn = "2.0.96" + +[lints.rust] +unsafe_code = "forbid" \ No newline at end of file diff --git a/gejdr-macros/src/crud/ir.rs b/georm-macros/src/georm/ir.rs similarity index 89% rename from gejdr-macros/src/crud/ir.rs rename to georm-macros/src/georm/ir.rs index a5d6e29..42a2af8 100644 --- a/gejdr-macros/src/crud/ir.rs +++ b/georm-macros/src/georm/ir.rs @@ -1,8 +1,9 @@ use quote::quote; +use std::fmt::{self, Display}; #[derive(deluxe::ExtractAttributes)] -#[deluxe(attributes(crud))] -pub struct CrudStructAttributes { +#[deluxe(attributes(georm))] +pub struct GeormStructAttributes { pub table: String, #[deluxe(default = Vec::new())] pub one_to_many: Vec, @@ -44,7 +45,7 @@ pub struct M2MLink { pub to: String, } -//#[crud( +//#[georm( // table = "users", // many_to_many = [ // { @@ -128,8 +129,8 @@ WHERE local.{} = $1 } #[derive(deluxe::ExtractAttributes, Clone)] -#[deluxe(attributes(crud))] -struct CrudFieldAttributes { +#[deluxe(attributes(georm))] +struct GeormFieldAttributes { #[deluxe(default = false)] pub id: bool, #[deluxe(default = None)] @@ -138,7 +139,7 @@ struct CrudFieldAttributes { pub relation: Option, } -// #[crud( +// #[georm( // table = "profileId", // one_to_one = { name = profile, id = "id", entity = Profile, nullable } // )] @@ -154,7 +155,7 @@ pub struct O2ORelationship { } #[derive(Clone)] -pub struct CrudField { +pub struct GeormField { pub ident: syn::Ident, pub field: syn::Field, pub ty: syn::Type, @@ -163,11 +164,11 @@ pub struct CrudField { pub relation: Option, } -impl CrudField { +impl GeormField { pub fn new(field: &mut syn::Field) -> Self { let ident = field.clone().ident.unwrap(); let ty = field.clone().ty; - let attrs: CrudFieldAttributes = + let attrs: GeormFieldAttributes = deluxe::extract_attributes(field).expect("Could not extract attributes from field"); Self { ident: ident.clone(), @@ -180,8 +181,20 @@ impl CrudField { } } -impl From<&CrudField> for proc_macro2::TokenStream { - fn from(value: &CrudField) -> Self { +impl Display for GeormField { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let default_column = self.ident.to_string(); + if self.column == default_column { + write!(f, "{}", self.column) + } else { + write!(f, "{} as {}", self.column, default_column) + } + } +} + + +impl From<&GeormField> for proc_macro2::TokenStream { + fn from(value: &GeormField) -> Self { let Some(relation) = value.relation.clone() else { return quote! {}; }; diff --git a/gejdr-macros/src/crud/mod.rs b/georm-macros/src/georm/mod.rs similarity index 80% rename from gejdr-macros/src/crud/mod.rs rename to georm-macros/src/georm/mod.rs index e9fb51d..9e3a1de 100644 --- a/gejdr-macros/src/crud/mod.rs +++ b/georm-macros/src/georm/mod.rs @@ -1,13 +1,13 @@ -use ir::CrudField; +use ir::GeormField; use quote::quote; mod ir; mod relationships; mod trait_implementation; -fn extract_crud_field_attrs( +fn extract_georm_field_attrs( ast: &mut syn::DeriveInput, -) -> deluxe::Result<(Vec, CrudField)> { +) -> deluxe::Result<(Vec, GeormField)> { let syn::Data::Struct(s) = &mut ast.data else { return Err(syn::Error::new_spanned( ast, @@ -18,9 +18,9 @@ fn extract_crud_field_attrs( .fields .clone() .into_iter() - .map(|mut field| CrudField::new(&mut field)) - .collect::>(); - let identifiers: Vec = fields + .map(|mut field| GeormField::new(&mut field)) + .collect::>(); + let identifiers: Vec = fields .clone() .into_iter() .filter(|field| field.id) @@ -42,13 +42,13 @@ fn extract_crud_field_attrs( } } -pub fn crud_derive_macro2( +pub fn georm_derive_macro2( item: proc_macro2::TokenStream, ) -> deluxe::Result { let mut ast: syn::DeriveInput = syn::parse2(item).expect("Failed to parse input"); - let struct_attrs: ir::CrudStructAttributes = + let struct_attrs: ir::GeormStructAttributes = deluxe::extract_attributes(&mut ast).expect("Could not extract attributes from struct"); - let (fields, id) = extract_crud_field_attrs(&mut ast)?; + 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! { diff --git a/gejdr-macros/src/crud/relationships.rs b/georm-macros/src/georm/relationships.rs similarity index 88% rename from gejdr-macros/src/crud/relationships.rs rename to georm-macros/src/georm/relationships.rs index a50b9cb..d88f7b6 100644 --- a/gejdr-macros/src/crud/relationships.rs +++ b/georm-macros/src/georm/relationships.rs @@ -1,8 +1,8 @@ use std::str::FromStr; -use crate::crud::ir::M2MRelationshipComplete; +use crate::georm::ir::M2MRelationshipComplete; -use super::ir::CrudField; +use super::ir::GeormField; use proc_macro2::TokenStream; use quote::quote; @@ -30,9 +30,9 @@ where pub fn derive_relationships( ast: &syn::DeriveInput, - struct_attrs: &super::ir::CrudStructAttributes, - fields: &[CrudField], - id: &CrudField, + 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()); diff --git a/gejdr-macros/src/crud/trait_implementation.rs b/georm-macros/src/georm/trait_implementation.rs similarity index 69% rename from gejdr-macros/src/crud/trait_implementation.rs rename to georm-macros/src/georm/trait_implementation.rs index 3b6d71b..5d6352f 100644 --- a/gejdr-macros/src/crud/trait_implementation.rs +++ b/georm-macros/src/georm/trait_implementation.rs @@ -1,8 +1,25 @@ -use super::ir::CrudField; +use super::ir::GeormField; use quote::quote; -fn generate_find_query(table: &str, id: &CrudField) -> proc_macro2::TokenStream { - let find_string = format!("SELECT * FROM {} WHERE {} = $1", table, id.column); +fn aliased_columns(fields: &[GeormField]) -> String { + fields.iter().map(std::string::ToString::to_string).collect::>().join(", ") +} + + +fn generate_find_query( + table: &str, + id: &GeormField, + fields: &[GeormField], +) -> proc_macro2::TokenStream { + let select_columns = fields + .iter() + .map(std::string::ToString::to_string) + .collect::>() + .join(", "); + let find_string = format!( + "SELECT {select_columns} FROM {table} WHERE {} = $1", + id.column + ); let ty = &id.ty; quote! { async fn find(pool: &::sqlx::PgPool, id: &#ty) -> ::sqlx::Result> { @@ -13,11 +30,11 @@ fn generate_find_query(table: &str, id: &CrudField) -> proc_macro2::TokenStream } } -fn generate_create_query(table: &str, fields: &[CrudField]) -> proc_macro2::TokenStream { +fn generate_create_query(table: &str, fields: &[GeormField]) -> proc_macro2::TokenStream { let inputs: Vec = (1..=fields.len()).map(|num| format!("${num}")).collect(); + let return_columns = aliased_columns(fields); let create_string = format!( - "INSERT INTO {} ({}) VALUES ({}) RETURNING *", - table, + "INSERT INTO {table} ({}) VALUES ({}) RETURNING {return_columns}", fields .iter() .map(|v| v.column.clone()) @@ -41,10 +58,11 @@ fn generate_create_query(table: &str, fields: &[CrudField]) -> proc_macro2::Toke fn generate_update_query( table: &str, - fields: &[CrudField], - id: &CrudField, + fields: &[GeormField], + id: &GeormField, ) -> proc_macro2::TokenStream { - let mut fields: Vec<&CrudField> = fields.iter().filter(|f| !f.id).collect(); + let return_columns = aliased_columns(fields); + let mut fields: Vec<&GeormField> = fields.iter().filter(|f| !f.id).collect(); let update_columns = fields .iter() .enumerate() @@ -52,9 +70,7 @@ fn generate_update_query( .collect::>() .join(", "); let update_string = format!( - "UPDATE {} SET {} WHERE {} = ${} RETURNING *", - table, - update_columns, + "UPDATE {table} SET {update_columns} WHERE {} = ${} RETURNING {return_columns}", id.column, fields.len() + 1 ); @@ -73,7 +89,7 @@ fn generate_update_query( } } -fn generate_delete_query(table: &str, id: &CrudField) -> proc_macro2::TokenStream { +fn generate_delete_query(table: &str, id: &GeormField) -> proc_macro2::TokenStream { let delete_string = format!("DELETE FROM {} WHERE {} = $1", table, id.column); let ty = &id.ty; @@ -87,16 +103,12 @@ fn generate_delete_query(table: &str, id: &CrudField) -> proc_macro2::TokenStrea } async fn delete(&self, pool: &::sqlx::PgPool) -> ::sqlx::Result { - let rows_affected = ::sqlx::query!(#delete_string, self.get_id()) - .execute(pool) - .await? - .rows_affected(); - Ok(rows_affected) + Self::delete_by_id(pool, self.get_id()).await } } } -fn generate_get_id(id: &CrudField) -> proc_macro2::TokenStream { +fn generate_get_id(id: &GeormField) -> proc_macro2::TokenStream { let ident = &id.ident; let ty = &id.ty; quote! { @@ -109,8 +121,8 @@ fn generate_get_id(id: &CrudField) -> proc_macro2::TokenStream { pub fn derive_trait( ast: &syn::DeriveInput, table: &str, - fields: &[CrudField], - id: &CrudField, + fields: &[GeormField], + id: &GeormField, ) -> proc_macro2::TokenStream { let ty = &id.ty; let id_ident = &id.ident; @@ -121,18 +133,15 @@ pub fn derive_trait( // generate let get_id = generate_get_id(id); - let find_query = generate_find_query(table, id); + let find_query = generate_find_query(table, id, fields); 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 Crud<#ty> for #ident #type_generics #where_clause { + 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 { diff --git a/gejdr-macros/src/lib.rs b/georm-macros/src/lib.rs similarity index 71% rename from gejdr-macros/src/lib.rs rename to georm-macros/src/lib.rs index a7d2436..73b9892 100644 --- a/gejdr-macros/src/lib.rs +++ b/georm-macros/src/lib.rs @@ -5,9 +5,9 @@ #![allow(clippy::unused_async)] #![forbid(unsafe_code)] -//! Create ``SQLx`` CRUD code for a struct in Postgres. +//! Creates ORM functionality for ``SQLx`` with `PostgreSQL`. //! -//! This crate provides the trait implementation `Crud` which +//! This crate provides the trait implementation `Georm` which //! generates the following ``SQLx`` queries: //! - find an entity by id //! @@ -25,7 +25,7 @@ //! - update an entity or create it if it does not already exist in //! the database //! -//! This macro relies on the trait `Crud` found in the `gejdr-core` +//! This macro relies on the trait `Georm` found in the `gejdr-core` //! crate. //! //! To use this macro, you need to add it to the derives of the @@ -33,25 +33,25 @@ //! //! # Usage //! -//! Add `#[crud(table = "my_table_name")]` atop of the structure, -//! after the `Crud` derive. +//! Add `#[georm(table = "my_table_name")]` atop of the structure, +//! after the `Georm` derive. //! //! ## Entity Identifier -//! You will also need to add `#[crud(id)]` atop of the field of your +//! 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 `#[crud(column = "...")]` to specify the +//! column, you can use `#[georm(column = "...")]` to specify the //! correct value. //! //! ```ignore -//! #[derive(Crud)] -//! #[crud(table = "users")] +//! #[derive(Georm)] +//! #[georm(table = "users")] //! pub struct User { -//! #[crud(id)] +//! #[georm(id)] //! id: String, -//! #[crud(column = "name")] +//! #[georm(column = "name")] //! username: String, //! created_at: Timestampz, //! last_updated: Timestampz, @@ -65,7 +65,7 @@ //! # 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 GeJDR’s Crud +//! a primary key, but it is strongly encouraged to use GeJDR’s Georm //! ID on a unique and non-null column of your database schema. //! //! ## Database type @@ -75,15 +75,15 @@ //! Otherwise, pull requests to add additional syntaxes are most //! welcome. -mod crud; -use crud::crud_derive_macro2; +mod georm; +use georm::georm_derive_macro2; -/// Generates CRUD code for Sqlx for a struct. +/// Generates GEORM code for Sqlx for a struct. /// /// # Panics /// /// May panic if errors arise while parsing and generating code. -#[proc_macro_derive(Crud, attributes(crud))] -pub fn crud_derive_macro(item: proc_macro::TokenStream) -> proc_macro::TokenStream { - crud_derive_macro2(item.into()).unwrap().into() +#[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() } diff --git a/georm/Cargo.toml b/georm/Cargo.toml new file mode 100644 index 0000000..1d7f8b1 --- /dev/null +++ b/georm/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "georm" +version = "0.1.0" +edition = "2021" +authors = ["Lucien Cartier-Tilet "] +description = "A small, opiniated ORM for SQLx and PostgreSQL" +homepage = "https://labs.phundrak.com/phundrak/gejdr-rs" +repository = "https://labs.phundrak.com/phundrak/gejdr-rs" +license = "MIT OR GPL-3.0-or-later" +keywords = ["sqlx", "orm", "postgres", "postgresql", "database", "async"] +categories = ["database"] + +[dependencies] +georm-macros = { path = "../georm-macros" } + +[dependencies.sqlx] +version = "0.8.3" +default-features = false +features = ["postgres", "runtime-tokio", "macros", "migrate"] + +[lints.rust] +unsafe_code = "forbid" \ No newline at end of file diff --git a/georm/README.md b/georm/README.md new file mode 100644 index 0000000..407c50b --- /dev/null +++ b/georm/README.md @@ -0,0 +1 @@ +# A small, opiniated ORM for SQLx with PostgreSQL diff --git a/georm/src/lib.rs b/georm/src/lib.rs new file mode 100644 index 0000000..a71a9de --- /dev/null +++ b/georm/src/lib.rs @@ -0,0 +1,82 @@ +#![deny(clippy::all)] +#![deny(clippy::pedantic)] +#![deny(clippy::nursery)] +#![allow(clippy::module_name_repetitions)] +#![allow(clippy::unused_async)] +#![forbid(unsafe_code)] + +pub use georm_macros::Georm; + +pub trait Georm { + /// 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>> + 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> + 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> + 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> + 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> + 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> + Send; + + /// Returns the identifier of the entity. + fn get_id(&self) -> &Id; +} diff --git a/georm/tests/fixtures/simple_struct.sql b/georm/tests/fixtures/simple_struct.sql new file mode 100644 index 0000000..e69de29 diff --git a/georm/tests/simple_struct.rs b/georm/tests/simple_struct.rs new file mode 100644 index 0000000..f6a069f --- /dev/null +++ b/georm/tests/simple_struct.rs @@ -0,0 +1,9 @@ +use georm::Georm; + +#[derive(Debug, Georm)] +#[georm(table = "tests.authors")] +struct Author { + #[georm(column = "author_id", id)] + id: i32, + name: String +}