parsql_tokio_postgres/
traits.rs

1use postgres::{types::{FromSql, ToSql}, Error, Row};
2
3/// Trait for generating SQL queries.
4/// This trait is implemented by the derive macro `Queryable`, `Insertable`, `Updateable`, and `Deletable`.
5pub trait SqlQuery {
6    /// Returns the SQL query string.
7    fn query() -> String;
8}
9
10/// Trait for providing SQL parameters.
11/// This trait is implemented by the derive macro `SqlParams`.
12pub trait SqlParams {
13    /// Returns a vector of references to SQL parameters.
14    fn params(&self) -> Vec<&(dyn ToSql + Sync)>;
15}
16
17/// Trait for providing UPDATE parameters.
18/// This trait is implemented by the derive macro `UpdateParams`.
19pub trait UpdateParams {
20    /// Returns a vector of references to SQL parameters for UPDATE operations.
21    fn params(&self) -> Vec<&(dyn ToSql + Sync)>;
22}
23
24/// Trait for converting database rows to Rust structs.
25/// This trait is implemented by the derive macro `FromRow`.
26pub trait FromRow {
27    /// Converts a database row to a Rust struct.
28    /// 
29    /// # Arguments
30    /// * `row` - A reference to a database row
31    /// 
32    /// # Returns
33    /// * `Result<Self, Error>` - The converted struct or an error
34    fn from_row(row: &Row) -> Result<Self, Error>
35    where
36        Self: Sized;
37}
38
39/// A trait for extending PostgreSQL client with CRUD operations.
40///
41/// This trait provides extension methods for tokio_postgres::Client to perform
42/// common database CRUD operations in a more ergonomic way.
43#[async_trait::async_trait]
44pub trait CrudOps {
45    /// Inserts a new record into the database.
46    ///
47    /// # Arguments
48    /// * `entity` - Data object to be inserted (must implement SqlQuery and SqlParams traits)
49    ///
50    /// # Return Value
51    /// * `Result<u64, Error>` - On success, returns the number of inserted records; on failure, returns Error
52    ///
53    /// # Example
54    /// ```rust,no_run
55    /// # use tokio_postgres::{NoTls, Client};
56    /// # use parsql::tokio_postgres::CrudOps;
57    /// # use parsql::macros::{Insertable, SqlParams};
58    /// #
59    /// #[derive(Insertable, SqlParams)]
60    /// #[table("users")]
61    /// struct InsertUser {
62    ///     name: String,
63    ///     email: String,
64    /// }
65    ///
66    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
67    /// # let (client, connection) = tokio_postgres::connect("", NoTls).await?;
68    /// # tokio::spawn(async move { connection.await; });
69    /// let user = InsertUser {
70    ///     name: "John".to_string(),
71    ///     email: "john@example.com".to_string(),
72    /// };
73    ///
74    /// let id = client.insert(user).await?;
75    /// # Ok(())
76    /// # }
77    /// ```
78    async fn insert<T, P:for<'a> FromSql<'a> + Send + Sync>(&self, entity: T) -> Result<P, Error>
79    where
80        T: SqlQuery + SqlParams + Send + Sync + 'static;
81
82    /// Updates an existing record in the database.
83    ///
84    /// # Arguments
85    /// * `entity` - Data object containing the update information (must implement SqlQuery and UpdateParams traits)
86    ///
87    /// # Return Value
88    /// * `Result<bool, Error>` - On success, returns true if at least one record was updated; on failure, returns Error
89    ///
90    /// # Example
91    /// ```rust,no_run
92    /// # use tokio_postgres::{NoTls, Client};
93    /// # use parsql::tokio_postgres::CrudOps;
94    /// # use parsql::macros::{Updateable, UpdateParams};
95    /// #
96    /// #[derive(Updateable, UpdateParams)]
97    /// #[table("users")]
98    /// #[update("name, email")]
99    /// #[where_clause("id = $")]
100    /// struct UpdateUser {
101    ///     id: i64,
102    ///     name: String,
103    ///     email: String,
104    /// }
105    ///
106    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
107    /// # let (client, connection) = tokio_postgres::connect("", NoTls).await?;
108    /// # tokio::spawn(async move { connection.await; });
109    /// let user = UpdateUser {
110    ///     id: 1,
111    ///     name: "John Smith".to_string(),
112    ///     email: "john.smith@example.com".to_string(),
113    /// };
114    ///
115    /// let updated = client.update(user).await?;
116    /// # Ok(())
117    /// # }
118    /// ```
119    async fn update<T>(&self, entity: T) -> Result<bool, Error>
120    where
121        T: SqlQuery + UpdateParams + Send + Sync + 'static;
122
123    /// Deletes a record from the database.
124    ///
125    /// # Arguments
126    /// * `entity` - Data object containing delete conditions (must implement SqlQuery and SqlParams traits)
127    ///
128    /// # Return Value
129    /// * `Result<u64, Error>` - On success, returns the number of deleted records; on failure, returns Error
130    ///
131    /// # Example
132    /// ```rust,no_run
133    /// # use tokio_postgres::{NoTls, Client};
134    /// # use parsql::tokio_postgres::CrudOps;
135    /// # use parsql::macros::{Deletable, SqlParams};
136    /// #
137    /// #[derive(Deletable, SqlParams)]
138    /// #[table("users")]
139    /// #[where_clause("id = $")]
140    /// struct DeleteUser {
141    ///     id: i64,
142    /// }
143    ///
144    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
145    /// # let (client, connection) = tokio_postgres::connect("", NoTls).await?;
146    /// # tokio::spawn(async move { connection.await; });
147    /// let user = DeleteUser { id: 1 };
148    ///
149    /// let deleted = client.delete(user).await?;
150    /// # Ok(())
151    /// # }
152    /// ```
153    async fn delete<T>(&self, entity: T) -> Result<u64, Error>
154    where
155        T: SqlQuery + SqlParams + Send + Sync + 'static;
156
157    /// Retrieves a single record from the database and converts it to a struct.
158    ///
159    /// # Arguments
160    /// * `params` - Data object containing query parameters (must implement SqlQuery, FromRow, and SqlParams traits)
161    ///
162    /// # Return Value
163    /// * `Result<T, Error>` - On success, returns the retrieved record as a struct; on failure, returns Error
164    ///
165    /// # Example
166    /// ```rust,no_run
167    /// # use tokio_postgres::{NoTls, Client};
168    /// # use parsql::tokio_postgres::CrudOps;
169    /// # use parsql::macros::{Queryable, FromRow, SqlParams};
170    /// #
171    /// #[derive(Queryable, FromRow, SqlParams, Debug)]
172    /// #[table("users")]
173    /// #[where_clause("id = $")]
174    /// struct GetUser {
175    ///     id: i64,
176    ///     name: String,
177    ///     email: String,
178    /// }
179    ///
180    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
181    /// # let (client, connection) = tokio_postgres::connect("", NoTls).await?;
182    /// # tokio::spawn(async move { connection.await; });
183    /// let query = GetUser {
184    ///     id: 1,
185    ///     name: Default::default(),
186    ///     email: Default::default(),
187    /// };
188    ///
189    /// let user = client.fetch(query).await?;
190    /// # Ok(())
191    /// # }
192    /// ```
193    async fn fetch<T>(&self, params: T) -> Result<T, Error>
194    where
195        T: SqlQuery + FromRow + SqlParams + Send + Sync + 'static;
196
197    /// Retrieves multiple records from the database and converts them to a vec of structs.
198    ///
199    /// # Arguments
200    /// * `params` - Data object containing query parameters (must implement SqlQuery, FromRow, and SqlParams traits)
201    ///
202    /// # Return Value
203    /// * `Result<Vec<T>, Error>` - On success, returns a vector of retrieved records; on failure, returns Error
204    ///
205    /// # Example
206    /// ```rust,no_run
207    /// # use tokio_postgres::{NoTls, Client};
208    /// # use parsql::tokio_postgres::CrudOps;
209    /// # use parsql::macros::{Queryable, FromRow, SqlParams};
210    /// #
211    /// #[derive(Queryable, FromRow, SqlParams, Debug)]
212    /// #[table("users")]
213    /// #[where_clause("state = $")]
214    /// struct GetActiveUsers {
215    ///     id: i64,
216    ///     name: String,
217    ///     email: String,
218    ///     state: i16,
219    /// }
220    ///
221    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
222    /// # let (client, connection) = tokio_postgres::connect("", NoTls).await?;
223    /// # tokio::spawn(async move { connection.await; });
224    /// let query = GetActiveUsers {
225    ///     id: 0,
226    ///     name: Default::default(),
227    ///     email: Default::default(),
228    ///     state: 1, // active users
229    /// };
230    ///
231    /// let users = client.fetch_all(query).await?;
232    /// # Ok(())
233    /// # }
234    /// ```
235    async fn fetch_all<T>(&self, params: T) -> Result<Vec<T>, Error>
236    where
237        T: SqlQuery + FromRow + SqlParams + Send + Sync + 'static;
238
239    /// Executes a custom SELECT query and converts the results using the provided function.
240    ///
241    /// # Arguments
242    /// * `entity` - Data object containing query parameters (must implement SqlQuery and SqlParams traits)
243    /// * `to_model` - Function to convert a row to the desired type
244    ///
245    /// # Return Value
246    /// * `Result<R, Error>` - On success, returns the converted record; on failure, returns Error
247    ///
248    /// # Example
249    /// ```rust,no_run
250    /// # use tokio_postgres::{NoTls, Client, Row};
251    /// # use parsql::tokio_postgres::CrudOps;
252    /// # use parsql::macros::{Queryable, SqlParams};
253    /// #
254    /// #[derive(Queryable, SqlParams)]
255    /// #[table("users")]
256    /// #[select("SELECT u.*, p.role FROM users u JOIN profiles p ON u.id = p.user_id")]
257    /// #[where_clause("u.state = $")]
258    /// struct UserQuery {
259    ///     state: i16,
260    /// }
261    ///
262    /// struct UserWithRole {
263    ///     id: i64,
264    ///     name: String,
265    ///     role: String,
266    /// }
267    ///
268    /// fn convert_row(row: &Row) -> Result<UserWithRole, tokio_postgres::Error> {
269    ///     Ok(UserWithRole {
270    ///         id: row.try_get("id")?,
271    ///         name: row.try_get("name")?,
272    ///         role: row.try_get("role")?,
273    ///     })
274    /// }
275    ///
276    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
277    /// # let (client, connection) = tokio_postgres::connect("", NoTls).await?;
278    /// # tokio::spawn(async move { connection.await; });
279    /// let query = UserQuery { state: 1 };
280    ///
281    /// let user = client.select(query, convert_row).await?;
282    /// # Ok(())
283    /// # }
284    /// ```
285    async fn select<T, F, R>(&self, entity: T, to_model: F) -> Result<R, Error>
286    where
287        T: SqlQuery + SqlParams + Send + Sync + 'static,
288        F: Fn(&Row) -> Result<R, Error> + Send + Sync + 'static,
289        R: Send + 'static;
290
291    /// Executes a custom SELECT query and converts all the results using the provided function.
292    ///
293    /// # Arguments
294    /// * `entity` - Data object containing query parameters (must implement SqlQuery and SqlParams traits)
295    /// * `to_model` - Function to convert a row to the desired type
296    ///
297    /// # Return Value
298    /// * `Result<Vec<R>, Error>` - On success, returns a vector of converted records; on failure, returns Error
299    ///
300    /// # Example
301    /// ```rust,no_run
302    /// # use tokio_postgres::{NoTls, Client, Row};
303    /// # use parsql::tokio_postgres::CrudOps;
304    /// # use parsql::macros::{Queryable, SqlParams};
305    /// #
306    /// #[derive(Queryable, SqlParams)]
307    /// #[table("users")]
308    /// #[select("SELECT u.*, p.role FROM users u JOIN profiles p ON u.id = p.user_id")]
309    /// #[where_clause("u.state = $")]
310    /// struct UserQuery {
311    ///     state: i16,
312    /// }
313    ///
314    /// struct UserWithRole {
315    ///     id: i64,
316    ///     name: String,
317    ///     role: String,
318    /// }
319    ///
320    /// fn convert_row(row: &Row) -> UserWithRole {
321    ///     UserWithRole {
322    ///         id: row.get("id"),
323    ///         name: row.get("name"),
324    ///         role: row.get("role"),
325    ///     }
326    /// }
327    ///
328    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
329    /// # let (client, connection) = tokio_postgres::connect("", NoTls).await?;
330    /// # tokio::spawn(async move { connection.await; });
331    /// let query = UserQuery { state: 1 };
332    ///
333    /// let users = client.select_all(query, convert_row).await?;
334    /// # Ok(())
335    /// # }
336    /// ```
337    async fn select_all<T, F, R>(&self, entity: T, to_model: F) -> Result<Vec<R>, Error>
338    where
339        T: SqlQuery + SqlParams + Send + Sync + 'static,
340        F: Fn(&Row) -> R + Send + Sync + 'static,
341        R: Send + 'static;
342
343    #[deprecated(
344        since = "0.2.0",
345        note = "Renamed to `fetch`. Please use `fetch` function instead."
346    )]
347    async fn get<T>(&self, params: T) -> Result<T, Error>
348    where
349        T: SqlQuery + FromRow + SqlParams + Send + Sync + 'static,
350    {
351        self.fetch(params).await
352    }
353
354    #[deprecated(
355        since = "0.2.0",
356        note = "Renamed to `fetch_all`. Please use `fetch_all` function instead."
357    )]
358    async fn get_all<T>(&self, params: T) -> Result<Vec<T>, Error>
359    where
360        T: SqlQuery + FromRow + SqlParams + Send + Sync + 'static,
361    {
362        self.fetch_all(params).await
363    }
364}