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