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}