use crate::{
connection::Connection,
error::{Error, Result},
pool::{Pool, PoolState},
result_set::ResultSet,
types::{appendable::AppendAble, value::DuckValue},
};
#[derive(Clone)]
pub struct AsyncPool {
inner: Pool,
}
impl AsyncPool {
pub fn new(pool: Pool) -> AsyncPool {
AsyncPool { inner: pool }
}
pub fn pool(&self) -> &Pool {
&self.inner
}
pub async fn state(&self) -> PoolState {
self.inner.state()
}
pub async fn with<F, T>(
&self,
f: F,
) -> Result<T>
where
F: FnOnce(&mut Connection) -> Result<T> + Send + 'static,
T: Send + 'static,
{
let pool = self.inner.clone();
tokio::task::spawn_blocking(move || {
let mut conn = pool.get().map_err(|e| Error::Pool(e.to_string()))?;
f(&mut conn)
})
.await
.map_err(|e| Error::BackgroundTaskFailed(e.to_string()))?
}
pub async fn execute_batch<S>(
&self,
sql: S,
) -> Result<()>
where
S: Into<String> + Send,
{
let sql = sql.into();
self.with(move |conn| conn.execute_batch(&sql)).await
}
pub async fn execute<S>(
&self,
sql: S,
) -> Result<ResultSet>
where
S: Into<String> + Send,
{
let sql = sql.into();
self.with(move |conn| conn.execute(&sql)?.materialize()).await
}
pub async fn execute_with<S>(
&self,
sql: S,
binds: Vec<DuckValue>,
) -> Result<ResultSet>
where
S: Into<String> + Send,
{
let sql = sql.into();
self.with(move |conn| {
let mut owned = binds;
let mut refs: Vec<&mut dyn AppendAble> =
owned.iter_mut().map(|v| v as &mut dyn AppendAble).collect();
conn.execute_with(&sql, &mut refs)?.materialize()
})
.await
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pool::DuckDbConnectionManager;
fn make_pool(max_size: u32) -> AsyncPool {
let manager = DuckDbConnectionManager::memory().unwrap();
let pool = Pool::builder().max_size(max_size).build(manager).unwrap();
AsyncPool::new(pool)
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn concurrent_pool_queries() {
let pool = make_pool(4);
pool.execute_batch("CREATE TABLE t (id INTEGER)").await.unwrap();
let mut handles = Vec::new();
for _ in 0..32 {
let p = pool.clone();
handles.push(tokio::spawn(async move {
p.execute_with("INSERT INTO t VALUES ($1)", vec![DuckValue::Int(1)]).await.unwrap();
}));
}
for h in handles {
h.await.unwrap();
}
let result = pool.execute("SELECT count(*) AS c FROM t").await.unwrap();
match result.rows()[0].get("c").unwrap() {
DuckValue::BigInt(n) => assert_eq!(*n, 32),
other => panic!("expected BigInt, got {other:?}"),
}
}
#[tokio::test]
async fn pool_with_runs_transaction() {
let pool = make_pool(2);
pool.execute_batch("CREATE TABLE t (id INTEGER)").await.unwrap();
pool.with(|conn| {
conn.execute_batch("BEGIN")?;
conn.execute_batch("INSERT INTO t VALUES (1)")?;
conn.execute_batch("COMMIT")?;
Ok(())
})
.await
.unwrap();
let result = pool.execute("SELECT count(*) AS c FROM t").await.unwrap();
match result.rows()[0].get("c").unwrap() {
DuckValue::BigInt(n) => assert_eq!(*n, 1),
other => panic!("expected BigInt, got {other:?}"),
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn pool_does_not_block_the_executor() {
let pool = make_pool(1);
let slow = {
let p = pool.clone();
tokio::spawn(async move {
p.with(|conn| {
conn.execute_batch("SELECT 1")?;
std::thread::sleep(std::time::Duration::from_millis(200));
Ok(())
})
.await
.unwrap();
})
};
let start = tokio::time::Instant::now();
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
assert!(start.elapsed() < std::time::Duration::from_millis(150));
slow.await.unwrap();
}
}