1mod types;
109pub use types::*;
110
111use std::time::Duration;
112
113use mssql_auth::Credentials;
114#[cfg(feature = "tls")]
115use mssql_tls::TlsConfig;
116use tds_protocol::version::TdsVersion;
117
118fn parse_conn_bool(key: &str, value: &str) -> Result<bool, crate::error::Error> {
124 match value.to_lowercase().as_str() {
125 "true" | "yes" | "1" => Ok(true),
126 "false" | "no" | "0" => Ok(false),
127 _ => Err(crate::error::Error::Config(format!(
128 "invalid boolean value for '{key}': '{value}' (expected true/false/yes/no/1/0)"
129 ))),
130 }
131}
132
133fn split_connection_string(conn_str: &str) -> Result<Vec<(String, String)>, crate::error::Error> {
142 let mut pairs = Vec::new();
143 let chars: Vec<char> = conn_str.chars().collect();
144 let len = chars.len();
145 let mut i = 0;
146
147 while i < len {
148 while i < len && (chars[i] == ';' || chars[i].is_whitespace()) {
150 i += 1;
151 }
152 if i >= len {
153 break;
154 }
155
156 let key_start = i;
158 while i < len && chars[i] != '=' {
159 i += 1;
160 }
161 if i >= len {
162 let remaining = chars[key_start..].iter().collect::<String>();
164 if remaining.trim().is_empty() {
165 break;
166 }
167 return Err(crate::error::Error::Config(format!(
168 "invalid key-value pair (missing '='): '{remaining}'"
169 )));
170 }
171 let key: String = chars[key_start..i].iter().collect();
172 i += 1; while i < len && chars[i].is_whitespace() {
177 i += 1;
178 }
179
180 let value = if i < len && (chars[i] == '"' || chars[i] == '\'') {
181 let quote_char = chars[i];
183 i += 1; let mut val = String::new();
185 loop {
186 if i >= len {
187 return Err(crate::error::Error::Config(format!(
188 "unterminated quoted value for key '{}'",
189 key.trim()
190 )));
191 }
192 if chars[i] == quote_char {
193 if i + 1 < len && chars[i + 1] == quote_char {
195 val.push(quote_char);
196 i += 2;
197 } else {
198 i += 1; break;
200 }
201 } else {
202 val.push(chars[i]);
203 i += 1;
204 }
205 }
206 while i < len && chars[i] != ';' {
208 i += 1;
209 }
210 val
211 } else {
212 let val_start = i;
214 while i < len && chars[i] != ';' {
215 i += 1;
216 }
217 chars[val_start..i].iter().collect::<String>()
218 };
219
220 let key_trimmed = key.trim().to_string();
221 if !key_trimmed.is_empty() {
222 pairs.push((key_trimmed, value));
223 }
224 }
225
226 Ok(pairs)
227}
228
229fn non_empty(value: &str) -> Option<String> {
234 if value.is_empty() {
235 None
236 } else {
237 Some(value.to_string())
238 }
239}
240
241#[derive(Debug, Clone)]
247#[non_exhaustive]
248pub struct Config {
249 pub host: String,
251
252 pub port: u16,
254
255 pub database: Option<String>,
257
258 pub credentials: Credentials,
260
261 #[cfg(feature = "tls")]
263 pub tls: TlsConfig,
264
265 pub application_name: String,
267
268 pub connect_timeout: Duration,
270
271 pub command_timeout: Duration,
273
274 pub max_response_size: usize,
284
285 pub packet_size: u16,
287
288 pub strict_mode: bool,
290
291 pub trust_server_certificate: bool,
293
294 pub instance: Option<String>,
296
297 pub mars: bool,
299
300 pub encrypt: bool,
304
305 pub no_tls: bool,
324
325 pub redirect: RedirectConfig,
327
328 pub retry: RetryPolicy,
330
331 pub timeouts: TimeoutConfig,
333
334 pub tds_version: TdsVersion,
347
348 pub application_intent: ApplicationIntent,
354
355 pub workstation_id: Option<String>,
362
363 pub language: Option<String>,
369
370 pub multi_subnet_failover: bool,
381
382 pub send_string_parameters_as_unicode: bool,
397
398 pub statement_cache: bool,
414
415 #[cfg(feature = "always-encrypted")]
427 pub column_encryption: Option<std::sync::Arc<crate::encryption::EncryptionConfig>>,
428}
429
430impl Default for Config {
431 fn default() -> Self {
432 let timeouts = TimeoutConfig::default();
433 Self {
434 host: "localhost".to_string(),
435 port: 1433,
436 database: None,
437 credentials: Credentials::sql_server("", ""),
438 #[cfg(feature = "tls")]
439 tls: TlsConfig::default(),
440 application_name: "mssql-client".to_string(),
441 connect_timeout: timeouts.connect_timeout,
442 command_timeout: timeouts.command_timeout,
443 max_response_size: 0,
444 packet_size: 4096,
445 strict_mode: false,
446 trust_server_certificate: false,
447 instance: None,
448 mars: false,
449 encrypt: true, no_tls: false, redirect: RedirectConfig::default(),
452 retry: RetryPolicy::default(),
453 timeouts,
454 tds_version: TdsVersion::V7_4, application_intent: ApplicationIntent::default(),
456 workstation_id: None,
457 language: None,
458 multi_subnet_failover: false,
459 send_string_parameters_as_unicode: true,
460 statement_cache: false,
461 #[cfg(feature = "always-encrypted")]
462 column_encryption: None,
463 }
464 }
465}
466
467impl Config {
468 #[must_use]
470 pub fn new() -> Self {
471 Self::default()
472 }
473
474 pub fn from_connection_string(conn_str: &str) -> Result<Self, crate::error::Error> {
485 let mut config = Self::default();
486 let pairs = split_connection_string(conn_str)?;
487
488 let mut authentication: Option<String> = None;
492
493 for (key, value) in &pairs {
494 let key = key.trim().to_lowercase();
495 let value = value.trim();
496
497 match key.as_str() {
498 "server" | "data source" | "addr" | "address" | "network address" | "host" => {
500 let lower_value = value.to_lowercase();
504 let server_value = if lower_value.starts_with("tcp:") {
505 &value[4..]
506 } else if lower_value.starts_with("np:") {
507 return Err(crate::error::Error::Config(
508 "Named Pipes connections (np:) are not supported. Use TCP connections instead."
509 .into(),
510 ));
511 } else if lower_value.starts_with("lpc:") {
512 return Err(crate::error::Error::Config(
513 "Shared Memory connections (lpc:) are not supported. Use TCP connections instead."
514 .into(),
515 ));
516 } else {
517 value
518 };
519
520 if let Some((host, port_or_instance)) = server_value.split_once(',') {
522 config.host = host.to_string();
523 config.port = port_or_instance.trim().parse().map_err(|_| {
524 crate::error::Error::Config(format!("invalid port: {port_or_instance}"))
525 })?;
526 } else if let Some((host, instance)) = server_value.split_once('\\') {
527 config.host = host.to_string();
528 config.instance = non_empty(instance);
529 } else {
530 config.host = server_value.to_string();
531 }
532 }
533 "port" => {
534 config.port = value.parse().map_err(|_| {
535 crate::error::Error::Config(format!("invalid port: {value}"))
536 })?;
537 }
538 "database" | "initial catalog" => {
540 config.database = non_empty(value);
541 }
542 "user id" | "uid" | "user" => {
544 if let Credentials::SqlServer { password, .. } = &config.credentials {
545 config.credentials =
546 Credentials::sql_server(value.to_string(), password.clone());
547 }
548 }
549 "password" | "pwd" => {
550 if let Credentials::SqlServer { username, .. } = &config.credentials {
551 config.credentials =
552 Credentials::sql_server(username.clone(), value.to_string());
553 }
554 }
555 "authentication" => {
559 authentication = non_empty(value).map(|v| v.to_lowercase().replace(' ', ""));
560 }
561 "application name" | "app" => {
563 config.application_name = value.to_string();
564 }
565 "applicationintent" | "application intent" => {
566 config.application_intent = match value.to_lowercase().as_str() {
567 "readonly" => ApplicationIntent::ReadOnly,
568 "readwrite" => ApplicationIntent::ReadWrite,
569 _ => {
570 return Err(crate::error::Error::Config(format!(
571 "invalid ApplicationIntent: '{value}' (expected ReadOnly or ReadWrite)"
572 )));
573 }
574 };
575 }
576 "workstation id" | "wsid" => {
577 config.workstation_id = non_empty(value);
578 }
579 "current language" | "language" => {
580 config.language = non_empty(value);
581 }
582 "connect timeout" | "connection timeout" | "timeout" => {
584 let secs: u64 = value.parse().map_err(|_| {
585 crate::error::Error::Config(format!("invalid timeout: {value}"))
586 })?;
587 config.connect_timeout = Duration::from_secs(secs);
588 }
589 "command timeout" => {
590 let secs: u64 = value.parse().map_err(|_| {
591 crate::error::Error::Config(format!("invalid timeout: {value}"))
592 })?;
593 config.command_timeout = Duration::from_secs(secs);
594 }
595 "trustservercertificate" | "trust server certificate" => {
597 config.trust_server_certificate = parse_conn_bool(&key, value)?;
598 }
599 "encrypt" => {
600 if value.eq_ignore_ascii_case("strict") {
608 config.strict_mode = true;
609 config.encrypt = true;
610 config.no_tls = false;
611 } else if value.eq_ignore_ascii_case("mandatory") {
612 config.encrypt = true;
613 config.no_tls = false;
614 } else if value.eq_ignore_ascii_case("optional") {
615 config.encrypt = false;
616 config.no_tls = false;
617 } else if value.eq_ignore_ascii_case("no_tls") {
618 config.no_tls = true;
619 config.encrypt = false;
620 } else {
621 let enabled = parse_conn_bool(&key, value)?;
623 config.encrypt = enabled;
624 config.no_tls = false;
625 }
626 }
627 "integrated security" | "trusted_connection" => {
628 let enabled =
630 value.eq_ignore_ascii_case("sspi") || parse_conn_bool(&key, value)?;
631 if enabled {
632 #[cfg(any(feature = "integrated-auth", feature = "sspi-auth"))]
633 {
634 config.credentials = Credentials::Integrated;
635 }
636 #[cfg(not(any(feature = "integrated-auth", feature = "sspi-auth")))]
637 {
638 return Err(crate::error::Error::Config(
639 "Integrated Security requires the 'integrated-auth' (Linux/macOS) \
640 or 'sspi-auth' (Windows) feature to be enabled"
641 .into(),
642 ));
643 }
644 }
645 }
646 "column encryption setting" | "columnencryptionsetting" => {
648 #[cfg(feature = "always-encrypted")]
649 if value.eq_ignore_ascii_case("enabled") {
650 config.column_encryption = Some(std::sync::Arc::new(
651 crate::encryption::EncryptionConfig::new(),
652 ));
653 }
654 #[cfg(not(feature = "always-encrypted"))]
655 if value.eq_ignore_ascii_case("enabled") {
656 return Err(crate::error::Error::Config(
657 "Column Encryption Setting=Enabled requires the 'always-encrypted' feature. \
658 Enable it in your Cargo.toml: mssql-client = { features = [\"always-encrypted\"] }"
659 .to_string(),
660 ));
661 }
662 }
663 "multipleactiveresultsets" | "mars" => {
665 config.mars = parse_conn_bool(&key, value)?;
666 }
667 "packet size" => {
668 config.packet_size = value.parse().map_err(|_| {
669 crate::error::Error::Config(format!("invalid packet size: {value}"))
670 })?;
671 }
672 "tdsversion" | "tds version" | "protocolversion" | "protocol version" => {
673 config.tds_version = TdsVersion::parse(value).ok_or_else(|| {
674 crate::error::Error::Config(format!(
675 "invalid TDS version: {value}. Supported values: 7.3, 7.3A, 7.3B, 7.4, 8.0"
676 ))
677 })?;
678 if config.tds_version.is_tds_8() {
679 config.strict_mode = true;
680 }
681 }
682 "connectretrycount" | "connect retry count" => {
684 config.retry.max_retries = value.parse().map_err(|_| {
685 crate::error::Error::Config(format!("invalid ConnectRetryCount: '{value}'"))
686 })?;
687 }
688 "connectretryinterval" | "connect retry interval" => {
689 let secs: u64 = value.parse().map_err(|_| {
690 crate::error::Error::Config(format!(
691 "invalid ConnectRetryInterval: '{value}'"
692 ))
693 })?;
694 config.retry.initial_backoff = Duration::from_secs(secs);
695 }
696 "max pool size"
698 | "min pool size"
699 | "pooling"
700 | "connection lifetime"
701 | "load balance timeout" => {
702 tracing::info!(
703 key = key.as_str(),
704 value = value,
705 "connection string keyword '{}' is recognized but pool settings \
706 must be configured via PoolConfig, not the connection string",
707 key,
708 );
709 }
710 "multisubnetfailover" | "multi subnet failover" => {
712 config.multi_subnet_failover = parse_conn_bool(&key, value)?;
713 }
714 "sendstringparametersasunicode" | "send string parameters as unicode" => {
716 config.send_string_parameters_as_unicode = parse_conn_bool(&key, value)?;
717 }
718 "statement cache" | "statementcache" => {
720 config.statement_cache = parse_conn_bool(&key, value)?;
721 }
722 "failover partner"
724 | "persist security info"
725 | "persistsecurityinfo"
726 | "enlist"
727 | "replication"
728 | "transaction binding"
729 | "type system version"
730 | "user instance"
731 | "attachdbfilename"
732 | "extended properties"
733 | "initial file name"
734 | "context connection"
735 | "network library"
736 | "network"
737 | "net"
738 | "asynchronous processing"
739 | "async"
740 | "transparentnetworkipresolution"
741 | "poolblockingperiod"
742 | "hostnameincertificate"
743 | "servercertificate" => {
744 tracing::info!(
745 key = key.as_str(),
746 value = value,
747 "connection string keyword '{}' is recognized but not supported by this driver",
748 key,
749 );
750 }
751 _ => {
752 tracing::debug!(
753 key = key.as_str(),
754 value = value,
755 "ignoring unknown connection string option"
756 );
757 }
758 }
759 }
760
761 if let Some(method) = authentication {
762 config.apply_authentication_keyword(&method)?;
763 }
764
765 Ok(config)
766 }
767
768 fn apply_authentication_keyword(&mut self, method: &str) -> Result<(), crate::error::Error> {
774 #[cfg(any(feature = "integrated-auth", feature = "sspi-auth"))]
777 if matches!(self.credentials, Credentials::Integrated) {
778 return Err(crate::error::Error::Config(
779 "the Authentication keyword cannot be combined with Integrated Security".into(),
780 ));
781 }
782
783 match method {
784 "sqlpassword" => {}
787 "activedirectorymanagedidentity" | "activedirectorymsi" => {
788 #[cfg(not(feature = "azure-identity"))]
789 return Err(crate::error::Error::Config(format!(
790 "Authentication={method} requires the 'azure-identity' feature. \
791 Enable it in your Cargo.toml: \
792 mssql-client = {{ features = [\"azure-identity\"] }}"
793 )));
794 #[cfg(feature = "azure-identity")]
795 {
796 let client_id = match &self.credentials {
798 Credentials::SqlServer { username, .. } if !username.is_empty() => {
799 Some(username.clone())
800 }
801 _ => None,
802 };
803 self.credentials = Credentials::AzureManagedIdentity { client_id };
804 }
805 }
806 "activedirectoryserviceprincipal" => {
807 #[cfg(not(feature = "azure-identity"))]
808 return Err(crate::error::Error::Config(format!(
809 "Authentication={method} requires the 'azure-identity' feature. \
810 Enable it in your Cargo.toml: \
811 mssql-client = {{ features = [\"azure-identity\"] }}"
812 )));
813 #[cfg(feature = "azure-identity")]
814 {
815 let Credentials::SqlServer { username, password } = &self.credentials else {
816 return Err(crate::error::Error::Config(
817 "Authentication=ActiveDirectoryServicePrincipal requires \
818 User Id and Password (client id and secret)"
819 .into(),
820 ));
821 };
822 let Some((client_id, tenant_id)) = username.split_once('@') else {
826 return Err(crate::error::Error::Config(
827 "Authentication=ActiveDirectoryServicePrincipal requires \
828 User Id=<client-id>@<tenant-id> (the tenant id is needed \
829 for client-side token acquisition)"
830 .into(),
831 ));
832 };
833 if client_id.is_empty() || tenant_id.is_empty() {
834 return Err(crate::error::Error::Config(
835 "Authentication=ActiveDirectoryServicePrincipal: client id \
836 and tenant id must both be non-empty in \
837 User Id=<client-id>@<tenant-id>"
838 .into(),
839 ));
840 }
841 if password.is_empty() {
842 return Err(crate::error::Error::Config(
843 "Authentication=ActiveDirectoryServicePrincipal requires \
844 Password=<client secret>"
845 .into(),
846 ));
847 }
848 let (client_id, tenant_id) = (client_id.to_string(), tenant_id.to_string());
849 let client_secret = password.clone();
850 self.credentials = Credentials::AzureServicePrincipal {
851 tenant_id: tenant_id.into(),
852 client_id: client_id.into(),
853 client_secret,
854 };
855 }
856 }
857 "activedirectorypassword"
858 | "activedirectoryintegrated"
859 | "activedirectoryinteractive"
860 | "activedirectorydefault"
861 | "activedirectorydevicecodeflow" => {
862 return Err(crate::error::Error::Config(format!(
863 "Authentication value '{method}' is not supported. Supported values: \
864 SqlPassword, ActiveDirectoryServicePrincipal, \
865 ActiveDirectoryManagedIdentity (alias ActiveDirectoryMSI). The remaining \
866 ADAL/MSAL workflows are tracked in \
867 https://github.com/praxiomlabs/rust-mssql-driver/issues/155"
868 )));
869 }
870 other => {
871 return Err(crate::error::Error::Config(format!(
872 "invalid Authentication value: '{other}'. Supported values: \
873 SqlPassword, ActiveDirectoryServicePrincipal, \
874 ActiveDirectoryManagedIdentity (alias ActiveDirectoryMSI)"
875 )));
876 }
877 }
878
879 Ok(())
880 }
881
882 #[must_use]
884 pub fn host(mut self, host: impl Into<String>) -> Self {
885 self.host = host.into();
886 self
887 }
888
889 #[must_use]
891 pub fn port(mut self, port: u16) -> Self {
892 self.port = port;
893 self
894 }
895
896 #[must_use]
898 pub fn database(mut self, database: impl Into<String>) -> Self {
899 self.database = Some(database.into());
900 self
901 }
902
903 #[must_use]
905 pub fn credentials(mut self, credentials: Credentials) -> Self {
906 self.credentials = credentials;
907 self
908 }
909
910 #[must_use]
912 pub fn application_name(mut self, name: impl Into<String>) -> Self {
913 self.application_name = name.into();
914 self
915 }
916
917 #[must_use]
919 pub fn connect_timeout(mut self, timeout: Duration) -> Self {
920 self.connect_timeout = timeout;
921 self
922 }
923
924 #[must_use]
926 pub fn trust_server_certificate(mut self, trust: bool) -> Self {
927 self.trust_server_certificate = trust;
928 #[cfg(feature = "tls")]
929 {
930 self.tls = self.tls.trust_server_certificate(trust);
931 }
932 self
933 }
934
935 #[must_use]
937 pub fn strict_mode(mut self, enabled: bool) -> Self {
938 self.strict_mode = enabled;
939 #[cfg(feature = "tls")]
940 {
941 self.tls = self.tls.strict_mode(enabled);
942 }
943 if enabled {
944 self.tds_version = TdsVersion::V8_0;
945 }
946 self
947 }
948
949 #[must_use]
973 pub fn tds_version(mut self, version: TdsVersion) -> Self {
974 self.tds_version = version;
975 if version.is_tds_8() {
977 self.strict_mode = true;
978 #[cfg(feature = "tls")]
979 {
980 self.tls = self.tls.strict_mode(true);
981 }
982 }
983 self
984 }
985
986 #[must_use]
994 pub fn encrypt(mut self, enabled: bool) -> Self {
995 self.encrypt = enabled;
996 self
997 }
998
999 #[must_use]
1040 pub fn no_tls(mut self, enabled: bool) -> Self {
1041 self.no_tls = enabled;
1042 if enabled {
1043 self.encrypt = false;
1044 }
1045 self
1046 }
1047
1048 #[cfg(feature = "always-encrypted")]
1070 #[must_use]
1071 pub fn with_column_encryption(mut self, config: crate::encryption::EncryptionConfig) -> Self {
1072 self.column_encryption = Some(std::sync::Arc::new(config));
1073 self
1074 }
1075
1076 #[must_use]
1078 pub fn with_host(mut self, host: &str) -> Self {
1079 self.host = host.to_string();
1080 self
1081 }
1082
1083 #[must_use]
1085 pub fn with_port(mut self, port: u16) -> Self {
1086 self.port = port;
1087 self
1088 }
1089
1090 #[must_use]
1094 pub fn with_statement_cache(mut self, enabled: bool) -> Self {
1095 self.statement_cache = enabled;
1096 self
1097 }
1098
1099 #[must_use]
1101 pub fn redirect(mut self, redirect: RedirectConfig) -> Self {
1102 self.redirect = redirect;
1103 self
1104 }
1105
1106 #[must_use]
1108 pub fn max_redirects(mut self, max: u8) -> Self {
1109 self.redirect.max_redirects = max;
1110 self
1111 }
1112
1113 #[must_use]
1115 pub fn retry(mut self, retry: RetryPolicy) -> Self {
1116 self.retry = retry;
1117 self
1118 }
1119
1120 #[must_use]
1122 pub fn max_retries(mut self, max: u32) -> Self {
1123 self.retry.max_retries = max;
1124 self
1125 }
1126
1127 #[must_use]
1129 pub fn timeouts(mut self, timeouts: TimeoutConfig) -> Self {
1130 self.connect_timeout = timeouts.connect_timeout;
1132 self.command_timeout = timeouts.command_timeout;
1133 self.timeouts = timeouts;
1134 self
1135 }
1136
1137 #[must_use]
1139 pub fn application_intent(mut self, intent: ApplicationIntent) -> Self {
1140 self.application_intent = intent;
1141 self
1142 }
1143
1144 #[must_use]
1149 pub fn workstation_id(mut self, id: impl Into<String>) -> Self {
1150 self.workstation_id = Some(id.into());
1151 self
1152 }
1153
1154 #[must_use]
1158 pub fn language(mut self, lang: impl Into<String>) -> Self {
1159 self.language = Some(lang.into());
1160 self
1161 }
1162
1163 #[must_use]
1168 pub fn multi_subnet_failover(mut self, enabled: bool) -> Self {
1169 self.multi_subnet_failover = enabled;
1170 self
1171 }
1172
1173 #[must_use]
1181 pub fn send_string_parameters_as_unicode(mut self, enabled: bool) -> Self {
1182 self.send_string_parameters_as_unicode = enabled;
1183 self
1184 }
1185}
1186
1187#[cfg(test)]
1188#[allow(clippy::unwrap_used)]
1189mod tests {
1190 use super::*;
1191
1192 #[test]
1193 fn test_connection_string_parsing() {
1194 let config = Config::from_connection_string(
1195 "Server=localhost;Database=test;User Id=sa;Password=secret;",
1196 )
1197 .unwrap();
1198
1199 assert_eq!(config.host, "localhost");
1200 assert_eq!(config.database, Some("test".to_string()));
1201 }
1202
1203 #[test]
1204 fn test_connection_string_with_port() {
1205 let config =
1206 Config::from_connection_string("Server=localhost,1434;Database=test;").unwrap();
1207
1208 assert_eq!(config.host, "localhost");
1209 assert_eq!(config.port, 1434);
1210 }
1211
1212 #[test]
1213 fn test_connection_string_with_instance() {
1214 let config =
1215 Config::from_connection_string("Server=localhost\\SQLEXPRESS;Database=test;").unwrap();
1216
1217 assert_eq!(config.host, "localhost");
1218 assert_eq!(config.instance, Some("SQLEXPRESS".to_string()));
1219 }
1220
1221 #[test]
1222 fn test_connection_string_dot_instance() {
1223 let config = Config::from_connection_string("Server=.\\SQLEXPRESS;Database=test;").unwrap();
1225
1226 assert_eq!(config.host, ".");
1227 assert_eq!(config.instance, Some("SQLEXPRESS".to_string()));
1228 }
1229
1230 #[test]
1231 fn test_connection_string_local_instance() {
1232 let config =
1234 Config::from_connection_string("Server=(local)\\SQLEXPRESS;Database=test;").unwrap();
1235
1236 assert_eq!(config.host, "(local)");
1237 assert_eq!(config.instance, Some("SQLEXPRESS".to_string()));
1238 }
1239
1240 #[test]
1241 fn test_redirect_config_defaults() {
1242 let config = RedirectConfig::default();
1243 assert_eq!(config.max_redirects, 2);
1244 assert!(config.follow_redirects);
1245 }
1246
1247 #[test]
1248 fn test_redirect_config_builder() {
1249 let config = RedirectConfig::new()
1250 .max_redirects(5)
1251 .follow_redirects(false);
1252 assert_eq!(config.max_redirects, 5);
1253 assert!(!config.follow_redirects);
1254 }
1255
1256 #[test]
1257 fn test_redirect_config_no_follow() {
1258 let config = RedirectConfig::no_follow();
1259 assert_eq!(config.max_redirects, 0);
1260 assert!(!config.follow_redirects);
1261 }
1262
1263 #[test]
1264 fn test_config_redirect_builder() {
1265 let config = Config::new().max_redirects(3);
1266 assert_eq!(config.redirect.max_redirects, 3);
1267
1268 let config2 = Config::new().redirect(RedirectConfig::no_follow());
1269 assert!(!config2.redirect.follow_redirects);
1270 }
1271
1272 #[test]
1273 fn test_retry_policy_defaults() {
1274 let policy = RetryPolicy::default();
1275 assert_eq!(policy.max_retries, 3);
1276 assert_eq!(policy.initial_backoff, Duration::from_millis(100));
1277 assert_eq!(policy.max_backoff, Duration::from_secs(30));
1278 assert!((policy.backoff_multiplier - 2.0).abs() < f64::EPSILON);
1279 assert!(policy.jitter);
1280 }
1281
1282 #[test]
1283 fn test_retry_policy_builder() {
1284 let policy = RetryPolicy::new()
1285 .max_retries(5)
1286 .initial_backoff(Duration::from_millis(200))
1287 .max_backoff(Duration::from_secs(60))
1288 .backoff_multiplier(3.0)
1289 .jitter(false);
1290
1291 assert_eq!(policy.max_retries, 5);
1292 assert_eq!(policy.initial_backoff, Duration::from_millis(200));
1293 assert_eq!(policy.max_backoff, Duration::from_secs(60));
1294 assert!((policy.backoff_multiplier - 3.0).abs() < f64::EPSILON);
1295 assert!(!policy.jitter);
1296 }
1297
1298 #[test]
1299 fn test_retry_policy_no_retry() {
1300 let policy = RetryPolicy::no_retry();
1301 assert_eq!(policy.max_retries, 0);
1302 assert!(!policy.should_retry(0));
1303 }
1304
1305 #[test]
1306 fn test_retry_policy_should_retry() {
1307 let policy = RetryPolicy::new().max_retries(3);
1308 assert!(policy.should_retry(0));
1309 assert!(policy.should_retry(1));
1310 assert!(policy.should_retry(2));
1311 assert!(!policy.should_retry(3));
1312 assert!(!policy.should_retry(4));
1313 }
1314
1315 #[test]
1316 fn test_retry_policy_backoff_calculation() {
1317 let policy = RetryPolicy::new()
1318 .initial_backoff(Duration::from_millis(100))
1319 .backoff_multiplier(2.0)
1320 .max_backoff(Duration::from_secs(10))
1321 .jitter(false);
1322
1323 assert_eq!(policy.backoff_for_attempt(0), Duration::ZERO);
1324 assert_eq!(policy.backoff_for_attempt(1), Duration::from_millis(100));
1325 assert_eq!(policy.backoff_for_attempt(2), Duration::from_millis(200));
1326 assert_eq!(policy.backoff_for_attempt(3), Duration::from_millis(400));
1327 }
1328
1329 #[test]
1330 fn test_retry_policy_backoff_capped() {
1331 let policy = RetryPolicy::new()
1332 .initial_backoff(Duration::from_secs(1))
1333 .backoff_multiplier(10.0)
1334 .max_backoff(Duration::from_secs(5))
1335 .jitter(false);
1336
1337 assert_eq!(policy.backoff_for_attempt(3), Duration::from_secs(5));
1339 }
1340
1341 #[test]
1342 fn test_config_retry_builder() {
1343 let config = Config::new().max_retries(5);
1344 assert_eq!(config.retry.max_retries, 5);
1345
1346 let config2 = Config::new().retry(RetryPolicy::no_retry());
1347 assert_eq!(config2.retry.max_retries, 0);
1348 }
1349
1350 #[test]
1351 fn test_timeout_config_defaults() {
1352 let config = TimeoutConfig::default();
1353 assert_eq!(config.connect_timeout, Duration::from_secs(15));
1354 assert_eq!(config.tls_timeout, Duration::from_secs(10));
1355 assert_eq!(config.login_timeout, Duration::from_secs(30));
1356 assert_eq!(config.command_timeout, Duration::from_secs(30));
1357 assert_eq!(config.idle_timeout, Duration::from_secs(300));
1358 assert_eq!(config.keepalive_interval, Some(Duration::from_secs(30)));
1359 }
1360
1361 #[test]
1362 fn test_timeout_config_builder() {
1363 let config = TimeoutConfig::new()
1364 .connect_timeout(Duration::from_secs(5))
1365 .tls_timeout(Duration::from_secs(3))
1366 .login_timeout(Duration::from_secs(10))
1367 .command_timeout(Duration::from_secs(60))
1368 .idle_timeout(Duration::from_secs(600))
1369 .keepalive_interval(Some(Duration::from_secs(60)));
1370
1371 assert_eq!(config.connect_timeout, Duration::from_secs(5));
1372 assert_eq!(config.tls_timeout, Duration::from_secs(3));
1373 assert_eq!(config.login_timeout, Duration::from_secs(10));
1374 assert_eq!(config.command_timeout, Duration::from_secs(60));
1375 assert_eq!(config.idle_timeout, Duration::from_secs(600));
1376 assert_eq!(config.keepalive_interval, Some(Duration::from_secs(60)));
1377 }
1378
1379 #[test]
1380 fn test_timeout_config_no_keepalive() {
1381 let config = TimeoutConfig::new().no_keepalive();
1382 assert_eq!(config.keepalive_interval, None);
1383 }
1384
1385 #[test]
1386 fn test_timeout_config_total_connect() {
1387 let config = TimeoutConfig::new()
1388 .connect_timeout(Duration::from_secs(5))
1389 .tls_timeout(Duration::from_secs(3))
1390 .login_timeout(Duration::from_secs(10));
1391
1392 assert_eq!(config.total_connect_timeout(), Duration::from_secs(18));
1394 }
1395
1396 #[test]
1397 fn test_config_timeouts_builder() {
1398 let timeouts = TimeoutConfig::new()
1399 .connect_timeout(Duration::from_secs(5))
1400 .command_timeout(Duration::from_secs(60));
1401
1402 let config = Config::new().timeouts(timeouts);
1403 assert_eq!(config.timeouts.connect_timeout, Duration::from_secs(5));
1404 assert_eq!(config.timeouts.command_timeout, Duration::from_secs(60));
1405 assert_eq!(config.connect_timeout, Duration::from_secs(5));
1407 assert_eq!(config.command_timeout, Duration::from_secs(60));
1408 }
1409
1410 #[test]
1411 fn test_tds_version_default() {
1412 let config = Config::default();
1413 assert_eq!(config.tds_version, TdsVersion::V7_4);
1414 assert!(!config.strict_mode);
1415 }
1416
1417 #[test]
1418 fn test_tds_version_builder() {
1419 let config = Config::new().tds_version(TdsVersion::V7_3A);
1420 assert_eq!(config.tds_version, TdsVersion::V7_3A);
1421 assert!(!config.strict_mode);
1422
1423 let config = Config::new().tds_version(TdsVersion::V7_3B);
1424 assert_eq!(config.tds_version, TdsVersion::V7_3B);
1425 assert!(!config.strict_mode);
1426
1427 let config = Config::new().tds_version(TdsVersion::V8_0);
1429 assert_eq!(config.tds_version, TdsVersion::V8_0);
1430 assert!(config.strict_mode);
1431 }
1432
1433 #[test]
1434 fn test_strict_mode_sets_tds_8() {
1435 let config = Config::new().strict_mode(true);
1436 assert!(config.strict_mode);
1437 assert_eq!(config.tds_version, TdsVersion::V8_0);
1438 }
1439
1440 #[test]
1441 fn test_connection_string_tds_version() {
1442 let config = Config::from_connection_string("Server=localhost;TDSVersion=7.3;").unwrap();
1444 assert_eq!(config.tds_version, TdsVersion::V7_3A);
1445
1446 let config = Config::from_connection_string("Server=localhost;TDSVersion=7.3A;").unwrap();
1448 assert_eq!(config.tds_version, TdsVersion::V7_3A);
1449
1450 let config = Config::from_connection_string("Server=localhost;TDSVersion=7.3B;").unwrap();
1452 assert_eq!(config.tds_version, TdsVersion::V7_3B);
1453
1454 let config = Config::from_connection_string("Server=localhost;TDSVersion=7.4;").unwrap();
1456 assert_eq!(config.tds_version, TdsVersion::V7_4);
1457
1458 let config = Config::from_connection_string("Server=localhost;TDSVersion=8.0;").unwrap();
1460 assert_eq!(config.tds_version, TdsVersion::V8_0);
1461 assert!(config.strict_mode);
1462
1463 let config =
1465 Config::from_connection_string("Server=localhost;ProtocolVersion=7.3;").unwrap();
1466 assert_eq!(config.tds_version, TdsVersion::V7_3A);
1467 }
1468
1469 #[test]
1470 fn test_connection_string_invalid_tds_version() {
1471 let result = Config::from_connection_string("Server=localhost;TDSVersion=invalid;");
1472 assert!(result.is_err());
1473
1474 let result = Config::from_connection_string("Server=localhost;TDSVersion=9.0;");
1475 assert!(result.is_err());
1476 }
1477
1478 #[test]
1479 fn test_connection_string_no_tls() {
1480 let config = Config::from_connection_string("Server=legacy;Encrypt=no_tls;").unwrap();
1482 assert!(config.no_tls);
1483 assert!(!config.encrypt);
1484 assert!(!config.strict_mode);
1485
1486 let config = Config::from_connection_string("Server=legacy;Encrypt=no_tls;").unwrap();
1488 assert!(config.no_tls);
1489
1490 let config = Config::from_connection_string("Server=localhost;Encrypt=true;").unwrap();
1492 assert!(!config.no_tls);
1493 assert!(config.encrypt);
1494
1495 let config = Config::from_connection_string("Server=localhost;Encrypt=strict;").unwrap();
1497 assert!(!config.no_tls);
1498 assert!(config.encrypt);
1499 assert!(config.strict_mode);
1500
1501 let config = Config::from_connection_string("Server=localhost;Encrypt=mandatory;").unwrap();
1503 assert!(config.encrypt);
1504 assert!(!config.no_tls);
1505
1506 let config = Config::from_connection_string("Server=localhost;Encrypt=optional;").unwrap();
1508 assert!(!config.encrypt);
1509 assert!(!config.no_tls);
1510 }
1511
1512 #[test]
1513 fn test_no_tls_builder() {
1514 let config = Config::new().no_tls(true);
1516 assert!(config.no_tls);
1517 assert!(!config.encrypt);
1518
1519 let config = Config::new().no_tls(true).no_tls(false);
1521 assert!(!config.no_tls);
1522 }
1523
1524 #[test]
1525 #[cfg(any(feature = "integrated-auth", feature = "sspi-auth"))]
1526 fn test_connection_string_integrated_security() {
1527 let config =
1529 Config::from_connection_string("Server=localhost;Integrated Security=true;").unwrap();
1530 assert_eq!(
1531 config.credentials.method_name(),
1532 "Integrated Authentication"
1533 );
1534
1535 let config =
1537 Config::from_connection_string("Server=localhost;Integrated Security=yes;").unwrap();
1538 assert_eq!(
1539 config.credentials.method_name(),
1540 "Integrated Authentication"
1541 );
1542
1543 let config =
1545 Config::from_connection_string("Server=localhost;Integrated Security=sspi;").unwrap();
1546 assert_eq!(
1547 config.credentials.method_name(),
1548 "Integrated Authentication"
1549 );
1550
1551 let config =
1553 Config::from_connection_string("Server=localhost;Integrated Security=1;").unwrap();
1554 assert_eq!(
1555 config.credentials.method_name(),
1556 "Integrated Authentication"
1557 );
1558
1559 let config =
1561 Config::from_connection_string("Server=localhost;Trusted_Connection=true;").unwrap();
1562 assert_eq!(
1563 config.credentials.method_name(),
1564 "Integrated Authentication"
1565 );
1566 }
1567
1568 #[test]
1569 #[cfg(not(any(feature = "integrated-auth", feature = "sspi-auth")))]
1570 fn test_connection_string_integrated_security_without_feature() {
1571 let result = Config::from_connection_string("Server=localhost;Integrated Security=true;");
1573 assert!(result.is_err());
1574 let err = result.unwrap_err().to_string();
1575 assert!(err.contains("integrated-auth"));
1576 }
1577
1578 #[test]
1583 fn test_parse_conn_bool_all_values() {
1584 assert!(parse_conn_bool("test", "true").unwrap());
1585 assert!(parse_conn_bool("test", "True").unwrap());
1586 assert!(parse_conn_bool("test", "TRUE").unwrap());
1587 assert!(parse_conn_bool("test", "yes").unwrap());
1588 assert!(parse_conn_bool("test", "Yes").unwrap());
1589 assert!(parse_conn_bool("test", "1").unwrap());
1590
1591 assert!(!parse_conn_bool("test", "false").unwrap());
1592 assert!(!parse_conn_bool("test", "False").unwrap());
1593 assert!(!parse_conn_bool("test", "FALSE").unwrap());
1594 assert!(!parse_conn_bool("test", "no").unwrap());
1595 assert!(!parse_conn_bool("test", "No").unwrap());
1596 assert!(!parse_conn_bool("test", "0").unwrap());
1597
1598 assert!(parse_conn_bool("test", "banana").is_err());
1600 assert!(parse_conn_bool("test", "tru").is_err());
1601 assert!(parse_conn_bool("test", "").is_err());
1602 }
1603
1604 #[test]
1605 fn test_boolean_validation_trust_server_certificate() {
1606 let config =
1608 Config::from_connection_string("Server=localhost;TrustServerCertificate=true;")
1609 .unwrap();
1610 assert!(config.trust_server_certificate);
1611
1612 let config =
1613 Config::from_connection_string("Server=localhost;TrustServerCertificate=no;").unwrap();
1614 assert!(!config.trust_server_certificate);
1615
1616 let result =
1618 Config::from_connection_string("Server=localhost;TrustServerCertificate=banana;");
1619 assert!(result.is_err());
1620 assert!(result.unwrap_err().to_string().contains("invalid boolean"));
1621 }
1622
1623 #[test]
1624 fn test_boolean_validation_mars() {
1625 let config = Config::from_connection_string("Server=localhost;MARS=true;").unwrap();
1626 assert!(config.mars);
1627
1628 let result = Config::from_connection_string("Server=localhost;MARS=tru;");
1630 assert!(result.is_err());
1631 }
1632
1633 #[test]
1634 fn test_quoted_value_semicolon() {
1635 let config = Config::from_connection_string(
1637 r#"Server=localhost;User Id=sa;Password="my;complex;pass";"#,
1638 )
1639 .unwrap();
1640 if let mssql_auth::Credentials::SqlServer { password, .. } = &config.credentials {
1641 assert_eq!(password.as_ref(), "my;complex;pass");
1642 } else {
1643 unreachable!("expected SqlServer credentials");
1644 }
1645 }
1646
1647 #[test]
1648 fn test_quoted_value_single_quotes() {
1649 let config =
1650 Config::from_connection_string("Server=localhost;User Id=sa;Password='my;pass';")
1651 .unwrap();
1652 if let mssql_auth::Credentials::SqlServer { password, .. } = &config.credentials {
1653 assert_eq!(password.as_ref(), "my;pass");
1654 } else {
1655 unreachable!("expected SqlServer credentials");
1656 }
1657 }
1658
1659 #[test]
1660 fn test_quoted_value_escaped_double_quotes() {
1661 let config = Config::from_connection_string(
1663 r#"Server=localhost;User Id=sa;Password="has ""quotes""";"#,
1664 )
1665 .unwrap();
1666 if let mssql_auth::Credentials::SqlServer { password, .. } = &config.credentials {
1667 assert_eq!(password.as_ref(), r#"has "quotes""#);
1668 } else {
1669 unreachable!("expected SqlServer credentials");
1670 }
1671 }
1672
1673 #[test]
1674 fn test_quoted_value_escaped_single_quotes() {
1675 let config =
1676 Config::from_connection_string("Server=localhost;User Id=sa;Password='it''s complex';")
1677 .unwrap();
1678 if let mssql_auth::Credentials::SqlServer { password, .. } = &config.credentials {
1679 assert_eq!(password.as_ref(), "it's complex");
1680 } else {
1681 unreachable!("expected SqlServer credentials");
1682 }
1683 }
1684
1685 #[test]
1686 fn test_quoted_value_unterminated() {
1687 let result = Config::from_connection_string(r#"Server=localhost;Password="unterminated;"#);
1688 assert!(result.is_err());
1689 assert!(result.unwrap_err().to_string().contains("unterminated"));
1690 }
1691
1692 #[test]
1693 fn test_tcp_prefix_stripped() {
1694 let config = Config::from_connection_string(
1696 "Server=tcp:myserver.database.windows.net,1433;Database=mydb;",
1697 )
1698 .unwrap();
1699 assert_eq!(config.host, "myserver.database.windows.net");
1700 assert_eq!(config.port, 1433);
1701 }
1702
1703 #[test]
1704 fn test_tcp_prefix_mixed_case() {
1705 let config = Config::from_connection_string("Server=Tcp:myhost,1433;").unwrap();
1707 assert_eq!(config.host, "myhost");
1708
1709 let config = Config::from_connection_string("Server=TCP:myhost,1433;").unwrap();
1710 assert_eq!(config.host, "myhost");
1711 }
1712
1713 #[test]
1714 fn test_tcp_prefix_with_instance() {
1715 let config =
1716 Config::from_connection_string("Server=tcp:myhost\\INST;Database=test;").unwrap();
1717 assert_eq!(config.host, "myhost");
1718 assert_eq!(config.instance, Some("INST".to_string()));
1719 }
1720
1721 #[test]
1722 fn test_np_prefix_rejected() {
1723 let result =
1724 Config::from_connection_string(r"Server=np:\\myhost\pipe\sql\query;Database=test;");
1725 assert!(result.is_err());
1726 assert!(result.unwrap_err().to_string().contains("Named Pipes"));
1727
1728 let result =
1730 Config::from_connection_string(r"Server=NP:\\myhost\pipe\sql\query;Database=test;");
1731 assert!(result.is_err());
1732 }
1733
1734 #[test]
1735 fn test_lpc_prefix_rejected() {
1736 let result = Config::from_connection_string("Server=lpc:myhost;Database=test;");
1737 assert!(result.is_err());
1738 assert!(result.unwrap_err().to_string().contains("Shared Memory"));
1739 }
1740
1741 #[test]
1742 fn test_server_alias_addr() {
1743 let config = Config::from_connection_string("Addr=myhost;").unwrap();
1744 assert_eq!(config.host, "myhost");
1745 }
1746
1747 #[test]
1748 fn test_server_alias_address() {
1749 let config = Config::from_connection_string("Address=myhost,1434;").unwrap();
1750 assert_eq!(config.host, "myhost");
1751 assert_eq!(config.port, 1434);
1752 }
1753
1754 #[test]
1755 fn test_server_alias_network_address() {
1756 let config = Config::from_connection_string("Network Address=myhost;").unwrap();
1757 assert_eq!(config.host, "myhost");
1758 }
1759
1760 #[test]
1761 fn test_timeout_alias() {
1762 let config = Config::from_connection_string("Server=localhost;Timeout=30;").unwrap();
1763 assert_eq!(config.connect_timeout, Duration::from_secs(30));
1764 }
1765
1766 #[test]
1767 fn test_application_intent_readonly() {
1768 let config =
1769 Config::from_connection_string("Server=localhost;ApplicationIntent=ReadOnly;").unwrap();
1770 assert_eq!(config.application_intent, ApplicationIntent::ReadOnly);
1771 }
1772
1773 #[test]
1774 fn test_application_intent_readwrite() {
1775 let config =
1776 Config::from_connection_string("Server=localhost;Application Intent=ReadWrite;")
1777 .unwrap();
1778 assert_eq!(config.application_intent, ApplicationIntent::ReadWrite);
1779 }
1780
1781 #[test]
1782 fn test_application_intent_invalid() {
1783 let result = Config::from_connection_string("Server=localhost;ApplicationIntent=banana;");
1784 assert!(result.is_err());
1785 assert!(
1786 result
1787 .unwrap_err()
1788 .to_string()
1789 .contains("ApplicationIntent")
1790 );
1791 }
1792
1793 #[test]
1794 fn test_workstation_id() {
1795 let config =
1796 Config::from_connection_string("Server=localhost;Workstation ID=MYPC;").unwrap();
1797 assert_eq!(config.workstation_id, Some("MYPC".to_string()));
1798 }
1799
1800 #[test]
1801 fn test_wsid_alias() {
1802 let config =
1803 Config::from_connection_string("Server=localhost;WSID=MYWORKSTATION;").unwrap();
1804 assert_eq!(config.workstation_id, Some("MYWORKSTATION".to_string()));
1805 }
1806
1807 #[test]
1808 fn test_language() {
1809 let config =
1810 Config::from_connection_string("Server=localhost;Language=us_english;").unwrap();
1811 assert_eq!(config.language, Some("us_english".to_string()));
1812 }
1813
1814 #[test]
1815 fn test_current_language_alias() {
1816 let config =
1817 Config::from_connection_string("Server=localhost;Current Language=Deutsch;").unwrap();
1818 assert_eq!(config.language, Some("Deutsch".to_string()));
1819 }
1820
1821 #[test]
1822 fn test_connect_retry_count() {
1823 let config =
1824 Config::from_connection_string("Server=localhost;ConnectRetryCount=5;").unwrap();
1825 assert_eq!(config.retry.max_retries, 5);
1826 }
1827
1828 #[test]
1829 fn test_connect_retry_interval() {
1830 let config =
1831 Config::from_connection_string("Server=localhost;ConnectRetryInterval=15;").unwrap();
1832 assert_eq!(config.retry.initial_backoff, Duration::from_secs(15));
1833 }
1834
1835 #[test]
1836 fn test_pool_keywords_accepted_without_error() {
1837 let result = Config::from_connection_string(
1839 "Server=localhost;Max Pool Size=10;Min Pool Size=2;Pooling=true;",
1840 );
1841 assert!(result.is_ok());
1842 }
1843
1844 #[test]
1845 fn test_known_unsupported_keywords_accepted() {
1846 let result = Config::from_connection_string(
1848 "Server=localhost;Failover Partner=backup;Persist Security Info=false;",
1849 );
1850 assert!(result.is_ok());
1851 }
1852
1853 #[test]
1854 fn test_multi_subnet_failover_connection_string() {
1855 let config =
1856 Config::from_connection_string("Server=ag-listener;MultiSubnetFailover=true;").unwrap();
1857 assert!(config.multi_subnet_failover);
1858
1859 let config =
1861 Config::from_connection_string("Server=ag-listener;Multi Subnet Failover=true;")
1862 .unwrap();
1863 assert!(config.multi_subnet_failover);
1864
1865 let config =
1867 Config::from_connection_string("Server=ag-listener;MultiSubnetFailover=false;")
1868 .unwrap();
1869 assert!(!config.multi_subnet_failover);
1870
1871 let config = Config::from_connection_string("Server=localhost;").unwrap();
1873 assert!(!config.multi_subnet_failover);
1874 }
1875
1876 #[test]
1877 fn test_multi_subnet_failover_builder() {
1878 let config = Config::new().multi_subnet_failover(true);
1879 assert!(config.multi_subnet_failover);
1880
1881 let config = Config::new().multi_subnet_failover(false);
1882 assert!(!config.multi_subnet_failover);
1883 }
1884
1885 #[test]
1886 fn test_multi_subnet_failover_invalid_value() {
1887 let result = Config::from_connection_string("Server=localhost;MultiSubnetFailover=banana;");
1888 assert!(result.is_err());
1889 }
1890
1891 #[test]
1892 fn test_application_intent_builder() {
1893 let config = Config::new().application_intent(ApplicationIntent::ReadOnly);
1894 assert_eq!(config.application_intent, ApplicationIntent::ReadOnly);
1895 }
1896
1897 #[test]
1898 fn test_workstation_id_builder() {
1899 let config = Config::new().workstation_id("MY-PC");
1900 assert_eq!(config.workstation_id, Some("MY-PC".to_string()));
1901 }
1902
1903 #[test]
1904 fn test_language_builder() {
1905 let config = Config::new().language("us_english");
1906 assert_eq!(config.language, Some("us_english".to_string()));
1907 }
1908
1909 #[test]
1910 fn test_send_string_parameters_as_unicode_connection_string() {
1911 let config =
1912 Config::from_connection_string("Server=localhost;SendStringParametersAsUnicode=false;")
1913 .unwrap();
1914 assert!(!config.send_string_parameters_as_unicode);
1915
1916 let config = Config::from_connection_string(
1918 "Server=localhost;Send String Parameters As Unicode=false;",
1919 )
1920 .unwrap();
1921 assert!(!config.send_string_parameters_as_unicode);
1922
1923 let config =
1925 Config::from_connection_string("Server=localhost;SendStringParametersAsUnicode=true;")
1926 .unwrap();
1927 assert!(config.send_string_parameters_as_unicode);
1928
1929 let config = Config::from_connection_string("Server=localhost;").unwrap();
1931 assert!(config.send_string_parameters_as_unicode);
1932 }
1933
1934 #[test]
1935 fn test_send_string_parameters_as_unicode_builder() {
1936 let config = Config::new().send_string_parameters_as_unicode(false);
1937 assert!(!config.send_string_parameters_as_unicode);
1938
1939 let config = Config::new().send_string_parameters_as_unicode(true);
1940 assert!(config.send_string_parameters_as_unicode);
1941 }
1942
1943 #[test]
1944 fn test_send_string_parameters_as_unicode_invalid_value() {
1945 let result = Config::from_connection_string(
1946 "Server=localhost;SendStringParametersAsUnicode=banana;",
1947 );
1948 assert!(result.is_err());
1949 }
1950
1951 #[test]
1952 fn test_statement_cache_default_off() {
1953 assert!(!Config::new().statement_cache);
1954 }
1955
1956 #[test]
1957 fn test_statement_cache_connection_string() {
1958 let config =
1959 Config::from_connection_string("Server=localhost;Statement Cache=true;").unwrap();
1960 assert!(config.statement_cache);
1961
1962 let config =
1963 Config::from_connection_string("Server=localhost;StatementCache=false;").unwrap();
1964 assert!(!config.statement_cache);
1965 }
1966
1967 #[test]
1968 fn test_statement_cache_builder() {
1969 assert!(Config::new().with_statement_cache(true).statement_cache);
1970 assert!(!Config::new().with_statement_cache(false).statement_cache);
1971 }
1972
1973 #[test]
1974 fn test_statement_cache_invalid_value() {
1975 let result = Config::from_connection_string("Server=localhost;Statement Cache=banana;");
1976 assert!(result.is_err());
1977 }
1978
1979 #[test]
1980 fn test_empty_values_become_none() {
1981 let config =
1983 Config::from_connection_string("Server=localhost;Database=;Language=;").unwrap();
1984 assert_eq!(config.database, None);
1985 assert_eq!(config.language, None);
1986 }
1987}