use std::borrow::Cow;
use std::pin::pin;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Duration;
use futures::StreamExt;
use futures::future::{self, Either};
use tds_driver::SqlBrowser;
use tokio::net::TcpStream;
use tokio::sync::{mpsc, oneshot};
use tokio_util::compat::{Compat, TokioAsyncWriteCompatExt};
use crate::command::{Command, Prepared};
use crate::config::Config;
use crate::dataset::{DataSet, DataTable};
use crate::error::{Error, Result, StatementKind};
use crate::row::{Column, Row};
use crate::stream::{RowStream, Streamed};
use crate::transaction::{IsolationLevel, Transaction};
use crate::value::{DataValue, FromSql, SqlType, ToSql};
type Driver = tds_driver::Client<Compat<TcpStream>>;
impl tds_driver::ToSql for DataValue {
fn to_sql(&self) -> tds_driver::ColumnData<'_> {
use tds_driver::ColumnData as C;
match self {
DataValue::TinyInt(v) => C::U8(Some(*v)),
DataValue::SmallInt(v) => C::I16(Some(*v)),
DataValue::Int(v) => C::I32(Some(*v)),
DataValue::BigInt(v) => C::I64(Some(*v)),
DataValue::Float(v) => C::F64(Some(*v)),
DataValue::Decimal(v) => v.to_sql(),
DataValue::Bool(v) => C::Bit(Some(*v)),
DataValue::Text(v) => C::String(Some(Cow::Borrowed(v.as_str()))),
DataValue::VarChar(v) => C::VarChar(Some(Cow::Borrowed(v.as_str()))),
DataValue::Binary(v) => C::Binary(Some(Cow::Borrowed(v.as_slice()))),
DataValue::Guid(v) => C::Guid(Some(*v)),
DataValue::Date(v) => v.to_sql(),
DataValue::Time(v) => v.to_sql(),
DataValue::DateTime(v) => v.to_sql(),
DataValue::DateTimeOffset(v) => v.to_sql(),
DataValue::Null => C::String(None),
DataValue::TypedNull(ty) => typed_null(*ty),
}
}
}
fn typed_null(ty: SqlType) -> tds_driver::ColumnData<'static> {
use tds_driver::ColumnData as C;
match ty {
SqlType::Bit | SqlType::BitN => C::Bit(None),
SqlType::TinyInt => C::U8(None),
SqlType::SmallInt => C::I16(None),
SqlType::Int => C::I32(None),
SqlType::BigInt | SqlType::IntN => C::I64(None),
SqlType::Real => C::F32(None),
SqlType::Float | SqlType::FloatN => C::F64(None),
SqlType::Decimal | SqlType::Numeric | SqlType::Money | SqlType::SmallMoney => {
C::Numeric(None)
}
SqlType::Guid => C::Guid(None),
SqlType::Date => C::Date(None),
SqlType::Time => C::Time(None),
SqlType::DateTime | SqlType::DateTimeN => C::DateTime(None),
SqlType::SmallDateTime => C::SmallDateTime(None),
SqlType::DateTime2 => C::DateTime2(None),
SqlType::DateTimeOffset => C::DateTimeOffset(None),
SqlType::Binary | SqlType::VarBinary | SqlType::Image => C::Binary(None),
SqlType::Xml => C::Xml(None),
SqlType::Char
| SqlType::VarChar
| SqlType::NChar
| SqlType::NVarChar
| SqlType::Text
| SqlType::NText => C::String(None),
SqlType::Null | SqlType::Udt | SqlType::Variant => {
tds_driver::ToSql::to_sql(&DataValue::Null)
}
}
}
impl<'a> tds_driver::IntoSql<'a> for DataValue {
fn into_sql(self) -> tds_driver::ColumnData<'a> {
use tds_driver::ColumnData as C;
match self {
DataValue::Text(v) => C::String(Some(Cow::Owned(v))),
DataValue::VarChar(v) => C::VarChar(Some(Cow::Owned(v))),
DataValue::Binary(v) => C::Binary(Some(Cow::Owned(v))),
other => match tds_driver::ToSql::to_sql(&other) {
C::U8(v) => C::U8(v),
C::I16(v) => C::I16(v),
C::I32(v) => C::I32(v),
C::I64(v) => C::I64(v),
C::F32(v) => C::F32(v),
C::F64(v) => C::F64(v),
C::Bit(v) => C::Bit(v),
C::Guid(v) => C::Guid(v),
C::Numeric(v) => C::Numeric(v),
C::DateTime(v) => C::DateTime(v),
C::SmallDateTime(v) => C::SmallDateTime(v),
C::Time(v) => C::Time(v),
C::Date(v) => C::Date(v),
C::DateTime2(v) => C::DateTime2(v),
C::DateTimeOffset(v) => C::DateTimeOffset(v),
C::String(v) => C::String(v.map(|s| Cow::Owned(s.into_owned()))),
C::VarChar(v) => C::VarChar(v.map(|s| Cow::Owned(s.into_owned()))),
C::Binary(v) => C::Binary(v.map(|b| Cow::Owned(b.into_owned()))),
C::Xml(v) => C::Xml(v.map(|x| Cow::Owned(x.into_owned()))),
},
}
}
}
fn to_decimal(n: tds_driver::numeric::Numeric) -> Result<rust_decimal::Decimal> {
let (mut value, mut scale) = (n.value(), n.scale() as u32);
loop {
if let Ok(d) = rust_decimal::Decimal::try_from_i128_with_scale(value, scale) {
return Ok(d);
}
if scale == 0 || value % 10 != 0 {
return Err(Error::Decimal {
value: n.value(),
scale: n.scale(),
});
}
value /= 10;
scale -= 1;
}
}
pub(crate) fn map_column_data(cd: tds_driver::ColumnData<'_>, column: &str) -> Result<DataValue> {
use tds_driver::ColumnData as C;
fn temporal(column: &str, target: &'static str) -> impl Fn(tds_driver::error::Error) -> Error {
let column = column.to_string();
move |_| Error::Conversion {
column: column.clone(),
actual: "temporal",
target,
}
}
Ok(match cd {
C::U8(v) => v.map(DataValue::TinyInt).unwrap_or_default(),
C::I16(v) => v.map(DataValue::SmallInt).unwrap_or_default(),
C::I32(v) => v.map(DataValue::Int).unwrap_or_default(),
C::I64(v) => v.map(DataValue::BigInt).unwrap_or_default(),
C::F32(v) => v.map(|f| DataValue::Float(f as f64)).unwrap_or_default(),
C::F64(v) => v.map(DataValue::Float).unwrap_or_default(),
C::Bit(v) => v.map(DataValue::Bool).unwrap_or_default(),
C::String(v) | C::VarChar(v) => v
.map(|s| DataValue::Text(s.into_owned()))
.unwrap_or_default(),
C::Guid(v) => v.map(DataValue::Guid).unwrap_or_default(),
C::Binary(v) => v
.map(|b| DataValue::Binary(b.into_owned()))
.unwrap_or_default(),
C::Xml(v) => v
.map(|x| DataValue::Text(x.as_ref().to_string()))
.unwrap_or_default(),
C::Numeric(v) => match v {
None => DataValue::Null,
Some(n) => DataValue::Decimal(to_decimal(n)?),
},
C::DateTime(v) => {
<chrono::NaiveDateTime as tds_driver::FromSqlOwned>::from_sql_owned(C::DateTime(v))
.map_err(temporal(column, "NaiveDateTime"))?
.map(DataValue::DateTime)
.unwrap_or_default()
}
C::SmallDateTime(v) => {
<chrono::NaiveDateTime as tds_driver::FromSqlOwned>::from_sql_owned(C::SmallDateTime(v))
.map_err(temporal(column, "NaiveDateTime"))?
.map(DataValue::DateTime)
.unwrap_or_default()
}
C::DateTime2(v) => {
<chrono::NaiveDateTime as tds_driver::FromSqlOwned>::from_sql_owned(C::DateTime2(v))
.map_err(temporal(column, "NaiveDateTime"))?
.map(DataValue::DateTime)
.unwrap_or_default()
}
C::Time(v) => <chrono::NaiveTime as tds_driver::FromSqlOwned>::from_sql_owned(C::Time(v))
.map_err(temporal(column, "NaiveTime"))?
.map(DataValue::Time)
.unwrap_or_default(),
C::Date(v) => <chrono::NaiveDate as tds_driver::FromSqlOwned>::from_sql_owned(C::Date(v))
.map_err(temporal(column, "NaiveDate"))?
.map(DataValue::Date)
.unwrap_or_default(),
C::DateTimeOffset(v) => {
<chrono::DateTime<chrono::FixedOffset> as tds_driver::FromSqlOwned>::from_sql_owned(
C::DateTimeOffset(v),
)
.map_err(temporal(column, "DateTime<FixedOffset>"))?
.map(DataValue::DateTimeOffset)
.unwrap_or_default()
}
})
}
fn columns_of(meta: &tds_driver::ResultMetadata) -> Arc<[Column]> {
meta.columns()
.iter()
.map(|c| Column::new(c.name(), SqlType::from(c.column_type())))
.collect()
}
fn row_of(row: tds_driver::Row, columns: &Arc<[Column]>) -> Result<Row> {
let mut values = Vec::with_capacity(columns.len());
for (i, cd) in row.into_iter().enumerate() {
let name = columns.get(i).map(Column::name).unwrap_or("?");
values.push(map_column_data(cd, name)?);
}
Ok(Row::new(Arc::clone(columns), values))
}
async fn collect_query_stream(mut stream: tds_driver::QueryStream<'_>) -> Result<DataSet> {
let mut dataset = DataSet::new();
let mut current: Option<(DataTable, Arc<[Column]>)> = None;
let mut failure: Option<Error> = None;
while let Some(item) = stream.next().await {
match item {
Ok(tds_driver::QueryItem::Metadata(meta)) => {
if let Some((table, _)) = current.take() {
dataset.push(table);
}
let columns = columns_of(&meta);
let name = format!("table{}", meta.result_index());
current = Some((DataTable::new(name, Arc::clone(&columns)), columns));
}
Ok(tds_driver::QueryItem::Row(row)) if failure.is_none() => {
let (table, columns) = current.get_or_insert_with(|| {
let columns: Arc<[Column]> = Arc::from(Vec::new());
(DataTable::new("table0", Arc::clone(&columns)), columns)
});
match row_of(row, columns) {
Ok(row) => table.push(row),
Err(e) => failure = Some(e),
}
}
Ok(tds_driver::QueryItem::Row(_)) => {}
Err(e) => {
failure.get_or_insert(e.into());
break;
}
}
}
if let Some(e) = failure {
return Err(e);
}
if let Some((table, _)) = current.take() {
dataset.push(table);
}
Ok(dataset)
}
fn to_legacy_datetime(dt: chrono::NaiveDateTime) -> tds_driver::time::DateTime {
use chrono::Timelike;
let base = chrono::NaiveDate::from_ymd_opt(1900, 1, 1).expect("a valid date");
let mut days = (dt.date() - base).num_days();
let time = dt.time();
let ns =
u64::from(time.num_seconds_from_midnight()) * 1_000_000_000 + u64::from(time.nanosecond());
let mut ticks = (ns * 3 + 5_000_000) / 10_000_000;
if ticks >= 300 * 86_400 {
days += 1;
ticks -= 300 * 86_400;
}
tds_driver::time::DateTime::new(days as i32, ticks as u32)
}
struct LegacyDateTime(tds_driver::time::DateTime);
impl tds_driver::ToSql for LegacyDateTime {
fn to_sql(&self) -> tds_driver::ColumnData<'_> {
tds_driver::ColumnData::DateTime(Some(self.0))
}
}
impl tds_driver::IntoSql<'static> for LegacyDateTime {
fn into_sql(self) -> tds_driver::ColumnData<'static> {
tds_driver::ColumnData::DateTime(Some(self.0))
}
}
enum Bound<'a> {
Plain(&'a DataValue),
Legacy(LegacyDateTime),
}
impl tds_driver::ToSql for Bound<'_> {
fn to_sql(&self) -> tds_driver::ColumnData<'_> {
match self {
Bound::Plain(v) => v.to_sql(),
Bound::Legacy(v) => v.to_sql(),
}
}
}
fn bind(values: &[DataValue], legacy_datetime: bool) -> Vec<Bound<'_>> {
values
.iter()
.map(|v| match v {
DataValue::DateTime(dt) if legacy_datetime => {
Bound::Legacy(LegacyDateTime(to_legacy_datetime(*dt)))
}
v => Bound::Plain(v),
})
.collect()
}
fn as_driver_refs<'a>(values: &'a [Bound<'a>]) -> Vec<&'a dyn tds_driver::ToSql> {
values.iter().map(|v| v as &dyn tds_driver::ToSql).collect()
}
pub(crate) fn to_values(params: &[&dyn ToSql]) -> Vec<DataValue> {
params.iter().map(|p| p.to_value()).collect()
}
type Reply<T> = oneshot::Sender<Result<T>>;
enum Request {
Query {
sql: String,
params: Vec<DataValue>,
reply: Reply<DataSet>,
},
QueryStream {
sql: String,
params: Vec<DataValue>,
header: Reply<Arc<[Column]>>,
rows: mpsc::Sender<Result<Streamed>>,
},
Execute {
sql: String,
params: Vec<DataValue>,
reply: Reply<u64>,
},
Batch {
sql: String,
reply: Reply<DataSet>,
},
ProcQuery {
name: String,
params: Vec<(String, DataValue)>,
reply: Reply<DataSet>,
},
ProcExecute {
name: String,
params: Vec<(String, DataValue)>,
reply: Reply<u64>,
},
Begin {
level: IsolationLevel,
reply: Reply<()>,
},
Commit {
reply: Reply<()>,
},
Savepoint {
name: String,
reply: Reply<()>,
},
Rollback {
reply: Option<Reply<()>>,
},
RollbackTo {
name: String,
reply: Option<Reply<()>>,
},
}
impl Request {
fn abandoned(&self) -> bool {
match self {
Request::Query { reply, .. }
| Request::Batch { reply, .. }
| Request::ProcQuery { reply, .. } => reply.is_closed(),
Request::Execute { reply, .. } | Request::ProcExecute { reply, .. } => {
reply.is_closed()
}
Request::QueryStream { header, .. } => header.is_closed(),
Request::Begin { reply, .. } => reply.is_closed(),
Request::Commit { .. }
| Request::Savepoint { .. }
| Request::Rollback { .. }
| Request::RollbackTo { .. } => false,
}
}
fn fail(self, err: Error) {
match self {
Request::Query { reply, .. }
| Request::Batch { reply, .. }
| Request::ProcQuery { reply, .. } => {
let _ = reply.send(Err(err));
}
Request::Execute { reply, .. } | Request::ProcExecute { reply, .. } => {
let _ = reply.send(Err(err));
}
Request::QueryStream { header, .. } => {
let _ = header.send(Err(err));
}
Request::Begin { reply, .. }
| Request::Commit { reply }
| Request::Savepoint { reply, .. } => {
let _ = reply.send(Err(err));
}
Request::Rollback { reply } | Request::RollbackTo { reply, .. } => {
if let Some(reply) = reply {
let _ = reply.send(Err(err));
}
}
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum TxState {
None,
Open,
Aborted,
}
const CANCEL_TIMEOUT: Duration = Duration::from_secs(30);
async fn cancellable<T>(
driver: &mut Driver,
gone: impl Future<Output = ()>,
work: impl AsyncFnOnce(&mut Driver) -> T,
) -> Option<T> {
let cancel = driver.cancel_handle();
let gone = pin!(tokio::task::unconstrained(gone));
let deadline;
let output = match future::select(pin!(work(&mut *driver)), gone).await {
Either::Left((output, _)) => return Some(output),
Either::Right(((), work)) => {
cancel.cancel();
deadline = tokio::time::Instant::now() + CANCEL_TIMEOUT;
tokio::time::timeout_at(deadline, work).await.ok()?
}
};
matches!(
tokio::time::timeout_at(deadline, driver.cancel_query()).await,
Ok(Ok(()))
)
.then_some(output)
}
async fn serve<T>(
driver: &mut Driver,
mut reply: Reply<T>,
work: impl AsyncFnOnce(&mut Driver) -> Result<T>,
) -> bool {
match cancellable(driver, reply.closed(), work).await {
Some(result) => {
let (result, lost) = connection_lost(result);
let _ = reply.send(result);
!lost
}
None => false,
}
}
fn connection_lost<T>(result: Result<T>) -> (Result<T>, bool) {
match result {
Err(e) if e.io_kind() == Some(std::io::ErrorKind::NotConnected) => {
(Err(Error::ConnectionClosed), true)
}
Err(e) if e.io_kind().is_some() => (Err(e), true),
other => (other, false),
}
}
async fn restore_isolation(driver: &mut Driver, level_changed: &mut bool) -> bool {
if !std::mem::take(level_changed) {
return true;
}
match driver
.simple_query("SET TRANSACTION ISOLATION LEVEL READ COMMITTED")
.await
{
Ok(stream) => collect_query_stream(stream).await.is_ok(),
Err(_) => false,
}
}
async fn open(mut cfg: tds_driver::Config) -> tds_driver::Result<Driver> {
let mut redirected = false;
loop {
let tcp = TcpStream::connect_named(&cfg).await?;
tcp.set_nodelay(true)?;
match tds_driver::Client::connect(cfg.clone(), tcp.compat_write()).await {
Err(tds_driver::error::Error::Routing { host, port }) if !redirected => {
redirected = true;
cfg.host(host);
cfg.port(port);
}
connected => return connected,
}
}
}
async fn connection_task(
mut driver: Driver,
mut rx: mpsc::UnboundedReceiver<Request>,
legacy_datetime: bool,
) {
let mut level_changed = false;
let mut tx = TxState::None;
while let Some(request) = rx.recv().await {
if request.abandoned() {
continue;
}
if tx == TxState::Aborted {
match request {
Request::Rollback { reply } => {
tx = TxState::None;
if let Some(reply) = reply {
let _ = reply.send(Ok(()));
}
if !restore_isolation(&mut driver, &mut level_changed).await {
return;
}
}
Request::Commit { reply } => {
tx = TxState::None;
let _ = reply.send(Err(Error::TransactionAborted));
if !restore_isolation(&mut driver, &mut level_changed).await {
return;
}
}
other => other.fail(Error::TransactionAborted),
}
continue;
}
match request {
Request::Query { sql, params, reply } => {
let work = async |driver: &mut Driver| {
run_query(driver, &sql, ¶ms, legacy_datetime)
.await
.map_err(|e| e.with_statement(StatementKind::Query, &sql, params.len()))
};
if !serve(&mut driver, reply, work).await {
return;
}
}
Request::QueryStream {
sql,
params,
header,
rows,
} => {
let work = async |driver: &mut Driver| {
stream_query(driver, &sql, ¶ms, legacy_datetime, header, &rows).await
};
if !matches!(
cancellable(&mut driver, rows.closed(), work).await,
Some(false)
) {
return;
}
}
Request::Execute { sql, params, reply } => {
let work = async |driver: &mut Driver| {
let result = if params.is_empty() {
driver.simple_execute(&sql).await
} else {
let bound = bind(¶ms, legacy_datetime);
let refs = as_driver_refs(&bound);
driver.execute(&sql, &refs).await
};
result.map(|r| r.total()).map_err(|e| {
Error::from(e).with_statement(StatementKind::Query, &sql, params.len())
})
};
if !serve(&mut driver, reply, work).await {
return;
}
}
Request::Batch { sql, reply } => {
let work = async |driver: &mut Driver| {
match driver.simple_query(&sql).await {
Ok(stream) => collect_query_stream(stream).await,
Err(e) => Err(Error::from(e)),
}
.map_err(|e| e.with_statement(StatementKind::Batch, &sql, 0))
};
if !serve(&mut driver, reply, work).await {
return;
}
}
Request::ProcQuery {
name,
params,
reply,
} => {
let count = params.len();
let work = async |driver: &mut Driver| {
run_proc_query(driver, &name, params, legacy_datetime)
.await
.map_err(|e| e.with_statement(StatementKind::StoredProcedure, &name, count))
};
if !serve(&mut driver, reply, work).await {
return;
}
}
Request::ProcExecute {
name,
params,
reply,
} => {
let count = params.len();
let work = async |driver: &mut Driver| {
run_proc_execute(driver, &name, params, legacy_datetime)
.await
.map_err(|e| e.with_statement(StatementKind::StoredProcedure, &name, count))
};
if !serve(&mut driver, reply, work).await {
return;
}
}
Request::Begin { level, reply } => {
let result = driver
.begin_transaction_with_isolation(level.into())
.await
.map_err(Error::from)
.map_err(|e| {
e.with_statement(StatementKind::TransactionControl, "BEGIN TRANSACTION", 0)
});
let began = result.is_ok();
if began
&& !matches!(
level,
IsolationLevel::ReadCommitted | IsolationLevel::Unspecified
)
{
level_changed = true;
}
let (result, lost) = connection_lost(result);
match reply.send(result) {
Ok(()) if began => tx = TxState::Open,
Err(_) if began => {
if driver.rollback_transaction().await.is_err() {
return;
}
if !restore_isolation(&mut driver, &mut level_changed).await {
return;
}
}
_ => {}
}
if lost {
return;
}
}
Request::Commit { reply } => {
tx = TxState::None;
let result = driver
.commit_transaction()
.await
.map_err(Error::from)
.map_err(|e| {
e.with_statement(StatementKind::TransactionControl, "COMMIT TRANSACTION", 0)
});
let (result, lost) = connection_lost(result);
let _ = reply.send(result);
if lost || !restore_isolation(&mut driver, &mut level_changed).await {
return;
}
}
Request::Savepoint { name, reply } => {
let sql = format!("SAVE TRANSACTION [{name}]");
let result = match driver.simple_query(&sql).await {
Ok(stream) => collect_query_stream(stream).await.map(|_| ()),
Err(e) => Err(Error::from(e)),
};
let (result, lost) = connection_lost(
result
.map_err(|e| e.with_statement(StatementKind::TransactionControl, &sql, 0)),
);
let _ = reply.send(result);
if lost {
return;
}
}
Request::Rollback { reply } => {
tx = TxState::None;
let result = driver
.rollback_transaction()
.await
.map_err(Error::from)
.map_err(|e| {
e.with_statement(
StatementKind::TransactionControl,
"ROLLBACK TRANSACTION",
0,
)
});
let (result, lost) = connection_lost(result);
if let Some(reply) = reply {
let _ = reply.send(result);
}
if lost || !restore_isolation(&mut driver, &mut level_changed).await {
return;
}
}
Request::RollbackTo { name, reply } => {
let sql = format!("ROLLBACK TRANSACTION [{name}]");
let result = match driver.simple_query(&sql).await {
Ok(stream) => collect_query_stream(stream).await.map(|_| ()),
Err(e) => Err(Error::from(e)),
}
.map_err(|e| e.with_statement(StatementKind::TransactionControl, &sql, 0));
let (result, lost) = connection_lost(result);
if let Some(reply) = reply {
let _ = reply.send(result);
}
if lost {
return;
}
}
}
if tx == TxState::Open && !driver.in_transaction() {
tx = TxState::Aborted;
}
}
let _ = driver.close().await;
}
async fn open_query<'a>(
driver: &'a mut Driver,
sql: &str,
params: &[DataValue],
legacy_datetime: bool,
) -> tds_driver::Result<tds_driver::QueryStream<'a>> {
if params.is_empty() {
driver.simple_query(sql.to_string()).await
} else {
let bound = bind(params, legacy_datetime);
let refs = as_driver_refs(&bound);
driver.query(sql.to_string(), &refs).await
}
}
async fn run_query(
driver: &mut Driver,
sql: &str,
params: &[DataValue],
legacy_datetime: bool,
) -> Result<DataSet> {
let stream = open_query(driver, sql, params, legacy_datetime).await?;
collect_query_stream(stream).await
}
const STREAM_BUFFER: usize = 64;
async fn stream_query(
driver: &mut Driver,
sql: &str,
params: &[DataValue],
legacy_datetime: bool,
header: Reply<Arc<[Column]>>,
rows: &mpsc::Sender<Result<Streamed>>,
) -> bool {
let mut header = Some(header);
let mut columns: Arc<[Column]> = Arc::from(Vec::new());
let mut forwarding = true;
let mut failure: Option<Error> = None;
match open_query(driver, sql, params, legacy_datetime).await {
Err(e) => failure = Some(e.into()),
Ok(mut stream) => {
while let Some(item) = stream.next().await {
match item {
Ok(tds_driver::QueryItem::Metadata(meta)) => match header.take() {
Some(header) => {
columns = columns_of(&meta);
forwarding = header.send(Ok(Arc::clone(&columns))).is_ok();
}
None => forwarding = false,
},
Ok(tds_driver::QueryItem::Row(row)) if forwarding => {
match row_of(row, &columns) {
Ok(row) => {
forwarding = rows.send(Ok(Streamed::Row(row))).await.is_ok();
}
Err(e) => {
failure = Some(e);
forwarding = false;
}
}
}
Ok(tds_driver::QueryItem::Row(_)) => {}
Err(e) => {
failure.get_or_insert(e.into());
break;
}
}
}
}
}
let (last, lost) = connection_lost(match failure {
Some(e) => Err(e.with_statement(StatementKind::Query, sql, params.len())),
None => Ok(Streamed::End),
});
match header {
Some(header) => match last {
Err(e) => {
let _ = header.send(Err(e));
}
Ok(end) => {
if header.send(Ok(columns)).is_ok() {
let _ = rows.send(Ok(end)).await;
}
}
},
None => {
let _ = rows.send(last).await;
}
}
lost
}
fn build_proc<'a>(
name: &str,
params: Vec<(String, DataValue)>,
legacy_datetime: bool,
) -> tds_driver::Command<'a> {
let mut cmd = tds_driver::Command::new(name.to_string());
for (pname, value) in params {
match value {
DataValue::DateTime(dt) if legacy_datetime => {
cmd.bind_param(pname, LegacyDateTime(to_legacy_datetime(dt)));
}
value => cmd.bind_param(pname, value),
}
}
cmd
}
async fn run_proc_query(
driver: &mut Driver,
name: &str,
params: Vec<(String, DataValue)>,
legacy_datetime: bool,
) -> Result<DataSet> {
let mut stream = build_proc(name, params, legacy_datetime)
.exec(driver)
.await?;
let mut dataset = DataSet::new();
let mut current: Option<(DataTable, Arc<[Column]>)> = None;
let mut failure: Option<Error> = None;
while let Some(item) = stream.next().await {
match item {
Ok(tds_driver::CommandItem::Metadata(meta)) => {
if let Some((table, _)) = current.take() {
dataset.push(table);
}
let columns = columns_of(&meta);
let tname = format!("table{}", meta.result_index());
current = Some((DataTable::new(tname, Arc::clone(&columns)), columns));
}
Ok(tds_driver::CommandItem::Row(row)) if failure.is_none() => {
let (table, columns) = current.get_or_insert_with(|| {
let columns: Arc<[Column]> = Arc::from(Vec::new());
(DataTable::new("table0", Arc::clone(&columns)), columns)
});
match row_of(row, columns) {
Ok(row) => table.push(row),
Err(e) => failure = Some(e),
}
}
Ok(tds_driver::CommandItem::Row(_)) => {}
Ok(_) => {}
Err(e) => {
failure.get_or_insert(e.into());
break;
}
}
}
if let Some(e) = failure {
return Err(e);
}
if let Some((table, _)) = current.take() {
dataset.push(table);
}
Ok(dataset)
}
async fn run_proc_execute(
driver: &mut Driver,
name: &str,
params: Vec<(String, DataValue)>,
legacy_datetime: bool,
) -> Result<u64> {
let mut stream = build_proc(name, params, legacy_datetime)
.exec(driver)
.await?;
let mut total = 0u64;
while let Some(item) = stream.next().await {
if let tds_driver::CommandItem::RowsAffected(n) = item? {
total += n;
}
}
Ok(total)
}
pub struct Client {
tx: mpsc::UnboundedSender<Request>,
savepoints: AtomicU64,
}
impl std::fmt::Debug for Client {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Client")
.field("open", &!self.tx.is_closed())
.finish()
}
}
impl Client {
pub async fn connect(config: &Config) -> Result<Self> {
let cfg = config.to_driver()?;
let server = cfg.get_addr();
let limit = cfg.connect_timeout();
let open = open(cfg);
let opened = match limit {
Some(limit) => tokio::time::timeout(limit, open).await.unwrap_or_else(|_| {
Err(tds_driver::error::Error::Io {
kind: std::io::ErrorKind::TimedOut,
message: format!("no connection within {limit:?}"),
})
}),
None => open.await,
};
let driver = opened.map_err(|e| Error::Connect {
server,
source: Box::new(e),
})?;
let (tx, rx) = mpsc::unbounded_channel();
tokio::spawn(connection_task(driver, rx, config.legacy_datetime()));
Ok(Self {
tx,
savepoints: AtomicU64::new(0),
})
}
pub async fn connect_str(connection_string: &str) -> Result<Self> {
Self::connect(&Config::from_ado_string(connection_string)).await
}
async fn call<T>(&self, build: impl FnOnce(Reply<T>) -> Request) -> Result<T> {
let (reply_tx, reply_rx) = oneshot::channel();
self.tx
.send(build(reply_tx))
.map_err(|_| Error::ConnectionClosed)?;
reply_rx.await.map_err(|_| Error::ConnectionClosed)?
}
fn enqueue(&self, request: Request) {
let _ = self.tx.send(request);
}
pub async fn query(&mut self, sql: &str, params: &[&dyn ToSql]) -> Result<Vec<Row>> {
Ok(self
.query_dataset_values(sql, to_values(params))
.await?
.into_rows())
}
pub async fn query_stream(
&mut self,
sql: &str,
params: &[&dyn ToSql],
) -> Result<RowStream<'_>> {
let (sql, params) = (sql.to_string(), to_values(params));
let (rows, receiver) = mpsc::channel(STREAM_BUFFER);
let columns = self
.call(|header| Request::QueryStream {
sql,
params,
header,
rows,
})
.await?;
Ok(RowStream::new(columns, receiver))
}
pub async fn query_one(&mut self, sql: &str, params: &[&dyn ToSql]) -> Result<Row> {
let mut rows = self.query(sql, params).await?;
match rows.len() {
1 => Ok(rows.pop().expect("length checked")),
found => Err(Error::UnexpectedRowCount { found }),
}
}
pub async fn query_opt(&mut self, sql: &str, params: &[&dyn ToSql]) -> Result<Option<Row>> {
let mut rows = self.query(sql, params).await?;
match rows.len() {
0 => Ok(None),
1 => Ok(Some(rows.pop().expect("length checked"))),
found => Err(Error::UnexpectedRowCount { found }),
}
}
pub async fn query_scalar<T: FromSql>(
&mut self,
sql: &str,
params: &[&dyn ToSql],
) -> Result<T> {
let row = self.query_one(sql, params).await?;
if row.is_empty() {
return Err(Error::ColumnIndexOutOfRange { index: 0, len: 0 });
}
row.try_get(0usize)
}
pub async fn execute(&mut self, sql: &str, params: &[&dyn ToSql]) -> Result<u64> {
let (sql, params) = (sql.to_string(), to_values(params));
self.call(|reply| Request::Execute { sql, params, reply })
.await
}
pub async fn batch(&mut self, sql: &str) -> Result<Vec<Row>> {
Ok(self.batch_dataset(sql).await?.into_rows())
}
pub async fn batch_dataset(&mut self, sql: &str) -> Result<DataSet> {
let sql = sql.to_string();
self.call(|reply| Request::Batch { sql, reply }).await
}
pub async fn query_dataset(&mut self, command: &Command) -> Result<DataSet> {
match command.prepare() {
Prepared::Text { sql, params } => self.query_dataset_values(&sql, params).await,
Prepared::Proc { name, params } => {
self.call(|reply| Request::ProcQuery {
name,
params,
reply,
})
.await
}
}
}
pub async fn execute_command(&mut self, command: &Command) -> Result<u64> {
match command.prepare() {
Prepared::Text { sql, params } => {
self.call(|reply| Request::Execute { sql, params, reply })
.await
}
Prepared::Proc { name, params } => {
self.call(|reply| Request::ProcExecute {
name,
params,
reply,
})
.await
}
}
}
async fn query_dataset_values(&self, sql: &str, params: Vec<DataValue>) -> Result<DataSet> {
let sql = sql.to_string();
self.call(|reply| Request::Query { sql, params, reply })
.await
}
pub async fn transaction(&mut self) -> Result<Transaction<'_>> {
Transaction::begin(self, IsolationLevel::ReadCommitted).await
}
pub async fn transaction_with_isolation(
&mut self,
level: IsolationLevel,
) -> Result<Transaction<'_>> {
Transaction::begin(self, level).await
}
pub async fn close(self) -> Result<()> {
drop(self);
Ok(())
}
pub(crate) async fn begin_transaction(&mut self, level: IsolationLevel) -> Result<()> {
self.call(|reply| Request::Begin { level, reply }).await
}
pub(crate) async fn commit_transaction(&mut self) -> Result<()> {
self.call(|reply| Request::Commit { reply }).await
}
pub(crate) async fn rollback_transaction(&mut self) -> Result<()> {
self.call(|reply| Request::Rollback { reply: Some(reply) })
.await
}
pub(crate) async fn save_transaction(&mut self, name: &str) -> Result<()> {
let name = name.to_string();
self.call(|reply| Request::Savepoint { name, reply }).await
}
pub(crate) async fn rollback_to_savepoint(&mut self, name: &str) -> Result<()> {
let name = name.to_string();
self.call(|reply| Request::RollbackTo {
name,
reply: Some(reply),
})
.await
}
pub(crate) fn enqueue_rollback(&self) {
self.enqueue(Request::Rollback { reply: None });
}
pub(crate) fn enqueue_rollback_to(&self, name: String) {
self.enqueue(Request::RollbackTo { name, reply: None });
}
pub(crate) fn next_savepoint_id(&self) -> u64 {
self.savepoints.fetch_add(1, Ordering::Relaxed)
}
}
#[cfg(test)]
mod tests;