1mod native_tls_opts;
10mod rustls_opts;
11
12use mysql_common::constants::MariadbCapabilities;
13#[cfg(feature = "native-tls-tls")]
14pub use native_tls_opts::ClientIdentity;
15
16#[cfg(feature = "rustls-tls")]
17pub use rustls_opts::ClientIdentity;
18
19use percent_encoding::percent_decode;
20use rand::RngExt as _;
21use tokio::sync::OnceCell;
22use url::{Host, Url};
23
24use std::{
25 borrow::Cow,
26 fmt, io,
27 net::{IpAddr, Ipv4Addr, Ipv6Addr},
28 path::{Path, PathBuf},
29 str::FromStr,
30 sync::Arc,
31 time::{Duration, Instant},
32 vec,
33};
34
35use crate::{
36 consts::CapabilityFlags,
37 error::*,
38 local_infile_handler::{GlobalHandler, GlobalHandlerObject},
39};
40
41pub const DEFAULT_POOL_CONSTRAINTS: PoolConstraints = PoolConstraints { min: 10, max: 100 };
43
44const_assert!(
46 _DEFAULT_POOL_CONSTRAINTS_ARE_CORRECT,
47 DEFAULT_POOL_CONSTRAINTS.min <= DEFAULT_POOL_CONSTRAINTS.max
48 && 0 < DEFAULT_POOL_CONSTRAINTS.max,
49);
50
51pub const DEFAULT_STMT_CACHE_SIZE: usize = 32;
53
54pub const DEFAULT_PORT: u16 = 3306;
56
57pub const DEFAULT_INACTIVE_CONNECTION_TTL: Duration = Duration::from_secs(0);
62
63pub const DEFAULT_TTL_CHECK_INTERVAL: Duration = Duration::from_secs(30);
67
68#[derive(Clone, Eq, PartialEq, Debug)]
71pub(crate) enum HostPortOrUrl {
72 HostPort {
73 host: String,
74 port: u16,
75 resolved_ips: Option<Vec<IpAddr>>,
78 },
79 Url(Url),
80}
81
82impl Default for HostPortOrUrl {
83 fn default() -> Self {
84 HostPortOrUrl::HostPort {
85 host: "127.0.0.1".to_string(),
86 port: DEFAULT_PORT,
87 resolved_ips: None,
88 }
89 }
90}
91
92impl HostPortOrUrl {
93 pub fn get_ip_or_hostname(&self) -> &str {
94 match self {
95 Self::HostPort { host, .. } => host,
96 Self::Url(url) => url.host_str().unwrap_or("127.0.0.1"),
97 }
98 }
99
100 pub fn get_tcp_port(&self) -> u16 {
101 match self {
102 Self::HostPort { port, .. } => *port,
103 Self::Url(url) => url.port().unwrap_or(DEFAULT_PORT),
104 }
105 }
106
107 pub fn get_resolved_ips(&self) -> &Option<Vec<IpAddr>> {
108 match self {
109 Self::HostPort { resolved_ips, .. } => resolved_ips,
110 Self::Url(_) => &None,
111 }
112 }
113
114 pub fn is_loopback(&self) -> bool {
115 match self {
116 Self::HostPort {
117 host, resolved_ips, ..
118 } => {
119 let v4addr: Option<Ipv4Addr> = FromStr::from_str(host).ok();
120 let v6addr: Option<Ipv6Addr> = FromStr::from_str(host).ok();
121 if resolved_ips
122 .as_ref()
123 .is_some_and(|s| s.iter().any(|ip| ip.is_loopback()))
124 {
125 true
126 } else if let Some(addr) = v4addr {
127 addr.is_loopback()
128 } else if let Some(addr) = v6addr {
129 addr.is_loopback()
130 } else {
131 host == "localhost"
132 }
133 }
134 Self::Url(url) => match url.host() {
135 Some(Host::Ipv4(ip)) => ip.is_loopback(),
136 Some(Host::Ipv6(ip)) => ip.is_loopback(),
137 Some(Host::Domain(s)) => s == "localhost",
138 _ => false,
139 },
140 }
141 }
142}
143
144#[derive(Debug, Clone, PartialEq, Eq, Hash)]
146pub enum PathOrBuf<'a> {
147 Path(Cow<'a, Path>),
148 Buf(Cow<'a, [u8]>),
149}
150
151impl<'a> PathOrBuf<'a> {
152 pub async fn read(&self) -> io::Result<Cow<'_, [u8]>> {
154 match self {
155 PathOrBuf::Path(x) => tokio::fs::read(x.as_ref()).await.map(Cow::Owned),
156 PathOrBuf::Buf(x) => Ok(Cow::Borrowed(x.as_ref())),
157 }
158 }
159
160 pub fn borrow(&self) -> PathOrBuf<'_> {
162 match self {
163 PathOrBuf::Path(path) => PathOrBuf::Path(Cow::Borrowed(path.as_ref())),
164 PathOrBuf::Buf(data) => PathOrBuf::Buf(Cow::Borrowed(data.as_ref())),
165 }
166 }
167}
168
169impl From<PathBuf> for PathOrBuf<'static> {
170 fn from(value: PathBuf) -> Self {
171 Self::Path(Cow::Owned(value))
172 }
173}
174
175impl<'a> From<&'a Path> for PathOrBuf<'a> {
176 fn from(value: &'a Path) -> Self {
177 Self::Path(Cow::Borrowed(value))
178 }
179}
180
181impl From<Vec<u8>> for PathOrBuf<'static> {
182 fn from(value: Vec<u8>) -> Self {
183 Self::Buf(Cow::Owned(value))
184 }
185}
186
187impl<'a> From<&'a [u8]> for PathOrBuf<'a> {
188 fn from(value: &'a [u8]) -> Self {
189 Self::Buf(Cow::Borrowed(value))
190 }
191}
192
193#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
216pub struct SslOpts {
217 #[cfg(any(feature = "native-tls-tls", feature = "rustls-tls"))]
218 client_identity: Option<ClientIdentity>,
219 root_certs: Vec<PathOrBuf<'static>>,
220 disable_built_in_roots: bool,
221 skip_domain_validation: bool,
222 accept_invalid_certs: bool,
223 tls_hostname_override: Option<Cow<'static, str>>,
224}
225
226impl SslOpts {
227 #[cfg(any(feature = "native-tls-tls", feature = "rustls-tls"))]
228 pub fn with_client_identity(mut self, identity: Option<ClientIdentity>) -> Self {
229 self.client_identity = identity;
230 self
231 }
232
233 pub fn with_root_certs(mut self, root_certs: Vec<PathOrBuf<'static>>) -> Self {
239 self.root_certs = root_certs;
240 self
241 }
242
243 pub fn with_disable_built_in_roots(mut self, disable_built_in_roots: bool) -> Self {
259 self.disable_built_in_roots = disable_built_in_roots;
260 self
261 }
262
263 pub fn with_danger_skip_domain_validation(mut self, value: bool) -> Self {
279 self.skip_domain_validation = value;
280 self
281 }
282
283 pub fn with_danger_accept_invalid_certs(mut self, value: bool) -> Self {
299 self.accept_invalid_certs = value;
300 self
301 }
302
303 pub fn with_danger_tls_hostname_override<T: Into<Cow<'static, str>>>(
308 mut self,
309 domain: Option<T>,
310 ) -> Self {
311 self.tls_hostname_override = domain.map(Into::into);
312 self
313 }
314
315 #[cfg(any(feature = "native-tls-tls", feature = "rustls-tls"))]
316 pub fn client_identity(&self) -> Option<&ClientIdentity> {
317 self.client_identity.as_ref()
318 }
319
320 pub fn root_certs(&self) -> &[PathOrBuf<'static>] {
321 &self.root_certs
322 }
323
324 pub fn disable_built_in_roots(&self) -> bool {
325 self.disable_built_in_roots
326 }
327
328 pub fn skip_domain_validation(&self) -> bool {
329 self.skip_domain_validation
330 }
331
332 pub fn accept_invalid_certs(&self) -> bool {
333 self.accept_invalid_certs
334 }
335
336 pub fn tls_hostname_override(&self) -> Option<&str> {
337 self.tls_hostname_override.as_deref()
338 }
339}
340
341#[derive(Debug, Clone, Eq, PartialEq, Hash)]
351pub struct PoolOpts {
352 constraints: PoolConstraints,
353 inactive_connection_ttl: Duration,
354 ttl_check_interval: Duration,
355 abs_conn_ttl: Option<Duration>,
356 abs_conn_ttl_jitter: Option<Duration>,
357 reset_connection: bool,
358}
359
360impl PoolOpts {
361 pub fn new() -> Self {
363 Self::default()
364 }
365
366 pub fn with_constraints(mut self, constraints: PoolConstraints) -> Self {
368 self.constraints = constraints;
369 self
370 }
371
372 pub fn constraints(&self) -> PoolConstraints {
374 self.constraints
375 }
376
377 pub fn with_reset_connection(mut self, reset_connection: bool) -> Self {
414 self.reset_connection = reset_connection;
415 self
416 }
417
418 pub fn reset_connection(&self) -> bool {
420 self.reset_connection
421 }
422
423 pub fn with_abs_conn_ttl(mut self, ttl: Option<Duration>) -> Self {
429 self.abs_conn_ttl = ttl;
430 self
431 }
432
433 pub fn with_abs_conn_ttl_jitter(mut self, jitter: Option<Duration>) -> Self {
438 self.abs_conn_ttl_jitter = jitter;
439 self
440 }
441
442 pub fn abs_conn_ttl(&self) -> Option<Duration> {
444 self.abs_conn_ttl
445 }
446
447 pub fn abs_conn_ttl_jitter(&self) -> Option<Duration> {
449 self.abs_conn_ttl_jitter
450 }
451
452 pub(crate) fn new_connection_ttl_deadline(&self) -> Option<Instant> {
454 if let Some(ttl) = self.abs_conn_ttl {
455 let jitter = if let Some(jitter) = self.abs_conn_ttl_jitter {
456 Duration::from_secs(rand::rng().random_range(0..=jitter.as_secs()))
457 } else {
458 Duration::ZERO
459 };
460 Some(Instant::now() + ttl + jitter)
461 } else {
462 None
463 }
464 }
465
466 pub fn with_inactive_connection_ttl(mut self, ttl: Duration) -> Self {
485 self.inactive_connection_ttl = ttl;
486 self
487 }
488
489 pub fn inactive_connection_ttl(&self) -> Duration {
491 self.inactive_connection_ttl
492 }
493
494 pub fn with_ttl_check_interval(mut self, interval: Duration) -> Self {
512 if interval < Duration::from_secs(1) {
513 self.ttl_check_interval = DEFAULT_TTL_CHECK_INTERVAL
514 } else {
515 self.ttl_check_interval = interval;
516 }
517 self
518 }
519
520 pub fn ttl_check_interval(&self) -> Duration {
522 self.ttl_check_interval
523 }
524
525 pub(crate) fn active_bound(&self) -> usize {
538 if self.inactive_connection_ttl > Duration::from_secs(0) {
539 self.constraints.max
540 } else {
541 self.constraints.min
542 }
543 }
544}
545
546impl Default for PoolOpts {
547 fn default() -> Self {
548 Self {
549 constraints: DEFAULT_POOL_CONSTRAINTS,
550 inactive_connection_ttl: DEFAULT_INACTIVE_CONNECTION_TTL,
551 ttl_check_interval: DEFAULT_TTL_CHECK_INTERVAL,
552 abs_conn_ttl: None,
553 abs_conn_ttl_jitter: None,
554 reset_connection: true,
555 }
556 }
557}
558
559#[derive(Clone, Eq, PartialEq, Default, Debug)]
560pub(crate) struct InnerOpts {
561 mysql_opts: MysqlOpts,
562 address: HostPortOrUrl,
563}
564
565type AfterConnectCallback =
567 Arc<dyn for<'a> Fn(&'a mut crate::Conn) -> crate::BoxFuture<'a, ()> + Send + Sync + 'static>;
568
569#[derive(Clone)]
570pub(crate) struct AfterConnectCallbackWrapper(AfterConnectCallback);
571
572impl Eq for AfterConnectCallbackWrapper {}
573
574impl PartialEq for AfterConnectCallbackWrapper {
575 fn eq(&self, other: &Self) -> bool {
576 Arc::ptr_eq(&self.0, &other.0)
577 }
578}
579
580impl fmt::Debug for AfterConnectCallbackWrapper {
581 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
582 f.debug_tuple("AfterConnectCallbackWrapper").finish()
583 }
584}
585
586#[derive(Clone, Eq, PartialEq, Debug)]
590pub(crate) struct MysqlOpts {
591 user: Option<String>,
593
594 pass: Option<String>,
596
597 db_name: Option<String>,
599
600 tcp_keepalive: Option<Duration>,
602
603 tcp_nodelay: bool,
608
609 local_infile_handler: Option<GlobalHandlerObject>,
611
612 pool_opts: PoolOpts,
614
615 conn_ttl: Option<Duration>,
618
619 after_connect: Option<AfterConnectCallbackWrapper>,
621
622 init: Vec<String>,
624
625 setup: Vec<String>,
628
629 stmt_cache_size: usize,
631
632 ssl_opts: Option<SslOptsAndCachedConnector>,
634
635 prefer_socket: bool,
647
648 socket: Option<String>,
650
651 compression: Option<crate::Compression>,
662
663 max_allowed_packet: Option<usize>,
668
669 wait_timeout: Option<usize>,
674
675 secure_auth: bool,
679
680 client_found_rows: bool,
685
686 enable_cleartext_plugin: bool,
696
697 connect_attributes: Option<std::collections::HashMap<String, String>>,
702}
703
704#[derive(Clone, Eq, PartialEq, Debug, Default)]
708pub struct Opts {
709 inner: Arc<InnerOpts>,
710}
711
712impl Opts {
713 #[doc(hidden)]
714 pub fn addr_is_loopback(&self) -> bool {
715 self.inner.address.is_loopback()
716 }
717
718 pub fn from_url(url: &str) -> std::result::Result<Opts, UrlError> {
719 let mut url = Url::parse(url)?;
720
721 if url.port().is_none() {
724 url.set_port(Some(DEFAULT_PORT))
725 .map_err(|_| UrlError::Invalid)?;
726 }
727
728 let mysql_opts = mysql_opts_from_url(&url)?;
729 let address = HostPortOrUrl::Url(url);
730
731 let inner_opts = InnerOpts {
732 mysql_opts,
733 address,
734 };
735
736 Ok(Opts {
737 inner: Arc::new(inner_opts),
738 })
739 }
740
741 pub fn ip_or_hostname(&self) -> &str {
743 self.inner.address.get_ip_or_hostname()
744 }
745
746 pub(crate) fn hostport_or_url(&self) -> &HostPortOrUrl {
747 &self.inner.address
748 }
749
750 pub fn tcp_port(&self) -> u16 {
752 self.inner.address.get_tcp_port()
753 }
754
755 pub fn resolved_ips(&self) -> &Option<Vec<IpAddr>> {
757 self.inner.address.get_resolved_ips()
758 }
759
760 pub fn user(&self) -> Option<&str> {
774 self.inner.mysql_opts.user.as_ref().map(AsRef::as_ref)
775 }
776
777 pub fn pass(&self) -> Option<&str> {
791 self.inner.mysql_opts.pass.as_ref().map(AsRef::as_ref)
792 }
793
794 pub fn db_name(&self) -> Option<&str> {
808 self.inner.mysql_opts.db_name.as_ref().map(AsRef::as_ref)
809 }
810
811 pub fn after_connect(&self) -> Option<AfterConnectCallback> {
830 self.inner
831 .mysql_opts
832 .after_connect
833 .as_ref()
834 .map(|cb| cb.0.clone())
835 }
836
837 pub fn init(&self) -> &[String] {
839 self.inner.mysql_opts.init.as_ref()
840 }
841
842 pub fn setup(&self) -> &[String] {
848 self.inner.mysql_opts.setup.as_ref()
849 }
850
851 pub fn tcp_keepalive(&self) -> Option<Duration> {
865 self.inner.mysql_opts.tcp_keepalive
866 }
867
868 pub fn tcp_nodelay(&self) -> bool {
885 self.inner.mysql_opts.tcp_nodelay
886 }
887
888 pub fn local_infile_handler(&self) -> Option<Arc<dyn GlobalHandler>> {
890 self.inner
891 .mysql_opts
892 .local_infile_handler
893 .as_ref()
894 .map(|x| x.clone_inner())
895 }
896
897 pub fn pool_opts(&self) -> &PoolOpts {
899 &self.inner.mysql_opts.pool_opts
900 }
901
902 pub fn conn_ttl(&self) -> Option<Duration> {
918 self.inner.mysql_opts.conn_ttl
919 }
920
921 pub fn abs_conn_ttl(&self) -> Option<Duration> {
940 self.inner.mysql_opts.pool_opts.abs_conn_ttl
941 }
942
943 pub fn abs_conn_ttl_jitter(&self) -> Option<Duration> {
961 self.inner.mysql_opts.pool_opts.abs_conn_ttl_jitter
962 }
963
964 pub fn stmt_cache_size(&self) -> usize {
986 self.inner.mysql_opts.stmt_cache_size
987 }
988
989 pub fn ssl_opts(&self) -> Option<&SslOpts> {
1011 self.inner.mysql_opts.ssl_opts.as_ref().map(|o| &o.ssl_opts)
1012 }
1013
1014 pub fn prefer_socket(&self) -> bool {
1038 self.inner.mysql_opts.prefer_socket
1039 }
1040
1041 pub fn socket(&self) -> Option<&str> {
1055 self.inner.mysql_opts.socket.as_deref()
1056 }
1057
1058 pub fn compression(&self) -> Option<crate::Compression> {
1072 self.inner.mysql_opts.compression
1073 }
1074
1075 pub fn max_allowed_packet(&self) -> Option<usize> {
1082 self.inner.mysql_opts.max_allowed_packet
1083 }
1084
1085 pub fn wait_timeout(&self) -> Option<usize> {
1092 self.inner.mysql_opts.wait_timeout
1093 }
1094
1095 pub fn secure_auth(&self) -> bool {
1099 self.inner.mysql_opts.secure_auth
1100 }
1101
1102 pub fn client_found_rows(&self) -> bool {
1119 self.inner.mysql_opts.client_found_rows
1120 }
1121
1122 pub fn enable_cleartext_plugin(&self) -> bool {
1144 self.inner.mysql_opts.enable_cleartext_plugin
1145 }
1146
1147 pub fn connect_attributes(&self) -> Option<&std::collections::HashMap<String, String>> {
1149 self.inner.mysql_opts.connect_attributes.as_ref()
1150 }
1151
1152 pub(crate) fn get_capabilities(&self) -> CapabilityFlags {
1153 let mut out = CapabilityFlags::CLIENT_PROTOCOL_41
1154 | CapabilityFlags::CLIENT_SECURE_CONNECTION
1155 | CapabilityFlags::CLIENT_LONG_PASSWORD
1156 | CapabilityFlags::CLIENT_TRANSACTIONS
1157 | CapabilityFlags::CLIENT_LOCAL_FILES
1158 | CapabilityFlags::CLIENT_MULTI_STATEMENTS
1159 | CapabilityFlags::CLIENT_MULTI_RESULTS
1160 | CapabilityFlags::CLIENT_PS_MULTI_RESULTS
1161 | CapabilityFlags::CLIENT_DEPRECATE_EOF
1162 | CapabilityFlags::CLIENT_PLUGIN_AUTH;
1163
1164 if self.inner.mysql_opts.db_name.is_some() {
1165 out |= CapabilityFlags::CLIENT_CONNECT_WITH_DB;
1166 }
1167 if self.inner.mysql_opts.ssl_opts.is_some() {
1168 out |= CapabilityFlags::CLIENT_SSL;
1169 }
1170 if self.inner.mysql_opts.compression.is_some() {
1171 out |= CapabilityFlags::CLIENT_COMPRESS;
1172 }
1173 if self.client_found_rows() {
1174 out |= CapabilityFlags::CLIENT_FOUND_ROWS;
1175 }
1176
1177 out
1178 }
1179
1180 pub(crate) fn get_mariadb_capabilities(&self) -> MariadbCapabilities {
1181 MariadbCapabilities::MARIADB_CLIENT_STMT_BULK_OPERATIONS
1182 | MariadbCapabilities::MARIADB_CLIENT_CACHE_METADATA
1183 | MariadbCapabilities::MARIADB_CLIENT_BULK_UNIT_RESULTS
1184 }
1185
1186 pub(crate) fn ssl_opts_and_connector(&self) -> Option<&SslOptsAndCachedConnector> {
1187 self.inner.mysql_opts.ssl_opts.as_ref()
1188 }
1189}
1190
1191impl Default for MysqlOpts {
1192 fn default() -> MysqlOpts {
1193 MysqlOpts {
1194 user: None,
1195 pass: None,
1196 db_name: None,
1197 after_connect: None,
1198 init: vec![],
1199 setup: vec![],
1200 tcp_keepalive: None,
1201 tcp_nodelay: true,
1202 local_infile_handler: None,
1203 pool_opts: Default::default(),
1204 conn_ttl: None,
1205 stmt_cache_size: DEFAULT_STMT_CACHE_SIZE,
1206 ssl_opts: None,
1207 prefer_socket: cfg!(not(target_os = "windows")),
1208 socket: None,
1209 compression: None,
1210 max_allowed_packet: None,
1211 wait_timeout: None,
1212 secure_auth: true,
1213 client_found_rows: false,
1214 enable_cleartext_plugin: false,
1215 connect_attributes: None,
1216 }
1217 }
1218}
1219
1220#[derive(Clone)]
1221pub(crate) struct SslOptsAndCachedConnector {
1222 ssl_opts: SslOpts,
1223 tls_connector: Arc<OnceCell<crate::io::TlsConnector>>,
1224}
1225
1226impl fmt::Debug for SslOptsAndCachedConnector {
1227 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1228 f.debug_struct("SslOptsAndCachedConnector")
1229 .field("ssl_opts", &self.ssl_opts)
1230 .finish()
1231 }
1232}
1233
1234impl SslOptsAndCachedConnector {
1235 fn new(ssl_opts: SslOpts) -> Self {
1236 Self {
1237 ssl_opts,
1238 tls_connector: Arc::new(OnceCell::new()),
1239 }
1240 }
1241
1242 pub(crate) fn ssl_opts(&self) -> &SslOpts {
1243 &self.ssl_opts
1244 }
1245
1246 pub(crate) async fn build_tls_connector(&self) -> Result<crate::io::TlsConnector> {
1247 self.tls_connector
1248 .get_or_try_init(move || self.ssl_opts.build_tls_connector())
1249 .await
1250 .cloned()
1251 }
1252}
1253
1254impl PartialEq for SslOptsAndCachedConnector {
1255 fn eq(&self, other: &Self) -> bool {
1256 self.ssl_opts == other.ssl_opts
1257 }
1258}
1259impl Eq for SslOptsAndCachedConnector {}
1260
1261#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
1265pub struct PoolConstraints {
1266 min: usize,
1267 max: usize,
1268}
1269
1270impl PoolConstraints {
1271 pub const fn new(min: usize, max: usize) -> Option<PoolConstraints> {
1285 if min <= max && max > 0 {
1286 Some(PoolConstraints { min, max })
1287 } else {
1288 None
1289 }
1290 }
1291
1292 pub fn min(&self) -> usize {
1294 self.min
1295 }
1296
1297 pub fn max(&self) -> usize {
1299 self.max
1300 }
1301}
1302
1303impl Default for PoolConstraints {
1304 fn default() -> Self {
1305 DEFAULT_POOL_CONSTRAINTS
1306 }
1307}
1308
1309impl From<PoolConstraints> for (usize, usize) {
1310 fn from(PoolConstraints { min, max }: PoolConstraints) -> Self {
1312 (min, max)
1313 }
1314}
1315
1316#[derive(Debug, Clone, Eq, PartialEq)]
1335pub struct OptsBuilder {
1336 opts: MysqlOpts,
1337 ip_or_hostname: String,
1338 tcp_port: u16,
1339 resolved_ips: Option<Vec<IpAddr>>,
1340}
1341
1342impl Default for OptsBuilder {
1343 fn default() -> Self {
1344 let address = HostPortOrUrl::default();
1345 Self {
1346 opts: MysqlOpts::default(),
1347 ip_or_hostname: address.get_ip_or_hostname().into(),
1348 tcp_port: address.get_tcp_port(),
1349 resolved_ips: None,
1350 }
1351 }
1352}
1353
1354impl OptsBuilder {
1355 pub fn from_opts<T>(opts: T) -> Self
1361 where
1362 Opts: TryFrom<T>,
1363 <Opts as TryFrom<T>>::Error: std::error::Error,
1364 {
1365 let opts = Opts::try_from(opts).unwrap();
1366
1367 OptsBuilder {
1368 tcp_port: opts.inner.address.get_tcp_port(),
1369 ip_or_hostname: opts.inner.address.get_ip_or_hostname().to_string(),
1370 resolved_ips: opts.inner.address.get_resolved_ips().clone(),
1371 opts: opts.inner.mysql_opts.clone(),
1372 }
1373 }
1374
1375 pub fn ip_or_hostname<T: Into<String>>(mut self, ip_or_hostname: T) -> Self {
1377 self.ip_or_hostname = ip_or_hostname.into();
1378 self
1379 }
1380
1381 pub fn tcp_port(mut self, tcp_port: u16) -> Self {
1383 self.tcp_port = tcp_port;
1384 self
1385 }
1386
1387 pub fn resolved_ips<T: Into<Vec<IpAddr>>>(mut self, ips: Option<T>) -> Self {
1391 self.resolved_ips = ips.map(Into::into);
1392 self
1393 }
1394
1395 pub fn user<T: Into<String>>(mut self, user: Option<T>) -> Self {
1397 self.opts.user = user.map(Into::into);
1398 self
1399 }
1400
1401 pub fn pass<T: Into<String>>(mut self, pass: Option<T>) -> Self {
1403 self.opts.pass = pass.map(Into::into);
1404 self
1405 }
1406
1407 pub fn db_name<T: Into<String>>(mut self, db_name: Option<T>) -> Self {
1409 self.opts.db_name = db_name.map(Into::into);
1410 self
1411 }
1412
1413 pub fn after_connect<F>(mut self, callback: F) -> Self
1415 where
1416 F: for<'a> Fn(&'a mut crate::Conn) -> crate::BoxFuture<'a, ()> + Send + Sync + 'static,
1417 {
1418 self.opts.after_connect = Some(AfterConnectCallbackWrapper(Arc::new(callback)));
1419 self
1420 }
1421
1422 pub fn init<T: Into<String>>(mut self, init: Vec<T>) -> Self {
1424 self.opts.init = init.into_iter().map(Into::into).collect();
1425 self
1426 }
1427
1428 pub fn setup<T: Into<String>>(mut self, setup: Vec<T>) -> Self {
1430 self.opts.setup = setup.into_iter().map(Into::into).collect();
1431 self
1432 }
1433
1434 pub fn tcp_keepalive(mut self, tcp_keepalive: Option<Duration>) -> Self {
1436 self.opts.tcp_keepalive = tcp_keepalive;
1437 self
1438 }
1439
1440 pub fn tcp_nodelay(mut self, nodelay: bool) -> Self {
1442 self.opts.tcp_nodelay = nodelay;
1443 self
1444 }
1445
1446 pub fn local_infile_handler<T>(mut self, handler: Option<T>) -> Self
1448 where
1449 T: GlobalHandler,
1450 {
1451 self.opts.local_infile_handler = handler.map(GlobalHandlerObject::new);
1452 self
1453 }
1454
1455 pub fn pool_opts<T: Into<Option<PoolOpts>>>(mut self, pool_opts: T) -> Self {
1457 self.opts.pool_opts = pool_opts.into().unwrap_or_default();
1458 self
1459 }
1460
1461 pub fn conn_ttl<T: Into<Option<Duration>>>(mut self, conn_ttl: T) -> Self {
1463 self.opts.conn_ttl = conn_ttl.into();
1464 self
1465 }
1466
1467 pub fn stmt_cache_size<T>(mut self, cache_size: T) -> Self
1469 where
1470 T: Into<Option<usize>>,
1471 {
1472 self.opts.stmt_cache_size = cache_size.into().unwrap_or(DEFAULT_STMT_CACHE_SIZE);
1473 self
1474 }
1475
1476 pub fn ssl_opts<T: Into<Option<SslOpts>>>(mut self, ssl_opts: T) -> Self {
1478 self.opts.ssl_opts = ssl_opts.into().map(SslOptsAndCachedConnector::new);
1479 self
1480 }
1481
1482 pub fn prefer_socket<T: Into<Option<bool>>>(mut self, prefer_socket: T) -> Self {
1484 self.opts.prefer_socket = prefer_socket.into().unwrap_or(true);
1485 self
1486 }
1487
1488 pub fn socket<T: Into<String>>(mut self, socket: Option<T>) -> Self {
1490 self.opts.socket = socket.map(Into::into);
1491 self
1492 }
1493
1494 pub fn compression<T: Into<Option<crate::Compression>>>(mut self, compression: T) -> Self {
1496 self.opts.compression = compression.into();
1497 self
1498 }
1499
1500 pub fn max_allowed_packet(mut self, max_allowed_packet: Option<usize>) -> Self {
1505 self.opts.max_allowed_packet = max_allowed_packet.map(|x| x.clamp(1024, 1073741824));
1506 self
1507 }
1508
1509 pub fn wait_timeout(mut self, wait_timeout: Option<usize>) -> Self {
1514 self.opts.wait_timeout = wait_timeout.map(|x| {
1515 #[cfg(windows)]
1516 let val = std::cmp::min(2147483, x);
1517 #[cfg(not(windows))]
1518 let val = std::cmp::min(31536000, x);
1519
1520 val
1521 });
1522 self
1523 }
1524
1525 pub fn secure_auth(mut self, secure_auth: bool) -> Self {
1529 self.opts.secure_auth = secure_auth;
1530 self
1531 }
1532
1533 pub fn client_found_rows(mut self, client_found_rows: bool) -> Self {
1535 self.opts.client_found_rows = client_found_rows;
1536 self
1537 }
1538
1539 pub fn enable_cleartext_plugin(mut self, enable_cleartext_plugin: bool) -> Self {
1561 self.opts.enable_cleartext_plugin = enable_cleartext_plugin;
1562 self
1563 }
1564
1565 pub fn connect_attributes(mut self, attrs: std::collections::HashMap<String, String>) -> Self {
1567 self.opts.connect_attributes = Some(attrs);
1568 self
1569 }
1570
1571 pub fn connect_attribute<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
1573 let map = self
1574 .opts
1575 .connect_attributes
1576 .get_or_insert_with(Default::default);
1577 map.insert(key.into(), value.into());
1578 self
1579 }
1580}
1581
1582impl From<OptsBuilder> for Opts {
1583 fn from(builder: OptsBuilder) -> Opts {
1584 let address = HostPortOrUrl::HostPort {
1585 host: builder.ip_or_hostname,
1586 port: builder.tcp_port,
1587 resolved_ips: builder.resolved_ips,
1588 };
1589 let inner_opts = InnerOpts {
1590 mysql_opts: builder.opts,
1591 address,
1592 };
1593
1594 Opts {
1595 inner: Arc::new(inner_opts),
1596 }
1597 }
1598}
1599
1600#[derive(Clone, Eq, PartialEq)]
1609pub struct ChangeUserOpts {
1610 user: Option<Option<String>>,
1611 pass: Option<Option<String>>,
1612 db_name: Option<Option<String>>,
1613}
1614
1615impl ChangeUserOpts {
1616 pub(crate) fn update_opts(self, opts: &mut Opts) {
1617 if self.user.is_none() && self.pass.is_none() && self.db_name.is_none() {
1618 return;
1619 }
1620
1621 let mut builder = OptsBuilder::from_opts(opts.clone());
1622
1623 if let Some(user) = self.user {
1624 builder = builder.user(user);
1625 }
1626
1627 if let Some(pass) = self.pass {
1628 builder = builder.pass(pass);
1629 }
1630
1631 if let Some(db_name) = self.db_name {
1632 builder = builder.db_name(db_name);
1633 }
1634
1635 *opts = Opts::from(builder);
1636 }
1637
1638 pub fn new() -> Self {
1640 Self {
1641 user: None,
1642 pass: None,
1643 db_name: None,
1644 }
1645 }
1646
1647 pub fn with_user(mut self, user: Option<String>) -> Self {
1649 self.user = Some(user);
1650 self
1651 }
1652
1653 pub fn with_pass(mut self, pass: Option<String>) -> Self {
1655 self.pass = Some(pass);
1656 self
1657 }
1658
1659 pub fn with_db_name(mut self, db_name: Option<String>) -> Self {
1661 self.db_name = Some(db_name);
1662 self
1663 }
1664
1665 pub fn user(&self) -> Option<Option<&str>> {
1671 self.user.as_ref().map(|x| x.as_deref())
1672 }
1673
1674 pub fn pass(&self) -> Option<Option<&str>> {
1680 self.pass.as_ref().map(|x| x.as_deref())
1681 }
1682
1683 pub fn db_name(&self) -> Option<Option<&str>> {
1689 self.db_name.as_ref().map(|x| x.as_deref())
1690 }
1691}
1692
1693impl Default for ChangeUserOpts {
1694 fn default() -> Self {
1695 Self::new()
1696 }
1697}
1698
1699impl fmt::Debug for ChangeUserOpts {
1700 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1701 f.debug_struct("ChangeUserOpts")
1702 .field("user", &self.user)
1703 .field(
1704 "pass",
1705 &self.pass.as_ref().map(|x| x.as_ref().map(|_| "...")),
1706 )
1707 .field("db_name", &self.db_name)
1708 .finish()
1709 }
1710}
1711
1712fn get_opts_user_from_url(url: &Url) -> Option<String> {
1713 let user = url.username();
1714 if !user.is_empty() {
1715 Some(
1716 percent_decode(user.as_ref())
1717 .decode_utf8_lossy()
1718 .into_owned(),
1719 )
1720 } else {
1721 None
1722 }
1723}
1724
1725fn get_opts_pass_from_url(url: &Url) -> Option<String> {
1726 url.password().map(|pass| {
1727 percent_decode(pass.as_ref())
1728 .decode_utf8_lossy()
1729 .into_owned()
1730 })
1731}
1732
1733fn get_opts_db_name_from_url(url: &Url) -> Option<String> {
1734 if let Some(mut segments) = url.path_segments() {
1735 segments
1736 .next()
1737 .map(|db_name| {
1738 percent_decode(db_name.as_ref())
1739 .decode_utf8_lossy()
1740 .into_owned()
1741 })
1742 .filter(|db| !db.is_empty())
1743 } else {
1744 None
1745 }
1746}
1747
1748fn from_url_basic(url: &Url) -> std::result::Result<(MysqlOpts, Vec<(String, String)>), UrlError> {
1749 if url.scheme() != "mysql" {
1750 return Err(UrlError::UnsupportedScheme {
1751 scheme: url.scheme().to_string(),
1752 });
1753 }
1754 if url.cannot_be_a_base() || !url.has_host() {
1755 return Err(UrlError::Invalid);
1756 }
1757 let user = get_opts_user_from_url(url);
1758 let pass = get_opts_pass_from_url(url);
1759 let db_name = get_opts_db_name_from_url(url);
1760
1761 let query_pairs = url.query_pairs().into_owned().collect();
1762 let opts = MysqlOpts {
1763 user,
1764 pass,
1765 db_name,
1766 ..MysqlOpts::default()
1767 };
1768
1769 Ok((opts, query_pairs))
1770}
1771
1772fn mysql_opts_from_url(url: &Url) -> std::result::Result<MysqlOpts, UrlError> {
1773 let (mut opts, query_pairs): (MysqlOpts, _) = from_url_basic(url)?;
1774 let mut pool_min = DEFAULT_POOL_CONSTRAINTS.min;
1775 let mut pool_max = DEFAULT_POOL_CONSTRAINTS.max;
1776
1777 let mut ssl_opts = None;
1778 let mut skip_domain_validation = false;
1779 let mut accept_invalid_certs = false;
1780 let mut disable_built_in_roots = false;
1781
1782 for (key, value) in query_pairs {
1783 if key == "pool_min" {
1784 match usize::from_str(&value) {
1785 Ok(value) => pool_min = value,
1786 _ => {
1787 return Err(UrlError::InvalidParamValue {
1788 param: "pool_min".into(),
1789 value,
1790 });
1791 }
1792 }
1793 } else if key == "pool_max" {
1794 match usize::from_str(&value) {
1795 Ok(value) => pool_max = value,
1796 _ => {
1797 return Err(UrlError::InvalidParamValue {
1798 param: "pool_max".into(),
1799 value,
1800 });
1801 }
1802 }
1803 } else if key == "inactive_connection_ttl" {
1804 match u64::from_str(&value) {
1805 Ok(value) => {
1806 opts.pool_opts = opts
1807 .pool_opts
1808 .with_inactive_connection_ttl(Duration::from_secs(value))
1809 }
1810 _ => {
1811 return Err(UrlError::InvalidParamValue {
1812 param: "inactive_connection_ttl".into(),
1813 value,
1814 });
1815 }
1816 }
1817 } else if key == "ttl_check_interval" {
1818 match u64::from_str(&value) {
1819 Ok(value) => {
1820 opts.pool_opts = opts
1821 .pool_opts
1822 .with_ttl_check_interval(Duration::from_secs(value))
1823 }
1824 _ => {
1825 return Err(UrlError::InvalidParamValue {
1826 param: "ttl_check_interval".into(),
1827 value,
1828 });
1829 }
1830 }
1831 } else if key == "conn_ttl" {
1832 match u64::from_str(&value) {
1833 Ok(value) => opts.conn_ttl = Some(Duration::from_secs(value)),
1834 _ => {
1835 return Err(UrlError::InvalidParamValue {
1836 param: "conn_ttl".into(),
1837 value,
1838 });
1839 }
1840 }
1841 } else if key == "abs_conn_ttl" {
1842 match u64::from_str(&value) {
1843 Ok(value) => {
1844 opts.pool_opts = opts
1845 .pool_opts
1846 .with_abs_conn_ttl(Some(Duration::from_secs(value)))
1847 }
1848 _ => {
1849 return Err(UrlError::InvalidParamValue {
1850 param: "abs_conn_ttl".into(),
1851 value,
1852 });
1853 }
1854 }
1855 } else if key == "abs_conn_ttl_jitter" {
1856 match u64::from_str(&value) {
1857 Ok(value) => {
1858 opts.pool_opts = opts
1859 .pool_opts
1860 .with_abs_conn_ttl_jitter(Some(Duration::from_secs(value)))
1861 }
1862 _ => {
1863 return Err(UrlError::InvalidParamValue {
1864 param: "abs_conn_ttl_jitter".into(),
1865 value,
1866 });
1867 }
1868 }
1869 } else if key == "tcp_keepalive" {
1870 match u32::from_str(&value) {
1871 Ok(value) => opts.tcp_keepalive = Some(Duration::from_millis(value.into())),
1872 _ => {
1873 return Err(UrlError::InvalidParamValue {
1874 param: "tcp_keepalive".into(),
1875 value,
1876 });
1877 }
1878 }
1879 } else if key == "max_allowed_packet" {
1880 match usize::from_str(&value) {
1881 Ok(value) => opts.max_allowed_packet = Some(value.clamp(1024, 1073741824)),
1882 _ => {
1883 return Err(UrlError::InvalidParamValue {
1884 param: "max_allowed_packet".into(),
1885 value,
1886 });
1887 }
1888 }
1889 } else if key == "wait_timeout" {
1890 match usize::from_str(&value) {
1891 #[cfg(windows)]
1892 Ok(value) => opts.wait_timeout = Some(std::cmp::min(2147483, value)),
1893 #[cfg(not(windows))]
1894 Ok(value) => opts.wait_timeout = Some(std::cmp::min(31536000, value)),
1895 _ => {
1896 return Err(UrlError::InvalidParamValue {
1897 param: "wait_timeout".into(),
1898 value,
1899 });
1900 }
1901 }
1902 } else if key == "enable_cleartext_plugin" {
1903 match bool::from_str(&value) {
1904 Ok(parsed) => opts.enable_cleartext_plugin = parsed,
1905 Err(_) => {
1906 return Err(UrlError::InvalidParamValue {
1907 param: key.to_string(),
1908 value,
1909 });
1910 }
1911 }
1912 } else if key == "reset_connection" {
1913 match bool::from_str(&value) {
1914 Ok(parsed) => opts.pool_opts = opts.pool_opts.with_reset_connection(parsed),
1915 Err(_) => {
1916 return Err(UrlError::InvalidParamValue {
1917 param: key.to_string(),
1918 value,
1919 });
1920 }
1921 }
1922 } else if key == "tcp_nodelay" {
1923 match bool::from_str(&value) {
1924 Ok(value) => opts.tcp_nodelay = value,
1925 _ => {
1926 return Err(UrlError::InvalidParamValue {
1927 param: "tcp_nodelay".into(),
1928 value,
1929 });
1930 }
1931 }
1932 } else if key == "stmt_cache_size" {
1933 match usize::from_str(&value) {
1934 Ok(stmt_cache_size) => {
1935 opts.stmt_cache_size = stmt_cache_size;
1936 }
1937 _ => {
1938 return Err(UrlError::InvalidParamValue {
1939 param: "stmt_cache_size".into(),
1940 value,
1941 });
1942 }
1943 }
1944 } else if key == "prefer_socket" {
1945 match bool::from_str(&value) {
1946 Ok(prefer_socket) => {
1947 opts.prefer_socket = prefer_socket;
1948 }
1949 _ => {
1950 return Err(UrlError::InvalidParamValue {
1951 param: "prefer_socket".into(),
1952 value,
1953 });
1954 }
1955 }
1956 } else if key == "secure_auth" {
1957 match bool::from_str(&value) {
1958 Ok(secure_auth) => {
1959 opts.secure_auth = secure_auth;
1960 }
1961 _ => {
1962 return Err(UrlError::InvalidParamValue {
1963 param: "secure_auth".into(),
1964 value,
1965 });
1966 }
1967 }
1968 } else if key == "client_found_rows" {
1969 match bool::from_str(&value) {
1970 Ok(client_found_rows) => {
1971 opts.client_found_rows = client_found_rows;
1972 }
1973 _ => {
1974 return Err(UrlError::InvalidParamValue {
1975 param: "client_found_rows".into(),
1976 value,
1977 });
1978 }
1979 }
1980 } else if key == "socket" {
1981 opts.socket = Some(value)
1982 } else if key == "compression" {
1983 if value == "fast" {
1984 opts.compression = Some(crate::Compression::fast());
1985 } else if value == "on" || value == "true" {
1986 opts.compression = Some(crate::Compression::default());
1987 } else if value == "best" {
1988 opts.compression = Some(crate::Compression::best());
1989 } else if value.len() == 1 && 0x30 <= value.as_bytes()[0] && value.as_bytes()[0] <= 0x39
1990 {
1991 opts.compression =
1992 Some(crate::Compression::new((value.as_bytes()[0] - 0x30) as u32));
1993 } else {
1994 return Err(UrlError::InvalidParamValue {
1995 param: "compression".into(),
1996 value,
1997 });
1998 }
1999 } else if key == "require_ssl" {
2000 match bool::from_str(&value) {
2001 Ok(x) => {
2002 ssl_opts = x.then(SslOpts::default);
2003 }
2004 _ => {
2005 return Err(UrlError::InvalidParamValue {
2006 param: "require_ssl".into(),
2007 value,
2008 });
2009 }
2010 }
2011 } else if key == "verify_ca" {
2012 match bool::from_str(&value) {
2013 Ok(x) => {
2014 accept_invalid_certs = !x;
2015 }
2016 _ => {
2017 return Err(UrlError::InvalidParamValue {
2018 param: "verify_ca".into(),
2019 value,
2020 });
2021 }
2022 }
2023 } else if key == "verify_identity" {
2024 match bool::from_str(&value) {
2025 Ok(x) => {
2026 skip_domain_validation = !x;
2027 }
2028 _ => {
2029 return Err(UrlError::InvalidParamValue {
2030 param: "verify_identity".into(),
2031 value,
2032 });
2033 }
2034 }
2035 } else if key == "built_in_roots" {
2036 match bool::from_str(&value) {
2037 Ok(x) => {
2038 disable_built_in_roots = !x;
2039 }
2040 _ => {
2041 return Err(UrlError::InvalidParamValue {
2042 param: "built_in_roots".into(),
2043 value,
2044 });
2045 }
2046 }
2047 } else {
2048 return Err(UrlError::UnknownParameter { param: key });
2049 }
2050 }
2051
2052 if let Some(pool_constraints) = PoolConstraints::new(pool_min, pool_max) {
2053 opts.pool_opts = opts.pool_opts.with_constraints(pool_constraints);
2054 } else {
2055 return Err(UrlError::InvalidPoolConstraints {
2056 min: pool_min,
2057 max: pool_max,
2058 });
2059 }
2060
2061 if let Some(ref mut ssl_opts) = ssl_opts {
2062 ssl_opts.accept_invalid_certs = accept_invalid_certs;
2063 ssl_opts.skip_domain_validation = skip_domain_validation;
2064 ssl_opts.disable_built_in_roots = disable_built_in_roots;
2065 }
2066
2067 opts.ssl_opts = ssl_opts.map(SslOptsAndCachedConnector::new);
2068
2069 Ok(opts)
2070}
2071
2072impl FromStr for Opts {
2073 type Err = UrlError;
2074
2075 fn from_str(s: &str) -> std::result::Result<Self, <Self as FromStr>::Err> {
2076 Opts::from_url(s)
2077 }
2078}
2079
2080impl TryFrom<&str> for Opts {
2081 type Error = UrlError;
2082
2083 fn try_from(s: &str) -> std::result::Result<Self, UrlError> {
2084 Opts::from_url(s)
2085 }
2086}
2087
2088#[cfg(test)]
2089mod test {
2090 use super::{HostPortOrUrl, MysqlOpts, Opts, Url};
2091 use crate::{error::UrlError::InvalidParamValue, SslOpts};
2092
2093 use std::{net::IpAddr, net::Ipv4Addr, net::Ipv6Addr, str::FromStr};
2094
2095 #[test]
2096 fn test_builder_eq_url() {
2097 const URL: &str = "mysql://iq-controller@localhost/iq_controller";
2098
2099 let url_opts = super::Opts::from_str(URL).unwrap();
2100 let builder = super::OptsBuilder::default()
2101 .user(Some("iq-controller"))
2102 .ip_or_hostname("localhost")
2103 .db_name(Some("iq_controller"));
2104 let builder_opts = Opts::from(builder);
2105
2106 assert_eq!(url_opts.addr_is_loopback(), builder_opts.addr_is_loopback());
2107 assert_eq!(url_opts.ip_or_hostname(), builder_opts.ip_or_hostname());
2108 assert_eq!(url_opts.tcp_port(), builder_opts.tcp_port());
2109 assert_eq!(url_opts.user(), builder_opts.user());
2110 assert_eq!(url_opts.pass(), builder_opts.pass());
2111 assert_eq!(url_opts.db_name(), builder_opts.db_name());
2112 assert_eq!(url_opts.init(), builder_opts.init());
2113 assert_eq!(url_opts.setup(), builder_opts.setup());
2114 assert_eq!(url_opts.tcp_keepalive(), builder_opts.tcp_keepalive());
2115 assert_eq!(url_opts.tcp_nodelay(), builder_opts.tcp_nodelay());
2116 assert_eq!(url_opts.pool_opts(), builder_opts.pool_opts());
2117 assert_eq!(url_opts.conn_ttl(), builder_opts.conn_ttl());
2118 assert_eq!(url_opts.abs_conn_ttl(), builder_opts.abs_conn_ttl());
2119 assert_eq!(
2120 url_opts.abs_conn_ttl_jitter(),
2121 builder_opts.abs_conn_ttl_jitter()
2122 );
2123 assert_eq!(url_opts.stmt_cache_size(), builder_opts.stmt_cache_size());
2124 assert_eq!(url_opts.ssl_opts(), builder_opts.ssl_opts());
2125 assert_eq!(url_opts.prefer_socket(), builder_opts.prefer_socket());
2126 assert_eq!(url_opts.socket(), builder_opts.socket());
2127 assert_eq!(url_opts.compression(), builder_opts.compression());
2128 assert_eq!(
2129 url_opts.hostport_or_url().get_ip_or_hostname(),
2130 builder_opts.hostport_or_url().get_ip_or_hostname()
2131 );
2132 assert_eq!(
2133 url_opts.hostport_or_url().get_tcp_port(),
2134 builder_opts.hostport_or_url().get_tcp_port()
2135 );
2136 }
2137
2138 #[test]
2139 fn should_convert_url_into_opts() {
2140 let url = "mysql://usr:pw@192.168.1.1:3309/dbname?prefer_socket=true";
2141 let parsed_url =
2142 Url::parse("mysql://usr:pw@192.168.1.1:3309/dbname?prefer_socket=true").unwrap();
2143
2144 let mysql_opts = MysqlOpts {
2145 user: Some("usr".to_string()),
2146 pass: Some("pw".to_string()),
2147 db_name: Some("dbname".to_string()),
2148 prefer_socket: true,
2149 ..MysqlOpts::default()
2150 };
2151 let host = HostPortOrUrl::Url(parsed_url);
2152
2153 let opts = Opts::from_url(url).unwrap();
2154
2155 assert_eq!(opts.inner.mysql_opts, mysql_opts);
2156 assert_eq!(opts.hostport_or_url(), &host);
2157 }
2158
2159 #[test]
2160 fn should_convert_ipv6_url_into_opts() {
2161 let url = "mysql://usr:pw@[::1]:3309/dbname";
2162
2163 let opts = Opts::from_url(url).unwrap();
2164
2165 assert_eq!(opts.ip_or_hostname(), "[::1]");
2166 }
2167
2168 #[test]
2169 fn should_parse_ssl_params() {
2170 const URL1: &str = "mysql://localhost/foo?require_ssl=false";
2171 let opts = Opts::from_url(URL1).unwrap();
2172 assert_eq!(opts.ssl_opts(), None);
2173
2174 const URL2: &str = "mysql://localhost/foo?require_ssl=true";
2175 let opts = Opts::from_url(URL2).unwrap();
2176 assert_eq!(opts.ssl_opts(), Some(&SslOpts::default()));
2177
2178 const URL3: &str = "mysql://localhost/foo?require_ssl=true&verify_ca=false";
2179 let opts = Opts::from_url(URL3).unwrap();
2180 assert_eq!(
2181 opts.ssl_opts(),
2182 Some(&SslOpts::default().with_danger_accept_invalid_certs(true))
2183 );
2184
2185 const URL4: &str =
2186 "mysql://localhost/foo?require_ssl=true&verify_ca=false&verify_identity=false&built_in_roots=false";
2187 let opts = Opts::from_url(URL4).unwrap();
2188 assert_eq!(
2189 opts.ssl_opts(),
2190 Some(
2191 &SslOpts::default()
2192 .with_danger_accept_invalid_certs(true)
2193 .with_danger_skip_domain_validation(true)
2194 .with_disable_built_in_roots(true)
2195 )
2196 );
2197
2198 const URL5: &str =
2199 "mysql://localhost/foo?require_ssl=false&verify_ca=false&verify_identity=false";
2200 let opts = Opts::from_url(URL5).unwrap();
2201 assert_eq!(opts.ssl_opts(), None);
2202 }
2203
2204 #[test]
2205 #[should_panic]
2206 fn should_panic_on_invalid_url() {
2207 let opts = "42";
2208 let _: Opts = Opts::from_str(opts).unwrap();
2209 }
2210
2211 #[test]
2212 #[should_panic]
2213 fn should_panic_on_invalid_scheme() {
2214 let opts = "postgres://localhost";
2215 let _: Opts = Opts::from_str(opts).unwrap();
2216 }
2217
2218 #[test]
2219 #[should_panic]
2220 fn should_panic_on_unknown_query_param() {
2221 let opts = "mysql://localhost/foo?bar=baz";
2222 let _: Opts = Opts::from_str(opts).unwrap();
2223 }
2224
2225 #[test]
2226 fn should_parse_compression() {
2227 let err = Opts::from_url("mysql://localhost/foo?compression=").unwrap_err();
2228 assert_eq!(
2229 err,
2230 InvalidParamValue {
2231 param: "compression".into(),
2232 value: "".into()
2233 }
2234 );
2235
2236 let err = Opts::from_url("mysql://localhost/foo?compression=a").unwrap_err();
2237 assert_eq!(
2238 err,
2239 InvalidParamValue {
2240 param: "compression".into(),
2241 value: "a".into()
2242 }
2243 );
2244
2245 let opts = Opts::from_url("mysql://localhost/foo?compression=fast").unwrap();
2246 assert_eq!(opts.compression(), Some(crate::Compression::fast()));
2247
2248 let opts = Opts::from_url("mysql://localhost/foo?compression=on").unwrap();
2249 assert_eq!(opts.compression(), Some(crate::Compression::default()));
2250
2251 let opts = Opts::from_url("mysql://localhost/foo?compression=true").unwrap();
2252 assert_eq!(opts.compression(), Some(crate::Compression::default()));
2253
2254 let opts = Opts::from_url("mysql://localhost/foo?compression=best").unwrap();
2255 assert_eq!(opts.compression(), Some(crate::Compression::best()));
2256
2257 let opts = Opts::from_url("mysql://localhost/foo?compression=0").unwrap();
2258 assert_eq!(opts.compression(), Some(crate::Compression::new(0)));
2259
2260 let opts = Opts::from_url("mysql://localhost/foo?compression=9").unwrap();
2261 assert_eq!(opts.compression(), Some(crate::Compression::new(9)));
2262 }
2263
2264 #[test]
2265 fn test_builder_eq_url_empty_db() {
2266 let builder = super::OptsBuilder::default();
2267 let builder_opts = Opts::from(builder);
2268
2269 let url: &str = "mysql://iq-controller@localhost";
2270 let url_opts = super::Opts::from_str(url).unwrap();
2271 assert_eq!(url_opts.db_name(), builder_opts.db_name());
2272
2273 let url: &str = "mysql://iq-controller@localhost/";
2274 let url_opts = super::Opts::from_str(url).unwrap();
2275 assert_eq!(url_opts.db_name(), builder_opts.db_name());
2276 }
2277
2278 #[test]
2279 fn test_builder_update_port_host_resolved_ips() {
2280 let builder = super::OptsBuilder::default()
2281 .ip_or_hostname("foo")
2282 .tcp_port(33306);
2283
2284 let resolved = vec![
2285 IpAddr::V4(Ipv4Addr::new(127, 0, 0, 7)),
2286 IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff)),
2287 ];
2288 let builder2 = builder
2289 .clone()
2290 .tcp_port(55223)
2291 .resolved_ips(Some(resolved.clone()));
2292
2293 let builder_opts = Opts::from(builder);
2294 assert_eq!(builder_opts.ip_or_hostname(), "foo");
2295 assert_eq!(builder_opts.tcp_port(), 33306);
2296 assert_eq!(
2297 builder_opts.hostport_or_url(),
2298 &HostPortOrUrl::HostPort {
2299 host: "foo".to_string(),
2300 port: 33306,
2301 resolved_ips: None
2302 }
2303 );
2304
2305 let builder_opts2 = Opts::from(builder2);
2306 assert_eq!(builder_opts2.ip_or_hostname(), "foo");
2307 assert_eq!(builder_opts2.tcp_port(), 55223);
2308 assert_eq!(
2309 builder_opts2.hostport_or_url(),
2310 &HostPortOrUrl::HostPort {
2311 host: "foo".to_string(),
2312 port: 55223,
2313 resolved_ips: Some(resolved),
2314 }
2315 );
2316 }
2317}