diff --git a/CHANGELOG.md b/CHANGELOG.md index e429568..682bf25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v0.5.0 + +### New features + +- `diesel::Pg` connection is now implemented + +### Changes + +* Updated examples to be able to use feature flags for the database selection + ## v0.4.1 ### New features @@ -70,8 +80,11 @@ None * Added `barrel` integration for `SQLite3` * Added `SQLite3` support, backed by `diesel` + * Added an example for `SqliteConnection` + * Added possibility for `custom` queries + * Added possibility to change the model before and after updating it in the database by implementing the `DatabaseUpdateHandler` trait. ### Changes diff --git a/Docker/database-pg.env b/Docker/database-pg.env new file mode 100644 index 0000000..7ac674d --- /dev/null +++ b/Docker/database-pg.env @@ -0,0 +1,3 @@ +POSTGRES_USER=naphtha +POSTGRES_PASSWORD=naphtha +POSTGRES_DB=naphtha diff --git a/Docker/docker-compose.yml b/Docker/docker-compose.yml index aa47e07..b35c144 100644 --- a/Docker/docker-compose.yml +++ b/Docker/docker-compose.yml @@ -8,3 +8,9 @@ services: ports: - 3306:3306 command: ["mysqld", "--default-authentication-plugin=mysql_native_password"] + database-pg: + image: "postgres:latest" + env_file: + - database-pg.env + ports: + - 5432:5432 diff --git a/README.md b/README.md index 690c762..f7217a8 100644 --- a/README.md +++ b/README.md @@ -6,18 +6,26 @@ SPDX-License-Identifier: MIT OR Apache-2.0 # naphtha +*Universal database connection layer* + [![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-informational?style=flat-square)](COPYRIGHT.md) [![Crates.io](https://img.shields.io/crates/v/naphtha.svg)](https://crates.io/crates/naphtha) [![docs.rs](https://img.shields.io/docsrs/naphtha?style=flat-square)](https://docs.rs/naphtha) **naphtha** [![Crates.io](https://img.shields.io/crates/v/naphtha-proc-macro.svg)](https://crates.io/crates/naphtha-proc-macro) **naphtha-proc-macro** -Please checkout the [documentation page](https://docs.rs/naphtha) for information and examples (also see examples folder in [naphtha](./naphtha/examples)) +Please checkout the [documentation page](https://docs.rs/naphtha) for more information (also see examples folder in [naphtha](./naphtha/examples)) If you have questions, want to contribute or have any other type of request, your invited to create an issue or visit the [openprobst.dev](https://openprobst.dev) discord server. [![](https://img.shields.io/discord/855726181142495242?color=154683&label=discord&style=flat-square)](https://discord.gg/nx7YtsjEbT) +## About + +This crate is to simplify the creation and usage of models that require a database connection. If applied correct, changing the database type is only a feature flag away. + +The models can also be compiled and used without database connection support for better usage on server and client side. + ## Roadmap - [x] Connect to database using a wrapper, defining the base for interchangeable Databases @@ -27,8 +35,13 @@ If you have questions, want to contribute or have any other type of request, you - [x] Thread safe sharing of the database connection - [x] Integrate `barrel` crate for writing migrations in Rust, available at runtime - [x] Implement support for `diesel::MySqlConnection` -- [ ] Implement support for `diesel::PgConnection` -- [ ] More databases!!! +- [x] Implement support for `diesel::PgConnection` +- [ ] Connection pooling? +- [ ] More databases? + +## Troubleshooting + +It is very easy to get a whole bunch of `trait bound not satisfied` error messages when your model is not configured correctly. Make sure that your `schema` module and the containing `table!` definition is in line with your `model` definition and that it uses the correct types defined by [barrel](https://docs.rs/barrel). ## Contributing diff --git a/naphtha-proc-macro/Cargo.toml b/naphtha-proc-macro/Cargo.toml index 524d565..3b542b3 100644 --- a/naphtha-proc-macro/Cargo.toml +++ b/naphtha-proc-macro/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "naphtha-proc-macro" -version = "0.4.1" +version = "0.5.0" authors = ["Lewin Probst "] -edition = "2018" +edition = "2021" license = "MIT OR Apache-2.0" description = "Supporting macro crate for naphtha" homepage = "https://github.com/emirror-de/naphtha" @@ -17,14 +17,16 @@ proc-macro = true [features] default = [] -full = ["sqlite", "mysql", "barrel-full"] -barrel-full = ["barrel-sqlite", "barrel-mysql"] +full = ["sqlite", "mysql", "pg", "barrel-full"] +barrel-full = ["barrel-sqlite", "barrel-mysql", "barrel-pg"] sqlite = [] mysql = [] +pg = [] barrel-sqlite = [] barrel-mysql = [] +barrel-pg = [] [dependencies] -syn = { version = "^1.0.74", features = ["parsing"] } -proc-macro2 = "^1.0.28" -quote = "^1.0.9" +syn = { version = "1.0.86", features = ["parsing"] } +proc-macro2 = "1.0.36" +quote = "1.0.15" diff --git a/naphtha-proc-macro/src/barrel_impl/mod.rs b/naphtha-proc-macro/src/barrel_impl/mod.rs index 5fa74e0..3cf4f1d 100644 --- a/naphtha-proc-macro/src/barrel_impl/mod.rs +++ b/naphtha-proc-macro/src/barrel_impl/mod.rs @@ -1,4 +1,6 @@ #[cfg(feature = "barrel-mysql")] pub(crate) mod mysql; +#[cfg(feature = "barrel-pg")] +pub(crate) mod pg; #[cfg(feature = "barrel-sqlite")] pub(crate) mod sqlite; diff --git a/naphtha-proc-macro/src/barrel_impl/pg.rs b/naphtha-proc-macro/src/barrel_impl/pg.rs new file mode 100644 index 0000000..7d983b5 --- /dev/null +++ b/naphtha-proc-macro/src/barrel_impl/pg.rs @@ -0,0 +1,56 @@ +use {quote::quote, syn::DeriveInput}; + +pub(crate) fn impl_pg(ast: &DeriveInput) -> ::proc_macro2::TokenStream { + let name = &ast.ident; + + quote! { + impl ::naphtha::barrel::DatabaseSqlMigrationExecutor<::naphtha::diesel::PgConnection, usize> for #name + where + Self: ::naphtha::barrel::DatabaseSqlMigration, + { + fn execute_migration_up(conn: &::naphtha::DatabaseConnection<::naphtha::diesel::PgConnection>) -> Result { + use { + ::naphtha::{barrel::Migration, DatabaseConnection, log::error, diesel::RunQueryDsl}, + }; + let mut m = Migration::new(); + Self::migration_up(&mut m); + let m = m.make::<::naphtha::barrel::backend::Pg>(); + + let c = match conn.lock() { + Ok(c) => c, + Err(msg) => { + error!("Could not aquire lock on DatabaseSqlMigrationExecutor::execute_migration_up: {}", msg.to_string()); + return Err(msg.to_string()); + } + }; + + match ::naphtha::diesel::sql_query(m).execute(&*c) { + Ok(u) => Ok(u), + Err(msg) => Err(msg.to_string()), + } + } + + fn execute_migration_down(conn: &::naphtha::DatabaseConnection<::naphtha::diesel::PgConnection>) -> Result { + use { + ::naphtha::{barrel::Migration, DatabaseConnection, log::error, diesel::RunQueryDsl}, + }; + let mut m = Migration::new(); + Self::migration_down(&mut m); + let m = m.make::<::naphtha::barrel::backend::Pg>(); + + let c = match conn.lock() { + Ok(c) => c, + Err(msg) => { + error!("Could not aquire lock on DatabaseSqlMigrationExecutor::execute_migration_down for model: {}", msg.to_string()); + return Err(msg.to_string()); + } + }; + + match ::naphtha::diesel::sql_query(m).execute(&*c) { + Ok(u) => Ok(u), + Err(msg) => Err(msg.to_string()), + } + } + } + } +} diff --git a/naphtha-proc-macro/src/database_impl/mod.rs b/naphtha-proc-macro/src/database_impl/mod.rs index 5f155e4..694cec4 100644 --- a/naphtha-proc-macro/src/database_impl/mod.rs +++ b/naphtha-proc-macro/src/database_impl/mod.rs @@ -1,4 +1,6 @@ #[cfg(feature = "mysql")] pub(crate) mod mysql; +#[cfg(feature = "pg")] +pub(crate) mod pg; #[cfg(feature = "sqlite")] pub(crate) mod sqlite; diff --git a/naphtha-proc-macro/src/database_impl/pg.rs b/naphtha-proc-macro/src/database_impl/pg.rs new file mode 100644 index 0000000..2508a31 --- /dev/null +++ b/naphtha-proc-macro/src/database_impl/pg.rs @@ -0,0 +1,258 @@ +use { + quote::quote, + syn::{Data::Struct, DeriveInput}, +}; + +pub(crate) fn impl_pg( + ast: &DeriveInput, + attr: &::proc_macro2::TokenStream, +) -> ::proc_macro2::TokenStream { + let database_modifier = impl_database_modifier(ast, attr); + let query_by_property = impl_query_by_property(ast, attr); + quote! { + #database_modifier + #query_by_property + } +} + +fn impl_database_modifier( + ast: &DeriveInput, + table_name: &::proc_macro2::TokenStream, +) -> ::proc_macro2::TokenStream { + let name = &ast.ident; + let table_name = crate::helper::extract_table_name(table_name); + //let table_name: syn::UsePath = syn::parse_quote! {#name::table_name()}; + + let insert_properties = generate_insert_properties(ast); + assert!( + crate::helper::has_id(ast), + "No `id` member found in model `{}`. Currently only models having an `id` column of type `i32` are supported.", + name + ); + + quote! { + impl ::naphtha::DatabaseModelModifier for #name + where + Self: ::naphtha::DatabaseUpdateHandler + + ::naphtha::DatabaseInsertHandler + + ::naphtha::DatabaseRemoveHandler, + { + fn insert(&mut self, conn: &::naphtha::DatabaseConnection) -> bool { + use { + ::naphtha::{log, DatabaseModel, diesel::{Connection, RunQueryDsl, ExpressionMethods, Table, QueryDsl}}, + schema::{#table_name, #table_name::dsl::*}, + }; + // preventing duplicate insertion if default primary key gets + // changed on database insertion. + if self.primary_key() != Self::default_primary_key() { + return false; + } + let c = match conn.lock() { + Ok(c) => c, + Err(msg) => { + log::error!("Could not aquire lock on DatabaseModifier::insert for model:\n{:#?}", self); + return false; + } + }; + self.pre_insert(conn); + let res_id = match c.transaction::<_, ::naphtha::diesel::result::Error, _>(|| { + ::naphtha::diesel::insert_into(#table_name) + .values((#insert_properties)) + .execute(&*c)?; + #table_name.select(#table_name.primary_key()) + .order(#table_name.primary_key().desc()) + .first(&*c) + }) { + Ok(v) => v, + Err(msg) => { + log::error!("Failed inserting entity:\n{:#?}", self); + return false; + } + }; + self.set_primary_key(&res_id); + self.post_insert(conn); + true + } + + fn update(&mut self, conn: &::naphtha::DatabaseConnection) -> bool { + use ::naphtha::{diesel::SaveChangesDsl, log}; + let c = match conn.lock() { + Ok(c) => c, + Err(msg) => { + log::error!("Could not aquire lock on DatabaseModifier::update for model:\n{:#?}", self); + return false; + } + }; + self.pre_update(conn); + let update_result = match self.save_changes::(&*c) { + Ok(_) => true, + Err(msg) => { + log::error!("Failed updating entity:\n{:#?}", self); + return false; + }, + }; + self.post_update(conn); + update_result + } + + fn remove(&mut self, conn: &::naphtha::DatabaseConnection) -> bool { + use { + ::naphtha::{log::{self, info}, DatabaseModel, diesel::{ExpressionMethods, RunQueryDsl, QueryDsl, Table}}, + schema::{#table_name, #table_name::dsl::*}, + }; + let c = match conn.lock() { + Ok(c) => c, + Err(msg) => { + log::error!("Could not aquire lock on DatabaseModifier::remove for model:\n{:#?}", self); + return false; + } + }; + self.pre_remove(conn); + let num_deleted = ::naphtha::diesel::delete( + #table_name.filter( + #table_name.primary_key().eq(self.primary_key()) + ) + ); + let num_deleted = match num_deleted.execute(&*c) { + Ok(_) => { + #[cfg(debug_assertions)] + info!("Removed entity with primary key {} from database!", self.primary_key()); + true + }, + Err(msg) => { + log::error!("Could not aquire lock on DatabaseModifier::remove for model:\n{:#?}", self); + false + } + }; + self.post_remove(conn); + num_deleted + } + } + } +} + +fn generate_insert_properties(ast: &DeriveInput) -> ::proc_macro2::TokenStream { + let data = match &ast.data { + Struct(data) => data, + _ => panic!("Other data formats than \"struct\" is not supported yet!"), + }; + let mut collected_properties = quote! {}; + for field in data.fields.iter() { + if field.ident.is_none() { + continue; + } + let fieldname = field.ident.as_ref().unwrap(); + if &fieldname.to_string()[..] == "id" { + // field id is currently used as primary key and therfore generated + // by the database, so it must not be set during insertion. + continue; + } + collected_properties = quote! { + #collected_properties + #fieldname.eq(&self.#fieldname), + }; + } + collected_properties +} + +pub fn impl_query_by_property( + ast: &::syn::DeriveInput, + table_name_attr: &::proc_macro2::TokenStream, +) -> ::proc_macro2::TokenStream { + let name = &ast.ident; + let table_name = crate::helper::extract_table_name(table_name_attr); + let data = match &ast.data { + ::syn::Data::Struct(data) => data, + _ => { + // return no code if it is not a struct + return quote! {}; + } + }; + let mut queries = quote! {}; + for field in data.fields.iter() { + if field.ident.is_none() { + continue; + } + let fieldname = field.ident.as_ref().unwrap(); + let (return_type, diesel_query_fn) = match &fieldname.to_string()[..] { + "updated_at" => continue, + "id" => (quote! { Self }, quote! { first }), + _ => (quote! { Vec }, quote! { load }), + }; + let function_name = ::proc_macro2::Ident::new( + &format!("query_by_{}", fieldname).to_lowercase(), + ::proc_macro2::Span::call_site(), + ); + let fieldtype = &field.ty; + let query = quote! { + fn #function_name(conn: &::naphtha::DatabaseConnection, property: &#fieldtype) + -> ::naphtha::diesel::result::QueryResult<#return_type> { + use schema::{#table_name, #table_name::dsl::*}; + use ::naphtha::diesel::{ExpressionMethods, QueryDsl, RunQueryDsl}; + conn.custom::<::naphtha::diesel::result::QueryResult<#return_type>, _>(|c| { + #table_name.filter(#fieldname.eq(property)) + .#diesel_query_fn::(&*c) + }) + } + }; + queries = quote! { + #queries + #query + }; + } + + let query_by_ids = if crate::helper::has_id(ast) { + impl_query_by_ids(ast, table_name_attr) + } else { + quote! {} + }; + + quote! { + impl QueryByProperties for #name { + type Error = ::naphtha::diesel::result::Error; + #queries + #query_by_ids + } + } +} + +fn impl_query_by_ids( + ast: &::syn::DeriveInput, + table_name_attr: &::proc_macro2::TokenStream, +) -> ::proc_macro2::TokenStream { + let table_name = crate::helper::extract_table_name(table_name_attr); + let data = match &ast.data { + ::syn::Data::Struct(data) => data, + _ => { + // return only defaults if it is not a struct + return quote! {}; + } + }; + let mut query = quote! {}; + for field in data.fields.iter() { + if field.ident.is_none() { + continue; + } + let fieldname = field.ident.as_ref().unwrap(); + match &fieldname.to_string()[..] { + "id" => (), + _ => continue, + }; + let fieldtype = &field.ty; + query = quote! { + fn query_by_ids(conn: &::naphtha::DatabaseConnection, ids: &[#fieldtype]) + -> ::naphtha::diesel::result::QueryResult> { + use { + schema::{#table_name, #table_name::dsl::*}, + ::naphtha::diesel::{ExpressionMethods, QueryDsl, RunQueryDsl}, + }; + conn.custom::<::naphtha::diesel::result::QueryResult>, _>(|c| { + #table_name.filter(#fieldname.eq_any(ids)).load::(&*c) + }) + } + }; + break; + } + + query +} diff --git a/naphtha-proc-macro/src/lib.rs b/naphtha-proc-macro/src/lib.rs index 0ce1320..94539e2 100644 --- a/naphtha-proc-macro/src/lib.rs +++ b/naphtha-proc-macro/src/lib.rs @@ -1,3 +1,5 @@ +//! This crate is a support crate that contains the necessary macros for naphtha to compile. + extern crate proc_macro; extern crate quote; @@ -6,7 +8,11 @@ use { syn::{parse, DeriveInput}, }; -#[cfg(any(feature = "barrel-sqlite", feature = "barrel-mysql"))] +#[cfg(any( + feature = "barrel-sqlite", + feature = "barrel-mysql", + feature = "barrel-pg" +))] mod barrel_impl; mod database_impl; mod database_traits; @@ -25,9 +31,9 @@ pub fn model( let attr: ::proc_macro2::TokenStream = attr.parse().unwrap(); // QUERY BY PROPERTY TRAIT - #[cfg(not(any(feature = "sqlite", feature = "mysql")))] + #[cfg(not(any(feature = "sqlite", feature = "mysql", feature = "pg")))] let impl_trait_query_by_properties = quote! {}; - #[cfg(any(feature = "sqlite", feature = "mysql"))] + #[cfg(any(feature = "sqlite", feature = "mysql", feature = "pg"))] let impl_trait_query_by_properties = database_traits::impl_trait_query_by_properties(&ast); @@ -53,9 +59,19 @@ pub fn model( #[cfg(feature = "barrel-mysql")] let impl_barrel_mysql = barrel_impl::mysql::impl_mysql(&ast); + // PostgreSQL + #[cfg(not(feature = "pg"))] + let impl_pg = quote! {}; + #[cfg(feature = "pg")] + let impl_pg = database_impl::pg::impl_pg(&ast, &attr); + #[cfg(not(feature = "barrel-pg"))] + let impl_barrel_pg = quote! {}; + #[cfg(feature = "barrel-pg")] + let impl_barrel_pg = barrel_impl::pg::impl_pg(&ast); + let output = quote! { use self::schema::*; - #[cfg(any(feature = "sqlite", feature = "mysql"))] + #[cfg(any(feature = "sqlite", feature = "mysql", feature = "pg"))] use { ::naphtha::diesel::{backend::Backend, prelude::*}, }; @@ -77,6 +93,9 @@ pub fn model( #impl_mysql #impl_barrel_mysql + + #impl_pg + #impl_barrel_pg }; ::proc_macro::TokenStream::from(output) diff --git a/naphtha/Cargo.toml b/naphtha/Cargo.toml index 9c55767..02d7dd3 100644 --- a/naphtha/Cargo.toml +++ b/naphtha/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "naphtha" -version = "0.4.1" +version = "0.5.0" authors = ["Lewin Probst "] -edition = "2018" +edition = "2021" license = "MIT OR Apache-2.0" description = "Universal database connection layer for your application." homepage = "https://github.com/emirror-de/naphtha" @@ -17,13 +17,15 @@ default = [] full = ["sqlite", "mysql", "barrel-full"] sqlite = ["naphtha-proc-macro/sqlite", "diesel/sqlite"] mysql = ["naphtha-proc-macro/mysql", "diesel/mysql"] +pg = ["naphtha-proc-macro/pg", "diesel/postgres"] barrel-full = ["barrel-sqlite"] barrel-sqlite = ["barrel_dep/sqlite3", "naphtha-proc-macro/barrel-sqlite"] barrel-mysql = ["barrel_dep/mysql", "naphtha-proc-macro/barrel-mysql"] +barrel-pg = ["barrel_dep/pg", "naphtha-proc-macro/barrel-pg"] [dependencies] -barrel_dep = { version = "^0.7.0", optional = true, package = "barrel" } -chrono = { version = "^0.4.19" } -diesel = { version = "^1.4.8", features = ["chrono"] } -naphtha-proc-macro = { path = "../naphtha-proc-macro", version = "^0.4.1" } -log = "^0.4.14" +barrel_dep = { version = "0.7.0", optional = true, package = "barrel" } +chrono = { version = "0.4.19" } +diesel = { version = "1.4.8", features = ["chrono"] } +naphtha-proc-macro = { path = "../naphtha-proc-macro", version = "0.5" } +log = "0.4.14" diff --git a/naphtha/examples/person.rs b/naphtha/examples/person.rs index ff32c91..b537fb4 100644 --- a/naphtha/examples/person.rs +++ b/naphtha/examples/person.rs @@ -1,23 +1,29 @@ // You can run this example on different databases (Docker files provided in -// the repository root). -// Select the database by compiling this example with the corresponding feature: +// the repository). +// +// Select the database by compiling this example with the corresponding features: // * sqlite and barrel-sqlite // * mysql and barrel-mysql -// Also make sure that the correct type DbBackend is uncommented, see below. -// Default is SqliteConnection. +// * pg and barrel-pg #[macro_use] extern crate diesel; +#[cfg(any( + feature = "barrel-sqlite", + feature = "barrel-mysql", + feature = "barrel-pg" +))] +use naphtha::barrel::{ + types, + DatabaseSqlMigration, + DatabaseSqlMigrationExecutor, + Migration, +}; + use { chrono::prelude::NaiveDateTime, naphtha::{ - barrel::{ - types, - DatabaseSqlMigration, - DatabaseSqlMigrationExecutor, - Migration, - }, diesel::prelude::*, model, DatabaseConnect, @@ -34,13 +40,19 @@ const DATABASE_URL: &'static str = if cfg!(feature = "sqlite") { ":memory:" } else if cfg!(feature = "mysql") { "mysql://naphtha:naphtha@127.0.0.1:3306/naphtha" +} else if cfg!(feature = "pg") { + "postgres://naphtha:naphtha@127.0.0.1:5432/naphtha" } else { "not supported" }; -// UNCOMMENT THE BACKEND THAT YOU WANT TO USE +// USE THE ACCORDING FEATURE FOR DATABASE TYPE SELECTION +#[cfg(feature = "sqlite")] type DbBackend = diesel::SqliteConnection; -//type DbBackend = diesel::MysqlConnection; +#[cfg(feature = "mysql")] +type DbBackend = diesel::MysqlConnection; +#[cfg(feature = "pg")] +type DbBackend = diesel::PgConnection; // The model attribute automatically adds: // @@ -155,7 +167,11 @@ impl DatabaseRemoveHandler for Person {} // } //} -#[cfg(any(feature = "barrel-sqlite", feature = "barrel-mysql",))] +#[cfg(any( + feature = "barrel-sqlite", + feature = "barrel-mysql", + feature = "barrel-pg" +))] impl DatabaseSqlMigration for Person { fn migration_up(migration: &mut Migration) { migration.create_table_if_not_exists(Self::table_name(), |t| { @@ -177,6 +193,11 @@ fn main() { // create the table if not existent // This method can be used on startup of your application to make sure // your database schema is always up to date. + #[cfg(any( + feature = "barrel-sqlite", + feature = "barrel-mysql", + feature = "barrel-pg" + ))] match Person::execute_migration_up(&db) { Ok(_) => (), Err(msg) => println!("Could not create table: {}", msg), @@ -204,6 +225,11 @@ fn main() { p.remove(&db); // p not available anymore + #[cfg(any( + feature = "barrel-sqlite", + feature = "barrel-mysql", + feature = "barrel-pg" + ))] match Person::execute_migration_down(&db) { Ok(_) => (), Err(msg) => println!("Could not drop table: {}", msg), diff --git a/naphtha/src/database_impl/mod.rs b/naphtha/src/database_impl/mod.rs index 23ff1aa..1ddc4bb 100644 --- a/naphtha/src/database_impl/mod.rs +++ b/naphtha/src/database_impl/mod.rs @@ -7,3 +7,8 @@ pub use sqlite::*; mod mysql; #[cfg(feature = "mysql")] pub use mysql::*; + +#[cfg(feature = "pg")] +mod pg; +#[cfg(feature = "pg")] +pub use pg::*; diff --git a/naphtha/src/database_impl/pg.rs b/naphtha/src/database_impl/pg.rs new file mode 100644 index 0000000..9755627 --- /dev/null +++ b/naphtha/src/database_impl/pg.rs @@ -0,0 +1,34 @@ +use { + crate::{DatabaseConnect, DatabaseConnection}, + diesel::{Connection, PgConnection}, + std::sync::{Arc, Mutex}, +}; + +impl From for DatabaseConnection { + fn from(c: PgConnection) -> Self { + DatabaseConnection::(Arc::new(Mutex::new(c))) + } +} + +impl From>> for DatabaseConnection { + fn from(c: Arc>) -> Self { + DatabaseConnection::(c) + } +} + +impl DatabaseConnect for DatabaseConnection { + fn connect( + database_url: &str, + ) -> Result, String> { + let connection = match Connection::establish(database_url) { + Ok(c) => c, + Err(msg) => { + return Err(format!( + "Connection to database \"{}\" could not be established: {}", + database_url, msg + )) + } + }; + Ok(DatabaseConnection(Arc::new(Mutex::new(connection)))) + } +} diff --git a/naphtha/src/lib.rs b/naphtha/src/lib.rs index d2a2b33..f6cd5e6 100644 --- a/naphtha/src/lib.rs +++ b/naphtha/src/lib.rs @@ -15,18 +15,21 @@ //! //! * Most common function implementations `insert`, `update`, `remove` for your //! models. +//! * Custom transactions provided by the `custom` function //! * [DatabaseUpdateHandler](DatabaseUpdateHandler) enables you to change the models values before and //! after the `update` transaction to the database. //! * Change database on specific model in your application without the need to //! change your code. -//! * Possibility to query a model from the database by using one of its member *(NOT FINISHED YET)*. -//! * Integrated [barrel] for writing your SQL migrations. +//! * Possibility to query a model from the database by using one of its member. +//! * Integrated [barrel] for writing your SQL migrations and the possibility to apply them during +//! runtime. //! * Thread safe handling of the database connection. //! //! ## Supported databases //! //! * SQlite3 (using [diesel](diesel) under the hood). //! * MySQL (using [diesel](diesel) under the hood). +//! * PostgreSQL (using [diesel](diesel) under the hood). //! //! ## Examples //! @@ -45,35 +48,17 @@ //! //! ### Defining a model and use database connection //! -//! ```rust -//! #[macro_use] -//! extern crate diesel; -//! #[macro_use] -//! extern crate naphtha; -//! -//! use { -//! naphtha::{ -//! model, -//! DatabaseModel, -//! DatabaseModelModifier, -//! DatabaseInsertHandler, -//! DatabaseUpdateHandler, -//! DatabaseRemoveHandler -//! }, -//! diesel::table -//! }; -//! #[cfg(any(feature = "barrel-sqlite", feature = "barrel-mysql"))] -//! use naphtha::barrel::{types, DatabaseSqlMigration, Migration}; +//! To create a model and its database integration, the following code is required. //! -//! // It is recommended to wrap the actual used database type in a crate-global -//! // alias. If the database is changed later, this is the only line that needs -//! // to be adjusted to the new database type. -//! pub(crate) type DbBackend = diesel::SqliteConnection; +//! *Note that this is an excerpt, see the `examples` folder in the repository for +//! a full working example.* //! +//! ```ignore //! #[model(table_name = "persons")] //! pub struct Person { //! id: i32, //! pub description: Option, +//! pub updated_at: NaiveDateTime, //! } //! //! pub mod schema { @@ -81,6 +66,7 @@ //! persons (id) { //! id -> Int4, //! description -> Nullable, +//! updated_at -> Timestamp, //! } //! } //! } @@ -104,16 +90,12 @@ //! } //! } //! -//! // do not implement custom changes before and after the transactions -//! // to the database. +//! // Define your custom changes to the model before and after the transactions. //! impl naphtha::DatabaseUpdateHandler for Person {} //! impl naphtha::DatabaseRemoveHandler for Person {} //! impl naphtha::DatabaseInsertHandler for Person {} //! -//! #[cfg(any( -//! feature = "barrel-full", -//! feature = "barrel-sqlite", -//! ))] +//! // This implements your database migration functions. //! impl DatabaseSqlMigration for Person { //! fn migration_up(migration: &mut Migration) { //! use naphtha::DatabaseModel; @@ -133,6 +115,8 @@ //! fn main() { //! use naphtha::{DatabaseConnection, DatabaseConnect}; //! let db = DatabaseConnection::connect(":memory:").unwrap(); +//! // p is to be mutable because the insert function updates the id member +//! // to the one given by the database. //! let mut p = Person { //! id: Person::default_primary_key(), //! description: Some("The new person is registered".into()), @@ -147,7 +131,7 @@ //! }); //! //! p.remove(&db); -//! // p not available anymore +//! // p not available anymore in the database //! } //! ``` @@ -161,8 +145,12 @@ use std::sync::{Arc, Mutex, MutexGuard, PoisonError}; /// supported. pub use naphtha_proc_macro::model; -#[cfg(any(feature = "barrel-sqlite", feature = "barrel-mysql",))] -/// Re-exports the [barrel] crate including small additions required by naphtha. +#[cfg(any( + feature = "barrel-sqlite", + feature = "barrel-mysql", + feature = "barrel-pg" +))] +/// Re-exports the [barrel] crate including small trait additions required by naphtha. pub mod barrel; mod database_impl; mod tests;