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