1use std::collections::VecDeque;
7
8use crate::protocol::{BackendMessage, FrontendMessage, TransactionStatus};
9
10use crate::auth::{self, ServerParams};
11use crate::config::Config;
12use crate::ensure_random_available;
13use crate::error::{Error, PgError, Result};
14use crate::notification::Notification;
15use crate::query::{Notice, NoticeHandler};
16use crate::transport::{
17 AsyncTransport, BufferedTransport, ClientTransport, PgTransport, SslMode, TlsConfig,
18};
19
20#[cfg(feature = "tracing")]
21use crate::tracing_ext::{TARGET_CONNECTION, TARGET_NOTIFICATION, TARGET_RECONNECT};
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29#[non_exhaustive]
30pub enum ConnectionState {
31 Disconnected,
33 Connecting,
35 StartingUp,
37 Authenticating,
39 Idle,
41 ActiveSimpleQuery,
43 ActiveExtendedQuery,
45 CopyIn,
47 CopyOut,
49 Streaming,
51 Closing,
53 Closed,
55}
56
57impl ConnectionState {
58 pub fn is_idle(self) -> bool {
60 matches!(self, ConnectionState::Idle)
61 }
62
63 pub fn is_closed(self) -> bool {
65 matches!(self, ConnectionState::Closed)
66 }
67}
68
69pub struct Connection {
78 pub(crate) transport: PgTransport<ClientTransport>,
79 pub(crate) codec: auth::Codec,
80 pub(crate) server_params: ServerParams,
81 pub(crate) state: ConnectionState,
82 pub(crate) config: Config,
83 pub(crate) transaction_status: TransactionStatus,
84 pub(crate) notification_queue: VecDeque<Notification>,
85 pub(crate) notice_handler: Option<NoticeHandler>,
86 pub(crate) statement_counter: u32,
87 pub(crate) needs_recovery: bool,
90 pub(crate) health: crate::reconnect::session::ConnectionHealth,
92 pub(crate) session_state: crate::reconnect::session::SessionState,
94}
95
96impl Connection {
97 #[must_use = "connection errors should be checked"]
110 pub async fn connect(config: &Config) -> Result<Self> {
111 #[cfg(feature = "tracing")]
112 let span = tracing::info_span!(
113 target: TARGET_CONNECTION,
114 "connect",
115 host = %config.get_host(),
116 port = config.get_port(),
117 database = ?config.get_database(),
118 user = %config.get_user(),
119 );
120 #[cfg(feature = "tracing")]
121 let _enter = span.enter();
122
123 ensure_random_available();
124
125 let mut transport = build_pg_transport(config).await?;
126 let mut codec = auth::Codec::new();
127
128 #[cfg(feature = "tracing")]
130 tracing::debug!(target: TARGET_CONNECTION, "Sending startup message");
131 let startup = crate::protocol::FrontendMessage::Startup {
132 params: config.startup_params(),
133 };
134 codec
135 .send(&mut transport, &startup)
136 .await
137 .map_err(Error::from)?;
138
139 let server_params = auth::authenticate(&mut transport, &mut codec, config)
141 .await
142 .map_err(Error::from)?;
143
144 #[cfg(feature = "tracing")]
145 tracing::info!(target: TARGET_CONNECTION, server_version = %server_params.server_version, process_id = server_params.process_id, "Connection established successfully");
146
147 Ok(Self {
148 transport,
149 codec,
150 server_params,
151 state: ConnectionState::Idle,
152 config: config.clone(),
153 transaction_status: TransactionStatus::Idle,
154 notification_queue: VecDeque::new(),
155 notice_handler: None,
156 statement_counter: 0,
157 needs_recovery: false,
158 health: crate::reconnect::session::ConnectionHealth::new(),
159 session_state: crate::reconnect::session::SessionState::new(),
160 })
161 }
162
163 #[must_use = "connection errors should be checked"]
169 pub async fn connect_with_retry(
170 config: &Config,
171 retry_policy: &crate::reconnect::RetryPolicy,
172 ) -> Result<Self> {
173 retry_policy.retry(|| Self::connect(config)).await
174 }
175
176 #[must_use = "connection errors should be checked"]
178 pub async fn connect_str(s: &str) -> Result<Self> {
179 let config = match Config::from_uri(s) {
180 Ok(c) => c,
181 Err(uri_err) => match Config::from_key_value(s) {
182 Ok(c) => c,
183 Err(kv_err) => {
184 return Err(PgError::Config(format!(
185 "could not parse connection string as URI ({uri_err}) or key=value format ({kv_err})"
186 )));
187 }
188 },
189 };
190 Self::connect(&config).await
191 }
192
193 pub fn config(&self) -> &Config {
199 &self.config
200 }
201
202 fn escape_sql_literal(value: &str) -> String {
203 value.replace('\\', "\\\\").replace('\'', "''")
204 }
205
206 pub(crate) fn build_set_param_sql(key: &str, value: &str) -> String {
207 format!(
208 "SET {} = '{}'",
209 crate::transaction::quote_identifier(key),
210 Self::escape_sql_literal(value)
211 )
212 }
213
214 pub(crate) fn build_listen_sql(channel: &str) -> String {
215 format!("LISTEN {}", crate::transaction::quote_identifier(channel))
216 }
217
218 pub(crate) fn build_unlisten_sql(channel: &str) -> String {
219 format!("UNLISTEN {}", crate::transaction::quote_identifier(channel))
220 }
221
222 #[must_use = "set_param errors should be checked"]
232 pub async fn set_param(&mut self, key: &str, value: &str) -> Result<()> {
233 let sql = Self::build_set_param_sql(key, value);
234 self.execute(&sql).await?;
235 self.session_state.track_set_guc(key, value);
236 Ok(())
237 }
238
239 pub fn set_reconnect_init_sql(&mut self, sql: impl Into<String>) {
246 self.session_state.set_reconnect_init_sql(sql);
247 }
248
249 pub fn clear_reconnect_init_sql(&mut self) {
251 self.session_state.clear_reconnect_init_sql();
252 }
253
254 pub fn reconnect_init_sql(&self) -> Option<&str> {
256 self.session_state.reconnect_init_sql()
257 }
258
259 pub fn state(&self) -> ConnectionState {
261 self.state
262 }
263
264 pub fn is_closed(&self) -> bool {
266 self.state.is_closed()
267 }
268
269 pub fn is_idle(&self) -> bool {
271 self.state.is_idle()
272 }
273
274 pub fn server_params(&self) -> &ServerParams {
276 &self.server_params
277 }
278
279 pub fn server_version(&self) -> &str {
281 &self.server_params.server_version
282 }
283
284 pub fn process_id(&self) -> i32 {
286 self.server_params.process_id
287 }
288
289 pub fn secret_key(&self) -> i32 {
291 self.server_params.secret_key
292 }
293
294 pub fn transaction_status(&self) -> TransactionStatus {
296 self.transaction_status
297 }
298
299 pub fn drain_notifications(&mut self) -> Vec<Notification> {
301 self.notification_queue.drain(..).collect()
302 }
303
304 pub fn cancel_token(&self) -> crate::cancel::CancelToken {
321 crate::cancel::CancelToken {
322 host: self.config.host.clone(),
323 port: self.config.port,
324 process_id: self.server_params.process_id,
325 secret_key: self.server_params.secret_key,
326 ssl_mode: self.config.ssl_mode,
327 accept_invalid_certs: self.config.accept_invalid_certs,
328 }
329 }
330
331 pub fn set_notice_handler(&mut self, handler: NoticeHandler) {
335 self.notice_handler = Some(handler);
336 }
337
338 pub fn clear_notice_handler(&mut self) {
340 self.notice_handler = None;
341 }
342
343 pub fn is_tls(&self) -> bool {
345 #[cfg(feature = "tls")]
346 {
347 matches!(self.transport, crate::transport::PgTransport::Tls(_))
348 }
349 #[cfg(not(feature = "tls"))]
350 {
351 false
352 }
353 }
354
355 #[cfg(feature = "tls")]
359 pub fn tls_info(&self) -> Option<crate::transport::TlsInfo> {
360 self.transport.tls_info()
361 }
362
363 #[must_use = "ping errors should be checked"]
365 pub async fn ping(&mut self) -> Result<()> {
366 self.query("SELECT 1").await?;
367 self.health.mark_alive();
368 Ok(())
369 }
370
371 pub fn is_healthy(&self) -> bool {
374 !self.is_closed() && self.transaction_status != crate::protocol::TransactionStatus::Failed
375 }
376
377 #[must_use = "reset errors should be checked"]
379 pub async fn reset(&mut self) -> Result<()> {
380 if self.transaction_status == crate::protocol::TransactionStatus::Failed
381 || self.transaction_status == crate::protocol::TransactionStatus::InTransaction
382 {
383 self.execute("ROLLBACK").await?;
384 }
385 self.execute("DISCARD ALL").await?;
386 self.session_state.clear();
387 Ok(())
388 }
389
390 pub fn needs_recovery(&self) -> bool {
397 self.needs_recovery
398 }
399
400 #[must_use = "recovery errors should be checked"]
406 pub async fn recover(&mut self) -> Result<()> {
407 if self.needs_recovery {
408 self.read_until_ready().await?;
409 self.needs_recovery = false;
410 }
411 Ok(())
412 }
413
414 pub fn is_alive(&self) -> bool {
423 self.health.is_alive()
424 }
425
426 pub fn is_stale(&self, threshold: std::time::Duration) -> bool {
432 match self.health.last_confirmed_alive() {
433 Some(last) => last.elapsed() > threshold,
434 None => true, }
436 }
437
438 pub fn reconnect_count(&self) -> u32 {
440 self.health.reconnect_count()
441 }
442
443 pub fn session_state(&self) -> &crate::reconnect::session::SessionState {
445 &self.session_state
446 }
447
448 #[must_use = "reconnection errors should be checked"]
462 pub async fn reconnect(&mut self) -> crate::error::Result<()> {
463 let session_state = self.session_state.clone();
464
465 #[cfg(feature = "tracing")]
466 tracing::info!(
467 target: TARGET_RECONNECT,
468 reconnect_count = self.health.reconnect_count(),
469 has_session_state = session_state.has_state(),
470 has_reconnect_init_sql = session_state.reconnect_init_sql().is_some(),
471 "Attempting to reconnect"
472 );
473
474 #[cfg(feature = "tracing")]
476 tracing::debug!(target: TARGET_RECONNECT, "Shutting down old transport before reconnect");
477 self.health.mark_broken();
478 if let Err(e) = self.transport.shutdown().await {
479 #[cfg(feature = "tracing")]
480 tracing::debug!(target: TARGET_RECONNECT, error = %e, "Old transport shutdown error (expected during reconnect)");
481 let _ = &e;
482 }
483
484 let mut new_conn = Self::connect(&self.config).await?;
488 new_conn.rebuild_reconnect_init_sql(&session_state).await?;
489 if self.config.get_reconnect().rebuild_session {
490 new_conn.rebuild_session(&session_state).await?;
491 }
492
493 std::mem::swap(&mut self.transport, &mut new_conn.transport);
496 std::mem::swap(&mut self.codec, &mut new_conn.codec);
497 std::mem::swap(&mut self.server_params, &mut new_conn.server_params);
498 self.transaction_status = new_conn.transaction_status;
499 self.notification_queue.clear();
500 self.state = new_conn.state;
501 self.health.reset_after_reconnect();
502 self.needs_recovery = false;
503
504 #[cfg(feature = "tracing")]
507 tracing::info!(
508 target: TARGET_RECONNECT,
509 reconnect_count = self.health.reconnect_count(),
510 "Reconnection successful"
511 );
512
513 Ok(())
514 }
515
516 async fn rebuild_reconnect_init_sql(
517 &mut self,
518 state: &crate::reconnect::session::SessionState,
519 ) -> crate::error::Result<()> {
520 if let Some(sql) = state.reconnect_init_sql() {
521 #[cfg(feature = "tracing")]
522 tracing::debug!(target: TARGET_RECONNECT, "Replaying reconnect initialization SQL");
523 self.execute(sql).await?;
524 }
525
526 Ok(())
527 }
528
529 async fn rebuild_session(
535 &mut self,
536 state: &crate::reconnect::session::SessionState,
537 ) -> crate::error::Result<()> {
538 #[cfg(feature = "tracing")]
541 if !state.prepared_statements().is_empty() {
542 tracing::debug!(
543 target: TARGET_RECONNECT,
544 count = state.prepared_statements().len(),
545 "Prepared statements will be re-prepared lazily on next use"
546 );
547 }
548
549 for channel in state.listen_channels() {
551 let sql = Self::build_listen_sql(channel);
552 match self.execute(&sql).await {
553 Ok(_) => {
554 #[cfg(feature = "tracing")]
555 tracing::debug!(target: TARGET_RECONNECT, channel = %channel, "Re-LISTENed on channel after reconnect");
556 self.session_state.track_listen(channel);
557 }
558 Err(e) => {
559 #[cfg(feature = "tracing")]
560 tracing::warn!(target: TARGET_RECONNECT, channel = %channel, error = %e, "Failed to re-LISTEN on channel after reconnect");
561 let _ = &e; }
563 }
564 }
565
566 for (key, value) in state.custom_gucs() {
568 let sql = Self::build_set_param_sql(key, value);
569 match self.execute(&sql).await {
570 Ok(_) => {
571 #[cfg(feature = "tracing")]
572 tracing::debug!(target: TARGET_RECONNECT, key = %key, "Re-SET GUC parameter after reconnect");
573 self.session_state.track_set_guc(key, value);
574 }
575 Err(e) => {
576 #[cfg(feature = "tracing")]
577 tracing::warn!(target: TARGET_RECONNECT, key = %key, error = %e, "Failed to re-SET GUC parameter after reconnect");
578 let _ = &e; }
580 }
581 }
582
583 Ok(())
584 }
585
586 #[must_use = "retry errors should be checked"]
602 pub async fn with_retry<T, F, Fut>(&mut self, f: F) -> crate::error::Result<T>
603 where
604 F: Fn(&mut Connection) -> Fut,
605 Fut: std::future::Future<Output = crate::error::Result<T>>,
606 {
607 let config = self.config.get_reconnect().clone();
608 let max_attempts = if config.enabled {
609 config.max_attempts.max(1)
610 } else {
611 1 };
613
614 let mut attempt = 0;
615
616 loop {
617 attempt += 1;
618
619 match f(self).await {
621 Ok(result) => {
622 self.health.mark_alive();
623 self.session_state.set_in_transaction(
624 self.transaction_status != crate::protocol::TransactionStatus::Idle,
625 );
626 return Ok(result);
627 }
628 Err(err) => {
629 let class = crate::reconnect::classify::classify_error(&err);
630
631 match class {
632 crate::reconnect::classify::ErrorClass::Permanent => {
633 return Err(err);
635 }
636 crate::reconnect::classify::ErrorClass::Transient => {
637 if attempt >= max_attempts {
639 #[cfg(feature = "tracing")]
640 tracing::warn!(
641 target: TARGET_RECONNECT,
642 attempt = attempt,
643 max_attempts = max_attempts,
644 "Transient error: max retry attempts reached"
645 );
646 return Err(err);
647 }
648
649 let delay = config.delay_for_attempt(attempt);
650 #[cfg(feature = "tracing")]
651 tracing::debug!(
652 target: TARGET_RECONNECT,
653 attempt = attempt,
654 delay_ms = delay.as_millis(),
655 "Transient error: retrying after backoff"
656 );
657 reconnect_sleep(delay).await;
658 continue;
659 }
660 crate::reconnect::classify::ErrorClass::Broken => {
661 if !config.enabled {
663 self.health.mark_broken();
664 return Err(err);
665 }
666
667 if !config.allow_mid_transaction && self.session_state.in_transaction()
669 {
670 #[cfg(feature = "tracing")]
671 tracing::error!(
672 target: TARGET_RECONNECT,
673 "Connection broken mid-transaction. \
674 Reconnection is disabled for mid-transaction failures \
675 (set allow_mid_transaction=true to override)."
676 );
677 self.health.mark_broken();
678 return Err(err);
679 }
680
681 if attempt >= max_attempts {
682 #[cfg(feature = "tracing")]
683 tracing::warn!(
684 target: TARGET_RECONNECT,
685 attempt = attempt,
686 max_attempts = max_attempts,
687 "Connection broken: max reconnection attempts reached"
688 );
689 return Err(err);
690 }
691
692 if let Some(ref callback) = config.on_before_reconnect {
694 callback(attempt, &err);
695 }
696
697 let delay = config.delay_for_attempt(attempt);
699 #[cfg(feature = "tracing")]
700 tracing::debug!(
701 target: TARGET_RECONNECT,
702 attempt = attempt,
703 delay_ms = delay.as_millis(),
704 "Connection broken: reconnecting after backoff"
705 );
706 reconnect_sleep(delay).await;
707
708 match self.reconnect().await {
709 Ok(()) => continue, Err(reconnect_err) => {
711 #[cfg(feature = "tracing")]
712 tracing::error!(
713 target: TARGET_RECONNECT,
714 error = %reconnect_err,
715 "Reconnection failed"
716 );
717 let _ = &reconnect_err; return Err(err);
720 }
721 }
722 }
723 }
724 }
725 }
726 }
727 }
728
729 #[must_use = "health check errors should be checked"]
735 pub async fn ensure_alive(&mut self) -> crate::error::Result<()> {
736 if !self.health.is_alive() {
737 #[cfg(feature = "tracing")]
739 tracing::warn!(target: TARGET_RECONNECT, "Connection is known to be broken");
740 if self.config.get_reconnect().enabled {
741 self.reconnect().await?;
742 } else {
743 return Err(crate::error::PgError::ConnectionClosed);
744 }
745 return Ok(());
746 }
747
748 if self.is_stale(self.config.get_stale().stale_threshold)
749 && self.config.get_stale().ping_on_stale
750 {
751 match self.ping().await {
752 Ok(()) => {
753 self.health.mark_alive();
754 }
755 Err(e) => {
756 #[cfg(feature = "tracing")]
757 tracing::debug!(target: TARGET_RECONNECT, error = %e, "Stale connection ping failed");
758 self.health.mark_broken();
759
760 if self.config.get_reconnect().enabled {
761 self.reconnect().await?;
762 } else {
763 return Err(e);
764 }
765 }
766 }
767 }
768
769 Ok(())
770 }
771
772 pub(crate) fn handle_notice(&self, notice: &Notice) {
773 if let Some(ref handler) = self.notice_handler {
774 handler(notice);
775 } else {
776 #[cfg(feature = "tracing")]
777 tracing::warn!(severity = %notice.severity(), code = %notice.code(), message = %notice.message(), "server notice");
778 }
779 }
780
781 #[must_use = "close errors should be checked"]
791 pub async fn close(&mut self) -> Result<()> {
792 if self.state.is_closed() {
793 return Ok(());
794 }
795
796 #[cfg(feature = "tracing")]
797 tracing::info!(target: TARGET_CONNECTION, "Closing connection");
798
799 self.state = ConnectionState::Closing;
800
801 let _ = self
803 .codec
804 .send(&mut self.transport, &FrontendMessage::Terminate)
805 .await;
806
807 let _ = self.transport.shutdown().await;
808
809 self.state = ConnectionState::Closed;
810
811 #[cfg(feature = "tracing")]
812 tracing::debug!(target: TARGET_CONNECTION, "Connection closed");
813
814 Ok(())
815 }
816
817 pub async fn abort(&mut self) {
821 self.state = ConnectionState::Closed;
822 let _ = self.transport.shutdown().await;
823 }
824
825 pub(crate) async fn read_until_ready(&mut self) -> Result<()> {
830 loop {
831 let msg = self.codec.read_message(&mut self.transport).await?;
832 match msg {
833 BackendMessage::ReadyForQuery(body) => {
834 self.transaction_status = TransactionStatus::from_u8(body.status())
835 .unwrap_or(TransactionStatus::Idle);
836 self.session_state.set_in_transaction(
837 self.transaction_status != crate::protocol::TransactionStatus::Idle,
838 );
839 self.state = ConnectionState::Idle;
840 return Ok(());
841 }
842 BackendMessage::ParameterStatus(body) => {
843 if let (Ok(name), Ok(value)) = (body.name(), body.value()) {
844 self.server_params
845 .params
846 .insert(name.to_string(), value.to_string());
847 }
848 }
849 BackendMessage::NotificationResponse(body) => {
850 let channel = body.channel().unwrap_or("").to_string();
851 let payload = body.message().unwrap_or("").to_string();
852 let process_id = body.process_id();
853 #[cfg(feature = "tracing")]
854 tracing::debug!(
855 target: TARGET_NOTIFICATION,
856 channel = %channel,
857 process_id = process_id,
858 payload_len = payload.len(),
859 "Received notification"
860 );
861 self.notification_queue.push_back(Notification {
862 process_id,
863 channel,
864 payload,
865 });
866 }
867 BackendMessage::NoticeResponse(body) => {
868 if let Ok(notice) = Notice::from_fields(&body) {
869 self.handle_notice(¬ice);
870 }
871 }
872 _ => {} }
874 }
875 }
876
877 pub(crate) fn handle_async_message(&mut self, msg: &BackendMessage) -> bool {
890 match msg {
891 BackendMessage::NotificationResponse(body) => {
892 let channel = body.channel().unwrap_or("").to_string();
893 let payload = body.message().unwrap_or("").to_string();
894 let process_id = body.process_id();
895 #[cfg(feature = "tracing")]
896 tracing::debug!(
897 target: TARGET_NOTIFICATION,
898 channel = %channel,
899 process_id = process_id,
900 payload_len = payload.len(),
901 "Received notification"
902 );
903 #[cfg(feature = "tracing")]
904 tracing::trace!(
905 target: TARGET_NOTIFICATION,
906 channel = %channel,
907 payload = %payload,
908 "Received notification (with payload)"
909 );
910 self.notification_queue.push_back(Notification {
911 process_id,
912 channel,
913 payload,
914 });
915 true
916 }
917 BackendMessage::NoticeResponse(body) => {
918 if let Ok(notice) = Notice::from_fields(body) {
919 self.handle_notice(¬ice);
920 }
921 true
922 }
923 BackendMessage::ParameterStatus(body) => {
924 if let (Ok(name), Ok(value)) = (body.name(), body.value()) {
925 self.server_params
926 .params
927 .insert(name.to_string(), value.to_string());
928 }
929 true
930 }
931 _ => false,
932 }
933 }
934
935 pub(crate) fn transition(&mut self, new_state: ConnectionState) -> Result<()> {
938 match (self.state, new_state) {
939 (_, ConnectionState::Closed) => {}
941 (ConnectionState::Idle, ConnectionState::ActiveSimpleQuery)
943 | (ConnectionState::Idle, ConnectionState::ActiveExtendedQuery)
944 | (ConnectionState::Idle, ConnectionState::CopyIn)
945 | (ConnectionState::Idle, ConnectionState::CopyOut)
946 | (ConnectionState::Idle, ConnectionState::Streaming) => {}
947 (ConnectionState::ActiveSimpleQuery, ConnectionState::Idle)
949 | (ConnectionState::ActiveExtendedQuery, ConnectionState::Idle)
950 | (ConnectionState::CopyIn, ConnectionState::Idle)
951 | (ConnectionState::CopyOut, ConnectionState::Idle)
952 | (ConnectionState::Streaming, ConnectionState::Idle) => {}
953 (ConnectionState::Idle, ConnectionState::Closing) => {}
955 (old, new) => {
957 return Err(PgError::InvalidState(format!(
958 "invalid state transition from {old:?} to {new:?}"
959 )));
960 }
961 }
962 self.state = new_state;
963 Ok(())
964 }
965
966 pub(crate) fn cancel_copy_in_sync(&mut self, reason: &str) {
985 let copy_fail = FrontendMessage::CopyFail {
987 message: reason.to_string(),
988 };
989 if self.codec.encode_to_buffer(©_fail).is_err() {
990 self.state = ConnectionState::Closed;
991 return;
992 }
993 let data = self.codec.write_buffer().to_vec();
995
996 let written = self.try_sync_write_and_flush(&data);
998
999 if written {
1000 self.try_sync_drain_until_ready();
1003 self.state = ConnectionState::Idle;
1004 } else {
1005 self.state = ConnectionState::Closed;
1007 }
1008 }
1009
1010 pub(crate) fn drain_copy_out_sync(&mut self) {
1021 if self.try_sync_drain_until_ready() {
1022 self.state = ConnectionState::Idle;
1023 } else {
1024 self.state = ConnectionState::Closed;
1025 }
1026 }
1027
1028 #[cfg(all(not(target_arch = "wasm32"), feature = "test-native"))]
1033 fn try_sync_write_and_flush(&mut self, data: &[u8]) -> bool {
1034 use std::io::Write;
1035
1036 match &mut self.transport {
1037 PgTransport::Plain(ref mut buffered) => {
1038 let inner = buffered.inner_mut();
1039 match inner {
1040 ClientTransport::Native(ref mut tcp) => {
1041 if let Err(e) = tcp.stream.write_all(data) {
1050 let _ = &e;
1051 false
1052 } else {
1053 tcp.stream.flush().is_ok()
1054 }
1055 }
1056 _ => false,
1057 }
1058 }
1059 PgTransport::Tls(_) => {
1060 false
1062 }
1063 }
1064 }
1065
1066 #[cfg(target_arch = "wasm32")]
1068 fn try_sync_write_and_flush(&mut self, _data: &[u8]) -> bool {
1069 false
1071 }
1072
1073 #[cfg(all(not(target_arch = "wasm32"), not(feature = "test-native")))]
1075 fn try_sync_write_and_flush(&mut self, _data: &[u8]) -> bool {
1076 false
1077 }
1078
1079 #[cfg(all(not(target_arch = "wasm32"), feature = "test-native"))]
1083 fn try_sync_drain_until_ready(&mut self) -> bool {
1084 use std::io::Read;
1085
1086 match &mut self.transport {
1087 PgTransport::Plain(ref mut buffered) => {
1088 let inner = buffered.inner_mut();
1089 match inner {
1090 ClientTransport::Native(ref mut tcp) => {
1091 let mut buf = [0u8; 4096];
1093 let mut scan_buf: Vec<u8> = Vec::new();
1094
1095 loop {
1096 if let Some(pos) = scan_buf.iter().position(|&b| b == b'Z') {
1100 if scan_buf.len() >= pos + 6 {
1102 let len = i32::from_be_bytes([
1103 scan_buf[pos + 1],
1104 scan_buf[pos + 2],
1105 scan_buf[pos + 3],
1106 scan_buf[pos + 4],
1107 ]);
1108 if len == 5 {
1109 let status = scan_buf[pos + 5];
1111 if status == b'I' || status == b'T' || status == b'E' {
1112 return true;
1113 }
1114 }
1115 }
1116 }
1117
1118 match tcp.stream.read(&mut buf) {
1119 Ok(0) => return false, Ok(n) => scan_buf.extend_from_slice(&buf[..n]),
1121 Err(_) => return false,
1122 }
1123
1124 if scan_buf.len() > 10 * 1024 * 1024 {
1126 return false;
1127 }
1128 }
1129 }
1130 _ => false,
1131 }
1132 }
1133 PgTransport::Tls(_) => false,
1134 }
1135 }
1136
1137 #[cfg(target_arch = "wasm32")]
1139 fn try_sync_drain_until_ready(&mut self) -> bool {
1140 false
1141 }
1142
1143 #[cfg(all(not(target_arch = "wasm32"), not(feature = "test-native")))]
1145 fn try_sync_drain_until_ready(&mut self) -> bool {
1146 false
1147 }
1148}
1149
1150impl Drop for Connection {
1151 fn drop(&mut self) {
1152 #[cfg(feature = "tracing")]
1167 tracing::debug!(target: TARGET_CONNECTION, state = ?self.state, "Connection dropped; transport Drop will close socket");
1168 self.state = ConnectionState::Closed;
1169 }
1172}
1173
1174#[cfg(target_arch = "wasm32")]
1179async fn build_pg_transport(config: &Config) -> Result<PgTransport<ClientTransport>> {
1180 let tcp = crate::transport::connect_with_timeout(
1181 config.get_host(),
1182 config.get_port(),
1183 config.get_connect_timeout(),
1184 )
1185 .await
1186 .map_err(PgError::Transport)?;
1187 apply_tls(ClientTransport::Wasi(tcp), config).await
1188}
1189
1190#[cfg(all(not(target_arch = "wasm32"), feature = "tokio-transport"))]
1191async fn build_pg_transport(config: &Config) -> Result<PgTransport<ClientTransport>> {
1192 let tcp = crate::transport::connect_with_timeout(
1193 config.get_host(),
1194 config.get_port(),
1195 config.get_connect_timeout(),
1196 )
1197 .await
1198 .map_err(PgError::Transport)?;
1199 apply_tls(ClientTransport::Tokio(tcp), config).await
1200}
1201
1202#[cfg(all(
1203 not(target_arch = "wasm32"),
1204 not(feature = "tokio-transport"),
1205 feature = "test-native"
1206))]
1207async fn build_pg_transport(config: &Config) -> Result<PgTransport<ClientTransport>> {
1208 let tcp = crate::transport::NativeTcpTransport::connect_with_timeout(
1209 config.get_host(),
1210 config.get_port(),
1211 config.get_connect_timeout(),
1212 )
1213 .map_err(PgError::Transport)?;
1214 apply_tls(ClientTransport::Native(tcp), config).await
1215}
1216
1217#[cfg(all(
1218 not(target_arch = "wasm32"),
1219 not(feature = "tokio-transport"),
1220 not(feature = "test-native")
1221))]
1222async fn build_pg_transport(_config: &Config) -> Result<PgTransport<ClientTransport>> {
1223 Err(PgError::Unsupported(
1224 "no transport available for this target. Enable the 'tokio-transport' feature (recommended) or 'test-native' feature, or compile for wasm32-wasip2".into(),
1225 ))
1226}
1227
1228#[allow(dead_code)]
1229async fn apply_tls(tcp: ClientTransport, config: &Config) -> Result<PgTransport<ClientTransport>> {
1230 if matches!(config.get_ssl_mode(), SslMode::Disable) {
1231 Ok(PgTransport::Plain(BufferedTransport::new(tcp)))
1232 } else {
1233 let tls_config = TlsConfig {
1234 mode: config.get_ssl_mode(),
1235 server_name: config.get_host().into(),
1236 accept_invalid_certs: config.get_accept_invalid_certs(),
1237 ..Default::default()
1238 };
1239 crate::transport::negotiate_tls(tcp, &tls_config)
1240 .await
1241 .map_err(PgError::Transport)
1242 }
1243}
1244
1245#[cfg(target_arch = "wasm32")]
1248async fn reconnect_sleep(duration: std::time::Duration) {
1249 wstd::time::Timer::after(duration.into()).wait().await;
1250}
1251
1252#[cfg(not(target_arch = "wasm32"))]
1253async fn reconnect_sleep(duration: std::time::Duration) {
1254 #[cfg(feature = "tokio-transport")]
1255 tokio::time::sleep(duration).await;
1256
1257 #[cfg(not(feature = "tokio-transport"))]
1258 {
1259 std::thread::sleep(duration);
1260 }
1261}
1262
1263#[cfg(test)]
1268mod tests {
1269 use super::*;
1270 use crate::transport::MockTransport;
1271
1272 #[test]
1275 #[cfg(target_arch = "wasm32")]
1276 fn connection_is_send() {
1277 fn assert_send<T: Send>() {}
1278 assert_send::<Connection>();
1279 }
1280
1281 #[test]
1282 fn test_connection_state_transitions() {
1283 let transport = PgTransport::Plain(BufferedTransport::new(ClientTransport::Mock(
1284 MockTransport::new(vec![]),
1285 )));
1286 let mut conn = Connection {
1287 transport,
1288 codec: auth::Codec::new(),
1289 server_params: ServerParams::default(),
1290 state: ConnectionState::Idle,
1291 config: Config::new(),
1292 transaction_status: TransactionStatus::Idle,
1293 notification_queue: VecDeque::new(),
1294 notice_handler: None,
1295 statement_counter: 0,
1296 needs_recovery: false,
1297 health: crate::reconnect::session::ConnectionHealth::new(),
1298 session_state: crate::reconnect::session::SessionState::new(),
1299 };
1300
1301 assert!(conn.is_idle());
1302 assert!(!conn.is_closed());
1303
1304 conn.transition(ConnectionState::ActiveSimpleQuery).unwrap();
1305 assert_eq!(conn.state(), ConnectionState::ActiveSimpleQuery);
1306
1307 conn.transition(ConnectionState::Idle).unwrap();
1308 assert!(conn.is_idle());
1309
1310 conn.transition(ConnectionState::Closing).unwrap();
1311 assert_eq!(conn.state(), ConnectionState::Closing);
1312
1313 conn.transition(ConnectionState::Closed).unwrap();
1314 assert!(conn.is_closed());
1315 }
1316
1317 #[test]
1318 fn test_invalid_state_transition() {
1319 let transport = PgTransport::Plain(BufferedTransport::new(ClientTransport::Mock(
1320 MockTransport::new(vec![]),
1321 )));
1322 let mut conn = Connection {
1323 transport,
1324 codec: auth::Codec::new(),
1325 server_params: ServerParams::default(),
1326 state: ConnectionState::ActiveSimpleQuery,
1327 config: Config::new(),
1328 transaction_status: TransactionStatus::Idle,
1329 notification_queue: VecDeque::new(),
1330 notice_handler: None,
1331 statement_counter: 0,
1332 needs_recovery: false,
1333 health: crate::reconnect::session::ConnectionHealth::new(),
1334 session_state: crate::reconnect::session::SessionState::new(),
1335 };
1336
1337 assert!(conn.transition(ConnectionState::Streaming).is_err());
1338 }
1339
1340 #[test]
1341 fn test_is_healthy_idle() {
1342 let transport = PgTransport::Plain(BufferedTransport::new(ClientTransport::Mock(
1343 MockTransport::new(vec![]),
1344 )));
1345 let conn = Connection {
1346 transport,
1347 codec: auth::Codec::new(),
1348 server_params: ServerParams::default(),
1349 state: ConnectionState::Idle,
1350 config: Config::new(),
1351 transaction_status: TransactionStatus::Idle,
1352 notification_queue: VecDeque::new(),
1353 notice_handler: None,
1354 statement_counter: 0,
1355 needs_recovery: false,
1356 health: crate::reconnect::session::ConnectionHealth::new(),
1357 session_state: crate::reconnect::session::SessionState::new(),
1358 };
1359 assert!(conn.is_healthy());
1360 }
1361
1362 #[test]
1363 fn test_is_healthy_closed() {
1364 let transport = PgTransport::Plain(BufferedTransport::new(ClientTransport::Mock(
1365 MockTransport::new(vec![]),
1366 )));
1367 let conn = Connection {
1368 transport,
1369 codec: auth::Codec::new(),
1370 server_params: ServerParams::default(),
1371 state: ConnectionState::Closed,
1372 config: Config::new(),
1373 transaction_status: TransactionStatus::Idle,
1374 notification_queue: VecDeque::new(),
1375 notice_handler: None,
1376 statement_counter: 0,
1377 needs_recovery: false,
1378 health: crate::reconnect::session::ConnectionHealth::new(),
1379 session_state: crate::reconnect::session::SessionState::new(),
1380 };
1381 assert!(!conn.is_healthy());
1382 }
1383
1384 #[test]
1385 fn test_is_healthy_failed_transaction() {
1386 let transport = PgTransport::Plain(BufferedTransport::new(ClientTransport::Mock(
1387 MockTransport::new(vec![]),
1388 )));
1389 let conn = Connection {
1390 transport,
1391 codec: auth::Codec::new(),
1392 server_params: ServerParams::default(),
1393 state: ConnectionState::Idle,
1394 config: Config::new(),
1395 transaction_status: TransactionStatus::Failed,
1396 notification_queue: VecDeque::new(),
1397 notice_handler: None,
1398 statement_counter: 0,
1399 needs_recovery: false,
1400 health: crate::reconnect::session::ConnectionHealth::new(),
1401 session_state: crate::reconnect::session::SessionState::new(),
1402 };
1403 assert!(!conn.is_healthy());
1404 }
1405
1406 #[test]
1407 fn test_is_healthy_in_transaction() {
1408 let transport = PgTransport::Plain(BufferedTransport::new(ClientTransport::Mock(
1409 MockTransport::new(vec![]),
1410 )));
1411 let conn = Connection {
1412 transport,
1413 codec: auth::Codec::new(),
1414 server_params: ServerParams::default(),
1415 state: ConnectionState::Idle,
1416 config: Config::new(),
1417 transaction_status: TransactionStatus::InTransaction,
1418 notification_queue: VecDeque::new(),
1419 notice_handler: None,
1420 statement_counter: 0,
1421 needs_recovery: false,
1422 health: crate::reconnect::session::ConnectionHealth::new(),
1423 session_state: crate::reconnect::session::SessionState::new(),
1424 };
1425 assert!(conn.is_healthy());
1427 }
1428
1429 fn build_command_complete_msg(tag: &str) -> Vec<u8> {
1430 let mut buf = vec![b'C'];
1431 let mut body = Vec::new();
1432 body.extend_from_slice(tag.as_bytes());
1433 body.push(0);
1434 let len = (body.len() + 4) as i32;
1435 buf.extend_from_slice(&len.to_be_bytes());
1436 buf.extend_from_slice(&body);
1437 buf
1438 }
1439
1440 fn build_ready_for_query(status: u8) -> Vec<u8> {
1441 vec![b'Z', 0, 0, 0, 5, status]
1442 }
1443
1444 #[test]
1445 fn test_build_set_param_sql_quotes_and_escapes() {
1446 let sql = Connection::build_set_param_sql("time\"zone", "O'Reilly\\UTC");
1447 assert_eq!(sql, "SET \"time\"\"zone\" = 'O''Reilly\\\\UTC'");
1448 }
1449
1450 #[test]
1451 fn test_build_listen_and_unlisten_sql_quote_identifier() {
1452 assert_eq!(
1453 Connection::build_listen_sql("chan\"nel"),
1454 "LISTEN \"chan\"\"nel\""
1455 );
1456 assert_eq!(
1457 Connection::build_unlisten_sql("chan\"nel"),
1458 "UNLISTEN \"chan\"\"nel\""
1459 );
1460 }
1461
1462 fn make_connection(read_data: Vec<u8>) -> Connection {
1463 let transport = PgTransport::Plain(BufferedTransport::new(ClientTransport::Mock(
1464 MockTransport::new(read_data),
1465 )));
1466 Connection {
1467 transport,
1468 codec: auth::Codec::new(),
1469 server_params: ServerParams::default(),
1470 state: ConnectionState::Idle,
1471 config: Config::new(),
1472 transaction_status: TransactionStatus::Idle,
1473 notification_queue: VecDeque::new(),
1474 notice_handler: None,
1475 statement_counter: 0,
1476 needs_recovery: false,
1477 health: crate::reconnect::session::ConnectionHealth::new(),
1478 session_state: crate::reconnect::session::SessionState::new(),
1479 }
1480 }
1481
1482 #[tokio::test]
1483 async fn test_set_param() {
1484 let mut data = Vec::new();
1486 data.extend_from_slice(&build_command_complete_msg("SET"));
1487 data.extend_from_slice(&build_ready_for_query(b'I'));
1488
1489 let mut conn = make_connection(data);
1490 assert!(conn.session_state.custom_gucs().get("timezone").is_none());
1491 conn.set_param("timezone", "UTC").await.unwrap();
1492 assert_eq!(
1493 conn.session_state.custom_gucs().get("timezone"),
1494 Some(&"UTC".to_string())
1495 );
1496 }
1497
1498 #[tokio::test]
1499 async fn test_reset_clears_session_state_but_preserves_reconnect_init_sql() {
1500 let mut data = Vec::new();
1501 data.extend_from_slice(&build_command_complete_msg("ROLLBACK"));
1502 data.extend_from_slice(&build_ready_for_query(b'I'));
1503 data.extend_from_slice(&build_command_complete_msg("DISCARD ALL"));
1504 data.extend_from_slice(&build_ready_for_query(b'I'));
1505
1506 let mut conn = make_connection(data);
1507 conn.transaction_status = TransactionStatus::Failed;
1508 conn.session_state.track_prepare("stmt1", "SELECT 1");
1509 conn.session_state.track_listen("events");
1510 conn.session_state.track_temp_table("tmp_data");
1511 conn.session_state.track_set_guc("timezone", "UTC");
1512 conn.session_state.set_in_transaction(true);
1513 conn.set_reconnect_init_sql("SET timezone = 'UTC'");
1514
1515 conn.reset().await.unwrap();
1516
1517 assert_eq!(conn.transaction_status(), TransactionStatus::Idle);
1518 assert!(!conn.session_state().has_state());
1519 assert!(conn.session_state().listen_channels().is_empty());
1520 assert!(conn.session_state().prepared_statements().is_empty());
1521 assert!(conn.session_state().temporary_tables().is_empty());
1522 assert!(conn.session_state().custom_gucs().is_empty());
1523 assert!(conn.session_state().is_reconnect_safe());
1524 assert_eq!(conn.reconnect_init_sql(), Some("SET timezone = 'UTC'"));
1525 }
1526
1527 #[test]
1528 fn test_reconnect_init_sql_accessors() {
1529 let mut conn = make_connection(vec![]);
1530 assert_eq!(conn.reconnect_init_sql(), None);
1531
1532 conn.set_reconnect_init_sql("SET timezone = 'UTC'");
1533 assert_eq!(conn.reconnect_init_sql(), Some("SET timezone = 'UTC'"));
1534 assert_eq!(
1535 conn.session_state.reconnect_init_sql(),
1536 Some("SET timezone = 'UTC'")
1537 );
1538
1539 conn.clear_reconnect_init_sql();
1540 assert_eq!(conn.reconnect_init_sql(), None);
1541 assert_eq!(conn.session_state.reconnect_init_sql(), None);
1542 }
1543}