use futures_util::io::{AsyncRead, AsyncWrite};
use names::{Generator, Name};
use once_cell::sync::Lazy;
use std::cell::RefCell;
use std::env;
use std::sync::Once;
use tiberius::{numeric::Numeric, Command, Result, TableValueRow};
use runtimes_macro::test_on_runtimes;
#[allow(dead_code)]
static LOGGER_SETUP: Once = Once::new();
static CONN_STR: Lazy<String> = Lazy::new(|| {
env::var("TIBERIUS_TEST_CONNECTION_STRING").unwrap_or_else(|_| {
"server=tcp:localhost,1433;user=SA;password=<YourStrong@Passw0rd>;IntegratedSecurity=true;TrustServerCertificate=true".to_owned()
})
});
thread_local! {
static NAMES: RefCell<Option<Generator<'static>>> =
const { RefCell::new(None) };
}
async fn random_table() -> String {
NAMES.with(|maybe_generator| {
maybe_generator
.borrow_mut()
.get_or_insert_with(|| Generator::with_naming(Name::Plain))
.next()
.unwrap()
.replace('-', "")
})
}
#[test_on_runtimes]
async fn basic_proc_exec<S>(mut conn: tiberius::Client<S>) -> Result<()>
where
S: AsyncRead + AsyncWrite + Unpin + Send,
{
let table = random_table().await;
let proc = random_table().await;
conn.simple_query(format!(
r#"
create table ##{} (
id int identity(1,1),
other varchar(50),
)
"#,
table
))
.await?;
conn.simple_query(format!(
r#"
create or alter procedure {}
@Param1 varchar(50)
as
insert into ##{} (other)
values (@Param1)
return scope_identity()
"#,
proc, table,
))
.await?;
let mut ins_cmd = Command::new(&proc);
ins_cmd.bind_param("@Param1", "some text");
let result = ins_cmd.exec(&mut conn).await?.into_command_result().await?;
assert_eq!(1, result.return_code());
let mut ins_cmd = Command::new(&proc);
ins_cmd.bind_param("@Param1", "another text");
let result = ins_cmd.exec(&mut conn).await?.into_command_result().await?;
assert_eq!(2, result.return_code());
Ok(())
}
struct GeoTest {
id: i32,
lat: Numeric,
lon: Numeric,
}
impl<'a> TableValueRow<'a> for GeoTest {
fn bind_fields(&self, data_row: &mut tiberius::SqlTableDataRow<'a>) {
data_row.add_field(self.id);
data_row.add_field(self.lat);
data_row.add_field(self.lon);
}
fn get_db_type() -> &'static str {
"GeoTest"
}
}
#[test_on_runtimes]
async fn tvp_proc_exec<S>(mut conn: tiberius::Client<S>) -> Result<()>
where
S: AsyncRead + AsyncWrite + Unpin + Send,
{
let table = random_table().await;
let proc_tvp = random_table().await;
let proc_ins = random_table().await;
let proc_get = random_table().await;
let db_type = random_table().await;
conn.simple_query(format!(
r#"
create type dbo.[{}] as table
(
[ID] int not null,
[lat] decimal(9,6),
[lon] decimal(9,6)
)
"#,
db_type
))
.await?;
conn.simple_query(format!(
r#"
create table ##{} (
id int not null,
lat decimal(9,6),
lon decimal(9,6),
n varchar(50)
)
"#,
table
))
.await?;
conn.simple_query(format!(
r#"
create or alter procedure {}
@id int,
@geo dbo.[{}] readonly
as
update t set
[lat] = g.[lat],
[lon] = g.[lon]
from
##{} t
inner join @geo g on g.id = t.id
"#,
proc_tvp, db_type, table,
))
.await?;
conn.simple_query(format!(
r#"
create or alter procedure {}
@id int,
@name varchar(50)
as
insert into ##{} (id, n)
values (@id, @name)
"#,
proc_ins, table,
))
.await?;
conn.simple_query(format!(
r#"
create or alter procedure {}
@id int,
@count int out
as
set @count = (select count(*) from ##{})
select * from ##{}
"#,
proc_get, table, table
))
.await?;
let mut ins_cmd = Command::new(&proc_ins);
ins_cmd.bind_param("@id", 23);
ins_cmd.bind_param("@name", "the twenty three");
let result = ins_cmd.exec(&mut conn).await?.into_command_result().await?;
assert_eq!(0, result.return_code());
let g1 = GeoTest {
id: 23,
lon: Numeric::new_with_scale(141, 6),
lat: Numeric::new_with_scale(192, 6),
};
let g2 = GeoTest {
id: 78,
lon: Numeric::new_with_scale(1141, 6),
lat: Numeric::new_with_scale(8192, 6),
};
let tbl = vec![g1, g2];
let mut tvp_cmd = Command::new(&proc_tvp);
tvp_cmd.bind_param("@id", 23);
tvp_cmd.bind_table_with_dbtype("@geo", &db_type, tbl);
let result = tvp_cmd.exec(&mut conn).await?.into_command_result().await?;
assert_eq!(0, result.return_code());
let count = 0;
let mut get_cmd = Command::new(&proc_get);
get_cmd.bind_param("@id", 23);
get_cmd.bind_out_param("@count", count);
let result = get_cmd.exec(&mut conn).await?.into_command_result().await?;
assert_eq!(0, result.return_code());
let count: i32 = result.try_return_value("@count")?.unwrap();
assert_eq!(1, count);
let rows = result.to_query_result(0).unwrap();
let lat: Numeric = rows[0].get("lat").unwrap();
let lon: Numeric = rows[0].get("lon").unwrap();
assert_eq!(Numeric::new_with_scale(141, 6), lon);
assert_eq!(Numeric::new_with_scale(192, 6), lat);
Ok(())
}
#[test]
fn derive_table_value_row_binds_owned_and_borrowed_fields() {
use tiberius::{ColumnData, TableValue};
#[derive(TableValueRow)]
struct OwnedRow {
#[colname = "Id"]
id: i32,
#[colname = "Name"]
name: String,
#[colname = "Payload"]
payload: Vec<u8>,
#[colname = "Score"]
score: Numeric,
}
assert_eq!(OwnedRow::get_db_type(), "OwnedRow");
let rows = vec![
OwnedRow {
id: 1,
name: "one".to_owned(),
payload: vec![0xDE, 0xAD],
score: Numeric::new_with_scale(10, 0),
},
OwnedRow {
id: 2,
name: "two".to_owned(),
payload: vec![0xBE, 0xEF],
score: Numeric::new_with_scale(20, 0),
},
];
let data = TableValue::into_sql(rows);
assert_eq!(data.rows().len(), 2);
let cols = data.rows()[0].columns();
assert_eq!(cols.len(), 4);
assert!(matches!(cols[0], ColumnData::I32(Some(1))));
match &cols[1] {
ColumnData::String(Some(s)) => assert_eq!(s.as_ref(), "one"),
other => panic!("expected String, got {other:?}"),
}
match &cols[2] {
ColumnData::Binary(Some(b)) => assert_eq!(b.as_ref(), [0xDE, 0xAD]),
other => panic!("expected Binary, got {other:?}"),
}
assert!(matches!(cols[3], ColumnData::Numeric(Some(_))));
match &data.rows()[1].columns()[1] {
ColumnData::String(Some(s)) => assert_eq!(s.as_ref(), "two"),
other => panic!("expected String, got {other:?}"),
}
#[derive(TableValueRow)]
struct BorrowedRow<'a> {
#[colname = "Id"]
id: i32,
#[colname = "Name"]
name: &'a str,
}
let owned = String::from("borrowed");
let brows = vec![BorrowedRow {
id: 7,
name: &owned,
}];
let bdata = TableValue::into_sql(brows);
let bcols = bdata.rows()[0].columns();
assert!(matches!(bcols[0], ColumnData::I32(Some(7))));
match &bcols[1] {
ColumnData::String(Some(v)) => assert_eq!(v.as_ref(), "borrowed"),
other => panic!("expected String, got {other:?}"),
}
}