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}