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}