1use super::auth_types::*;
5use super::builder::PgDriverBuilder;
6use super::connection::PgConnection;
7use super::pool;
8use super::rls::RlsContext;
9use super::types::*;
10
11pub struct PgDriver {
13 pub(super) connection: PgConnection,
14 pub(super) rls_context: Option<RlsContext>,
16}
17
18impl PgDriver {
19 pub fn new(connection: PgConnection) -> Self {
21 Self {
22 connection,
23 rls_context: None,
24 }
25 }
26
27 pub fn builder() -> PgDriverBuilder {
40 PgDriverBuilder::new()
41 }
42
43 pub async fn connect(host: &str, port: u16, user: &str, database: &str) -> PgResult<Self> {
52 let connection = PgConnection::connect(host, port, user, database).await?;
53 Ok(Self::new(connection))
54 }
55
56 pub async fn connect_with_password(
59 host: &str,
60 port: u16,
61 user: &str,
62 database: &str,
63 password: &str,
64 ) -> PgResult<Self> {
65 let connection =
66 PgConnection::connect_with_password(host, port, user, database, Some(password)).await?;
67 Ok(Self::new(connection))
68 }
69
70 pub async fn connect_with_options(
72 host: &str,
73 port: u16,
74 user: &str,
75 database: &str,
76 password: Option<&str>,
77 options: ConnectOptions,
78 ) -> PgResult<Self> {
79 let connection =
80 PgConnection::connect_with_options(host, port, user, database, password, options)
81 .await?;
82 Ok(Self::new(connection))
83 }
84
85 pub async fn connect_logical_replication(
90 host: &str,
91 port: u16,
92 user: &str,
93 database: &str,
94 password: Option<&str>,
95 ) -> PgResult<Self> {
96 let options = ConnectOptions::default().with_logical_replication();
97 Self::connect_with_options(host, port, user, database, password, options).await
98 }
99
100 pub async fn connect_logical_replication_with_options(
102 host: &str,
103 port: u16,
104 user: &str,
105 database: &str,
106 password: Option<&str>,
107 options: ConnectOptions,
108 ) -> PgResult<Self> {
109 Self::connect_with_options(
110 host,
111 port,
112 user,
113 database,
114 password,
115 options.with_logical_replication(),
116 )
117 .await
118 }
119
120 pub async fn connect_env() -> PgResult<Self> {
131 let url = std::env::var("DATABASE_URL").map_err(|_| {
132 PgError::Connection("DATABASE_URL environment variable not set".to_string())
133 })?;
134 Self::connect_url(&url).await
135 }
136
137 pub async fn connect_url(url: &str) -> PgResult<Self> {
150 let (host, port, user, database, password) = Self::parse_database_url(url)?;
151
152 let mut pool_cfg = pool::PoolConfig::new(&host, port, &user, &database);
154 if let Some(pw) = &password {
155 pool_cfg = pool_cfg.password(pw);
156 }
157 if let Some((_, query)) = url.split_once('?') {
158 pool::apply_url_query_params(&mut pool_cfg, query, &host)?;
159 }
160
161 let mut opts = ConnectOptions {
162 tls_mode: pool_cfg.tls_mode,
163 gss_enc_mode: pool_cfg.gss_enc_mode,
164 tls_ca_cert_pem: pool_cfg.tls_ca_cert_pem,
165 mtls: pool_cfg.mtls,
166 gss_token_provider: pool_cfg.gss_token_provider,
167 gss_token_provider_ex: pool_cfg.gss_token_provider_ex,
168 auth: pool_cfg.auth_settings,
169 io_uring: pool_cfg.io_uring,
170 startup_params: Vec::new(),
171 };
172
173 if let Some((_, query)) = url.split_once('?') {
175 for pair in query.split('&') {
176 let mut kv = pair.splitn(2, '=');
177 let key = kv.next().unwrap_or_default().trim();
178 let value = kv.next().unwrap_or_default().trim();
179 if key.eq_ignore_ascii_case("replication") {
180 let replication_mode = if value.eq_ignore_ascii_case("database") {
181 "database"
182 } else if value.eq_ignore_ascii_case("true")
183 || value.eq_ignore_ascii_case("on")
184 || value == "1"
185 {
186 "database"
189 } else {
190 return Err(PgError::Connection(format!(
191 "Invalid replication startup mode '{}': expected database|true|on|1",
192 value
193 )));
194 };
195 opts = opts.with_startup_param("replication", replication_mode);
196 }
197 }
198 }
199
200 Self::connect_with_options(&host, port, &user, &database, password.as_deref(), opts).await
201 }
202
203 pub(crate) fn parse_database_url(
210 url: &str,
211 ) -> PgResult<(String, u16, String, String, Option<String>)> {
212 let after_scheme = if let Some(rest) = url.strip_prefix("postgres://") {
213 rest
214 } else if let Some(rest) = url.strip_prefix("postgresql://") {
215 rest
216 } else {
217 return Err(PgError::Connection(
218 "Invalid DATABASE_URL: expected postgres:// or postgresql://".to_string(),
219 ));
220 };
221
222 let (auth_part, host_db_part) = if let Some(at_pos) = after_scheme.rfind('@') {
224 (Some(&after_scheme[..at_pos]), &after_scheme[at_pos + 1..])
225 } else {
226 (None, after_scheme)
227 };
228
229 let (user, password) = if let Some(auth) = auth_part {
231 if auth.is_empty() {
232 return Err(PgError::Connection(
233 "Invalid DATABASE_URL: missing user".to_string(),
234 ));
235 }
236 let parts: Vec<&str> = auth.splitn(2, ':').collect();
237 if parts.len() == 2 {
238 let user = Self::percent_decode(parts[0])?;
240 if user.is_empty() {
241 return Err(PgError::Connection(
242 "Invalid DATABASE_URL: missing user".to_string(),
243 ));
244 }
245 (user, Some(Self::percent_decode(parts[1])?))
246 } else {
247 let user = Self::percent_decode(parts[0])?;
248 if user.is_empty() {
249 return Err(PgError::Connection(
250 "Invalid DATABASE_URL: missing user".to_string(),
251 ));
252 }
253 (user, None)
254 }
255 } else {
256 ("postgres".to_string(), None)
257 };
258
259 let (host_port, database) = if let Some(slash_pos) = host_db_part.find('/') {
261 let raw_db = &host_db_part[slash_pos + 1..];
262 let db = Self::percent_decode(raw_db.split('?').next().unwrap_or(raw_db))?;
264 if db.is_empty() {
265 return Err(PgError::Connection(
266 "Invalid DATABASE_URL: missing database name".to_string(),
267 ));
268 }
269 (&host_db_part[..slash_pos], db)
270 } else {
271 return Err(PgError::Connection(
272 "Invalid DATABASE_URL: missing database name".to_string(),
273 ));
274 };
275
276 let (host, port) = if host_port.starts_with('[') {
278 let end = host_port.find(']').ok_or_else(|| {
279 PgError::Connection("Invalid DATABASE_URL: malformed IPv6 host".to_string())
280 })?;
281 let host = &host_port[..=end];
282 if host == "[]" {
283 return Err(PgError::Connection(
284 "Invalid DATABASE_URL: missing host".to_string(),
285 ));
286 }
287 let suffix = &host_port[end + 1..];
288 let port = if suffix.is_empty() {
289 5432
290 } else if let Some(port_str) = suffix.strip_prefix(':') {
291 Self::parse_database_url_port(port_str)?
292 } else {
293 return Err(PgError::Connection(
294 "Invalid DATABASE_URL: malformed IPv6 host".to_string(),
295 ));
296 };
297 (host.to_string(), port)
298 } else if let Some(colon_pos) = host_port.rfind(':') {
299 let port_str = &host_port[colon_pos + 1..];
300 let host = &host_port[..colon_pos];
301 if host.is_empty() {
302 return Err(PgError::Connection(
303 "Invalid DATABASE_URL: missing host".to_string(),
304 ));
305 }
306 let port = Self::parse_database_url_port(port_str)?;
307 (host.to_string(), port)
308 } else {
309 if host_port.is_empty() {
310 return Err(PgError::Connection(
311 "Invalid DATABASE_URL: missing host".to_string(),
312 ));
313 }
314 (host_port.to_string(), 5432) };
316
317 Ok((host, port, user, database, password))
318 }
319
320 fn parse_database_url_port(port_str: &str) -> PgResult<u16> {
321 if port_str.is_empty() {
322 return Err(PgError::Connection(
323 "Invalid DATABASE_URL: missing port after ':'".to_string(),
324 ));
325 }
326 let port = port_str
327 .parse::<u16>()
328 .map_err(|_| PgError::Connection(format!("Invalid port: {}", port_str)))?;
329 if port == 0 {
330 return Err(PgError::Connection(
331 "Invalid port: 0 (expected 1..=65535)".to_string(),
332 ));
333 }
334 Ok(port)
335 }
336
337 pub(crate) fn percent_decode(s: &str) -> PgResult<String> {
340 fn hex_value(byte: u8) -> Option<u8> {
341 match byte {
342 b'0'..=b'9' => Some(byte - b'0'),
343 b'a'..=b'f' => Some(byte - b'a' + 10),
344 b'A'..=b'F' => Some(byte - b'A' + 10),
345 _ => None,
346 }
347 }
348
349 let bytes = s.as_bytes();
350 let mut decoded = Vec::with_capacity(bytes.len());
351 let mut i = 0;
352
353 while i < bytes.len() {
354 if bytes[i] == b'%' {
355 if i + 2 >= bytes.len() {
356 return Err(PgError::Connection(
357 "Invalid DATABASE_URL percent-encoding: '%' must be followed by two hex digits"
358 .to_string(),
359 ));
360 }
361 let (Some(hi), Some(lo)) = (hex_value(bytes[i + 1]), hex_value(bytes[i + 2]))
362 else {
363 return Err(PgError::Connection(
364 "Invalid DATABASE_URL percent-encoding: '%' must be followed by two hex digits"
365 .to_string(),
366 ));
367 };
368 decoded.push((hi << 4) | lo);
369 i += 3;
370 } else {
371 decoded.push(bytes[i]);
372 i += 1;
373 }
374 }
375
376 String::from_utf8(decoded).map_err(|_| {
377 PgError::Connection(
378 "Invalid DATABASE_URL percent-encoding: decoded value is not UTF-8".to_string(),
379 )
380 })
381 }
382
383 pub async fn connect_with_timeout(
394 host: &str,
395 port: u16,
396 user: &str,
397 database: &str,
398 password: &str,
399 timeout: std::time::Duration,
400 ) -> PgResult<Self> {
401 tokio::time::timeout(
402 timeout,
403 Self::connect_with_password(host, port, user, database, password),
404 )
405 .await
406 .map_err(|_| PgError::Timeout(format!("connection after {:?}", timeout)))?
407 }
408 pub fn clear_cache(&mut self) {
412 self.connection.clear_prepared_statement_state();
413 }
414
415 pub fn cache_stats(&self) -> (usize, usize) {
418 (
419 self.connection.stmt_cache.len(),
420 self.connection.stmt_cache.cap().get(),
421 )
422 }
423}