better_duck_core/asynchronous/
pool.rs1use crate::{
2 connection::Connection,
3 error::{Error, Result},
4 pool::{Pool, PoolState},
5 result_set::ResultSet,
6 types::{appendable::AppendAble, value::DuckValue},
7};
8
9#[derive(Clone)]
18pub struct AsyncPool {
19 inner: Pool,
20}
21
22impl AsyncPool {
23 pub fn new(pool: Pool) -> AsyncPool {
25 AsyncPool { inner: pool }
26 }
27
28 pub fn pool(&self) -> &Pool {
30 &self.inner
31 }
32
33 pub async fn state(&self) -> PoolState {
35 self.inner.state()
36 }
37
38 pub async fn with<F, T>(
48 &self,
49 f: F,
50 ) -> Result<T>
51 where
52 F: FnOnce(&mut Connection) -> Result<T> + Send + 'static,
53 T: Send + 'static,
54 {
55 let pool = self.inner.clone();
56 tokio::task::spawn_blocking(move || {
57 let mut conn = pool.get().map_err(|e| Error::Pool(e.to_string()))?;
58 f(&mut conn)
59 })
60 .await
61 .map_err(|e| Error::BackgroundTaskFailed(e.to_string()))?
62 }
63
64 pub async fn execute_batch<S>(
71 &self,
72 sql: S,
73 ) -> Result<()>
74 where
75 S: Into<String> + Send,
76 {
77 let sql = sql.into();
78 self.with(move |conn| conn.execute_batch(&sql)).await
79 }
80
81 pub async fn execute<S>(
88 &self,
89 sql: S,
90 ) -> Result<ResultSet>
91 where
92 S: Into<String> + Send,
93 {
94 let sql = sql.into();
95 self.with(move |conn| conn.execute(&sql)?.materialize()).await
96 }
97
98 pub async fn execute_with<S>(
106 &self,
107 sql: S,
108 binds: Vec<DuckValue>,
109 ) -> Result<ResultSet>
110 where
111 S: Into<String> + Send,
112 {
113 let sql = sql.into();
114 self.with(move |conn| {
115 let mut owned = binds;
116 let mut refs: Vec<&mut dyn AppendAble> =
117 owned.iter_mut().map(|v| v as &mut dyn AppendAble).collect();
118 conn.execute_with(&sql, &mut refs)?.materialize()
119 })
120 .await
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use crate::pool::DuckDbConnectionManager;
128
129 fn make_pool(max_size: u32) -> AsyncPool {
130 let manager = DuckDbConnectionManager::memory().unwrap();
131 let pool = Pool::builder().max_size(max_size).build(manager).unwrap();
132 AsyncPool::new(pool)
133 }
134
135 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
136 async fn concurrent_pool_queries() {
137 let pool = make_pool(4);
138 pool.execute_batch("CREATE TABLE t (id INTEGER)").await.unwrap();
139
140 let mut handles = Vec::new();
141 for _ in 0..32 {
142 let p = pool.clone();
143 handles.push(tokio::spawn(async move {
144 p.execute_with("INSERT INTO t VALUES ($1)", vec![DuckValue::Int(1)]).await.unwrap();
145 }));
146 }
147 for h in handles {
148 h.await.unwrap();
149 }
150
151 let result = pool.execute("SELECT count(*) AS c FROM t").await.unwrap();
152 match result.rows()[0].get("c").unwrap() {
153 DuckValue::BigInt(n) => assert_eq!(*n, 32),
154 other => panic!("expected BigInt, got {other:?}"),
155 }
156 }
157
158 #[tokio::test]
159 async fn pool_with_runs_transaction() {
160 let pool = make_pool(2);
161 pool.execute_batch("CREATE TABLE t (id INTEGER)").await.unwrap();
162 pool.with(|conn| {
163 conn.execute_batch("BEGIN")?;
164 conn.execute_batch("INSERT INTO t VALUES (1)")?;
165 conn.execute_batch("COMMIT")?;
166 Ok(())
167 })
168 .await
169 .unwrap();
170 let result = pool.execute("SELECT count(*) AS c FROM t").await.unwrap();
171 match result.rows()[0].get("c").unwrap() {
172 DuckValue::BigInt(n) => assert_eq!(*n, 1),
173 other => panic!("expected BigInt, got {other:?}"),
174 }
175 }
176
177 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
178 async fn pool_does_not_block_the_executor() {
179 let pool = make_pool(1);
180 let slow = {
181 let p = pool.clone();
182 tokio::spawn(async move {
183 p.with(|conn| {
184 conn.execute_batch("SELECT 1")?;
185 std::thread::sleep(std::time::Duration::from_millis(200));
186 Ok(())
187 })
188 .await
189 .unwrap();
190 })
191 };
192 let start = tokio::time::Instant::now();
193 tokio::time::sleep(std::time::Duration::from_millis(10)).await;
194 assert!(start.elapsed() < std::time::Duration::from_millis(150));
195 slow.await.unwrap();
196 }
197}