sqlx_crud/lib.rs
1//! sqlx-crud is an extension to [sqlx] to derive Create, Read, Update, and
2//! Delete (CRUD) methods for a struct representing a table in a sqlx database.
3//!
4//! This removes much of the common, repetitive code needed when dealing with
5//! the typical CRUD operations.
6//!
7//! This library strives to do a few, narrowly defined things well in an effort
8//! to reduce 80% of the redundant code you might write for a database
9//! application when operating on a single table at a time. It provides
10//! mechanisms: [Schema] and [Crud], to access and reuse the generated id,
11//! column, and query metadata. This can help with writing more complex queries
12//! outside of the single table CRUD paradigm.
13//!
14//! [sqlx]: https://github.com/launchbadge/sqlx
15//!
16//! # Design Considerations
17//!
18//! The code currently assumes identifiers are assigned outside of the database.
19//! This likely means the identifier is a UUID. Database generated IDs will be
20//! added in a subsequent release.
21//!
22//! The primary key for the table can be indicated by use of the [sqlx_crud_macros::SqlxCrud]
23//! `#[id]` field attribute. If no field is tagged as the [sqlx_crud_macros::SqlxCrud] `#[id]`
24//! then the first field in the struct is assumed to be the ID.
25//!
26//! The ordering of the columns used by queries and which columns are present
27//! is controlled by the field order of the struct. Ignored fields are not
28//! currently supported but will be added.
29//!
30//! # Features
31//!
32//! Because sqlx-crud depends on sqlx you need to use the same executor and TLS
33//! feature pair as you did with sqlx. If for example you used `tokio-rustls`
34//! with sqlx you should also use the same feature with sqlx-crud.
35//!
36//! Hopefully I can figure out a way to remove this requirement. I think
37//! I might need to use a build.rs script and interrogate the sqlx features that way.
38//!
39//! # Examples
40//!
41//! Given a table `users` defined as:
42//!
43//! ```sql
44//! CREATE TABLE users (
45//! id INTEGER PRIMARY KEY NOT NULL,
46//! username TEXT NOT NULL
47//! );
48//! ```
49//!
50//! To define a `User` struct with generated [Crud] methods:
51//!
52//! ```rust
53//! use sqlx::FromRow;
54//! use sqlx_crud::SqlxCrud;
55//!
56//! #[derive(Debug, FromRow, SqlxCrud)]
57//! pub struct User {
58//! pub user_id: i32,
59//! pub name: String,
60//! }
61//! ```
62//!
63//! [Crud]: traits/trait.Crud.html
64//!
65//! To create a new `User` in the database:
66//!
67//! ```rust
68//! # sqlx_crud::doctest_setup! { |pool| {
69//! use sqlx_crud::Crud;
70//!
71//! let new_user = User { user_id: 2, name: "new_user".to_string() };
72//! new_user.create(&pool).await?;
73//! # }}
74//! ```
75//!
76//! To query for a `User` where `user_id = 1`:
77//!
78//! ```rust
79//! # sqlx_crud::doctest_setup! { |pool| {
80//! use sqlx_crud::Crud;
81//!
82//! if let Some(user) = User::by_id(&pool, 1).await? {
83//! println!("User: {:?}", user);
84//! }
85//! # }}
86//! ```
87//!
88//! To update an existing record:
89//!
90//! ```rust
91//! # sqlx_crud::doctest_setup! { |pool| {
92//! use sqlx_crud::Crud;
93//!
94//! if let Some(mut user) = User::by_id(&pool, 1).await? {
95//! user.name = "something else".to_string();
96//! user.update(&pool).await?;
97//! }
98//! # }}
99//! ```
100//!
101//! To delete a record:
102//!
103//! ```rust
104//! # sqlx_crud::doctest_setup! { |pool| {
105//! use sqlx_crud::Crud;
106//!
107//! if let Some(mut user) = User::by_id(&pool, 1).await? {
108//! user.delete(&pool).await?;
109//! }
110//! # }}
111//! ```
112//!
113//! Reusing and modifying the [select_sql] query string:
114//!
115//! ```rust
116//! # sqlx_crud::doctest_setup! { |pool| {
117//! use futures::stream::TryStreamExt;
118//! use sqlx_crud::{Schema, SqlxCrud};
119//!
120//! #[derive(Debug, FromRow, SqlxCrud)]
121//! pub struct User {
122//! pub user_id: i32,
123//! pub name: String,
124//! }
125//!
126//! impl User {
127//! pub async fn all_limit(pool: &SqlitePool, limit: i32) -> Result<Vec<Self>, sqlx::Error> {
128//! let query = format!(
129//! "{} ORDER BY users.id ASC LIMIT ?",
130//! <Self as Schema>::select_sql()
131//! );
132//!
133//! let mut users = Vec::new();
134//! let mut stream = sqlx::query_as::<_, Self>(&query)
135//! .bind(limit)
136//! .fetch(pool);
137//!
138//! while let Some(user) = stream.try_next().await? {
139//! users.push(user);
140//! }
141//!
142//! Ok(users)
143//! }
144//! }
145//! # }}
146//! ```
147//!
148//! # Planned Future Improvements
149//!
150//! Subsequent updates will extend the library to be more useful in a larger
151//! variety of situations.
152//!
153//! * Allow database assigned primary keys
154//! * Crud::create() should return the assigned ID
155//! * Add a field attribute to ignore fields
156
157pub mod schema;
158pub mod traits;
159
160pub use sqlx_crud_macros::SqlxCrud;
161pub use traits::{Crud, Schema};
162
163#[macro_export]
164#[doc(hidden)]
165macro_rules! doctest_setup {
166 (|$pool:ident| $($t:tt)*) => {
167 use sqlx::FromRow;
168 use sqlx::SqlitePool;
169 use sqlx_crud::SqlxCrud;
170
171 #[derive(Debug, FromRow, SqlxCrud)]
172 pub struct User {
173 pub user_id: i32,
174 pub name: String,
175 }
176
177 fn main() -> Result<(), sqlx::Error> {
178 tokio_test::block_on(async {
179 let $pool = SqlitePool::connect(":memory:")
180 .await?;
181 sqlx::query("CREATE TABLE users (user_id INTEGER NOT NULL, name TEXT NOT NULL)")
182 .execute(&$pool)
183 .await?;
184 sqlx::query("INSERT INTO users (user_id, name) VALUES(?, ?)")
185 .bind::<i32>(1)
186 .bind("test")
187 .execute(&$pool)
188 .await?;
189
190 $($t)*;
191
192 Ok::<(), sqlx::Error>(())
193 });
194 Ok::<(), sqlx::Error>(())
195 }
196 }
197}