1use super::auth::{self, Scram};
4use super::protocol::{
5 self, Authentication, Backend, Buffer, Field, ServerError, TransactionStatus,
6};
7use super::types;
8use crate::config::DatabaseConfig;
9use crate::random;
10use crate::row::{Columns, Row};
11use crate::value::Value;
12use crate::driver::{BoxFuture, Driver, DriverConnection, QueryResult};
13use rustlavel_core::events::Event;
14use rustlavel_core::{Error, Result};
15use std::sync::Arc;
16use std::sync::atomic::{AtomicBool, Ordering};
17use std::time::Instant;
18use tokio::net::TcpStream;
19
20static LOG_BINDINGS: AtomicBool = AtomicBool::new(true);
27
28pub fn set_log_bindings(enabled: bool) {
29 LOG_BINDINGS.store(enabled, Ordering::Relaxed);
30}
31
32pub fn log_bindings() -> bool {
33 LOG_BINDINGS.load(Ordering::Relaxed)
34}
35
36pub struct Connection {
37 stream: crate::tls::DbStream,
38 buffer: Vec<u8>,
40 config: DatabaseConfig,
41 process_id: i32,
42 secret: i32,
43 status: TransactionStatus,
44 broken: bool,
46}
47
48impl Connection {
49 pub async fn connect(config: &DatabaseConfig) -> Result<Connection> {
51 let address = format!("{}:{}", config.host, config.port);
52
53 let stream = tokio::time::timeout(config.connect_timeout, TcpStream::connect(&address))
54 .await
55 .map_err(|_| {
56 Error::msg(format!(
57 "timed out connecting to {address}. Is PostgreSQL running and reachable?"
58 ))
59 })?
60 .map_err(|e| {
61 Error::msg(format!(
62 "cannot connect to {}: {e}",
63 config.redacted_url()
64 ))
65 })?;
66
67 let _ = stream.set_nodelay(true);
68
69 let mut connection = Connection {
70 stream: crate::tls::DbStream::Plain(stream),
71 buffer: Vec::with_capacity(8 * 1024),
72 config: config.clone(),
73 process_id: 0,
74 secret: 0,
75 status: TransactionStatus::Idle,
76 broken: false,
77 };
78
79 connection.negotiate_tls().await?;
83 connection.startup().await?;
84 Ok(connection)
85 }
86
87 pub fn is_broken(&self) -> bool {
88 self.broken
89 }
90
91 pub fn in_transaction(&self) -> bool {
94 self.status != TransactionStatus::Idle
95 }
96
97 pub fn is_encrypted(&self) -> bool {
99 self.stream.is_encrypted()
100 }
101
102 async fn negotiate_tls(&mut self) -> Result<()> {
110 let mode = self.config.tls_mode;
111 if !mode.wants_tls() {
112 return Ok(());
113 }
114
115 let mut request = Vec::with_capacity(8);
116 request.extend_from_slice(&8i32.to_be_bytes());
117 request.extend_from_slice(&protocol::SSL_REQUEST_CODE.to_be_bytes());
118 if let Err(e) = self.stream.write_all(&request).await {
119 self.broken = true;
120 return Err(Error::Io(e));
121 }
122 if let Err(e) = self.stream.flush().await {
123 self.broken = true;
124 return Err(Error::Io(e));
125 }
126
127 let mut answer = [0u8; 1];
128 match self.stream.read(&mut answer).await {
129 Ok(1) => {}
130 Ok(_) => {
131 self.broken = true;
132 return Err(Error::msg(
133 "the server closed the connection when asked about TLS. A PostgreSQL older than 8.0 does not understand SSLRequest; set sslmode=disable if that is what this is.",
134 ));
135 }
136 Err(e) => {
137 self.broken = true;
138 return Err(Error::Io(e));
139 }
140 }
141
142 match answer[0] {
143 b'S' => {
144 let plain = self.stream.take_plain()?;
145 let encrypted =
146 crate::tls::upgrade(plain, &self.config.host, &self.config).await?;
147 self.stream = crate::tls::DbStream::Tls(Box::new(encrypted));
148 Ok(())
149 }
150 b'N' if !mode.demands_tls() => Ok(()),
153 b'N' => {
154 self.broken = true;
155 Err(Error::msg(format!(
156 "sslmode is `{mode}` but this PostgreSQL refused to encrypt the connection. Either the server was built without SSL support or `ssl` is off in postgresql.conf. Turn it on, or set sslmode=prefer to accept a connection in clear text."
157 )))
158 }
159 b'E' => {
160 self.broken = true;
161 Err(Error::msg(
162 "the server reported an error in response to SSLRequest. This usually means it is not actually PostgreSQL.",
163 ))
164 }
165 other => {
166 self.broken = true;
167 Err(Error::msg(format!(
168 "the server answered SSLRequest with {other:?}, which is not `S` or `N`. Whatever is on this port, it is not speaking the PostgreSQL protocol."
169 )))
170 }
171 }
172 }
173
174 async fn startup(&mut self) -> Result<()> {
175 let mut buffer = Buffer::new();
176 buffer.startup(&[
177 ("user", self.config.user.as_str()),
178 ("database", self.config.database.as_str()),
179 ("application_name", self.config.application_name.as_str()),
180 ("DateStyle", "ISO, MDY"),
182 ("client_encoding", "UTF8"),
183 ]);
184 self.write(buffer).await?;
185
186 let mut scram: Option<Scram> = None;
187
188 loop {
189 match self.read_message().await? {
190 Backend::Authentication(Authentication::Ok) => continue,
191 Backend::Authentication(Authentication::CleartextPassword) => {
192 let mut buffer = Buffer::new();
193 buffer.password(&self.config.password);
194 self.write(buffer).await?;
195 }
196 Backend::Authentication(Authentication::Md5Password { salt }) => {
197 let digest =
198 auth::md5_password(&self.config.user, &self.config.password, &salt);
199 let mut buffer = Buffer::new();
200 buffer.password(&digest);
201 self.write(buffer).await?;
202 }
203 Backend::Authentication(Authentication::Sasl { mechanisms }) => {
204 if !mechanisms.iter().any(|m| m == Scram::MECHANISM) {
205 return Err(Error::msg(format!(
206 "the server offers only {mechanisms:?}; this driver implements {}",
207 Scram::MECHANISM
208 )));
209 }
210 let exchange = Scram::new(&self.config.password, random::nonce(24));
211 let mut buffer = Buffer::new();
212 buffer.sasl_initial(Scram::MECHANISM, &exchange.client_first());
213 self.write(buffer).await?;
214 scram = Some(exchange);
215 }
216 Backend::Authentication(Authentication::SaslContinue { data }) => {
217 let exchange = scram
218 .as_mut()
219 .ok_or_else(|| Error::Protocol("SASL continue before SASL start".into()))?;
220 let response = exchange.client_final(&data)?;
221 let mut buffer = Buffer::new();
222 buffer.sasl_response(&response);
223 self.write(buffer).await?;
224 }
225 Backend::Authentication(Authentication::SaslFinal { data }) => {
226 scram
227 .as_ref()
228 .ok_or_else(|| Error::Protocol("SASL final before SASL start".into()))?
229 .verify(&data)?;
230 }
231 Backend::Authentication(Authentication::Unsupported(code)) => {
232 return Err(Error::msg(format!(
233 "the server requested authentication method {code}, which this driver does not implement"
234 )));
235 }
236 Backend::BackendKeyData { process_id, secret } => {
237 self.process_id = process_id;
238 self.secret = secret;
239 }
240 Backend::ParameterStatus { .. } | Backend::Notice(_) => continue,
241 Backend::ReadyForQuery(status) => {
242 self.status = status;
243 return Ok(());
244 }
245 Backend::Error(error) => {
246 self.broken = true;
247 return Err(authentication_error(error, &self.config));
248 }
249 other => {
250 return Err(Error::Protocol(format!(
251 "unexpected message during startup: {other:?}"
252 )));
253 }
254 }
255 }
256 }
257
258 pub async fn simple_query(&mut self, sql: &str) -> Result<QueryResult> {
263 let started = Instant::now();
264 let mut buffer = Buffer::new();
265 buffer.query(sql);
266 self.write(buffer).await?;
267
268 let result = self.collect(sql).await;
269 self.record(sql, &[], started, &result);
270 result
271 }
272
273 pub async fn query(&mut self, sql: &str, params: &[Value]) -> Result<QueryResult> {
279 if params.is_empty() {
280 return self.extended(sql, params).await;
283 }
284 self.extended(sql, params).await
285 }
286
287 async fn extended(&mut self, sql: &str, params: &[Value]) -> Result<QueryResult> {
288 let started = Instant::now();
289 let encoded: Vec<Option<String>> = params.iter().map(Value::to_sql_text).collect();
290
291 let mut buffer = Buffer::new();
292 buffer.parse("", sql);
293 buffer.bind("", "", &encoded);
294 buffer.describe_portal("");
295 buffer.execute("", 0);
296 buffer.sync();
297 self.write(buffer).await?;
298
299 let result = self.collect(sql).await;
300 self.record(sql, params, started, &result);
301 result
302 }
303
304 async fn collect(&mut self, sql: &str) -> Result<QueryResult> {
309 let mut columns: Columns = Arc::new(Vec::new());
310 let mut fields: Vec<Field> = Vec::new();
311 let mut result = QueryResult::default();
312 let mut failure: Option<ServerError> = None;
313
314 loop {
315 match self.read_message().await? {
316 Backend::RowDescription(described) => {
317 columns = Arc::new(described.iter().map(|f| f.name.clone()).collect());
318 fields = described;
319 }
320 Backend::DataRow(raw) => {
321 let values = raw
322 .iter()
323 .enumerate()
324 .map(|(index, bytes)| {
325 let oid = fields.get(index).map_or(types::TEXT, |f| f.type_oid);
326 types::decode(oid, bytes.as_deref())
327 })
328 .collect();
329 result.rows.push(Row::new(Arc::clone(&columns), values));
330 }
331 Backend::CommandComplete(tag) => result.affected = affected_rows(&tag),
332 Backend::Error(error) => failure = Some(error),
333 Backend::ReadyForQuery(status) => {
334 self.status = status;
335 break;
336 }
337 Backend::EmptyQueryResponse
338 | Backend::ParseComplete
339 | Backend::BindComplete
340 | Backend::CloseComplete
341 | Backend::NoData
342 | Backend::PortalSuspended
343 | Backend::Notice(_)
344 | Backend::ParameterStatus { .. }
345 | Backend::NotificationResponse { .. }
346 | Backend::BackendKeyData { .. }
347 | Backend::Other(_)
348 | Backend::Authentication(_) => {}
349 }
350 }
351
352 match failure {
353 Some(error) => Err(error.into_error(Some(sql))),
354 None => Ok(result),
355 }
356 }
357
358 fn record(&self, sql: &str, params: &[Value], started: Instant, result: &Result<QueryResult>) {
360 let elapsed = started.elapsed();
361
362 if rustlavel_core::events::has_subscribers() {
363 let bindings = if log_bindings() {
364 params.iter().map(Value::to_display).collect::<Vec<_>>().join(", ")
365 } else {
366 format!("{} value(s) hidden", params.len())
367 };
368 Event::new("db.query")
369 .with("sql", sql)
370 .with("bindings", bindings)
371 .with("rows", result.as_ref().map(|r| r.rows.len()).unwrap_or(0))
372 .with("ok", result.is_ok())
373 .took(elapsed)
374 .dispatch();
375 }
376
377 rustlavel_core::debug!("db: {sql} ({:.1}ms)", elapsed.as_secs_f64() * 1000.0);
378 }
379
380 async fn write(&mut self, buffer: Buffer) -> Result<()> {
381 let bytes = buffer.into_bytes();
382 if let Err(e) = self.stream.write_all(&bytes).await {
383 self.broken = true;
384 return Err(Error::Io(e));
385 }
386 if let Err(e) = self.stream.flush().await {
387 self.broken = true;
388 return Err(Error::Io(e));
389 }
390 Ok(())
391 }
392
393 async fn read_message(&mut self) -> Result<Backend> {
395 self.fill_to(5).await?;
397 let tag = self.buffer[0];
398 let length = i32::from_be_bytes(self.buffer[1..5].try_into().expect("4 bytes")) as usize;
399
400 if length < 4 {
401 self.broken = true;
402 return Err(Error::Protocol("message length is impossibly small".into()));
403 }
404
405 let total = length + 1;
406 self.fill_to(total).await?;
407 let body = self.buffer[5..total].to_vec();
408 self.buffer.drain(..total);
409
410 Backend::parse(tag, &body)
411 }
412
413 async fn fill_to(&mut self, wanted: usize) -> Result<()> {
415 while self.buffer.len() < wanted {
416 let mut chunk = [0u8; 8192];
417 let read = match self.stream.read(&mut chunk).await {
418 Ok(read) => read,
419 Err(e) => {
420 self.broken = true;
421 return Err(Error::Io(e));
422 }
423 };
424 if read == 0 {
425 self.broken = true;
426 return Err(Error::Protocol(
427 "the database closed the connection unexpectedly".into(),
428 ));
429 }
430 self.buffer.extend_from_slice(&chunk[..read]);
431 }
432 Ok(())
433 }
434
435 pub async fn close(mut self) {
437 let mut buffer = Buffer::new();
438 buffer.terminate();
439 let _ = self.write(buffer).await;
440 let _ = self.stream.shutdown().await;
441 }
442}
443
444pub struct PostgresDriver {
449 config: DatabaseConfig,
450 dialect: Arc<dyn crate::dialect::Dialect>,
451}
452
453impl PostgresDriver {
454 pub fn new(config: DatabaseConfig) -> Self {
455 PostgresDriver { config, dialect: Arc::new(crate::dialect::Postgres) }
456 }
457
458 pub fn config(&self) -> &DatabaseConfig {
459 &self.config
460 }
461}
462
463impl Driver for PostgresDriver {
464 fn generation(&self) -> u64 {
465 self.config.generation()
466 }
467
468 fn dialect(&self) -> Arc<dyn crate::dialect::Dialect> {
469 Arc::clone(&self.dialect)
470 }
471
472 fn connect(&self) -> BoxFuture<'_, Result<Box<dyn DriverConnection>>> {
473 Box::pin(async move {
474 let connection = Connection::connect(&self.config.resolved()).await?;
475 Ok(Box::new(connection) as Box<dyn DriverConnection>)
476 })
477 }
478
479 fn describe(&self) -> String {
480 self.config.redacted_url()
481 }
482
483 fn max_connections(&self) -> usize {
484 self.config.max_connections
485 }
486}
487
488impl DriverConnection for Connection {
489 fn query<'a>(
490 &'a mut self,
491 sql: &'a str,
492 params: &'a [Value],
493 ) -> BoxFuture<'a, Result<QueryResult>> {
494 Box::pin(Connection::query(self, sql, params))
495 }
496
497 fn simple_query<'a>(&'a mut self, sql: &'a str) -> BoxFuture<'a, Result<QueryResult>> {
498 Box::pin(Connection::simple_query(self, sql))
499 }
500
501 fn is_broken(&self) -> bool {
502 Connection::is_broken(self)
503 }
504
505 fn in_transaction(&self) -> bool {
506 Connection::in_transaction(self)
507 }
508
509 fn close(self: Box<Self>) -> BoxFuture<'static, ()> {
510 Box::pin(async move { Connection::close(*self).await })
511 }
512}
513
514fn affected_rows(tag: &str) -> u64 {
516 tag.split_whitespace().next_back().and_then(|n| n.parse().ok()).unwrap_or(0)
517}
518
519fn authentication_error(error: ServerError, config: &DatabaseConfig) -> Error {
521 let base = error.clone().into_error(None);
522
523 let advice = match error.code.as_str() {
524 "28P01" => Some(format!(
525 "The password for `{}` was rejected. Check DATABASE_URL in your .env.",
526 config.user
527 )),
528 "3D000" => Some(format!(
529 "Database `{}` does not exist. Create it, or point DATABASE_URL at an existing one.",
530 config.database
531 )),
532 "28000" => Some(
533 "The server rejected this role or host. Check pg_hba.conf allows this connection."
534 .to_string(),
535 ),
536 _ => None,
537 };
538
539 match advice {
540 Some(advice) => Error::msg(format!("{base}\n {advice}")),
541 None => base,
542 }
543}
544
545#[cfg(test)]
546mod tests {
547 use super::*;
548
549 #[test]
550 fn reads_the_row_count_from_a_command_tag() {
551 assert_eq!(affected_rows("INSERT 0 3"), 3);
552 assert_eq!(affected_rows("UPDATE 2"), 2);
553 assert_eq!(affected_rows("DELETE 0"), 0);
554 assert_eq!(affected_rows("CREATE TABLE"), 0);
555 }
556
557 #[test]
558 fn bindings_can_be_kept_out_of_the_event_stream() {
559 assert!(log_bindings());
561 set_log_bindings(false);
562 assert!(!log_bindings());
563 set_log_bindings(true);
564 }
565
566 #[test]
567 fn a_wrong_password_explains_where_to_look() {
568 let error = ServerError {
569 code: "28P01".into(),
570 message: "password authentication failed".into(),
571 ..ServerError::default()
572 };
573 let config = DatabaseConfig { user: "ada".into(), ..DatabaseConfig::default() };
574
575 let rendered = authentication_error(error, &config).to_string();
576 assert!(rendered.contains("DATABASE_URL"));
577 assert!(rendered.contains("`ada`"));
578 }
579
580 #[tokio::test]
581 async fn connecting_to_a_closed_port_names_the_server() {
582 let config = DatabaseConfig { port: 1, ..DatabaseConfig::default() };
583 let error = match Connection::connect(&config).await {
584 Err(error) => error.to_string(),
585 Ok(_) => panic!("nothing should be listening on port 1"),
586 };
587
588 assert!(error.contains("127.0.0.1:1"));
589 assert!(!error.contains("***@") || !error.contains("hunter"));
591 }
592}