parsql_tokio_postgres/
traits.rs

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