common_meta/error/retry_hint/
postgres.rs1use common_error::ext::RetryHint;
16
17pub fn retry_hint_from_postgres_error(error: &tokio_postgres::Error) -> RetryHint {
19 if error.is_closed() {
20 return RetryHint::Retryable;
21 }
22
23 retry_hint_from_postgres_sql_state(error.code())
24}
25
26fn retry_hint_from_postgres_sql_state(
28 state: Option<&tokio_postgres::error::SqlState>,
29) -> RetryHint {
30 use tokio_postgres::error::SqlState;
31
32 let Some(state) = state else {
33 return RetryHint::NonRetryable;
34 };
35
36 match state {
39 &SqlState::T_R_SERIALIZATION_FAILURE
41 | &SqlState::T_R_DEADLOCK_DETECTED
43 | &SqlState::LOCK_NOT_AVAILABLE
45 | &SqlState::TOO_MANY_CONNECTIONS
47 | &SqlState::ADMIN_SHUTDOWN
49 | &SqlState::CRASH_SHUTDOWN
51 | &SqlState::CANNOT_CONNECT_NOW
53 | &SqlState::CONNECTION_EXCEPTION
55 | &SqlState::SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION
57 | &SqlState::CONNECTION_DOES_NOT_EXIST
59 | &SqlState::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION
61 | &SqlState::CONNECTION_FAILURE => RetryHint::Retryable,
63 _ => RetryHint::NonRetryable,
64 }
65}
66
67pub fn retry_hint_from_postgres_pool_error(
69 error: &deadpool::managed::PoolError<tokio_postgres::Error>,
70) -> RetryHint {
71 match error {
72 deadpool::managed::PoolError::Timeout(_) => RetryHint::Retryable,
73 deadpool::managed::PoolError::Backend(error) => retry_hint_from_postgres_error(error),
74 deadpool::managed::PoolError::PostCreateHook(error) => match error {
75 deadpool::managed::HookError::Backend(error) => retry_hint_from_postgres_error(error),
76 deadpool::managed::HookError::Message(_) => RetryHint::NonRetryable,
77 },
78 deadpool::managed::PoolError::Closed | deadpool::managed::PoolError::NoRuntimeSpecified => {
79 RetryHint::NonRetryable
80 }
81 }
82}
83
84pub fn is_postgres_serialization_error(error: &tokio_postgres::Error) -> bool {
85 is_postgres_serialization_state(error.code())
86}
87
88fn is_postgres_serialization_state(state: Option<&tokio_postgres::error::SqlState>) -> bool {
89 use tokio_postgres::error::SqlState;
90
91 matches!(
92 state,
93 Some(&SqlState::T_R_SERIALIZATION_FAILURE | &SqlState::T_R_DEADLOCK_DETECTED)
94 )
95}
96
97#[cfg(test)]
98mod tests {
99 use common_error::ext::RetryHint;
100 use tokio_postgres::error::SqlState;
101
102 use super::*;
103
104 #[test]
105 fn test_postgres_sql_state_retry_hint() {
106 let retryable_states = [
107 &SqlState::T_R_SERIALIZATION_FAILURE,
108 &SqlState::T_R_DEADLOCK_DETECTED,
109 &SqlState::LOCK_NOT_AVAILABLE,
110 &SqlState::TOO_MANY_CONNECTIONS,
111 &SqlState::ADMIN_SHUTDOWN,
112 &SqlState::CRASH_SHUTDOWN,
113 &SqlState::CANNOT_CONNECT_NOW,
114 &SqlState::CONNECTION_EXCEPTION,
115 &SqlState::SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION,
116 &SqlState::CONNECTION_DOES_NOT_EXIST,
117 &SqlState::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION,
118 &SqlState::CONNECTION_FAILURE,
119 ];
120
121 for state in retryable_states {
122 assert_eq!(
123 retry_hint_from_postgres_sql_state(Some(state)),
124 RetryHint::Retryable,
125 "SQLSTATE {} should be retryable",
126 state.code()
127 );
128 }
129
130 assert_eq!(
131 retry_hint_from_postgres_sql_state(Some(&SqlState::UNDEFINED_TABLE)),
132 RetryHint::NonRetryable
133 );
134 assert_eq!(
135 retry_hint_from_postgres_sql_state(None),
136 RetryHint::NonRetryable
137 );
138 }
139
140 #[test]
141 fn test_postgres_serialization_state() {
142 assert!(is_postgres_serialization_state(Some(
143 &SqlState::T_R_SERIALIZATION_FAILURE
144 )));
145 assert!(is_postgres_serialization_state(Some(
146 &SqlState::T_R_DEADLOCK_DETECTED
147 )));
148 assert!(!is_postgres_serialization_state(Some(
149 &SqlState::UNDEFINED_TABLE
150 )));
151 assert!(!is_postgres_serialization_state(None));
152 }
153}