1#![doc = include_str!("../README.md")]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3#![deny(
4 nonstandard_style,
5 rust_2018_idioms,
6 rustdoc::broken_intra_doc_links,
7 rustdoc::private_intra_doc_links
8)]
9#![forbid(non_ascii_idents, unsafe_code)]
10#![warn(
11 deprecated_in_future,
12 missing_copy_implementations,
13 missing_debug_implementations,
14 missing_docs,
15 unreachable_pub,
16 unused_import_braces,
17 unused_labels,
18 unused_lifetimes,
19 unused_qualifications,
20 unused_results
21)]
22#![allow(clippy::uninlined_format_args)]
23
24use tokio_postgres::{types::ToSql, Error, Row, Statement};
25
26pub use tusker_query_derive::Query;
27
28pub mod types;
30
31#[doc(hidden)]
32pub mod __private {
33 use super::{Error, Row, Statement, ToSql};
34
35 pub mod sealed {
36 pub trait Sealed {}
37
38 impl Sealed for tokio_postgres::Client {}
39 impl Sealed for tokio_postgres::Transaction<'_> {}
40
41 #[cfg(feature = "deadpool")]
42 impl Sealed for deadpool_postgres::Client {}
43 #[cfg(feature = "deadpool")]
44 impl Sealed for deadpool_postgres::Transaction<'_> {}
45 }
46
47 #[doc(hidden)]
48 pub trait QueryClient: sealed::Sealed {
49 fn prepare_query(
50 &self,
51 query: &str,
52 ) -> impl core::future::Future<Output = Result<Statement, Error>> + Send;
53
54 fn query_prepared<'a>(
55 &'a self,
56 statement: &'a Statement,
57 params: &'a [&'a (dyn ToSql + Sync)],
58 ) -> impl core::future::Future<Output = Result<Vec<Row>, Error>> + Send + 'a;
59
60 fn query_one_prepared<'a>(
61 &'a self,
62 statement: &'a Statement,
63 params: &'a [&'a (dyn ToSql + Sync)],
64 ) -> impl core::future::Future<Output = Result<Row, Error>> + Send + 'a;
65 }
66
67 impl QueryClient for tokio_postgres::Client {
68 async fn prepare_query(&self, query: &str) -> Result<Statement, Error> {
69 self.prepare(query).await
70 }
71
72 async fn query_prepared<'a>(
73 &'a self,
74 statement: &'a Statement,
75 params: &'a [&'a (dyn ToSql + Sync)],
76 ) -> Result<Vec<Row>, Error> {
77 self.query(statement, params).await
78 }
79
80 async fn query_one_prepared<'a>(
81 &'a self,
82 statement: &'a Statement,
83 params: &'a [&'a (dyn ToSql + Sync)],
84 ) -> Result<Row, Error> {
85 self.query_one(statement, params).await
86 }
87 }
88
89 impl QueryClient for tokio_postgres::Transaction<'_> {
90 async fn prepare_query(&self, query: &str) -> Result<Statement, Error> {
91 self.prepare(query).await
92 }
93
94 async fn query_prepared<'a>(
95 &'a self,
96 statement: &'a Statement,
97 params: &'a [&'a (dyn ToSql + Sync)],
98 ) -> Result<Vec<Row>, Error> {
99 self.query(statement, params).await
100 }
101
102 async fn query_one_prepared<'a>(
103 &'a self,
104 statement: &'a Statement,
105 params: &'a [&'a (dyn ToSql + Sync)],
106 ) -> Result<Row, Error> {
107 self.query_one(statement, params).await
108 }
109 }
110
111 #[cfg(feature = "deadpool")]
112 impl QueryClient for deadpool_postgres::Client {
113 async fn prepare_query(&self, query: &str) -> Result<Statement, Error> {
114 deadpool_postgres::GenericClient::prepare_cached(self, query).await
115 }
116
117 async fn query_prepared<'a>(
118 &'a self,
119 statement: &'a Statement,
120 params: &'a [&'a (dyn ToSql + Sync)],
121 ) -> Result<Vec<Row>, Error> {
122 deadpool_postgres::GenericClient::query(self, statement, params).await
123 }
124
125 async fn query_one_prepared<'a>(
126 &'a self,
127 statement: &'a Statement,
128 params: &'a [&'a (dyn ToSql + Sync)],
129 ) -> Result<Row, Error> {
130 deadpool_postgres::GenericClient::query_one(self, statement, params).await
131 }
132 }
133
134 #[cfg(feature = "deadpool")]
135 impl QueryClient for deadpool_postgres::Transaction<'_> {
136 async fn prepare_query(&self, query: &str) -> Result<Statement, Error> {
137 deadpool_postgres::GenericClient::prepare_cached(self, query).await
138 }
139
140 async fn query_prepared<'a>(
141 &'a self,
142 statement: &'a Statement,
143 params: &'a [&'a (dyn ToSql + Sync)],
144 ) -> Result<Vec<Row>, Error> {
145 deadpool_postgres::GenericClient::query(self, statement, params).await
146 }
147
148 async fn query_one_prepared<'a>(
149 &'a self,
150 statement: &'a Statement,
151 params: &'a [&'a (dyn ToSql + Sync)],
152 ) -> Result<Row, Error> {
153 deadpool_postgres::GenericClient::query_one(self, statement, params).await
154 }
155 }
156
157 pub trait RowFieldCount<const N: usize> {}
158
159 pub trait RowFieldType<const I: usize> {
160 type Ty;
161 }
162}
163
164pub trait Query: Sized {
166 const SQL: &'static str;
168 type Row: FromRow;
170 fn as_params(&self) -> Box<[&(dyn ToSql + Sync)]>;
172}
173
174pub trait FromRow {
176 fn from_row(row: Row) -> Self;
178}
179
180pub use tusker_query_derive::FromRow;
181
182impl FromRow for () {
183 fn from_row(_: Row) -> Self {}
184}
185
186impl __private::RowFieldCount<0> for () {}
187
188pub async fn query_one<Q: Query, C>(client: &C, query: Q) -> Result<Q::Row, Error>
190where
191 C: __private::QueryClient + ?Sized,
192{
193 let stmt = client.prepare_query(Q::SQL).await?;
194 Ok(Q::Row::from_row(
195 client.query_one_prepared(&stmt, &query.as_params()).await?,
196 ))
197}
198
199pub async fn query<Q: Query, C>(client: &C, query: Q) -> Result<Vec<Q::Row>, Error>
201where
202 C: __private::QueryClient + ?Sized,
203{
204 let stmt = client.prepare_query(Q::SQL).await?;
205 let rows = client.query_prepared(&stmt, &query.as_params()).await?;
206 Ok(rows.into_iter().map(Q::Row::from_row).collect())
207}
208
209#[cfg(all(test, feature = "deadpool"))]
210mod tests {
211 use super::__private::QueryClient;
212
213 fn assert_query_client<T: QueryClient>() {}
214
215 #[test]
216 fn deadpool_client_implements_query_client() {
217 assert_query_client::<deadpool_postgres::Client>();
218 }
219
220 #[test]
221 fn deadpool_transaction_implements_query_client() {
222 fn assert_transaction<'a>()
223 where
224 deadpool_postgres::Transaction<'a>: QueryClient,
225 {
226 }
227
228 let _ = assert_transaction;
229 }
230}
231
232#[cfg(test)]
233mod doc_tests {
234 #[test]
235 fn readme_does_not_reference_public_query_client_trait() {
236 assert!(!include_str!("../README.md").contains("tusker_query::QueryClient"));
237 }
238}