1use super::driver;
34use super::errors::MsSQLSourceError;
35use super::typesystem::{FloatN, IntN, MsSQLTypeSystem};
36use crate::constants::DB_BUFFER_SIZE;
37use crate::{
38 data_order::DataOrder,
39 errors::ConnectorXError,
40 sources::{PartitionParser, Produce, Source, SourcePartition},
41 sql::{count_query, get_partition_range_query, CXQuery},
42};
43use anyhow::anyhow;
44use bb8::{ManageConnection, Pool, PooledConnection};
45use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Utc};
46use fehler::{throw, throws};
47use log::{debug, warn};
48use mssql_tds::connection::client_context::{ClientContext, TdsAuthenticationMethod};
49use mssql_tds::connection::tds_client::{ResultSet, TdsClient};
50use mssql_tds::connection_provider::tds_connection_provider::TdsConnectionProvider;
51use mssql_tds::core::{EncryptionOptions, EncryptionSetting};
52use mssql_tds::datatypes::column_values::{
53 ColumnValues, SqlDate, SqlDateTime, SqlDateTime2, SqlDateTimeOffset, SqlSmallDateTime, SqlTime,
54};
55use mssql_tds::datatypes::decoder::DecimalParts;
56use mssql_tds::datatypes::sqldatatypes::TdsDataType;
57use rust_decimal::Decimal;
58use sqlparser::dialect::MsSqlDialect;
59use std::collections::HashMap;
60use std::convert::TryFrom;
61use std::ops::{Deref, DerefMut};
62use std::sync::{Arc, OnceLock};
63use std::time::Duration;
64use tokio::runtime::Runtime;
65use url::Url;
66use urlencoding::decode;
67use uuid_old::Uuid;
68
69#[derive(Clone, PartialEq)]
70struct UserAgentInfo {
71 version: String,
72 runtime: String,
73}
74
75static USER_AGENT_INFO: OnceLock<UserAgentInfo> = OnceLock::new();
76
77pub fn set_user_agent_info(version: String, runtime: String) -> Result<(), &'static str> {
81 if version.is_empty() {
82 return Err("MSSQL User-Agent version must not be empty");
83 }
84 if runtime.is_empty() {
85 return Err("MSSQL User-Agent runtime must not be empty");
86 }
87 let info = UserAgentInfo { version, runtime };
88 if USER_AGENT_INFO.get_or_init(|| info.clone()) != &info {
89 return Err("MSSQL User-Agent is already initialized with different values");
90 }
91 Ok(())
92}
93
94#[throws(MsSQLSourceError)]
99fn build_client_context(url: &Url) -> (String, ClientContext) {
100 let host = decode(url.host_str().unwrap_or("localhost"))?.into_owned();
101 let port = url.port().unwrap_or(1433);
102 let hosts: Vec<&str> = host.split('\\').collect();
103 let datasource = match hosts.len() {
104 1 => format!("tcp:{},{}", hosts[0], port),
105 2 => format!("{}\\{}", hosts[0], hosts[1]),
108 _ => throw!(anyhow!("MsSQL hostname parse error: {}", host)),
109 };
110
111 let mut context = ClientContext::with_data_source(&datasource);
112 context
114 .user_agent
115 .set_library_name("connectorx".to_string());
116 if let Some(info) = USER_AGENT_INFO.get() {
117 context.user_agent.set_driver_version(info.version.clone());
118 context.user_agent.set_runtime(info.runtime.clone());
119 }
120 context.application_name = "ConnectorX".to_string();
121 context.database = decode(&url.path()[1..])?.into_owned();
122
123 let params: HashMap<String, String> = url.query_pairs().into_owned().collect();
124
125 match params.get("trusted_connection") {
126 Some(v) if v == "true" && cfg!(any(windows, feature = "integrated-auth-gssapi")) => {
127 debug!("mssql-tds auth through integrated (SSPI) authentication");
128 context.tds_authentication_method = TdsAuthenticationMethod::SSPI;
129 }
130 _ => {
131 debug!("mssql-tds auth through sqlserver authentication");
132 context.tds_authentication_method = TdsAuthenticationMethod::Password;
133 context.user_name = decode(url.username())?.into_owned();
134 context.password = decode(url.password().unwrap_or(""))?.into_owned();
135 }
136 }
137
138 if let Some(v) = params.get("trust_server_certificate_ca") {
139 throw!(anyhow!(
143 "trust_server_certificate_ca={} is not supported with the src_mssql_tds backend: \
144 mssql-tds only supports exact-match certificate pinning, not CA validation",
145 v
146 ));
147 }
148
149 let mut encryption = EncryptionOptions::new();
150 encryption.mode = EncryptionSetting::PreferOff;
151 match params.get("encrypt") {
152 Some(v) if v.to_lowercase() == "true" => encryption.mode = EncryptionSetting::Required,
153 Some(v) if v.to_lowercase() == "false" => encryption.mode = EncryptionSetting::PreferOff,
154 _ => {}
155 }
156 if let Some(v) = params.get("trust_server_certificate") {
157 if v.to_lowercase() == "true" {
158 encryption.trust_server_certificate = true;
159 }
160 }
161 context.encryption_options = encryption;
162
163 if let Some(appname) = params.get("appname") {
164 context.application_name = decode(appname)?.into_owned();
165 }
166
167 (datasource, context)
168}
169
170#[cfg(test)]
171mod configuration_tests {
172 use super::*;
173
174 #[test]
175 fn python_user_agent_info_preserves_login_version() {
176 let version = "0.4.7a1";
177 let runtime = "Python 3.12.3";
178 assert!(set_user_agent_info(String::new(), runtime.to_string()).is_err());
179 assert!(set_user_agent_info(version.to_string(), String::new()).is_err());
180 set_user_agent_info(version.to_string(), runtime.to_string()).unwrap();
181 set_user_agent_info(version.to_string(), runtime.to_string()).unwrap();
182 assert!(set_user_agent_info("0.4.8".to_string(), runtime.to_string()).is_err());
183 assert!(set_user_agent_info(version.to_string(), "Python 3.13.0".to_string()).is_err());
184
185 let url = Url::parse("mssql://localhost/db").unwrap();
186 let (_, context) = build_client_context(&url).unwrap();
187 assert_eq!(context.user_agent.driver_version, version);
188 assert_eq!(context.user_agent.runtime, runtime);
189 assert_eq!(
190 context.driver_version,
191 ClientContext::with_data_source("tcp:localhost,1433").driver_version
192 );
193 }
194
195 #[test]
196 fn driver_identity_and_application_name() {
197 for (query, expected_appname) in [
198 ("", "ConnectorX"),
199 ("?appname=Custom%20Application", "Custom Application"),
200 ("?appname=", ""),
201 ] {
202 let url = Url::parse(&format!("mssql://localhost/db{query}")).unwrap();
203 let (_, context) = build_client_context(&url).unwrap();
204 assert_eq!(
205 context.library_name,
206 ClientContext::with_data_source("tcp:localhost,1433").library_name
207 );
208 assert_eq!(context.user_agent.library_name, "connectorx");
209 assert_eq!(context.application_name, expected_appname);
210 }
211 }
212
213 #[test]
214 fn integrated_auth_respects_tiberius_platform_and_feature_gates() {
215 for trusted in ["true", "false", "TrUe"] {
216 let url = Url::parse(&format!(
217 "mssql://test_user:test_password@localhost/db?trusted_connection={trusted}"
218 ))
219 .unwrap();
220 let (_, context) = build_client_context(&url).unwrap();
221 if trusted == "true" && cfg!(any(windows, feature = "integrated-auth-gssapi")) {
222 assert!(matches!(
223 context.tds_authentication_method,
224 TdsAuthenticationMethod::SSPI
225 ));
226 } else {
227 assert!(matches!(
228 context.tds_authentication_method,
229 TdsAuthenticationMethod::Password
230 ));
231 assert_eq!(context.user_name, "test_user");
232 assert_eq!(context.password, "test_password");
233 }
234 }
235 }
236
237 #[test]
238 fn tls_mapping_preserves_explicit_encryption_and_trust() {
239 for (encrypt, mode) in [
240 ("", EncryptionSetting::PreferOff),
241 ("true", EncryptionSetting::Required),
242 ("TrUe", EncryptionSetting::Required),
243 ("false", EncryptionSetting::PreferOff),
244 ("FaLsE", EncryptionSetting::PreferOff),
245 ("invalid", EncryptionSetting::PreferOff),
246 ] {
247 for (trust, expected_trust) in [
248 ("", false),
249 ("true", true),
250 ("TrUe", true),
251 ("false", false),
252 ("FaLsE", false),
253 ("invalid", false),
254 ] {
255 let mut url = Url::parse("mssql://user:password@localhost/db").unwrap();
256 if !encrypt.is_empty() {
257 url.query_pairs_mut().append_pair("encrypt", encrypt);
258 }
259 if !trust.is_empty() {
260 url.query_pairs_mut()
261 .append_pair("trust_server_certificate", trust);
262 }
263 let (_, context) = build_client_context(&url).unwrap();
264 assert_eq!(
265 context.encryption_options,
266 EncryptionOptions {
267 mode,
268 trust_server_certificate: expected_trust,
269 host_name_in_cert: None,
270 server_certificate: None,
271 },
272 "encrypt={encrypt:?}, trust_server_certificate={trust:?}"
273 );
274 }
275 }
276 }
277
278 #[test]
279 fn custom_ca_is_rejected_even_when_trust_is_enabled() {
280 for trust in ["true", "false"] {
281 let url = Url::parse(&format!(
282 "mssql://localhost/db?encrypt=true&trust_server_certificate={trust}\
283 &trust_server_certificate_ca=ca.pem"
284 ))
285 .unwrap();
286 let err = build_client_context(&url).err().unwrap();
287 assert!(err.to_string().contains("not CA validation"));
288 }
289 }
290
291 #[test]
292 fn pool_size_must_be_positive_and_fit_u32() {
293 let rt = Arc::new(Runtime::new().unwrap());
294 assert!(MsSQLSource::new(rt.clone(), "mssql://localhost/db", 0).is_err());
295 if usize::BITS > 32 {
296 assert!(MsSQLSource::new(rt, "mssql://localhost/db", usize::MAX).is_err());
297 }
298 }
299}
300
301#[throws(MsSQLSourceError)]
302fn open_connection(rt: &Runtime, url: &Url) -> TdsClient {
303 let (datasource, context) = build_client_context(url)?;
304 let provider = TdsConnectionProvider::new();
305 rt.block_on(provider.create_client(context, &datasource, None))?
306}
307
308struct ConnectionManager {
309 url: Url,
310}
311
312impl ManageConnection for ConnectionManager {
313 type Connection = TdsClient;
314 type Error = MsSQLSourceError;
315
316 async fn connect(&self) -> Result<TdsClient, Self::Error> {
317 let (datasource, context) = build_client_context(&self.url)?;
318 Ok(TdsConnectionProvider::new()
319 .create_client(context, &datasource, None)
320 .await?)
321 }
322
323 async fn is_valid(&self, client: &mut TdsClient) -> Result<(), Self::Error> {
324 if client.is_connection_dead() {
325 return Err(anyhow!("MsSQL pooled connection is closed").into());
326 }
327 client.prepare_reset_connection(false);
329 Ok(())
330 }
331
332 fn has_broken(&self, client: &mut TdsClient) -> bool {
333 client.is_connection_dead() || client.has_open_batch()
334 }
335}
336
337#[throws(MsSQLSourceError)]
338fn connection_pool(rt: &Runtime, url: &Url, nconn: usize) -> Pool<ConnectionManager> {
339 let max_size = u32::try_from(nconn)
340 .ok()
341 .filter(|&size| size > 0)
342 .ok_or_else(|| anyhow!("MsSQL pool size must be between 1 and {}", u32::MAX))?;
343 build_client_context(url)?;
345 rt.block_on(
346 Pool::builder()
347 .max_size(max_size)
348 .test_on_check_out(true)
349 .build(ConnectionManager { url: url.clone() }),
350 )?
351}
352
353struct Connection {
355 rt: Arc<Runtime>,
356 lease: Option<PooledConnection<'static, ConnectionManager>>,
357}
358
359impl Connection {
360 #[throws(MsSQLSourceError)]
361 fn get(rt: Arc<Runtime>, pool: &Pool<ConnectionManager>) -> Self {
362 let lease = rt.block_on(pool.get_owned()).map_err(|err| match err {
363 bb8::RunError::User(err) => err,
364 bb8::RunError::TimedOut => anyhow!("Timed out waiting for an MsSQL connection").into(),
365 })?;
366 Self {
367 rt,
368 lease: Some(lease),
369 }
370 }
371
372 #[throws(MsSQLSourceError)]
373 fn finish(&mut self) {
374 let client = self.lease.as_mut().unwrap();
375 if let Err(err) = self.rt.block_on(client.close_query()) {
376 client.mark_connection_dead();
377 throw!(err);
378 }
379 }
380}
381
382impl Deref for Connection {
383 type Target = TdsClient;
384
385 fn deref(&self) -> &TdsClient {
386 self.lease.as_ref().unwrap()
387 }
388}
389
390impl DerefMut for Connection {
391 fn deref_mut(&mut self) -> &mut TdsClient {
392 self.lease.as_mut().unwrap()
393 }
394}
395
396impl Drop for Connection {
397 fn drop(&mut self) {
398 let mut client = self.lease.take().unwrap();
399 if !client.is_connection_dead() && client.has_open_batch() {
400 match self.rt.block_on(async {
403 tokio::time::timeout(Duration::from_secs(5), client.close_query()).await
404 }) {
405 Ok(Ok(())) => {}
406 Ok(Err(err)) => {
407 warn!("Discarding MsSQL connection after cleanup failure: {}", err);
408 client.mark_connection_dead();
409 }
410 Err(_) => {
411 warn!("Discarding MsSQL connection after cleanup timeout");
412 client.mark_connection_dead();
413 }
414 }
415 }
416 let _guard = self.rt.enter();
418 drop(client);
419 }
420}
421
422#[throws(MsSQLSourceError)]
424fn first_row(rt: &Runtime, client: &mut TdsClient, sql: String) -> Option<Vec<ColumnValues>> {
425 rt.block_on(client.execute(sql, ()))?;
426 rt.block_on(client.next_row())?
427}
428
429#[throws(MsSQLSourceError)]
430pub(crate) fn tds_get_partition_range(url: &Url, query: &str, col: &str) -> (i64, i64) {
431 let range_query = get_partition_range_query(query, col, &MsSqlDialect {})?;
432 let rt = Runtime::new().map_err(anyhow::Error::from)?;
433 let mut client = open_connection(&rt, url)?;
434 rt.block_on(client.execute(range_query, ()))?;
435 let types: Vec<_> = client
436 .get_metadata()
437 .iter()
438 .map(|column| column.data_type)
439 .collect();
440 let row = rt
441 .block_on(client.next_row())?
442 .ok_or_else(|| anyhow!("MsSQL partition range query returned no row"))?;
443 if row.len() != 2 || types.len() != 2 {
444 throw!(anyhow!(
445 "MsSQL partition range query must return two columns"
446 ));
447 }
448 (
449 partition_range_value(&row[0], types[0])?,
450 partition_range_value(&row[1], types[1])?,
451 )
452}
453
454#[throws(MsSQLSourceError)]
455fn partition_range_value(value: &ColumnValues, ty: TdsDataType) -> i64 {
456 if !matches!(
457 ty,
458 TdsDataType::Int1
459 | TdsDataType::Int2
460 | TdsDataType::Int4
461 | TdsDataType::Int8
462 | TdsDataType::IntN
463 | TdsDataType::Flt4
464 | TdsDataType::Flt8
465 | TdsDataType::FltN
466 ) {
467 throw!(anyhow!(
468 "Partition can only be done on int or float columns"
469 ));
470 }
471 match value {
473 ColumnValues::Null => 0,
474 ColumnValues::TinyInt(n) => i64::from(*n),
475 ColumnValues::SmallInt(n) => i64::from(*n),
476 ColumnValues::Int(n) => i64::from(*n),
477 ColumnValues::BigInt(n) => *n,
478 ColumnValues::Real(n) => *n as i64,
479 ColumnValues::Float(n) => *n as i64,
480 other => throw!(anyhow!(
481 "Unexpected MsSQL partition range value: {:?}",
482 other
483 )),
484 }
485}
486
487#[cfg(test)]
488mod partition_range_tests {
489 use super::*;
490
491 #[test]
492 fn numeric_bounds_preserve_tiberius_conversions() {
493 for (value, ty, expected) in [
494 (ColumnValues::TinyInt(255), TdsDataType::Int1, 255),
495 (ColumnValues::SmallInt(-32768), TdsDataType::Int2, -32768),
496 (
497 ColumnValues::Int(i32::MIN),
498 TdsDataType::Int4,
499 i64::from(i32::MIN),
500 ),
501 (ColumnValues::BigInt(i64::MIN), TdsDataType::Int8, i64::MIN),
502 (ColumnValues::BigInt(i64::MAX), TdsDataType::IntN, i64::MAX),
503 (ColumnValues::Real(-12.75), TdsDataType::Flt4, -12),
504 (ColumnValues::Float(23.5), TdsDataType::Flt8, 23),
505 (ColumnValues::Float(-12.75), TdsDataType::FltN, -12),
506 (ColumnValues::Null, TdsDataType::IntN, 0),
507 (ColumnValues::Null, TdsDataType::FltN, 0),
508 ] {
509 assert_eq!(partition_range_value(&value, ty).unwrap(), expected);
510 }
511 }
512
513 #[test]
514 fn unsupported_bounds_are_errors_even_when_null() {
515 for ty in [
516 TdsDataType::BigVarChar,
517 TdsDataType::DecimalN,
518 TdsDataType::MoneyN,
519 ] {
520 let err = partition_range_value(&ColumnValues::Null, ty).unwrap_err();
521 assert!(err
522 .to_string()
523 .contains("Partition can only be done on int or float columns"));
524 }
525 assert!(partition_range_value(&ColumnValues::Bit(true), TdsDataType::IntN).is_err());
526 }
527}
528
529pub struct MsSQLSource {
530 rt: Arc<Runtime>,
531 conn_url: Url,
532 pool: Pool<ConnectionManager>,
533 origin_query: Option<String>,
534 queries: Vec<CXQuery<String>>,
535 names: Vec<String>,
536 schema: Vec<MsSQLTypeSystem>,
537}
538
539impl MsSQLSource {
540 #[throws(MsSQLSourceError)]
541 pub fn new(rt: Arc<Runtime>, conn: &str, nconn: usize) -> Self {
542 debug!("mssql source using driver: {:?}", driver::active_driver());
543 let conn_url = Url::parse(conn)?;
544 let pool = connection_pool(&rt, &conn_url, nconn)?;
545
546 Self {
547 rt,
548 conn_url,
549 pool,
550 origin_query: None,
551 queries: vec![],
552 names: vec![],
553 schema: vec![],
554 }
555 }
556}
557
558impl Source for MsSQLSource
559where
560 MsSQLSourcePartition: SourcePartition<TypeSystem = MsSQLTypeSystem, Error = MsSQLSourceError>,
561{
562 const DATA_ORDERS: &'static [DataOrder] = &[DataOrder::RowMajor];
563 type Partition = MsSQLSourcePartition;
564 type TypeSystem = MsSQLTypeSystem;
565 type Error = MsSQLSourceError;
566
567 #[throws(MsSQLSourceError)]
568 fn set_data_order(&mut self, data_order: DataOrder) {
569 if !matches!(data_order, DataOrder::RowMajor) {
570 throw!(ConnectorXError::UnsupportedDataOrder(data_order));
571 }
572 }
573
574 fn set_queries<Q: ToString>(&mut self, queries: &[CXQuery<Q>]) {
575 self.queries = queries.iter().map(|q| q.map(Q::to_string)).collect();
576 }
577
578 fn set_origin_query(&mut self, query: Option<String>) {
579 self.origin_query = query;
580 }
581
582 #[throws(MsSQLSourceError)]
583 fn fetch_metadata(&mut self) {
584 assert!(!self.queries.is_empty());
585
586 let first_query = self.queries[0].clone();
587 let mut client = Connection::get(self.rt.clone(), &self.pool)?;
588 self.rt
589 .block_on(client.execute(first_query.as_str().to_string(), ()))?;
590
591 let metadata = client.get_metadata();
592 if metadata.is_empty() {
593 throw!(anyhow!(
594 "MsSQL returned no columns for query: {}",
595 first_query
596 ));
597 }
598
599 let (names, types) = metadata
600 .iter()
601 .map(|col| (col.column_name.clone(), MsSQLTypeSystem::from(col)))
602 .unzip();
603 client.finish()?;
604
605 self.names = names;
606 self.schema = types;
607 }
608
609 #[throws(MsSQLSourceError)]
610 fn result_rows(&mut self) -> Option<usize> {
611 match &self.origin_query {
612 Some(q) => {
613 let cxq = CXQuery::Naked(q.clone());
614 let cquery = count_query(&cxq, &MsSqlDialect {})?;
615 let mut client = Connection::get(self.rt.clone(), &self.pool)?;
616 let row = first_row(&self.rt, &mut client, cquery.as_str().to_string())?
617 .ok_or_else(|| anyhow!("MsSQL failed to get the count of query: {}", q))?;
618 client.finish()?;
619 Some(row_count_value(&row, q)?)
620 }
621 None => None,
622 }
623 }
624
625 fn names(&self) -> Vec<String> {
626 self.names.clone()
627 }
628
629 fn schema(&self) -> Vec<Self::TypeSystem> {
630 self.schema.clone()
631 }
632
633 #[throws(MsSQLSourceError)]
634 fn partition(self) -> Vec<Self::Partition> {
635 let mut ret = vec![];
636 for query in self.queries {
637 let mut partition = MsSQLSourcePartition::new(
638 self.rt.clone(),
639 self.conn_url.clone(),
640 &query,
641 &self.schema,
642 );
643 partition.pool = Some(self.pool.clone());
644 ret.push(partition);
645 }
646 ret
647 }
648}
649
650#[throws(MsSQLSourceError)]
653fn row_count_value(row: &[ColumnValues], query: &str) -> usize {
654 match row.first() {
655 Some(ColumnValues::Int(n)) => *n as usize,
656 Some(ColumnValues::BigInt(n)) => *n as usize,
657 other => throw!(anyhow!(
658 "MsSQL count query for '{}' returned unexpected value: {:?}",
659 query,
660 other
661 )),
662 }
663}
664
665pub struct MsSQLSourcePartition {
666 rt: Arc<Runtime>,
667 conn_url: Url,
668 pool: Option<Pool<ConnectionManager>>,
669 query: CXQuery<String>,
670 schema: Vec<MsSQLTypeSystem>,
671 nrows: usize,
672 ncols: usize,
673}
674
675impl MsSQLSourcePartition {
676 pub fn new(
677 rt: Arc<Runtime>,
678 conn_url: Url,
679 query: &CXQuery<String>,
680 schema: &[MsSQLTypeSystem],
681 ) -> Self {
682 Self {
683 rt,
684 conn_url,
685 pool: None,
686 query: query.clone(),
687 schema: schema.to_vec(),
688 nrows: 0,
689 ncols: schema.len(),
690 }
691 }
692
693 #[throws(MsSQLSourceError)]
694 fn connection(&mut self) -> Connection {
695 if self.pool.is_none() {
697 self.pool = Some(connection_pool(&self.rt, &self.conn_url, 1)?);
698 }
699 Connection::get(self.rt.clone(), self.pool.as_ref().unwrap())?
700 }
701}
702
703impl SourcePartition for MsSQLSourcePartition {
704 type TypeSystem = MsSQLTypeSystem;
705 type Parser<'a> = MsSQLSourceParser;
707 type Error = MsSQLSourceError;
708
709 #[throws(MsSQLSourceError)]
710 fn result_rows(&mut self) {
711 let cquery = count_query(&self.query, &MsSqlDialect {})?;
712 let mut client = self.connection()?;
713 let row = first_row(&self.rt, &mut client, cquery.as_str().to_string())?
714 .ok_or_else(|| anyhow!("MsSQL failed to get the count of query: {}", self.query))?;
715 client.finish()?;
716 self.nrows = row_count_value(&row, self.query.as_str())?;
717 }
718
719 #[throws(MsSQLSourceError)]
720 fn parser<'a>(&'a mut self) -> Self::Parser<'a> {
721 let mut client = self.connection()?;
722 self.rt
723 .block_on(client.execute(self.query.as_str().to_string(), ()))?;
724 MsSQLSourceParser::new(self.rt.clone(), client, self.schema.len())
725 }
726
727 fn nrows(&self) -> usize {
728 self.nrows
729 }
730
731 fn ncols(&self) -> usize {
732 self.ncols
733 }
734}
735
736#[derive(Debug)]
742enum TdsCell {
743 Null,
744 U8(u8),
745 I16(i16),
746 I32(i32),
747 I64(i64),
748 F32(f32),
749 F64(f64),
750 Bool(bool),
751 Str(String),
752 Bytes(Vec<u8>),
753 Uuid(Uuid),
754 Decimal(Decimal),
755 NaiveDate(NaiveDate),
756 NaiveTime(NaiveTime),
757 NaiveDateTime(NaiveDateTime),
758 DateTimeUtc(DateTime<Utc>),
759}
760
761pub struct MsSQLSourceParser {
762 rt: Arc<Runtime>,
763 client: Connection,
764 rowbuf: Vec<Vec<TdsCell>>,
765 ncols: usize,
766 current_col: usize,
767 current_row: usize,
768 is_finished: bool,
769}
770
771impl MsSQLSourceParser {
772 fn new(rt: Arc<Runtime>, client: Connection, ncols: usize) -> Self {
773 Self {
774 rt,
775 client,
776 rowbuf: Vec::with_capacity(DB_BUFFER_SIZE),
777 ncols,
778 current_row: 0,
779 current_col: 0,
780 is_finished: false,
781 }
782 }
783
784 #[throws(MsSQLSourceError)]
785 fn next_loc(&mut self) -> (usize, usize) {
786 let ret = (self.current_row, self.current_col);
787 self.current_row += (self.current_col + 1) / self.ncols;
788 self.current_col = (self.current_col + 1) % self.ncols;
789 ret
790 }
791
792 fn take_cell(&mut self, ridx: usize, cidx: usize) -> TdsCell {
796 std::mem::replace(&mut self.rowbuf[ridx][cidx], TdsCell::Null)
797 }
798}
799
800impl<'a> PartitionParser<'a> for MsSQLSourceParser {
801 type TypeSystem = MsSQLTypeSystem;
802 type Error = MsSQLSourceError;
803
804 #[throws(MsSQLSourceError)]
805 fn fetch_next(&mut self) -> (usize, bool) {
806 assert!(self.current_col == 0);
807 let remaining_rows = self.rowbuf.len() - self.current_row;
808 if remaining_rows > 0 {
809 return (remaining_rows, self.is_finished);
810 } else if self.is_finished {
811 return (0, self.is_finished);
812 }
813
814 self.rowbuf.clear();
815
816 for _ in 0..DB_BUFFER_SIZE {
817 match self.rt.block_on(self.client.next_row())? {
818 Some(row) => {
819 let mut cells = Vec::with_capacity(row.len());
820 for value in row {
821 cells.push(column_value_to_cell(value)?);
822 }
823 self.rowbuf.push(cells);
824 }
825 None => {
826 self.client.finish()?;
827 self.is_finished = true;
828 break;
829 }
830 }
831 }
832 self.current_row = 0;
833 self.current_col = 0;
834 (self.rowbuf.len(), self.is_finished)
835 }
836}
837
838#[throws(MsSQLSourceError)]
839fn column_value_to_cell(value: ColumnValues) -> TdsCell {
840 match value {
841 ColumnValues::Null => TdsCell::Null,
842 ColumnValues::TinyInt(n) => TdsCell::U8(n),
843 ColumnValues::SmallInt(n) => TdsCell::I16(n),
844 ColumnValues::Int(n) => TdsCell::I32(n),
845 ColumnValues::BigInt(n) => TdsCell::I64(n),
846 ColumnValues::Real(f) => TdsCell::F32(f),
847 ColumnValues::Float(f) => TdsCell::F64(f),
848 ColumnValues::Bit(b) => TdsCell::Bool(b),
849 ColumnValues::String(s) => TdsCell::Str(s.to_utf8_string()),
850 ColumnValues::Bytes(b) => TdsCell::Bytes(b),
851 ColumnValues::Uuid(u) => TdsCell::Uuid(Uuid::from_bytes(*u.as_bytes())),
852 ColumnValues::Decimal(parts) | ColumnValues::Numeric(parts) => {
853 TdsCell::Decimal(decimal_parts_to_decimal(&parts)?)
854 }
855 ColumnValues::Date(d) => TdsCell::NaiveDate(sql_date_to_naive_date(&d)?),
856 ColumnValues::Time(t) => TdsCell::NaiveTime(sql_time_to_naive_time(&t)),
857 ColumnValues::DateTime(dt) => TdsCell::NaiveDateTime(sql_datetime_to_naive_datetime(&dt)?),
858 ColumnValues::DateTime2(dt2) => {
859 TdsCell::NaiveDateTime(sql_datetime2_to_naive_datetime(&dt2)?)
860 }
861 ColumnValues::SmallDateTime(sdt) => {
862 TdsCell::NaiveDateTime(sql_smalldatetime_to_naive_datetime(&sdt))
863 }
864 ColumnValues::DateTimeOffset(dto) => {
865 TdsCell::DateTimeUtc(sql_datetimeoffset_to_datetime_utc(&dto)?)
866 }
867 ColumnValues::Money(m) => TdsCell::F64(sql_money_to_f64(&m)),
868 ColumnValues::SmallMoney(sm) => TdsCell::F64((sm.int_val as f64) / 10_000.0),
878 other => throw!(anyhow!(
879 "MsSQL: unsupported value for src_mssql_tds backend: {:?}",
880 other
881 )),
882 }
883}
884
885#[throws(MsSQLSourceError)]
892fn decimal_parts_to_decimal(parts: &DecimalParts) -> Decimal {
893 let magnitude = parts.magnitude();
894 let mantissa = i128::try_from(magnitude)
895 .map_err(|_| anyhow!("MsSQL decimal/numeric value overflows 128 bits"))?;
896 let mantissa = if parts.is_positive {
897 mantissa
898 } else {
899 -mantissa
900 };
901 Decimal::try_from_i128_with_scale(mantissa, parts.scale as u32)
902 .map_err(|e| anyhow!("MsSQL decimal/numeric value out of range: {}", e))?
903}
904
905#[throws(MsSQLSourceError)]
908fn sql_date_to_naive_date(d: &SqlDate) -> NaiveDate {
909 NaiveDate::from_ymd_opt(1, 1, 1)
910 .unwrap()
911 .checked_add_signed(chrono::Duration::days(d.get_days() as i64))
912 .ok_or_else(|| {
913 anyhow!(
914 "MsSQL date out of range: {} days since 0001-01-01",
915 d.get_days()
916 )
917 })?
918}
919
920fn sql_time_to_naive_time(t: &SqlTime) -> NaiveTime {
927 let ns = t.time_nanoseconds * 100;
928 let secs = (ns / 1_000_000_000) as u32;
929 let nanos = (ns % 1_000_000_000) as u32;
930 NaiveTime::from_num_seconds_from_midnight_opt(secs, nanos).unwrap_or_default()
931}
932
933#[throws(MsSQLSourceError)]
934fn sql_datetime2_to_naive_datetime(dt2: &SqlDateTime2) -> NaiveDateTime {
935 let date = NaiveDate::from_ymd_opt(1, 1, 1)
936 .unwrap()
937 .checked_add_signed(chrono::Duration::days(dt2.days as i64))
938 .ok_or_else(|| {
939 anyhow!(
940 "MsSQL datetime2 out of range: {} days since 0001-01-01",
941 dt2.days
942 )
943 })?;
944 date.and_time(sql_time_to_naive_time(&dt2.time))
945}
946
947fn sql_smalldatetime_to_naive_datetime(sdt: &SqlSmallDateTime) -> NaiveDateTime {
948 let date = NaiveDate::from_ymd_opt(1900, 1, 1)
950 .unwrap()
951 .checked_add_signed(chrono::Duration::days(sdt.days as i64))
952 .expect("smalldatetime day count is always in range (u16 days since 1900-01-01)");
953 let time = NaiveTime::from_hms_opt((sdt.time / 60) as u32, (sdt.time % 60) as u32, 0)
954 .unwrap_or_default();
955 date.and_time(time)
956}
957
958#[throws(MsSQLSourceError)]
964fn sql_datetime_to_naive_datetime(dt: &SqlDateTime) -> NaiveDateTime {
965 let total_ms = (dt.time as i64 * 10 + 1) / 3;
966 let (extra_days, ms_in_day) = if total_ms >= 86_400_000 {
967 (1i64, total_ms - 86_400_000)
968 } else {
969 (0, total_ms)
970 };
971
972 let time = NaiveTime::from_num_seconds_from_midnight_opt(
973 (ms_in_day / 1000) as u32,
974 ((ms_in_day % 1000) * 1_000_000) as u32,
975 )
976 .ok_or_else(|| anyhow!("MsSQL datetime: invalid tick count {}", dt.time))?;
977
978 let date = NaiveDate::from_ymd_opt(1900, 1, 1)
979 .unwrap()
980 .checked_add_signed(chrono::Duration::days(dt.days as i64 + extra_days))
981 .ok_or_else(|| {
982 anyhow!(
983 "MsSQL datetime out of range: {} days since 1900-01-01",
984 dt.days
985 )
986 })?;
987
988 date.and_time(time)
989}
990
991#[throws(MsSQLSourceError)]
992fn sql_datetimeoffset_to_datetime_utc(dto: &SqlDateTimeOffset) -> DateTime<Utc> {
993 let naive_local = sql_datetime2_to_naive_datetime(&dto.datetime2)?;
999 let naive_utc = naive_local - chrono::Duration::minutes(dto.offset as i64);
1000 DateTime::<Utc>::from_naive_utc_and_offset(naive_utc, Utc)
1001}
1002
1003fn sql_money_to_f64(m: &mssql_tds::datatypes::column_values::SqlMoney) -> f64 {
1006 let lsb_in_i64 = (m.lsb_part as i64) & 0x0000_0000_FFFF_FFFF;
1007 let money_val = lsb_in_i64 | ((m.msb_part as i64) << 32);
1008 (money_val as f64) / 10_000.0
1009}
1010
1011macro_rules! impl_produce_direct {
1012 ($($t:ty => $variant:ident),+ $(,)?) => {
1013 $(
1014 impl<'r> Produce<'r, $t> for MsSQLSourceParser {
1015 type Error = MsSQLSourceError;
1016
1017 #[throws(MsSQLSourceError)]
1018 fn produce(&'r mut self) -> $t {
1019 let (ridx, cidx) = self.next_loc()?;
1020 match self.take_cell(ridx, cidx) {
1021 TdsCell::$variant(v) => v,
1022 TdsCell::Null => throw!(anyhow!("MsSQL get None at position: ({}, {})", ridx, cidx)),
1023 other => throw!(anyhow!("MsSQL type mismatch at ({}, {}): {:?}", ridx, cidx, other)),
1024 }
1025 }
1026 }
1027
1028 impl<'r> Produce<'r, Option<$t>> for MsSQLSourceParser {
1029 type Error = MsSQLSourceError;
1030
1031 #[throws(MsSQLSourceError)]
1032 fn produce(&'r mut self) -> Option<$t> {
1033 let (ridx, cidx) = self.next_loc()?;
1034 match self.take_cell(ridx, cidx) {
1035 TdsCell::$variant(v) => Some(v),
1036 TdsCell::Null => None,
1037 other => throw!(anyhow!("MsSQL type mismatch at ({}, {}): {:?}", ridx, cidx, other)),
1038 }
1039 }
1040 }
1041 )+
1042 };
1043}
1044
1045impl_produce_direct!(
1046 u8 => U8,
1047 i16 => I16,
1048 i32 => I32,
1049 i64 => I64,
1050 bool => Bool,
1051 Uuid => Uuid,
1052 Decimal => Decimal,
1053 NaiveDateTime => NaiveDateTime,
1054 NaiveDate => NaiveDate,
1055 NaiveTime => NaiveTime,
1056 DateTime<Utc> => DateTimeUtc,
1057);
1058
1059impl<'r> Produce<'r, f32> for MsSQLSourceParser {
1065 type Error = MsSQLSourceError;
1066
1067 #[throws(MsSQLSourceError)]
1068 fn produce(&'r mut self) -> f32 {
1069 let (ridx, cidx) = self.next_loc()?;
1070 match self.take_cell(ridx, cidx) {
1071 TdsCell::F32(v) => v,
1072 TdsCell::F64(v) => v as f32,
1073 TdsCell::Null => throw!(anyhow!("MsSQL get None at position: ({}, {})", ridx, cidx)),
1074 other => throw!(anyhow!(
1075 "MsSQL type mismatch at ({}, {}): {:?}",
1076 ridx,
1077 cidx,
1078 other
1079 )),
1080 }
1081 }
1082}
1083
1084impl<'r> Produce<'r, Option<f32>> for MsSQLSourceParser {
1085 type Error = MsSQLSourceError;
1086
1087 #[throws(MsSQLSourceError)]
1088 fn produce(&'r mut self) -> Option<f32> {
1089 let (ridx, cidx) = self.next_loc()?;
1090 match self.take_cell(ridx, cidx) {
1091 TdsCell::F32(v) => Some(v),
1092 TdsCell::F64(v) => Some(v as f32),
1093 TdsCell::Null => None,
1094 other => throw!(anyhow!(
1095 "MsSQL type mismatch at ({}, {}): {:?}",
1096 ridx,
1097 cidx,
1098 other
1099 )),
1100 }
1101 }
1102}
1103
1104impl<'r> Produce<'r, f64> for MsSQLSourceParser {
1112 type Error = MsSQLSourceError;
1113
1114 #[throws(MsSQLSourceError)]
1115 fn produce(&'r mut self) -> f64 {
1116 let (ridx, cidx) = self.next_loc()?;
1117 match self.take_cell(ridx, cidx) {
1118 TdsCell::F64(v) => v,
1119 TdsCell::F32(v) => v as f64,
1120 TdsCell::Null => throw!(anyhow!("MsSQL get None at position: ({}, {})", ridx, cidx)),
1121 other => throw!(anyhow!(
1122 "MsSQL type mismatch at ({}, {}): {:?}",
1123 ridx,
1124 cidx,
1125 other
1126 )),
1127 }
1128 }
1129}
1130
1131impl<'r> Produce<'r, Option<f64>> for MsSQLSourceParser {
1132 type Error = MsSQLSourceError;
1133
1134 #[throws(MsSQLSourceError)]
1135 fn produce(&'r mut self) -> Option<f64> {
1136 let (ridx, cidx) = self.next_loc()?;
1137 match self.take_cell(ridx, cidx) {
1138 TdsCell::F64(v) => Some(v),
1139 TdsCell::F32(v) => Some(v as f64),
1140 TdsCell::Null => None,
1141 other => throw!(anyhow!(
1142 "MsSQL type mismatch at ({}, {}): {:?}",
1143 ridx,
1144 cidx,
1145 other
1146 )),
1147 }
1148 }
1149}
1150
1151impl<'r> Produce<'r, IntN> for MsSQLSourceParser {
1152 type Error = MsSQLSourceError;
1153
1154 #[throws(MsSQLSourceError)]
1155 fn produce(&'r mut self) -> IntN {
1156 let (ridx, cidx) = self.next_loc()?;
1157 match self.take_cell(ridx, cidx) {
1158 TdsCell::U8(v) => IntN(v as i64),
1159 TdsCell::I16(v) => IntN(v as i64),
1160 TdsCell::I32(v) => IntN(v as i64),
1161 TdsCell::I64(v) => IntN(v),
1162 TdsCell::Null => throw!(anyhow!("MsSQL get None at position: ({}, {})", ridx, cidx)),
1163 other => throw!(anyhow!(
1164 "MsSQL type mismatch at ({}, {}): {:?}",
1165 ridx,
1166 cidx,
1167 other
1168 )),
1169 }
1170 }
1171}
1172
1173impl<'r> Produce<'r, Option<IntN>> for MsSQLSourceParser {
1174 type Error = MsSQLSourceError;
1175
1176 #[throws(MsSQLSourceError)]
1177 fn produce(&'r mut self) -> Option<IntN> {
1178 let (ridx, cidx) = self.next_loc()?;
1179 match self.take_cell(ridx, cidx) {
1180 TdsCell::U8(v) => Some(IntN(v as i64)),
1181 TdsCell::I16(v) => Some(IntN(v as i64)),
1182 TdsCell::I32(v) => Some(IntN(v as i64)),
1183 TdsCell::I64(v) => Some(IntN(v)),
1184 TdsCell::Null => None,
1185 other => throw!(anyhow!(
1186 "MsSQL type mismatch at ({}, {}): {:?}",
1187 ridx,
1188 cidx,
1189 other
1190 )),
1191 }
1192 }
1193}
1194
1195impl<'r> Produce<'r, FloatN> for MsSQLSourceParser {
1196 type Error = MsSQLSourceError;
1197
1198 #[throws(MsSQLSourceError)]
1199 fn produce(&'r mut self) -> FloatN {
1200 let (ridx, cidx) = self.next_loc()?;
1201 match self.take_cell(ridx, cidx) {
1202 TdsCell::F32(v) => FloatN(v as f64),
1203 TdsCell::F64(v) => FloatN(v),
1204 TdsCell::Null => throw!(anyhow!("MsSQL get None at position: ({}, {})", ridx, cidx)),
1205 other => throw!(anyhow!(
1206 "MsSQL type mismatch at ({}, {}): {:?}",
1207 ridx,
1208 cidx,
1209 other
1210 )),
1211 }
1212 }
1213}
1214
1215impl<'r> Produce<'r, Option<FloatN>> for MsSQLSourceParser {
1216 type Error = MsSQLSourceError;
1217
1218 #[throws(MsSQLSourceError)]
1219 fn produce(&'r mut self) -> Option<FloatN> {
1220 let (ridx, cidx) = self.next_loc()?;
1221 match self.take_cell(ridx, cidx) {
1222 TdsCell::F32(v) => Some(FloatN(v as f64)),
1223 TdsCell::F64(v) => Some(FloatN(v)),
1224 TdsCell::Null => None,
1225 other => throw!(anyhow!(
1226 "MsSQL type mismatch at ({}, {}): {:?}",
1227 ridx,
1228 cidx,
1229 other
1230 )),
1231 }
1232 }
1233}
1234
1235impl<'r> Produce<'r, &'r str> for MsSQLSourceParser {
1236 type Error = MsSQLSourceError;
1237
1238 #[throws(MsSQLSourceError)]
1239 fn produce(&'r mut self) -> &'r str {
1240 let (ridx, cidx) = self.next_loc()?;
1241 match &self.rowbuf[ridx][cidx] {
1242 TdsCell::Str(s) => s.as_str(),
1243 TdsCell::Null => throw!(anyhow!("MsSQL get None at position: ({}, {})", ridx, cidx)),
1244 other => throw!(anyhow!(
1245 "MsSQL type mismatch at ({}, {}): {:?}",
1246 ridx,
1247 cidx,
1248 other
1249 )),
1250 }
1251 }
1252}
1253
1254impl<'r> Produce<'r, Option<&'r str>> for MsSQLSourceParser {
1255 type Error = MsSQLSourceError;
1256
1257 #[throws(MsSQLSourceError)]
1258 fn produce(&'r mut self) -> Option<&'r str> {
1259 let (ridx, cidx) = self.next_loc()?;
1260 match &self.rowbuf[ridx][cidx] {
1261 TdsCell::Str(s) => Some(s.as_str()),
1262 TdsCell::Null => None,
1263 other => throw!(anyhow!(
1264 "MsSQL type mismatch at ({}, {}): {:?}",
1265 ridx,
1266 cidx,
1267 other
1268 )),
1269 }
1270 }
1271}
1272
1273impl<'r> Produce<'r, &'r [u8]> for MsSQLSourceParser {
1274 type Error = MsSQLSourceError;
1275
1276 #[throws(MsSQLSourceError)]
1277 fn produce(&'r mut self) -> &'r [u8] {
1278 let (ridx, cidx) = self.next_loc()?;
1279 match &self.rowbuf[ridx][cidx] {
1280 TdsCell::Bytes(b) => b.as_slice(),
1281 TdsCell::Null => throw!(anyhow!("MsSQL get None at position: ({}, {})", ridx, cidx)),
1282 other => throw!(anyhow!(
1283 "MsSQL type mismatch at ({}, {}): {:?}",
1284 ridx,
1285 cidx,
1286 other
1287 )),
1288 }
1289 }
1290}
1291
1292impl<'r> Produce<'r, Option<&'r [u8]>> for MsSQLSourceParser {
1293 type Error = MsSQLSourceError;
1294
1295 #[throws(MsSQLSourceError)]
1296 fn produce(&'r mut self) -> Option<&'r [u8]> {
1297 let (ridx, cidx) = self.next_loc()?;
1298 match &self.rowbuf[ridx][cidx] {
1299 TdsCell::Bytes(b) => Some(b.as_slice()),
1300 TdsCell::Null => None,
1301 other => throw!(anyhow!(
1302 "MsSQL type mismatch at ({}, {}): {:?}",
1303 ridx,
1304 cidx,
1305 other
1306 )),
1307 }
1308 }
1309}