1use std::sync::Arc;
4
5use url::Url;
6
7use crate::buffer_pool::{BufferPool, GLOBAL_BUFFER_POOL};
8use crate::error::Error;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
12pub enum SslMode {
13 Disable,
15 #[default]
17 Prefer,
18 Require,
20}
21
22#[derive(Debug, Clone)]
24pub struct Opts {
25 pub host: String,
29
30 pub port: u16,
34
35 pub socket: Option<String>,
39
40 pub user: String,
44
45 pub database: Option<String>,
49
50 pub password: Option<String>,
54
55 pub application_name: Option<String>,
59
60 pub ssl_mode: SslMode,
64
65 pub params: Vec<(String, String)>,
69
70 pub prefer_unix_socket: bool,
74
75 pub pool_max_idle_conn: usize,
79
80 pub pool_max_concurrency: Option<usize>,
84
85 pub buffer_pool: Arc<BufferPool>,
89}
90
91impl Default for Opts {
92 fn default() -> Self {
93 Self {
94 host: String::new(),
95 port: 5432,
96 socket: None,
97 user: String::new(),
98 database: None,
99 password: None,
100 application_name: None,
101 ssl_mode: SslMode::Prefer,
102 params: Vec::new(),
103 prefer_unix_socket: true,
104 pool_max_idle_conn: 100,
105 pool_max_concurrency: None,
106 buffer_pool: Arc::clone(&GLOBAL_BUFFER_POOL),
107 }
108 }
109}
110
111impl TryFrom<&Url> for Opts {
112 type Error = Error;
113
114 fn try_from(url: &Url) -> Result<Self, Self::Error> {
125 if !["postgres", "postgresql", "pg"].contains(&url.scheme()) {
126 return Err(Error::InvalidUsage(format!(
127 "Invalid scheme: expected 'postgres://', 'postgresql://', or 'pg://', got '{}://'",
128 url.scheme()
129 )));
130 }
131
132 let mut opts = Opts {
133 host: url.host_str().unwrap_or("localhost").to_string(),
134 port: url.port().unwrap_or(5432),
135 user: url.username().to_string(),
136 password: url.password().map(|s| s.to_string()),
137 database: url.path().strip_prefix('/').and_then(|s| {
138 if s.is_empty() {
139 None
140 } else {
141 Some(s.to_string())
142 }
143 }),
144 ..Opts::default()
145 };
146
147 for (key, value) in url.query_pairs() {
148 match key.as_ref() {
149 "sslmode" => {
150 opts.ssl_mode = match value.as_ref() {
151 "disable" => SslMode::Disable,
152 "prefer" => SslMode::Prefer,
153 "require" => SslMode::Require,
154 _ => {
155 return Err(Error::InvalidUsage(format!(
156 "Invalid sslmode: expected one of ['disable', 'prefer', 'require'], got {}",
157 value
158 )));
159 }
160 };
161 }
162 "application_name" => {
163 opts.application_name = Some(value.to_string());
164 }
165 "prefer_unix_socket" => {
166 opts.prefer_unix_socket = match value.as_ref() {
167 "true" | "True" | "1" | "yes" | "on" => true,
168 "false" | "False" | "0" | "no" | "off" => false,
169 _ => {
170 return Err(Error::InvalidUsage(format!(
171 "Invalid prefer_unix_socket: {}",
172 value
173 )));
174 }
175 };
176 }
177 "pool_max_idle_conn" => {
178 opts.pool_max_idle_conn = value.parse().map_err(|_| {
179 Error::InvalidUsage(format!("Invalid pool_max_idle_conn: {}", value))
180 })?;
181 }
182 "pool_max_concurrency" => {
183 opts.pool_max_concurrency = Some(value.parse().map_err(|_| {
184 Error::InvalidUsage(format!("Invalid pool_max_concurrency: {}", value))
185 })?);
186 }
187 _ => {
188 opts.params.push((key.to_string(), value.to_string()));
189 }
190 }
191 }
192
193 Ok(opts)
194 }
195}
196
197impl TryFrom<&str> for Opts {
198 type Error = Error;
199
200 fn try_from(s: &str) -> Result<Self, Self::Error> {
201 let url = Url::parse(s).map_err(|e| Error::InvalidUsage(format!("Invalid URL: {}", e)))?;
202 Self::try_from(&url)
203 }
204}