use std::future::Future;
use std::time::Duration;
use futures::FutureExt;
use tdsql::{Client, Command, Config};
fn test_config() -> Config {
Config::new()
.host("localhost")
.port(1433)
.database("master")
.auth("sa", "YourStrong!Passw0rd")
.trust_cert()
}
async fn connect() -> Client {
Client::connect(&test_config())
.await
.expect("failed to connect; is SQL Server running on localhost:1433?")
}
const ENDLESS: &str = "SELECT CAST(1 AS INT) AS n \
FROM sys.all_columns a CROSS JOIN sys.all_columns b CROSS JOIN sys.all_columns c";
const GIVE_UP_AFTER: Duration = Duration::from_millis(200);
const PROMPTLY: Duration = Duration::from_secs(10);
async fn promptly<T>(future: impl Future<Output = T>) -> T {
tokio::time::timeout(PROMPTLY, future)
.await
.expect("the connection is still busy with the abandoned request")
}
async fn give_up<T>(future: impl Future<Output = T>) {
let finished = tokio::time::timeout(GIVE_UP_AFTER, future).await.is_ok();
assert!(
!finished,
"the request finished before it could be given up on"
);
}
async fn assert_free(client: &mut Client) {
let n: i32 = promptly(client.query_scalar("SELECT 42", &[]))
.await
.unwrap();
assert_eq!(n, 42);
}
#[tokio::test]
#[ignore]
async fn dropping_a_stream_cancels_its_query() {
let mut client = connect().await;
let mut rows = client.query_stream(ENDLESS, &[]).await.unwrap();
rows.try_next().await.unwrap().unwrap();
drop(rows);
assert_free(&mut client).await;
}
#[tokio::test]
#[ignore]
async fn giving_up_on_query_cancels_it() {
let mut client = connect().await;
give_up(client.query(ENDLESS, &[])).await;
assert_free(&mut client).await;
}
#[tokio::test]
#[ignore]
async fn giving_up_on_a_batch_cancels_it() {
let mut client = connect().await;
give_up(client.batch(ENDLESS)).await;
assert_free(&mut client).await;
}
#[tokio::test]
#[ignore]
async fn giving_up_on_a_stored_procedure_cancels_it() {
let mut client = connect().await;
client
.batch(&format!("CREATE PROCEDURE #tdsql_endless AS {ENDLESS}"))
.await
.unwrap();
let endless = Command::stored_procedure("#tdsql_endless");
give_up(client.query_dataset(&endless)).await;
assert_free(&mut client).await;
give_up(client.execute_command(&endless)).await;
assert_free(&mut client).await;
}
#[tokio::test]
#[ignore]
async fn cancelling_stops_the_rest_of_the_batch() {
let mut client = connect().await;
client
.batch("CREATE TABLE #tdsql_cancel_after (id INT)")
.await
.unwrap();
let mut rows = client
.query_stream(
&format!(
"INSERT INTO #tdsql_cancel_after (id) VALUES (1); \
{ENDLESS}; \
INSERT INTO #tdsql_cancel_after (id) VALUES (2)"
),
&[],
)
.await
.unwrap();
rows.try_next().await.unwrap().unwrap();
drop(rows);
let ids: Vec<i32> = promptly(client.query("SELECT id FROM #tdsql_cancel_after", &[]))
.await
.unwrap()
.iter()
.map(|r| r.get("id"))
.collect();
assert_eq!(ids, [1]);
}
#[tokio::test]
#[ignore]
async fn a_request_given_up_on_while_queued_never_runs() {
let mut client = connect().await;
client
.batch("CREATE TABLE #tdsql_cancel_queued (id INT)")
.await
.unwrap();
let insert = client.execute("INSERT INTO #tdsql_cancel_queued (id) VALUES (1)", &[]);
assert!(insert.now_or_never().is_none());
let count: i32 =
promptly(client.query_scalar("SELECT COUNT(*) FROM #tdsql_cancel_queued", &[]))
.await
.unwrap();
assert_eq!(count, 0);
}
#[tokio::test]
#[ignore]
async fn cancelling_inside_a_transaction_leaves_it_open() {
let mut client = connect().await;
client
.batch("CREATE TABLE #tdsql_cancel_tx (id INT)")
.await
.unwrap();
let mut tx = client.transaction().await.unwrap();
tx.execute("INSERT INTO #tdsql_cancel_tx (id) VALUES (1)", &[])
.await
.unwrap();
give_up(tx.query(ENDLESS, &[])).await;
let depth: i32 = promptly(tx.query_scalar("SELECT @@TRANCOUNT", &[]))
.await
.unwrap();
assert_eq!(depth, 1);
tx.commit().await.unwrap();
let count: i32 = client
.query_scalar("SELECT COUNT(*) FROM #tdsql_cancel_tx", &[])
.await
.unwrap();
assert_eq!(count, 1);
}
#[tokio::test]
#[ignore]
async fn cancelling_after_the_server_has_finished_leaves_the_connection_clean() {
let mut client = connect().await;
let mut rows = client
.query_stream(
"SELECT TOP (1000) CAST(1 AS INT) AS n FROM sys.all_columns",
&[],
)
.await
.unwrap();
rows.try_next().await.unwrap().unwrap();
tokio::time::sleep(Duration::from_millis(200)).await;
drop(rows);
assert_free(&mut client).await;
}
const SILENT: &str = "WAITFOR DELAY '00:00:30'; SELECT 1 AS n";
#[tokio::test]
#[ignore]
async fn giving_up_on_a_query_that_has_sent_nothing_back_cancels_it() {
let mut client = connect().await;
give_up(client.query(SILENT, &[])).await;
assert_free(&mut client).await;
}
#[tokio::test]
#[ignore]
async fn giving_up_on_execute_cancels_it() {
let mut client = connect().await;
client
.batch("CREATE TABLE #tdsql_cancel_execute (id INT)")
.await
.unwrap();
give_up(client.execute(
"WAITFOR DELAY '00:00:30'; INSERT INTO #tdsql_cancel_execute (id) VALUES (1)",
&[],
))
.await;
let count: i32 =
promptly(client.query_scalar("SELECT COUNT(*) FROM #tdsql_cancel_execute", &[]))
.await
.unwrap();
assert_eq!(count, 0);
}
#[tokio::test]
#[ignore]
async fn giving_up_and_dropping_the_client_stops_the_query_on_the_server() {
let mut observer = connect().await;
let mut client = connect().await;
let session: i16 = client.query_scalar("SELECT @@SPID", &[]).await.unwrap();
give_up(client.query(SILENT, &[])).await;
drop(client);
promptly(async {
while observer
.query_scalar::<i32>(
"SELECT COUNT(*) FROM sys.dm_exec_requests WHERE session_id = @P1",
&[&session],
)
.await
.unwrap()
> 0
{
tokio::time::sleep(Duration::from_millis(50)).await;
}
})
.await;
}