1use crate::{
2 PostgresDriver, PostgresPrepared, PostgresTransaction, ValueWrap,
3 util::{
4 postgres_type_to_value, stream_postgres_row_to_tank_row,
5 stream_postgres_simple_query_message_to_tank_query_result, value_to_postgres_type,
6 },
7};
8use async_stream::try_stream;
9use openssl::ssl::{SslConnector, SslFiletype, SslMethod, SslVerifyMode};
10use postgres_openssl::MakeTlsConnector;
11use postgres_types::ToSql;
12use std::{
13 borrow::Cow,
14 env, mem,
15 path::PathBuf,
16 pin::{Pin, pin},
17 str::FromStr,
18};
19use tank_core::{
20 AsQuery, Connection, Driver, DynQuery, Entity, Error, ErrorContext, Executor, Query,
21 QueryResult, RawQuery, Result, RowsAffected, SqlWriter, Transaction,
22 future::Either,
23 stream::{Stream, StreamExt, TryStreamExt},
24 truncate_long,
25};
26use tokio::{spawn, task::JoinHandle};
27use tokio_postgres::{NoTls, binary_copy::BinaryCopyInWriter};
28use url::Url;
29
30#[derive(Debug)]
32pub struct PostgresConnection {
33 pub(crate) client: tokio_postgres::Client,
34 pub(crate) handle: JoinHandle<()>,
35 pub(crate) url: Url,
36}
37
38impl Executor for PostgresConnection {
39 type Driver = PostgresDriver;
40
41 async fn do_prepare(&mut self, sql: String) -> Result<Query<PostgresDriver>> {
42 let sql = sql.as_str().trim_end().trim_end_matches(';');
43 Ok(
44 PostgresPrepared::new(self.client.prepare(&sql).await.map_err(|e| {
45 let error = Error::new(e).context(format!(
46 "While preparing the query:\n{}",
47 truncate_long!(sql)
48 ));
49 log::error!("{:#}", error);
50 error
51 })?)
52 .into(),
53 )
54 }
55
56 fn run<'s>(
57 &'s mut self,
58 query: impl AsQuery<PostgresDriver> + 's,
59 ) -> impl Stream<Item = Result<QueryResult>> + Send {
60 let mut query = query.as_query();
61 let context = format!("While running the query:\n{}", query.as_mut());
62 let mut owned = mem::take(query.as_mut());
63 match owned {
64 Query::Raw(raw) => Either::Left(try_stream! {
65 let sql = &raw.0;
66 {
67 let stream = stream_postgres_simple_query_message_to_tank_query_result(
68 async move || self.client.simple_query_raw(sql).await.map_err(Into::into),
69 );
70 let mut stream = pin!(stream);
71 while let Some(value) = stream.next().await.transpose()? {
72 yield value;
73 }
74 }
75 *query.as_mut() = Query::Raw(raw);
76 }),
77 Query::Prepared(..) => Either::Right(try_stream! {
78 let mut transaction = self.begin().await?;
79 {
80 let mut stream = pin!(transaction.run(&mut owned));
81 while let Some(value) = stream.next().await.transpose()? {
82 yield value;
83 }
84 }
85 transaction.commit().await?;
86 *query.as_mut() = mem::take(&mut owned);
87 }),
88 }
89 .map_err(move |e: Error| {
90 let error = e.context(context.clone());
91 log::error!("{:#}", error);
92 error
93 })
94 }
95
96 fn fetch<'s>(
97 &'s mut self,
98 query: impl AsQuery<PostgresDriver> + 's,
99 ) -> impl Stream<Item = Result<tank_core::Row>> + Send {
100 let mut query = query.as_query();
101 let context = format!("While fetching the query:\n{}", query.as_mut());
102 let owned = mem::take(query.as_mut());
103 stream_postgres_row_to_tank_row(async move || {
104 let row_stream = match owned {
105 Query::Raw(RawQuery(sql)) => {
106 let stream = self
107 .client
108 .query_raw(&sql, Vec::<ValueWrap>::new())
109 .await
110 .map_err(|e| Error::new(e).context(context.clone()))?;
111 *query.as_mut() = Query::raw(sql);
112 stream
113 }
114 Query::Prepared(mut prepared) => {
115 let mut params = prepared.take_params();
116 let types = prepared.statement.params();
117
118 for (i, param) in params.iter_mut().enumerate() {
119 *param = ValueWrap(Cow::Owned(
120 mem::take(param)
121 .0
122 .into_owned()
123 .try_as(&postgres_type_to_value(&types[i]))?,
124 ));
125 }
126 let stream = self
127 .client
128 .query_raw(&prepared.statement, params)
129 .await
130 .map_err(|e| Error::new(e).context(context.clone()))?;
131 *query.as_mut() = Query::Prepared(prepared);
132 stream
133 }
134 };
135 Ok(row_stream).map_err(|e| {
136 log::error!("{:#}", e);
137 e
138 })
139 })
140 }
141
142 async fn append<'a, E, It>(&mut self, entities: It) -> Result<RowsAffected>
143 where
144 E: Entity + 'a,
145 It: IntoIterator<Item = &'a E> + Send,
146 <It as IntoIterator>::IntoIter: Send,
147 {
148 let writer = self.driver().sql_writer();
149 let context = || {
150 format!(
151 "While appending to the table `{}`",
152 E::table().full_name(writer.separator())
153 )
154 };
155 let mut query = DynQuery::default();
156 writer.write_copy::<E>(&mut query);
157 let sink = match self
158 .client
159 .copy_in(&query.as_str() as &str)
160 .await
161 .with_context(context)
162 {
163 Ok(v) => v,
164 Err(e) => {
165 log::error!("{e:#}");
166 return Err(e);
167 }
168 };
169 let types: Vec<_> = E::columns()
170 .into_iter()
171 .map(|c| value_to_postgres_type(&c.value))
172 .collect();
173 let writer = BinaryCopyInWriter::new(sink, &types);
174 let mut writer = pin!(writer);
175 let columns_len = E::columns().len();
176 let mut values = Vec::<ValueWrap>::with_capacity(columns_len);
177 let mut refs = Vec::<&(dyn ToSql + Sync)>::with_capacity(columns_len);
178 for entity in entities.into_iter() {
179 values.extend(
180 entity
181 .row_values()
182 .into_iter()
183 .map(|v| ValueWrap(Cow::Owned(v))),
184 );
185 refs.extend(
186 values
187 .iter()
188 .map(|v| unsafe { &*(v as &(dyn ToSql + Sync) as *const _) }),
189 );
190 match Pin::as_mut(&mut writer)
191 .write(&refs)
192 .await
193 .with_context(context)
194 {
195 Ok(_) => {}
196 Err(e) => {
197 log::error!("{e:#}");
198 return Err(e);
199 }
200 };
201 refs.clear();
202 values.clear();
203 }
204 match writer.finish().await.with_context(context) {
205 Ok(v) => Ok(RowsAffected {
206 rows_affected: Some(v),
207 last_affected_id: None,
208 }),
209 Err(e) => {
210 log::error!("{e:#}");
211 return Err(e);
212 }
213 }
214 }
215}
216
217impl Connection for PostgresConnection {
218 async fn connect(driver: &PostgresDriver, url: Cow<'static, str>) -> Result<Self> {
219 let context = "While trying to connect to Postgres";
220 let mut url = Self::sanitize_url(driver, url)?;
221 let mut take_url_param = |key: &str, env_var: &str, remove: bool| {
222 let value = url
223 .query_pairs()
224 .find_map(|(k, v)| if k == key { Some(v) } else { None })
225 .map(|v| v.to_string());
226 if remove && let Some(..) = value {
227 let mut result = url.clone();
228 result.set_query(None);
229 result
230 .query_pairs_mut()
231 .extend_pairs(url.query_pairs().filter(|(k, _)| k != key));
232 url = result;
233 };
234 value.or_else(|| env::var(env_var).ok().map(Into::into))
235 };
236 let sslmode = take_url_param("sslmode", "PGSSLMODE", false).unwrap_or("disable".into());
237 let (client, handle) = if sslmode == "disable" {
238 let (client, connection) = tokio_postgres::connect(url.as_str(), NoTls).await?;
239 let handle = spawn(async move {
240 if let Err(error) = connection.await
241 && !error.is_closed()
242 {
243 log::error!("Postgres connection error: {:#?}", error);
244 }
245 });
246 (client, handle)
247 } else {
248 let mut builder = SslConnector::builder(SslMethod::tls())?;
249 let path = PathBuf::from_str(
250 take_url_param("sslrootcert", "PGSSLROOTCERT", true)
251 .as_deref()
252 .unwrap_or("~/.postgresql/root.crt"),
253 )
254 .context(context)?;
255 if path.exists() {
256 builder.set_ca_file(path)?;
257 }
258 let path = PathBuf::from_str(
259 take_url_param("sslcert", "PGSSLCERT", true)
260 .as_deref()
261 .unwrap_or("~/.postgresql/postgresql.crt"),
262 )
263 .context(context)?;
264 if path.exists() {
265 builder.set_certificate_chain_file(path)?;
266 }
267 let path = PathBuf::from_str(
268 take_url_param("sslkey", "PGSSLKEY", true)
269 .as_deref()
270 .unwrap_or("~/.postgresql/postgresql.key"),
271 )
272 .context(context)?;
273 if path.exists() {
274 builder.set_private_key_file(path, SslFiletype::PEM)?;
275 }
276 builder.set_verify(SslVerifyMode::PEER);
277 let connector = MakeTlsConnector::new(builder.build());
278 let (client, connection) = tokio_postgres::connect(url.as_str(), connector).await?;
279 let handle = spawn(async move {
280 if let Err(error) = connection.await
281 && !error.is_closed()
282 {
283 log::error!("Postgres connection error: {:#?}", error);
284 }
285 });
286 (client, handle)
287 };
288 Ok(Self {
289 client,
290 handle,
291 url,
292 })
293 }
294
295 fn begin(&mut self) -> impl Future<Output = Result<PostgresTransaction<'_>>> {
296 PostgresTransaction::new(self)
297 }
298
299 async fn duplicate(&self) -> Result<PostgresConnection>
300 where
301 Self: Sized,
302 {
303 Self::connect(&self.driver(), self.url.to_string().into()).await
304 }
305
306 async fn disconnect(self) -> Result<()> {
307 drop(self.client);
308 if let Err(e) = self.handle.await {
309 let error = Error::new(e).context("While disconnecting from Postgres");
310 log::error!("{:#}", error);
311 return Err(error);
312 }
313 Ok(())
314 }
315}