naphtha/
lib.rs

1#![deny(missing_docs)]
2//! This library provides several traits in order to make database access a lot
3//! easier. In addition when using `naphtha`, it is possible to change
4//! the database that is used for specific models in your application without
5//! the requirement of changing any code.
6//!
7//! It implements the most common operations on a database like `insert`, `update`
8//! and `remove` for you, while also providing the ability to send custom queries
9//! to the database.
10//! In addition to that, when using the `barrel-XXX` features, you can write your
11//! SQL migrations and use them in your application during runtime.
12//! See the [examples](#examples) below.
13//!
14//! ## Features overview
15//!
16//! * Most common function implementations `insert`, `update`, `remove` for your
17//! models.
18//! * Custom transactions provided by the `custom` function
19//! * [DatabaseUpdateHandler](DatabaseUpdateHandler) enables you to change the models values before and
20//! after the `update` transaction to the database.
21//! * Change database on specific model in your application without the need to
22//! change your code.
23//! * Possibility to query a model from the database by using one of its member.
24//! * Integrated [barrel] for writing your SQL migrations and the possibility to apply them during
25//! runtime.
26//! * Thread safe handling of the database connection.
27//!
28//! ## Supported databases
29//!
30//! * SQlite3 (using [diesel](diesel) under the hood).
31//! * MySQL (using [diesel](diesel) under the hood).
32//! * PostgreSQL (using [diesel](diesel) under the hood).
33//!
34//! ## Examples
35//!
36//! In this chapter, minimal usages are shown. Please have a look at the examples
37//! in the repository for more and detailed use.
38//!
39//! ### Connecting to a database
40//!
41//! ```rust
42//! use naphtha::{DatabaseConnection, DatabaseConnect};
43//! // This is the only line required to be changed to switch database types.
44//! type DbBackend = diesel::SqliteConnection;
45//! let db: DatabaseConnection<DbBackend> = DatabaseConnection::connect(":memory:").unwrap();
46//! // do some database work
47//! ```
48//!
49//! ### Defining a model and use database connection
50//!
51//! To create a model and its database integration, the following code is required.
52//!
53//! *Note that this is an excerpt, see the `examples` folder in the repository for
54//! a full working example.*
55//!
56//! ```ignore
57//! #[model(table_name = "persons")]
58//! pub struct Person {
59//!     id: i32,
60//!     pub description: Option<String>,
61//!     pub updated_at: NaiveDateTime,
62//! }
63//!
64//! pub mod schema {
65//!     table! {
66//!         persons (id) {
67//!             id -> Int4,
68//!             description -> Nullable<Varchar>,
69//!             updated_at -> Timestamp,
70//!         }
71//!     }
72//! }
73//!
74//! impl DatabaseModel for Person {
75//!     type PrimaryKey = i32;
76//!     fn primary_key(&self) -> Self::PrimaryKey {
77//!         self.id
78//!     }
79//!
80//!     fn set_primary_key(&mut self, value: &Self::PrimaryKey) {
81//!         self.id = *value;
82//!     }
83//!
84//!     fn default_primary_key() -> Self::PrimaryKey {
85//!         0
86//!     }
87//!
88//!     fn table_name() -> &'static str {
89//!         "persons"
90//!     }
91//! }
92//!
93//! // Define your custom changes to the model before and after the transactions.
94//! impl<T> naphtha::DatabaseUpdateHandler<T> for Person {}
95//! impl<T> naphtha::DatabaseRemoveHandler<T> for Person {}
96//! impl<T> naphtha::DatabaseInsertHandler<T> for Person {}
97//!
98//! // This implements your database migration functions.
99//! impl DatabaseSqlMigration for Person {
100//!     fn migration_up(migration: &mut Migration) {
101//!         use naphtha::DatabaseModel;
102//!         migration.create_table_if_not_exists(Self::table_name(), |t| {
103//!             t.add_column("id", types::primary());
104//!             t.add_column("description", types::text().nullable(true));
105//!             t.add_column("updated_at", types::custom("timestamp"));
106//!         });
107//!     }
108//!
109//!     fn migration_down(migration: &mut Migration) {
110//!         use naphtha::DatabaseModel;
111//!         migration.drop_table_if_exists(Self::table_name());
112//!     }
113//! }
114//!
115//! fn main() {
116//!     use naphtha::{DatabaseConnection, DatabaseConnect};
117//!     let db = DatabaseConnection::connect(":memory:").unwrap();
118//!     // p is to be mutable because the insert function updates the id member
119//!     // to the one given by the database.
120//!     let mut p = Person {
121//!         id: Person::default_primary_key(),
122//!         description: Some("The new person is registered".into()),
123//!     };
124//!     p.insert(&db);
125//!     // id member is set to the correct number given by the database.
126//!
127//!     // do a custom query to the database
128//!     db.custom::<diesel::result::QueryResult::<Person>, _>(|c: &DbBackend| {
129//!         use schema::{persons, persons::dsl::*};
130//!         persons.filter(id.eq(1)).first(c)
131//!     });
132//!
133//!     p.remove(&db);
134//!     // p not available anymore in the database
135//! }
136//! ```
137
138pub use diesel;
139pub extern crate log;
140
141use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
142
143/// Defines your `struct` as a model and implements the required traits for
144/// interacting with the database. Currently only *named* `struct` member are
145/// supported.
146pub use naphtha_proc_macro::model;
147
148#[cfg(any(
149    feature = "barrel-sqlite",
150    feature = "barrel-mysql",
151    feature = "barrel-pg"
152))]
153/// Re-exports the [barrel] crate including small trait additions required by naphtha.
154pub mod barrel;
155mod database_impl;
156mod tests;
157
158/// Thin wrapper around a [Connection](diesel::Connection).
159pub struct DatabaseConnection<T>(Arc<Mutex<T>>);
160
161impl<T> DatabaseConnection<T> {
162    /// Aquires a lock to the wrapped connection.
163    pub fn lock(
164        &self,
165    ) -> Result<MutexGuard<'_, T>, PoisonError<MutexGuard<'_, T>>> {
166        self.0.lock()
167    }
168
169    /// Executes the custom function to the database instance.
170    pub fn custom<R, F>(&self, query: F) -> R
171    where
172        F: Fn(&T) -> R,
173    {
174        let c = self.0.lock().expect("Could not aquire connection lock!");
175        query(&*c)
176    }
177}
178
179/// Contains functions database connection handling.
180pub trait DatabaseConnect<T> {
181    /// Establishes a new connection to the given database string.
182    fn connect(database_url: &str) -> Result<DatabaseConnection<T>, String>;
183}
184
185/// Defines the relation of the model to the database.
186pub trait DatabaseModel {
187    /// Defines the primary key type on the database table.
188    type PrimaryKey;
189
190    /// Returns the primary key value.
191    fn primary_key(&self) -> Self::PrimaryKey;
192    /// Sets the primary key value.
193    fn set_primary_key(&mut self, value: &Self::PrimaryKey);
194    /// Gets the default primary key value.
195    fn default_primary_key() -> Self::PrimaryKey;
196    /// Returns the table name related to the model.
197    fn table_name() -> &'static str;
198}
199
200/// Defines functions to modify the stored model instance on the database.
201pub trait DatabaseModelModifier<T>
202where
203    Self: DatabaseUpdateHandler<T>
204        + DatabaseInsertHandler<T>
205        + DatabaseRemoveHandler<T>,
206{
207    /// Inserts `self` to the given database.
208    /// *Updates the `primary_key` to the one that has been assigned by the database*.
209    fn insert(&mut self, conn: &DatabaseConnection<T>) -> bool;
210    /// Removes `self` from the database, selects by `id`.
211    fn remove(&mut self, conn: &DatabaseConnection<T>) -> bool;
212    /// Updates `self` on the given database.
213    /// *Updates the `updated_at` member if available before updating the database.*.
214    fn update(&mut self, conn: &DatabaseConnection<T>) -> bool;
215}
216
217/// Methods that are called before and after the transaction executed when
218/// the [insert](DatabaseModelModifier::insert) method is called.
219/// Can be used to do custom changes to the database or the model instance.
220/// Useful for extending the basic CRUD model.
221#[allow(unused_variables)]
222pub trait DatabaseInsertHandler<T> {
223    /// This method is called before the transaction to the database takes place.
224    fn pre_insert(&mut self, conn: &DatabaseConnection<T>) {}
225    /// This method is called after the transaction to the database took place.
226    fn post_insert(&mut self, conn: &DatabaseConnection<T>) {}
227}
228
229/// Methods that are called before and after the transaction executed when
230/// the [update](DatabaseModelModifier::update) method is called.
231/// Can be used to do custom changes to the database or the model instance.
232/// Useful for extending the basic CRUD model.
233#[allow(unused_variables)]
234pub trait DatabaseUpdateHandler<T> {
235    /// This method is called before the transaction to the database takes place.
236    fn pre_update(&mut self, conn: &DatabaseConnection<T>) {}
237    /// This method is called after the transaction to the database took place.
238    fn post_update(&mut self, conn: &DatabaseConnection<T>) {}
239}
240
241/// Methods that are called before and after the transaction executed when
242/// the [remove](DatabaseModelModifier::remove) method is called.
243/// Can be used to do custom changes to the database or the model instance.
244/// Useful for extending the basic CRUD model.
245#[allow(unused_variables)]
246pub trait DatabaseRemoveHandler<T> {
247    /// This method is called before the transaction to the database takes place.
248    fn pre_remove(&mut self, conn: &DatabaseConnection<T>) {}
249    /// This method is called after the transaction to the database took place.
250    fn post_remove(&mut self, conn: &DatabaseConnection<T>) {}
251}