better_duck_core/asynchronous/
connection.rs1use std::{path::Path, sync::Arc};
2
3use parking_lot::Mutex;
4
5use crate::{
6 connection::Connection,
7 error::{Error, Result},
8 raw::{
9 connection::{ConnectionInner, QueryControl},
10 statement::CachedStatement,
11 },
12 result_set::ResultSet,
13 types::{appendable::AppendAble, value::DuckValue},
14 Appender,
15};
16
17#[derive(Clone)]
42pub struct AsyncConnection {
43 inner: Arc<Mutex<Connection>>,
44 control_src: Arc<ConnectionInner>,
49}
50
51impl AsyncConnection {
52 pub fn new(conn: Connection) -> AsyncConnection {
54 let control_src = Arc::clone(conn.inner());
55 AsyncConnection { inner: Arc::new(Mutex::new(conn)), control_src }
56 }
57
58 pub fn try_into_inner(self) -> std::result::Result<Connection, AsyncConnection> {
62 let control_src = Arc::clone(&self.control_src);
63 Arc::try_unwrap(self.inner)
64 .map(Mutex::into_inner)
65 .map_err(|inner| AsyncConnection { inner, control_src })
66 }
67
68 #[must_use]
74 pub fn query_control(&self) -> QueryControl {
75 QueryControl::from_inner(Arc::clone(&self.control_src))
76 }
77
78 pub fn interrupt(&self) -> bool {
83 self.query_control().interrupt()
84 }
85
86 pub async fn open<P>(path: P) -> Result<AsyncConnection>
92 where
93 P: AsRef<Path> + Send + 'static,
94 {
95 let conn = tokio::task::spawn_blocking(move || Connection::open(path))
96 .await
97 .map_err(|e| Error::BackgroundTaskFailed(e.to_string()))??;
98 Ok(AsyncConnection::new(conn))
99 }
100
101 pub async fn open_in_memory() -> Result<AsyncConnection> {
107 let conn = tokio::task::spawn_blocking(Connection::open_in_memory)
108 .await
109 .map_err(|e| Error::BackgroundTaskFailed(e.to_string()))??;
110 Ok(AsyncConnection::new(conn))
111 }
112
113 pub async fn with_connection<F, T>(
123 &self,
124 f: F,
125 ) -> Result<T>
126 where
127 F: FnOnce(&mut Connection) -> Result<T> + Send + 'static,
128 T: Send + 'static,
129 {
130 let inner = Arc::clone(&self.inner);
131 let handle = tokio::task::spawn_blocking(move || {
132 let mut guard = inner.lock();
133 f(&mut guard)
134 });
135
136 let mut interrupt_on_drop = InterruptOnDrop::new(self.query_control());
142 let result = handle.await.map_err(|e| Error::BackgroundTaskFailed(e.to_string()));
143 interrupt_on_drop.disarm();
144 result?
145 }
146
147 pub async fn execute_batch<S>(
153 &self,
154 sql: S,
155 ) -> Result<()>
156 where
157 S: Into<String> + Send,
158 {
159 let sql = sql.into();
160 self.with_connection(move |conn| conn.execute_batch(&sql)).await
161 }
162
163 pub async fn execute<S>(
169 &self,
170 sql: S,
171 ) -> Result<ResultSet>
172 where
173 S: Into<String> + Send,
174 {
175 let sql = sql.into();
176 self.with_connection(move |conn| conn.execute(&sql)?.materialize()).await
177 }
178
179 pub async fn execute_with<S>(
185 &self,
186 sql: S,
187 binds: Vec<DuckValue>,
188 ) -> Result<ResultSet>
189 where
190 S: Into<String> + Send,
191 {
192 let sql = sql.into();
193 self.with_connection(move |conn| {
194 let mut owned = binds;
195 let mut refs: Vec<&mut dyn AppendAble> =
196 owned.iter_mut().map(|v| v as &mut dyn AppendAble).collect();
197 conn.execute_with(&sql, &mut refs)?.materialize()
198 })
199 .await
200 }
201
202 pub async fn execute_pending<S>(
217 &self,
218 sql: S,
219 ) -> Result<ResultSet>
220 where
221 S: Into<String> + Send,
222 {
223 use crate::raw::pending::{OwnedPending, PendingState};
224
225 let sql = sql.into();
226
227 let mut pending: OwnedPending = self
229 .with_connection(move |conn| CachedStatement::prepare(conn.db(), &sql)?.into_pending())
230 .await?;
231
232 loop {
236 let (next, returned) = self
239 .dispatch(move || {
240 let state = pending.execute_task();
241 (state, pending)
242 })
243 .await?;
244 pending = returned;
245
246 match next {
247 PendingState::Ready => break,
248 PendingState::NotReady => {
249 tokio::task::yield_now().await;
252 },
253 PendingState::NoTasksAvailable => {
254 break;
260 },
261 PendingState::Error => {
262 let msg = pending.error();
265 return Err(Error::Engine(crate::error::EngineError::unavailable(Some(
266 msg.unwrap_or_else(|| "pending execution failed".to_owned()),
267 ))));
268 },
269 PendingState::Unknown(_) => {
270 tokio::task::yield_now().await;
272 },
273 }
274 }
275
276 self.dispatch(move || pending.execute()?.materialize()).await?
278 }
279
280 async fn dispatch<F, T>(
285 &self,
286 f: F,
287 ) -> Result<T>
288 where
289 F: FnOnce() -> T + Send + 'static,
290 T: Send + 'static,
291 {
292 let handle = tokio::task::spawn_blocking(f);
293 let mut interrupt_on_drop = InterruptOnDrop::new(self.query_control());
294 let out = handle.await.map_err(|e| Error::BackgroundTaskFailed(e.to_string()));
295 interrupt_on_drop.disarm();
296 out
297 }
298
299 pub async fn with_appender<F, T>(
310 &self,
311 table: impl Into<String> + Send,
312 schema: impl Into<String> + Send,
313 f: F,
314 ) -> Result<T>
315 where
316 F: FnOnce(&mut Appender) -> Result<T> + Send + 'static,
317 T: Send + 'static,
318 {
319 let table = table.into();
320 let schema = schema.into();
321 self.with_connection(move |conn| {
322 let mut appender = conn.appender(&table, &schema)?;
323 f(&mut appender)
324 })
325 .await
326 }
327
328 #[must_use]
333 pub fn progress(&self) -> Option<crate::raw::connection::QueryProgress> {
334 self.query_control().progress()
335 }
336}
337
338struct InterruptOnDrop {
349 control: QueryControl,
350 armed: bool,
351}
352
353impl InterruptOnDrop {
354 fn new(control: QueryControl) -> InterruptOnDrop {
355 InterruptOnDrop { control, armed: true }
356 }
357
358 fn disarm(&mut self) {
359 self.armed = false;
360 }
361}
362
363impl Drop for InterruptOnDrop {
364 fn drop(&mut self) {
365 if self.armed {
366 self.control.interrupt();
367 }
368 }
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374
375 fn assert_send<T: Send>(_: T) {}
376 fn assert_send_sync<T: Send + Sync>() {}
377 fn assert_clone<T: Clone>() {}
378
379 #[test]
380 fn async_connection_is_send_sync_clone() {
381 assert_send_sync::<AsyncConnection>();
382 assert_clone::<AsyncConnection>();
383 }
384
385 #[tokio::test]
386 async fn execute_future_is_send() {
387 let conn = AsyncConnection::open_in_memory().await.unwrap();
388 assert_send(conn.execute("SELECT 1"));
389 }
390
391 #[tokio::test]
392 async fn execute_batch_then_execute() {
393 let conn = AsyncConnection::open_in_memory().await.unwrap();
394 conn.execute_batch("CREATE TABLE t (id INTEGER)").await.unwrap();
395 conn.execute_batch("INSERT INTO t VALUES (1)").await.unwrap();
396 let result = conn.execute("SELECT id FROM t").await.unwrap();
397 assert_eq!(result.len(), 1);
398 }
399
400 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
401 async fn execute_pending_steps_a_query_to_completion() {
402 let conn = AsyncConnection::open_in_memory().await.unwrap();
403 conn.execute_batch("CREATE TABLE t (id INTEGER)").await.unwrap();
404 conn.execute_batch("INSERT INTO t VALUES (1), (2), (3)").await.unwrap();
405
406 let set = conn.execute_pending("SELECT count(*) AS n FROM t").await.unwrap();
408 match set.rows()[0].get("n").unwrap() {
409 DuckValue::BigInt(n) => assert_eq!(*n, 3),
410 other => panic!("expected BigInt(3), got {other:?}"),
411 }
412 }
413
414 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
415 async fn execute_pending_reports_a_prepare_error() {
416 let conn = AsyncConnection::open_in_memory().await.unwrap();
417 let err = conn.execute_pending("SELECT * FROM no_such_table").await.unwrap_err();
423 assert!(
424 matches!(err, Error::DuckDBFailure(..) | Error::Engine(_)),
425 "expected a prepare/engine error, got {err:?}"
426 );
427 }
428
429 #[tokio::test]
430 async fn execute_with_owned_binds() {
431 let conn = AsyncConnection::open_in_memory().await.unwrap();
432 conn.execute_batch("CREATE TABLE t (id INTEGER)").await.unwrap();
433 conn.execute_batch("INSERT INTO t VALUES (1), (2), (3)").await.unwrap();
434 let result = conn
435 .execute_with("SELECT id FROM t WHERE id = $1", vec![DuckValue::Int(2)])
436 .await
437 .unwrap();
438 assert_eq!(result.len(), 1);
439 }
440
441 #[tokio::test]
442 async fn error_variant_survives_boundary() {
443 let conn = AsyncConnection::open_in_memory().await.unwrap();
444 let err = conn.execute_batch("NOT VALID SQL").await.unwrap_err();
445 assert!(matches!(err, Error::Engine(_)), "unexpected error variant: {err:?}");
448 }
449
450 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
451 async fn concurrent_queries_on_cloned_handles() {
452 let conn = AsyncConnection::open_in_memory().await.unwrap();
453 conn.execute_batch("CREATE TABLE t (id INTEGER)").await.unwrap();
454 let mut handles = Vec::new();
455 for i in 0..8 {
456 let c = conn.clone();
457 handles.push(tokio::spawn(async move {
458 c.execute_batch(format!("INSERT INTO t VALUES ({i})")).await.unwrap();
459 }));
460 }
461 for h in handles {
462 h.await.unwrap();
463 }
464 let result = conn.execute("SELECT count(*) AS c FROM t").await.unwrap();
465 match result.rows()[0].get("c").unwrap() {
466 DuckValue::BigInt(n) => assert_eq!(*n, 8),
467 other => panic!("expected BigInt, got {other:?}"),
468 }
469 }
470
471 #[tokio::test]
472 async fn with_appender_bulk_insert() {
473 let conn = AsyncConnection::open_in_memory().await.unwrap();
474 conn.execute_batch("CREATE TABLE t (id INTEGER)").await.unwrap();
475 conn.with_appender("t", "main", |appender| {
476 for i in 0..100i32 {
477 appender.append(&mut DuckValue::Int(i))?;
478 }
479 Ok(())
480 })
481 .await
482 .unwrap();
483 let result = conn.execute("SELECT count(*) AS c FROM t").await.unwrap();
484 match result.rows()[0].get("c").unwrap() {
485 DuckValue::BigInt(n) => assert_eq!(*n, 100),
486 other => panic!("expected BigInt, got {other:?}"),
487 }
488 }
489
490 #[tokio::test]
491 async fn interrupt_is_a_noop_when_idle_and_control_is_send_sync() {
492 fn assert_send_sync<T: Send + Sync>() {}
493 assert_send_sync::<QueryControl>();
494
495 let conn = AsyncConnection::open_in_memory().await.unwrap();
496 let control = conn.query_control();
500 assert!(control.is_active(), "a fresh control targets the next query");
501 conn.execute_batch("CREATE TABLE t (id INTEGER)").await.unwrap();
502 assert!(!control.is_active(), "control is stale once a query has run");
503 assert!(!control.interrupt(), "stale interrupt is a no-op");
504 }
505
506 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
511 async fn control_does_not_block_on_the_running_query() {
512 use std::time::Duration;
513
514 let conn = AsyncConnection::open_in_memory().await.unwrap();
515 conn.execute_batch("CREATE TABLE t (id INTEGER)").await.unwrap();
516
517 let runner = conn.clone();
520 let query = tokio::spawn(async move {
521 runner
522 .execute_batch(
523 "CREATE TABLE big AS \
524 SELECT t1.range AS a FROM range(200000) t1, range(200) t2",
525 )
526 .await
527 });
528
529 tokio::time::sleep(Duration::from_millis(20)).await;
531
532 let control_calls = tokio::time::timeout(Duration::from_secs(5), async {
535 let _ = conn.interrupt();
536 let _ = conn.progress();
537 });
538 control_calls.await.expect("interrupt/progress must not block on the running query");
539
540 let _ = query.await.unwrap();
543 }
544
545 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
550 async fn dropped_query_future_leaves_connection_usable() {
551 use std::time::Duration;
552
553 let conn = AsyncConnection::open_in_memory().await.unwrap();
554
555 let work = conn.execute_batch("CREATE TABLE small AS SELECT range AS a FROM range(1000)");
560 let _ = tokio::time::timeout(Duration::from_millis(1), work).await;
561
562 let followup =
564 tokio::time::timeout(Duration::from_secs(30), conn.execute("SELECT 42 AS v"));
565 let result = followup.await.expect("connection must recover within 30s").unwrap();
566 match result.rows()[0].get("v").unwrap() {
567 DuckValue::Int(n) => assert_eq!(*n, 42),
568 other => panic!("expected Int(42), got {other:?}"),
569 }
570 }
571
572 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
575 async fn stale_control_does_not_observe_a_later_query() {
576 let conn = AsyncConnection::open_in_memory().await.unwrap();
577 conn.execute_batch("CREATE TABLE t (id INTEGER)").await.unwrap();
578
579 let stale = conn.query_control();
581 conn.execute("INSERT INTO t VALUES (1)").await.unwrap();
582
583 assert!(!stale.is_active(), "control is stale after its query completed");
584 assert!(!stale.interrupt(), "stale interrupt is a no-op");
585 assert!(stale.progress().is_none(), "stale control observes no later query");
586
587 let result = conn.execute("SELECT count(*) AS c FROM t").await.unwrap();
589 match result.rows()[0].get("c").unwrap() {
590 DuckValue::BigInt(n) => assert_eq!(*n, 1),
591 other => panic!("expected BigInt(1), got {other:?}"),
592 }
593 }
594
595 #[tokio::test]
596 async fn with_connection_transaction_rollback() {
597 let conn = AsyncConnection::open_in_memory().await.unwrap();
598 conn.execute_batch("CREATE TABLE t (id INTEGER)").await.unwrap();
599 conn.with_connection(|c| {
600 c.execute_batch("BEGIN")?;
601 c.execute_batch("INSERT INTO t VALUES (1)")?;
602 c.execute_batch("ROLLBACK")?;
603 Ok(())
604 })
605 .await
606 .unwrap();
607 let result = conn.execute("SELECT count(*) AS c FROM t").await.unwrap();
608 match result.rows()[0].get("c").unwrap() {
609 DuckValue::BigInt(n) => assert_eq!(*n, 0),
610 other => panic!("expected BigInt, got {other:?}"),
611 }
612 }
613}