elefant_tools/
postgres_client_wrapper.rs1use crate::Result;
2use elefant_client::tokio_connection::TokioPostgresPool;
3use elefant_client::{
4 CollectBatch, ElefantClientError, FlattenTuple, FromSqlOwned, FromSqlRowOwned,
5 PostgresConnectionSettings, PostgresDataRow,
6};
7use tracing::instrument;
8
9pub struct PostgresClientWrapper {
11 pool: TokioPostgresPool,
12 version: i32,
14}
15
16impl PostgresClientWrapper {
17 #[instrument(skip_all)]
22 pub async fn new(settings: PostgresConnectionSettings) -> Result<Self> {
23 let pool = TokioPostgresPool::new(
24 elefant_client::tokio_connection::TokioConnectionFactory,
25 settings,
26 )
27 .await?;
28
29 let client = pool.get_client().await?;
30
31 let version_str = client
32 .get_parameter("server_version")
33 .ok_or(crate::ElefantToolsError::InvalidPostgresVersionResponse)?;
34
35 let major_version: i32 = version_str
37 .split('.')
38 .next()
39 .and_then(|s| s.parse().ok())
40 .ok_or(crate::ElefantToolsError::InvalidPostgresVersionResponse)?;
41
42 if major_version < 12 {
43 return Err(crate::ElefantToolsError::UnsupportedPostgresVersion(
44 version_str.to_string(),
45 ));
46 }
47
48 let version = major_version * 10;
49
50 Ok(PostgresClientWrapper { pool, version })
51 }
52
53 pub fn version(&self) -> i32 {
55 self.version
56 }
57
58 pub fn pool(&self) -> &TokioPostgresPool {
60 &self.pool
61 }
62
63 pub async fn execute_non_query(&self, sql: &str) -> Result {
65 let mut client = self.pool.get_client().await?;
66 client.execute_non_query_simple(sql).await.map_err(|e| {
67 crate::ElefantToolsError::PostgresErrorWithQuery {
68 source: e,
69 query: sql.to_string(),
70 }
71 })?;
72
73 Ok(())
74 }
75
76 pub async fn get_results<T: FromSqlRowOwned>(&self, sql: &str) -> Result<Vec<T>> {
78 let mut client = self.pool.get_client().await?;
79 let query_result = client.query_simple(sql).await.map_err(|e| {
80 crate::ElefantToolsError::PostgresErrorWithQuery {
81 source: e,
82 query: sql.to_string(),
83 }
84 })?;
85
86 let rows = query_result.collect_to_vec::<T>().await.map_err(|e| {
87 crate::ElefantToolsError::PostgresErrorWithQuery {
88 source: e,
89 query: sql.to_string(),
90 }
91 })?;
92
93 Ok(rows)
94 }
95
96 pub async fn get_result<T: FromSqlRowOwned>(&self, sql: &str) -> Result<T> {
98 let results = self.get_results(sql).await?;
99 if results.len() != 1 {
100 return Err(crate::ElefantToolsError::InvalidNumberOfResults {
101 actual: results.len(),
102 expected: 1,
103 });
104 }
105
106 let r = results.into_iter().next().unwrap();
108
109 Ok(r)
110 }
111
112 pub async fn get_single_results<T: FromSqlOwned>(&self, sql: &str) -> Result<Vec<T>> {
114 let r = self
115 .get_results::<(T,)>(sql)
116 .await?
117 .into_iter()
118 .map(|t| t.0)
119 .collect();
120
121 Ok(r)
122 }
123
124 pub async fn get_single_result<T: FromSqlOwned>(&self, sql: &str) -> Result<T> {
126 let result = self.get_result::<(T,)>(sql).await?;
127 Ok(result.0)
128 }
129}
130
131pub(crate) trait FromPgChar: Sized {
133 fn from_pg_char(c: char) -> std::result::Result<Self, crate::ElefantToolsError>;
134}
135
136pub(crate) trait RowEnumExt {
138 fn try_get_enum_value<T: FromPgChar>(
140 &self,
141 idx: usize,
142 ) -> std::result::Result<T, ElefantClientError>;
143 fn try_get_opt_enum_value<T: FromPgChar>(
145 &self,
146 idx: usize,
147 ) -> std::result::Result<Option<T>, ElefantClientError>;
148}
149
150impl RowEnumExt for PostgresDataRow<'_, '_> {
151 fn try_get_enum_value<T: FromPgChar>(
152 &self,
153 idx: usize,
154 ) -> std::result::Result<T, ElefantClientError> {
155 let c: char = self.get(idx)?;
156 T::from_pg_char(c).map_err(|e| ElefantClientError::PostgresError(e.to_string()))
157 }
158
159 fn try_get_opt_enum_value<T: FromPgChar>(
160 &self,
161 idx: usize,
162 ) -> std::result::Result<Option<T>, ElefantClientError> {
163 let c: Option<char> = self.get(idx)?;
164 match c {
165 Some('\0') => Ok(None),
166 Some(c) => {
167 Ok(Some(T::from_pg_char(c).map_err(|e| {
168 ElefantClientError::PostgresError(e.to_string())
169 })?))
170 }
171 None => Ok(None),
172 }
173 }
174}
175
176pub(crate) trait QueryResult: FromSqlRowOwned {
179 fn query(version: i32) -> &'static str;
180}
181
182pub(crate) struct BatchQueryBuilder<Batch> {
186 query: String,
187 version: i32,
188 _batch: std::marker::PhantomData<Batch>,
189}
190
191impl BatchQueryBuilder<()> {
192 pub(crate) fn new(connection: &PostgresClientWrapper) -> Self {
193 Self {
194 query: String::new(),
195 version: connection.version(),
196 _batch: std::marker::PhantomData,
197 }
198 }
199}
200
201impl<Batch> BatchQueryBuilder<Batch> {
202 pub(crate) fn add<T: QueryResult>(mut self) -> BatchQueryBuilder<(Batch, Vec<T>)> {
203 self.query.push_str(T::query(self.version));
204 BatchQueryBuilder {
205 query: self.query,
206 version: self.version,
207 _batch: std::marker::PhantomData,
208 }
209 }
210}
211
212impl<Batch: CollectBatch + FlattenTuple> BatchQueryBuilder<Batch> {
213 pub(crate) async fn execute(self, connection: &PostgresClientWrapper) -> Result<Batch::Output> {
214 let mut client = connection.pool().get_client().await?;
215 let mut result = client.query_simple(&self.query).await?;
216 Ok(Batch::collect(&mut result).await?.flatten())
217 }
218}