Pass connections instead of pool to ops functions
Enables callers to compose multiple ops within a single
transaction when atomicity is needed.

Assisted-by: Claude Opus 4.6 via pi
change upxorlxpkzlxxzqnzvkomqqtzymprtsx
commit 7ddaa1572f79b3b92e6ef4a8d0b0f4acc06ffeb3
author Alpha Chen <alpha@kejadlen.dev>
date
parent xuusmvkl
diff --git a/src/bin/ranger/commands/backlog.rs b/src/bin/ranger/commands/backlog.rs
index 7afea79..a86d3f3 100644
--- a/src/bin/ranger/commands/backlog.rs
+++ b/src/bin/ranger/commands/backlog.rs
@@ -26,22 +26,24 @@ pub enum BacklogCommands {
 }
 
 pub async fn run(pool: &SqlitePool, command: BacklogCommands, json: bool) -> Result<()> {
+    let mut conn = pool.acquire().await?;
+
     match command {
         BacklogCommands::Create { name } => {
-            let backlog = ops::backlog::create(pool, &name).await?;
+            let backlog = ops::backlog::create(&mut conn, &name).await?;
             output::print(&backlog, json, print_backlog);
         }
         BacklogCommands::List => {
-            let backlogs = ops::backlog::list(pool).await?;
+            let backlogs = ops::backlog::list(&mut conn).await?;
             output::print_list(&backlogs, json, print_backlog);
         }
         BacklogCommands::Show { name } => {
-            let backlog = ops::backlog::get_by_name(pool, &name).await?;
+            let backlog = ops::backlog::get_by_name(&mut conn, &name).await?;
 
             if json {
                 let mut state_groups = serde_json::Map::new();
                 for state in [State::Done, State::InProgress, State::Queued, State::Icebox] {
-                    let tasks = ops::task::list(pool, backlog.id, Some(state.clone())).await?;
+                    let tasks = ops::task::list(&mut conn, backlog.id, Some(state.clone())).await?;
                     if !tasks.is_empty() {
                         state_groups
                             .insert(state.to_string(), serde_json::to_value(&tasks).unwrap());
@@ -56,7 +58,7 @@ pub async fn run(pool: &SqlitePool, command: BacklogCommands, json: bool) -> Res
                 print_backlog_detail(&backlog);
 
                 for state in [State::Done, State::InProgress, State::Queued, State::Icebox] {
-                    let tasks = ops::task::list(pool, backlog.id, Some(state.clone())).await?;
+                    let tasks = ops::task::list(&mut conn, backlog.id, Some(state.clone())).await?;
                     if !tasks.is_empty() {
                         println!("\n[{}]", state);
                         for t in &tasks {
diff --git a/src/bin/ranger/commands/blocker.rs b/src/bin/ranger/commands/blocker.rs
index 9a126c8..cf3f958 100644
--- a/src/bin/ranger/commands/blocker.rs
+++ b/src/bin/ranger/commands/blocker.rs
@@ -24,19 +24,21 @@ pub enum BlockerCommands {
 }
 
 pub async fn run(pool: &SqlitePool, command: BlockerCommands, json: bool) -> Result<()> {
+    let mut conn = pool.acquire().await?;
+
     match command {
         BlockerCommands::Add { task, blocked_by } => {
-            let t = ops::task::get_by_key_prefix(pool, &task).await?;
-            let bt = ops::task::get_by_key_prefix(pool, &blocked_by).await?;
-            let blocker = ops::blocker::add(pool, t.id, bt.id).await?;
+            let t = ops::task::get_by_key_prefix(&mut conn, &task).await?;
+            let bt = ops::task::get_by_key_prefix(&mut conn, &blocked_by).await?;
+            let blocker = ops::blocker::add(&mut conn, t.id, bt.id).await?;
             output::print(&blocker, json, |_| {
                 println!("{} blocked by {} {}", &t.key[..8], &bt.key[..8], bt.title);
             });
         }
         BlockerCommands::Remove { task, blocked_by } => {
-            let t = ops::task::get_by_key_prefix(pool, &task).await?;
-            let bt = ops::task::get_by_key_prefix(pool, &blocked_by).await?;
-            ops::blocker::remove(pool, t.id, bt.id).await?;
+            let t = ops::task::get_by_key_prefix(&mut conn, &task).await?;
+            let bt = ops::task::get_by_key_prefix(&mut conn, &blocked_by).await?;
+            ops::blocker::remove(&mut conn, t.id, bt.id).await?;
             println!("Removed blocker {} from {}", &bt.key[..8], &t.key[..8]);
         }
     }
diff --git a/src/bin/ranger/commands/comment.rs b/src/bin/ranger/commands/comment.rs
index d304ce1..15704d6 100644
--- a/src/bin/ranger/commands/comment.rs
+++ b/src/bin/ranger/commands/comment.rs
@@ -22,17 +22,19 @@ pub enum CommentCommands {
 }
 
 pub async fn run(pool: &SqlitePool, command: CommentCommands, json: bool) -> Result<()> {
+    let mut conn = pool.acquire().await?;
+
     match command {
         CommentCommands::Add { task, body } => {
-            let t = ops::task::get_by_key_prefix(pool, &task).await?;
-            let comment = ops::comment::add(pool, t.id, &body).await?;
+            let t = ops::task::get_by_key_prefix(&mut conn, &task).await?;
+            let comment = ops::comment::add(&mut conn, t.id, &body).await?;
             output::print(&comment, json, |c| {
                 println!("[{}] {}", c.created_at, c.body);
             });
         }
         CommentCommands::List { task } => {
-            let t = ops::task::get_by_key_prefix(pool, &task).await?;
-            let comments = ops::comment::list(pool, t.id).await?;
+            let t = ops::task::get_by_key_prefix(&mut conn, &task).await?;
+            let comments = ops::comment::list(&mut conn, t.id).await?;
             output::print_list(&comments, json, |c| {
                 println!("[{}] {}", c.created_at, c.body);
             });
diff --git a/src/bin/ranger/commands/tag.rs b/src/bin/ranger/commands/tag.rs
index 4ff0487..b6e5826 100644
--- a/src/bin/ranger/commands/tag.rs
+++ b/src/bin/ranger/commands/tag.rs
@@ -12,9 +12,11 @@ pub enum TagCommands {
 }
 
 pub async fn run(pool: &SqlitePool, command: TagCommands, json: bool) -> Result<()> {
+    let mut conn = pool.acquire().await?;
+
     match command {
         TagCommands::List => {
-            let tags = ops::tag::list(pool).await?;
+            let tags = ops::tag::list(&mut conn).await?;
             output::print_list(&tags, json, |t| {
                 println!("{}", t.name);
             });
diff --git a/src/bin/ranger/commands/task.rs b/src/bin/ranger/commands/task.rs
index 8cb2bc6..f333300 100644
--- a/src/bin/ranger/commands/task.rs
+++ b/src/bin/ranger/commands/task.rs
@@ -1,6 +1,6 @@
 use clap::{Args, Subcommand};
 use color_eyre::eyre::Result;
-use ranger::db::SqlitePool;
+use ranger::db::{SqliteConnection, SqlitePool};
 use ranger::models::{State, Task};
 use ranger::ops;
 
@@ -18,14 +18,14 @@ pub struct PositionArgs {
 }
 
 impl PositionArgs {
-    async fn resolve(self, pool: &SqlitePool) -> Result<(Option<i64>, Option<i64>)> {
+    async fn resolve(self, conn: &mut SqliteConnection) -> Result<(Option<i64>, Option<i64>)> {
         let before_id = if let Some(k) = &self.before {
-            Some(ops::task::get_by_key_prefix(pool, k).await?.id)
+            Some(ops::task::get_by_key_prefix(conn, k).await?.id)
         } else {
             None
         };
         let after_id = if let Some(k) = &self.after {
-            Some(ops::task::get_by_key_prefix(pool, k).await?.id)
+            Some(ops::task::get_by_key_prefix(conn, k).await?.id)
         } else {
             None
         };
@@ -36,7 +36,6 @@ impl PositionArgs {
 #[derive(Subcommand)]
 pub enum TaskCommands {
     /// Create a new task
-    #[command(visible_alias = "new")]
     Create {
         /// Task title
         title: String,
@@ -59,7 +58,6 @@ pub enum TaskCommands {
         position: PositionArgs,
     },
     /// List tasks
-    #[command(visible_alias = "ls")]
     List {
         /// Filter by backlog key or prefix
         #[arg(long, env = "RANGER_DEFAULT_BACKLOG")]
@@ -88,7 +86,6 @@ pub enum TaskCommands {
         state: Option<String>,
     },
     /// Move a task's position within a backlog
-    #[command(visible_alias = "mv")]
     Move {
         /// Task key or prefix
         key: String,
@@ -115,7 +112,6 @@ pub enum TaskCommands {
         backlog: String,
     },
     /// Delete a task entirely
-    #[command(visible_alias = "rm")]
     Delete {
         /// Task key or prefix
         key: String,
@@ -123,6 +119,8 @@ pub enum TaskCommands {
 }
 
 pub async fn run(pool: &SqlitePool, command: TaskCommands, json: bool) -> Result<()> {
+    let mut conn = pool.acquire().await?;
+
     match command {
         TaskCommands::Create {
             title,
@@ -133,17 +131,21 @@ pub async fn run(pool: &SqlitePool, command: TaskCommands, json: bool) -> Result
             tag,
             position,
         } => {
-            let bl = ops::backlog::get_by_name(pool, &backlog).await?;
+            let bl = ops::backlog::get_by_name(&mut conn, &backlog).await?;
             let parent_id = if let Some(parent_key) = &parent {
-                Some(ops::task::get_by_key_prefix(pool, parent_key).await?.id)
+                Some(
+                    ops::task::get_by_key_prefix(&mut conn, parent_key)
+                        .await?
+                        .id,
+                )
             } else {
                 None
             };
-            let (before_id, after_id) = position.resolve(pool).await?;
+            let (before_id, after_id) = position.resolve(&mut conn).await?;
             let state = state.map(|s| s.parse::<State>()).transpose()?;
 
             let task = ops::task::create(
-                pool,
+                &mut conn,
                 ops::task::CreateTask {
                     title: &title,
                     backlog_id: bl.id,
@@ -158,8 +160,8 @@ pub async fn run(pool: &SqlitePool, command: TaskCommands, json: bool) -> Result
 
             if let Some(tags) = &tag {
                 for tag_name in tags.split(',').map(str::trim) {
-                    let t = ops::tag::get_or_create(pool, tag_name).await?;
-                    ops::tag::add_to_task(pool, task.id, t.id).await?;
+                    let t = ops::tag::get_or_create(&mut conn, tag_name).await?;
+                    ops::tag::add_to_task(&mut conn, task.id, t.id).await?;
                 }
             }
 
@@ -169,15 +171,15 @@ pub async fn run(pool: &SqlitePool, command: TaskCommands, json: bool) -> Result
             let state = state.map(|s| s.parse::<State>()).transpose()?;
 
             if let Some(backlog_key) = &backlog {
-                let bl = ops::backlog::get_by_name(pool, backlog_key).await?;
-                let tasks = ops::task::list(pool, bl.id, state).await?;
+                let bl = ops::backlog::get_by_name(&mut conn, backlog_key).await?;
+                let tasks = ops::task::list(&mut conn, bl.id, state).await?;
                 output::print_list(&tasks, json, print_task);
             } else {
                 // List all tasks (no backlog filter)
-                let backlogs = ops::backlog::list(pool).await?;
+                let backlogs = ops::backlog::list(&mut conn).await?;
                 let mut all_tasks = Vec::new();
                 for bl in &backlogs {
-                    let tasks = ops::task::list(pool, bl.id, state.clone()).await?;
+                    let tasks = ops::task::list(&mut conn, bl.id, state.clone()).await?;
                     for t in tasks {
                         if !all_tasks.iter().any(|at: &Task| at.id == t.id) {
                             all_tasks.push(t);
@@ -188,10 +190,10 @@ pub async fn run(pool: &SqlitePool, command: TaskCommands, json: bool) -> Result
             }
         }
         TaskCommands::Show { key } => {
-            let task = ops::task::get_by_key_prefix(pool, &key).await?;
-            let comments = ops::comment::list(pool, task.id).await?;
-            let tags = ops::tag::list_for_task(pool, task.id).await?;
-            let blockers = ops::blocker::list_for_task(pool, task.id).await?;
+            let task = ops::task::get_by_key_prefix(&mut conn, &key).await?;
+            let comments = ops::comment::list(&mut conn, task.id).await?;
+            let tags = ops::tag::list_for_task(&mut conn, task.id).await?;
+            let blockers = ops::blocker::list_for_task(&mut conn, task.id).await?;
 
             if json {
                 let detail = serde_json::json!({
@@ -210,7 +212,8 @@ pub async fn run(pool: &SqlitePool, command: TaskCommands, json: bool) -> Result
                 if !blockers.is_empty() {
                     println!("Blocked by:");
                     for b in &blockers {
-                        if let Ok(bt) = ops::task::get_by_id(pool, b.blocked_by_task_id).await {
+                        if let Ok(bt) = ops::task::get_by_id(&mut conn, b.blocked_by_task_id).await
+                        {
                             println!("  {} {}", &bt.key[..8], bt.title);
                         }
                     }
@@ -232,9 +235,9 @@ pub async fn run(pool: &SqlitePool, command: TaskCommands, json: bool) -> Result
         } => {
             let state = state.map(|s| s.parse::<State>()).transpose()?;
 
-            let task = ops::task::get_by_key_prefix(pool, &key).await?;
+            let task = ops::task::get_by_key_prefix(&mut conn, &key).await?;
             let updated = ops::task::edit(
-                pool,
+                &mut conn,
                 task.id,
                 title.as_deref(),
                 description.as_deref(),
@@ -248,28 +251,28 @@ pub async fn run(pool: &SqlitePool, command: TaskCommands, json: bool) -> Result
             backlog,
             position,
         } => {
-            let bl = ops::backlog::get_by_name(pool, &backlog).await?;
-            let task = ops::task::get_by_key_prefix(pool, &key).await?;
-            let (before_id, after_id) = position.resolve(pool).await?;
+            let bl = ops::backlog::get_by_name(&mut conn, &backlog).await?;
+            let task = ops::task::get_by_key_prefix(&mut conn, &key).await?;
+            let (before_id, after_id) = position.resolve(&mut conn).await?;
 
-            ops::task::move_task(pool, task.id, bl.id, before_id, after_id).await?;
+            ops::task::move_task(&mut conn, task.id, bl.id, before_id, after_id).await?;
             println!("Moved {} {}", &task.key[..8], task.title);
         }
         TaskCommands::Add { task, backlog } => {
-            let t = ops::task::get_by_key_prefix(pool, &task).await?;
-            let bl = ops::backlog::get_by_name(pool, &backlog).await?;
-            ops::task::add_to_backlog(pool, t.id, bl.id).await?;
+            let t = ops::task::get_by_key_prefix(&mut conn, &task).await?;
+            let bl = ops::backlog::get_by_name(&mut conn, &backlog).await?;
+            ops::task::add_to_backlog(&mut conn, t.id, bl.id).await?;
             println!("Added {} to {}", &t.key[..8], bl.name);
         }
         TaskCommands::Remove { task, backlog } => {
-            let t = ops::task::get_by_key_prefix(pool, &task).await?;
-            let bl = ops::backlog::get_by_name(pool, &backlog).await?;
-            ops::task::remove_from_backlog(pool, t.id, bl.id).await?;
+            let t = ops::task::get_by_key_prefix(&mut conn, &task).await?;
+            let bl = ops::backlog::get_by_name(&mut conn, &backlog).await?;
+            ops::task::remove_from_backlog(&mut conn, t.id, bl.id).await?;
             println!("Removed {} from {}", &t.key[..8], bl.name);
         }
         TaskCommands::Delete { key } => {
-            let task = ops::task::get_by_key_prefix(pool, &key).await?;
-            ops::task::delete(pool, task.id).await?;
+            let task = ops::task::get_by_key_prefix(&mut conn, &key).await?;
+            ops::task::delete(&mut conn, task.id).await?;
             println!("Deleted {} {}", &task.key[..8], task.title);
         }
     }
diff --git a/src/db.rs b/src/db.rs
index 0eaa40c..35d6f6c 100644
--- a/src/db.rs
+++ b/src/db.rs
@@ -3,6 +3,7 @@ use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
 use std::path::Path;
 
 pub use sqlx::SqlitePool;
+pub type SqliteConnection = sqlx::sqlite::SqliteConnection;
 
 pub async fn connect(path: &Path) -> Result<SqlitePool, RangerError> {
     if let Some(parent) = path.parent() {
diff --git a/src/ops/backlog.rs b/src/ops/backlog.rs
index 4f48ce7..31babef 100644
--- a/src/ops/backlog.rs
+++ b/src/ops/backlog.rs
@@ -1,9 +1,9 @@
 use crate::error::RangerError;
 use crate::key;
 use crate::models::Backlog;
-use sqlx::SqlitePool;
+use sqlx::sqlite::SqliteConnection;
 
-pub async fn create(pool: &SqlitePool, name: &str) -> Result<Backlog, RangerError> {
+pub async fn create(conn: &mut SqliteConnection, name: &str) -> Result<Backlog, RangerError> {
     // key column still exists in the schema but is unused; generate a dummy value
     let key = key::generate_key();
     let backlog = sqlx::query_as::<_, Backlog>(
@@ -11,26 +11,26 @@ pub async fn create(pool: &SqlitePool, name: &str) -> Result<Backlog, RangerErro
     )
     .bind(&key)
     .bind(name)
-    .fetch_one(pool)
+    .fetch_one(&mut *conn)
     .await?;
     Ok(backlog)
 }
 
-pub async fn list(pool: &SqlitePool) -> Result<Vec<Backlog>, RangerError> {
+pub async fn list(conn: &mut SqliteConnection) -> Result<Vec<Backlog>, RangerError> {
     let backlogs = sqlx::query_as::<_, Backlog>(
         "SELECT id, name, created_at, updated_at FROM backlogs ORDER BY name",
     )
-    .fetch_all(pool)
+    .fetch_all(&mut *conn)
     .await?;
     Ok(backlogs)
 }
 
-pub async fn get_by_name(pool: &SqlitePool, name: &str) -> Result<Backlog, RangerError> {
+pub async fn get_by_name(conn: &mut SqliteConnection, name: &str) -> Result<Backlog, RangerError> {
     let backlog = sqlx::query_as::<_, Backlog>(
         "SELECT id, name, created_at, updated_at FROM backlogs WHERE name = ?",
     )
     .bind(name)
-    .fetch_optional(pool)
+    .fetch_optional(&mut *conn)
     .await?
     .ok_or_else(|| RangerError::KeyNotFound(name.to_string()))?;
     Ok(backlog)
@@ -42,7 +42,7 @@ mod tests {
     use crate::db;
     use tempfile::tempdir;
 
-    async fn test_pool() -> SqlitePool {
+    async fn test_pool() -> sqlx::SqlitePool {
         let dir = tempdir().unwrap();
         let dir = Box::leak(Box::new(dir));
         db::connect(&dir.path().join("test.db")).await.unwrap()
@@ -51,27 +51,30 @@ mod tests {
     #[tokio::test]
     async fn create_and_get_backlog() {
         let pool = test_pool().await;
-        let backlog = create(&pool, "My Backlog").await.unwrap();
+        let mut conn = pool.acquire().await.unwrap();
+        let backlog = create(&mut conn, "My Backlog").await.unwrap();
         assert_eq!(backlog.name, "My Backlog");
 
-        let fetched = get_by_name(&pool, "My Backlog").await.unwrap();
+        let fetched = get_by_name(&mut conn, "My Backlog").await.unwrap();
         assert_eq!(fetched.id, backlog.id);
     }
 
     #[tokio::test]
     async fn list_backlogs() {
         let pool = test_pool().await;
-        create(&pool, "First").await.unwrap();
-        create(&pool, "Second").await.unwrap();
+        let mut conn = pool.acquire().await.unwrap();
+        create(&mut conn, "First").await.unwrap();
+        create(&mut conn, "Second").await.unwrap();
 
-        let backlogs = list(&pool).await.unwrap();
+        let backlogs = list(&mut conn).await.unwrap();
         assert_eq!(backlogs.len(), 2);
     }
 
     #[tokio::test]
     async fn get_by_name_not_found() {
         let pool = test_pool().await;
-        let result = get_by_name(&pool, "nonexistent").await;
+        let mut conn = pool.acquire().await.unwrap();
+        let result = get_by_name(&mut conn, "nonexistent").await;
         assert!(result.is_err());
     }
 }
diff --git a/src/ops/blocker.rs b/src/ops/blocker.rs
index 83876cf..5446cc6 100644
--- a/src/ops/blocker.rs
+++ b/src/ops/blocker.rs
@@ -1,9 +1,9 @@
 use crate::error::RangerError;
 use crate::models::Blocker;
-use sqlx::SqlitePool;
+use sqlx::sqlite::SqliteConnection;
 
 pub async fn add(
-    pool: &SqlitePool,
+    conn: &mut SqliteConnection,
     task_id: i64,
     blocked_by_task_id: i64,
 ) -> Result<Blocker, RangerError> {
@@ -13,30 +13,33 @@ pub async fn add(
     )
     .bind(task_id)
     .bind(blocked_by_task_id)
-    .fetch_one(pool)
+    .fetch_one(&mut *conn)
     .await?;
     Ok(blocker)
 }
 
 pub async fn remove(
-    pool: &SqlitePool,
+    conn: &mut SqliteConnection,
     task_id: i64,
     blocked_by_task_id: i64,
 ) -> Result<(), RangerError> {
     sqlx::query("DELETE FROM blockers WHERE task_id = ? AND blocked_by_task_id = ?")
         .bind(task_id)
         .bind(blocked_by_task_id)
-        .execute(pool)
+        .execute(&mut *conn)
         .await?;
     Ok(())
 }
 
-pub async fn list_for_task(pool: &SqlitePool, task_id: i64) -> Result<Vec<Blocker>, RangerError> {
+pub async fn list_for_task(
+    conn: &mut SqliteConnection,
+    task_id: i64,
+) -> Result<Vec<Blocker>, RangerError> {
     let blockers = sqlx::query_as::<_, Blocker>(
         "SELECT id, task_id, blocked_by_task_id FROM blockers WHERE task_id = ?",
     )
     .bind(task_id)
-    .fetch_all(pool)
+    .fetch_all(&mut *conn)
     .await?;
     Ok(blockers)
 }
@@ -48,7 +51,7 @@ mod tests {
     use crate::ops::{backlog, task};
     use tempfile::tempdir;
 
-    async fn test_pool() -> SqlitePool {
+    async fn test_pool() -> sqlx::SqlitePool {
         let dir = tempdir().unwrap();
         let dir = Box::leak(Box::new(dir));
         db::connect(&dir.path().join("test.db")).await.unwrap()
@@ -57,9 +60,10 @@ mod tests {
     #[tokio::test]
     async fn add_and_list_blockers() {
         let pool = test_pool().await;
-        let bl = backlog::create(&pool, "Test").await.unwrap();
+        let mut conn = pool.acquire().await.unwrap();
+        let bl = backlog::create(&mut conn, "Test").await.unwrap();
         let t1 = task::create(
-            &pool,
+            &mut conn,
             task::CreateTask {
                 title: "Blocked",
                 backlog_id: bl.id,
@@ -73,7 +77,7 @@ mod tests {
         .await
         .unwrap();
         let t2 = task::create(
-            &pool,
+            &mut conn,
             task::CreateTask {
                 title: "Blocker",
                 backlog_id: bl.id,
@@ -87,9 +91,9 @@ mod tests {
         .await
         .unwrap();
 
-        add(&pool, t1.id, t2.id).await.unwrap();
+        add(&mut conn, t1.id, t2.id).await.unwrap();
 
-        let blockers = list_for_task(&pool, t1.id).await.unwrap();
+        let blockers = list_for_task(&mut conn, t1.id).await.unwrap();
         assert_eq!(blockers.len(), 1);
         assert_eq!(blockers[0].blocked_by_task_id, t2.id);
     }
@@ -97,9 +101,10 @@ mod tests {
     #[tokio::test]
     async fn remove_blocker() {
         let pool = test_pool().await;
-        let bl = backlog::create(&pool, "Test").await.unwrap();
+        let mut conn = pool.acquire().await.unwrap();
+        let bl = backlog::create(&mut conn, "Test").await.unwrap();
         let t1 = task::create(
-            &pool,
+            &mut conn,
             task::CreateTask {
                 title: "Blocked",
                 backlog_id: bl.id,
@@ -113,7 +118,7 @@ mod tests {
         .await
         .unwrap();
         let t2 = task::create(
-            &pool,
+            &mut conn,
             task::CreateTask {
                 title: "Blocker",
                 backlog_id: bl.id,
@@ -127,10 +132,10 @@ mod tests {
         .await
         .unwrap();
 
-        add(&pool, t1.id, t2.id).await.unwrap();
-        remove(&pool, t1.id, t2.id).await.unwrap();
+        add(&mut conn, t1.id, t2.id).await.unwrap();
+        remove(&mut conn, t1.id, t2.id).await.unwrap();
 
-        let blockers = list_for_task(&pool, t1.id).await.unwrap();
+        let blockers = list_for_task(&mut conn, t1.id).await.unwrap();
         assert_eq!(blockers.len(), 0);
     }
 }
diff --git a/src/ops/comment.rs b/src/ops/comment.rs
index 8196b74..fe3f966 100644
--- a/src/ops/comment.rs
+++ b/src/ops/comment.rs
@@ -1,25 +1,29 @@
 use crate::error::RangerError;
 use crate::models::Comment;
-use sqlx::SqlitePool;
+use sqlx::sqlite::SqliteConnection;
 
-pub async fn add(pool: &SqlitePool, task_id: i64, body: &str) -> Result<Comment, RangerError> {
+pub async fn add(
+    conn: &mut SqliteConnection,
+    task_id: i64,
+    body: &str,
+) -> Result<Comment, RangerError> {
     let comment = sqlx::query_as::<_, Comment>(
         "INSERT INTO comments (task_id, body) VALUES (?, ?) \
          RETURNING id, task_id, body, created_at",
     )
     .bind(task_id)
     .bind(body)
-    .fetch_one(pool)
+    .fetch_one(&mut *conn)
     .await?;
     Ok(comment)
 }
 
-pub async fn list(pool: &SqlitePool, task_id: i64) -> Result<Vec<Comment>, RangerError> {
+pub async fn list(conn: &mut SqliteConnection, task_id: i64) -> Result<Vec<Comment>, RangerError> {
     let comments = sqlx::query_as::<_, Comment>(
         "SELECT id, task_id, body, created_at FROM comments WHERE task_id = ? ORDER BY created_at",
     )
     .bind(task_id)
-    .fetch_all(pool)
+    .fetch_all(&mut *conn)
     .await?;
     Ok(comments)
 }
@@ -40,9 +44,10 @@ mod tests {
     #[tokio::test]
     async fn add_and_list_comments() {
         let pool = test_pool().await;
-        let bl = backlog::create(&pool, "Test").await.unwrap();
+        let mut conn = pool.acquire().await.unwrap();
+        let bl = backlog::create(&mut conn, "Test").await.unwrap();
         let t = task::create(
-            &pool,
+            &mut conn,
             task::CreateTask {
                 title: "Task",
                 backlog_id: bl.id,
@@ -56,10 +61,10 @@ mod tests {
         .await
         .unwrap();
 
-        add(&pool, t.id, "First comment").await.unwrap();
-        add(&pool, t.id, "Second comment").await.unwrap();
+        add(&mut conn, t.id, "First comment").await.unwrap();
+        add(&mut conn, t.id, "Second comment").await.unwrap();
 
-        let comments = list(&pool, t.id).await.unwrap();
+        let comments = list(&mut conn, t.id).await.unwrap();
         assert_eq!(comments.len(), 2);
         assert_eq!(comments[0].body, "First comment");
         assert_eq!(comments[1].body, "Second comment");
diff --git a/src/ops/tag.rs b/src/ops/tag.rs
index 178228c..e09d842 100644
--- a/src/ops/tag.rs
+++ b/src/ops/tag.rs
@@ -1,46 +1,53 @@
 use crate::error::RangerError;
 use crate::models::Tag;
-use sqlx::SqlitePool;
+use sqlx::sqlite::SqliteConnection;
 
 /// Get or create a tag by name.
-pub async fn get_or_create(pool: &SqlitePool, name: &str) -> Result<Tag, RangerError> {
+pub async fn get_or_create(conn: &mut SqliteConnection, name: &str) -> Result<Tag, RangerError> {
     // Try insert, ignore conflict
     sqlx::query("INSERT OR IGNORE INTO tags (name) VALUES (?)")
         .bind(name)
-        .execute(pool)
+        .execute(&mut *conn)
         .await?;
 
     let tag = sqlx::query_as::<_, Tag>("SELECT id, name FROM tags WHERE name = ?")
         .bind(name)
-        .fetch_one(pool)
+        .fetch_one(&mut *conn)
         .await?;
     Ok(tag)
 }
 
-pub async fn list(pool: &SqlitePool) -> Result<Vec<Tag>, RangerError> {
+pub async fn list(conn: &mut SqliteConnection) -> Result<Vec<Tag>, RangerError> {
     let tags = sqlx::query_as::<_, Tag>("SELECT id, name FROM tags ORDER BY name")
-        .fetch_all(pool)
+        .fetch_all(&mut *conn)
         .await?;
     Ok(tags)
 }
 
-pub async fn add_to_task(pool: &SqlitePool, task_id: i64, tag_id: i64) -> Result<(), RangerError> {
+pub async fn add_to_task(
+    conn: &mut SqliteConnection,
+    task_id: i64,
+    tag_id: i64,
+) -> Result<(), RangerError> {
     sqlx::query("INSERT OR IGNORE INTO task_tags (task_id, tag_id) VALUES (?, ?)")
         .bind(task_id)
         .bind(tag_id)
-        .execute(pool)
+        .execute(&mut *conn)
         .await?;
     Ok(())
 }
 
-pub async fn list_for_task(pool: &SqlitePool, task_id: i64) -> Result<Vec<Tag>, RangerError> {
+pub async fn list_for_task(
+    conn: &mut SqliteConnection,
+    task_id: i64,
+) -> Result<Vec<Tag>, RangerError> {
     let tags = sqlx::query_as::<_, Tag>(
         "SELECT t.id, t.name FROM tags t \
          JOIN task_tags tt ON tt.tag_id = t.id \
          WHERE tt.task_id = ? ORDER BY t.name",
     )
     .bind(task_id)
-    .fetch_all(pool)
+    .fetch_all(&mut *conn)
     .await?;
     Ok(tags)
 }
@@ -52,7 +59,7 @@ mod tests {
     use crate::ops::{backlog, task};
     use tempfile::tempdir;
 
-    async fn test_pool() -> SqlitePool {
+    async fn test_pool() -> sqlx::SqlitePool {
         let dir = tempdir().unwrap();
         let dir = Box::leak(Box::new(dir));
         db::connect(&dir.path().join("test.db")).await.unwrap()
@@ -61,18 +68,20 @@ mod tests {
     #[tokio::test]
     async fn get_or_create_is_idempotent() {
         let pool = test_pool().await;
-        let t1 = get_or_create(&pool, "urgent").await.unwrap();
-        let t2 = get_or_create(&pool, "urgent").await.unwrap();
+        let mut conn = pool.acquire().await.unwrap();
+        let t1 = get_or_create(&mut conn, "urgent").await.unwrap();
+        let t2 = get_or_create(&mut conn, "urgent").await.unwrap();
         assert_eq!(t1.id, t2.id);
     }
 
     #[tokio::test]
     async fn list_tags() {
         let pool = test_pool().await;
-        get_or_create(&pool, "beta").await.unwrap();
-        get_or_create(&pool, "alpha").await.unwrap();
+        let mut conn = pool.acquire().await.unwrap();
+        get_or_create(&mut conn, "beta").await.unwrap();
+        get_or_create(&mut conn, "alpha").await.unwrap();
 
-        let tags = list(&pool).await.unwrap();
+        let tags = list(&mut conn).await.unwrap();
         assert_eq!(tags.len(), 2);
         assert_eq!(tags[0].name, "alpha");
         assert_eq!(tags[1].name, "beta");
@@ -81,9 +90,10 @@ mod tests {
     #[tokio::test]
     async fn add_tag_to_task_and_list() {
         let pool = test_pool().await;
-        let bl = backlog::create(&pool, "Test").await.unwrap();
+        let mut conn = pool.acquire().await.unwrap();
+        let bl = backlog::create(&mut conn, "Test").await.unwrap();
         let t = task::create(
-            &pool,
+            &mut conn,
             task::CreateTask {
                 title: "Task",
                 backlog_id: bl.id,
@@ -96,11 +106,11 @@ mod tests {
         )
         .await
         .unwrap();
-        let tag = get_or_create(&pool, "important").await.unwrap();
+        let tag = get_or_create(&mut conn, "important").await.unwrap();
 
-        add_to_task(&pool, t.id, tag.id).await.unwrap();
+        add_to_task(&mut conn, t.id, tag.id).await.unwrap();
 
-        let tags = list_for_task(&pool, t.id).await.unwrap();
+        let tags = list_for_task(&mut conn, t.id).await.unwrap();
         assert_eq!(tags.len(), 1);
         assert_eq!(tags[0].name, "important");
     }
@@ -108,9 +118,10 @@ mod tests {
     #[tokio::test]
     async fn add_tag_to_task_is_idempotent() {
         let pool = test_pool().await;
-        let bl = backlog::create(&pool, "Test").await.unwrap();
+        let mut conn = pool.acquire().await.unwrap();
+        let bl = backlog::create(&mut conn, "Test").await.unwrap();
         let t = task::create(
-            &pool,
+            &mut conn,
             task::CreateTask {
                 title: "Task",
                 backlog_id: bl.id,
@@ -123,12 +134,12 @@ mod tests {
         )
         .await
         .unwrap();
-        let tag = get_or_create(&pool, "dup").await.unwrap();
+        let tag = get_or_create(&mut conn, "dup").await.unwrap();
 
-        add_to_task(&pool, t.id, tag.id).await.unwrap();
-        add_to_task(&pool, t.id, tag.id).await.unwrap();
+        add_to_task(&mut conn, t.id, tag.id).await.unwrap();
+        add_to_task(&mut conn, t.id, tag.id).await.unwrap();
 
-        let tags = list_for_task(&pool, t.id).await.unwrap();
+        let tags = list_for_task(&mut conn, t.id).await.unwrap();
         assert_eq!(tags.len(), 1);
     }
 }
diff --git a/src/ops/task.rs b/src/ops/task.rs
index 3a58069..1ddd695 100644
--- a/src/ops/task.rs
+++ b/src/ops/task.rs
@@ -2,7 +2,7 @@ use crate::error::RangerError;
 use crate::key;
 use crate::models::{State, Task};
 use crate::position;
-use sqlx::SqlitePool;
+use sqlx::sqlite::SqliteConnection;
 
 pub struct CreateTask<'a> {
     pub title: &'a str,
@@ -14,7 +14,10 @@ pub struct CreateTask<'a> {
     pub after_task_id: Option<i64>,
 }
 
-pub async fn create(pool: &SqlitePool, params: CreateTask<'_>) -> Result<Task, RangerError> {
+pub async fn create(
+    conn: &mut SqliteConnection,
+    params: CreateTask<'_>,
+) -> Result<Task, RangerError> {
     let key = key::generate_key();
     let state = params.state.unwrap_or(State::Icebox);
 
@@ -28,11 +31,11 @@ pub async fn create(pool: &SqlitePool, params: CreateTask<'_>) -> Result<Task, R
     .bind(params.title)
     .bind(params.description)
     .bind(state.as_str())
-    .fetch_one(pool)
+    .fetch_one(&mut *conn)
     .await?;
 
     let new_pos = resolve_position(
-        pool,
+        &mut *conn,
         params.backlog_id,
         task.id,
         params.before_task_id,
@@ -44,14 +47,14 @@ pub async fn create(pool: &SqlitePool, params: CreateTask<'_>) -> Result<Task, R
         .bind(params.backlog_id)
         .bind(task.id)
         .bind(&new_pos)
-        .execute(pool)
+        .execute(&mut *conn)
         .await?;
 
     Ok(task)
 }
 
 pub async fn list(
-    pool: &SqlitePool,
+    conn: &mut SqliteConnection,
     backlog_id: i64,
     state_filter: Option<State>,
 ) -> Result<Vec<Task>, RangerError> {
@@ -66,7 +69,7 @@ pub async fn list(
         )
         .bind(backlog_id)
         .bind(state.as_str())
-        .fetch_all(pool)
+        .fetch_all(&mut *conn)
         .await?
     } else {
         sqlx::query_as::<_, Task>(
@@ -78,31 +81,34 @@ pub async fn list(
              ORDER BY bt.position",
         )
         .bind(backlog_id)
-        .fetch_all(pool)
+        .fetch_all(&mut *conn)
         .await?
     };
     Ok(tasks)
 }
 
-pub async fn get_by_id(pool: &SqlitePool, id: i64) -> Result<Task, RangerError> {
+pub async fn get_by_id(conn: &mut SqliteConnection, id: i64) -> Result<Task, RangerError> {
     let task = sqlx::query_as::<_, Task>(
         "SELECT id, key, parent_id, title, description, state, created_at, updated_at \
          FROM tasks WHERE id = ?",
     )
     .bind(id)
-    .fetch_one(pool)
+    .fetch_one(&mut *conn)
     .await?;
     Ok(task)
 }
 
-pub async fn get_by_key_prefix(pool: &SqlitePool, prefix: &str) -> Result<Task, RangerError> {
+pub async fn get_by_key_prefix(
+    conn: &mut SqliteConnection,
+    prefix: &str,
+) -> Result<Task, RangerError> {
     let pattern = format!("{prefix}%");
     let matches = sqlx::query_as::<_, Task>(
         "SELECT id, key, parent_id, title, description, state, created_at, updated_at \
          FROM tasks WHERE key LIKE ?",
     )
     .bind(&pattern)
-    .fetch_all(pool)
+    .fetch_all(&mut *conn)
     .await?;
 
     match matches.len() {
@@ -113,7 +119,7 @@ pub async fn get_by_key_prefix(pool: &SqlitePool, prefix: &str) -> Result<Task,
 }
 
 pub async fn edit(
-    pool: &SqlitePool,
+    conn: &mut SqliteConnection,
     task_id: i64,
     title: Option<&str>,
     description: Option<&str>,
@@ -123,21 +129,21 @@ pub async fn edit(
         sqlx::query("UPDATE tasks SET title = ?, updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now') WHERE id = ?")
             .bind(title)
             .bind(task_id)
-            .execute(pool)
+            .execute(&mut *conn)
             .await?;
     }
     if let Some(description) = description {
         sqlx::query("UPDATE tasks SET description = ?, updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now') WHERE id = ?")
             .bind(description)
             .bind(task_id)
-            .execute(pool)
+            .execute(&mut *conn)
             .await?;
     }
     if let Some(state) = &state {
         sqlx::query("UPDATE tasks SET state = ?, updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now') WHERE id = ?")
             .bind(state.as_str())
             .bind(task_id)
-            .execute(pool)
+            .execute(&mut *conn)
             .await?;
     }
 
@@ -146,7 +152,7 @@ pub async fn edit(
          FROM tasks WHERE id = ?",
     )
     .bind(task_id)
-    .fetch_one(pool)
+    .fetch_one(&mut *conn)
     .await?;
     Ok(task)
 }
@@ -158,7 +164,7 @@ pub async fn edit(
 /// to those tasks. `task_id` is excluded from adjacent-position lookups
 /// (relevant for moves where the task already has a position).
 async fn resolve_position(
-    pool: &SqlitePool,
+    conn: &mut SqliteConnection,
     backlog_id: i64,
     task_id: i64,
     before_task_id: Option<i64>,
@@ -172,7 +178,7 @@ async fn resolve_position(
              ORDER BY bt.position DESC LIMIT 1",
         )
         .bind(backlog_id)
-        .fetch_optional(pool)
+        .fetch_optional(&mut *conn)
         .await?;
 
         return Ok(position::midpoint(last_pos.as_deref(), None));
@@ -186,7 +192,7 @@ async fn resolve_position(
         )
         .bind(backlog_id)
         .bind(id)
-        .fetch_optional(pool)
+        .fetch_optional(&mut *conn)
         .await?
     } else {
         None
@@ -198,7 +204,7 @@ async fn resolve_position(
         )
         .bind(backlog_id)
         .bind(id)
-        .fetch_optional(pool)
+        .fetch_optional(&mut *conn)
         .await?
     } else {
         None
@@ -217,7 +223,7 @@ async fn resolve_position(
             .bind(backlog_id)
             .bind(task_id)
             .bind(low)
-            .fetch_optional(pool)
+            .fetch_optional(&mut *conn)
             .await?;
             (Some(low.clone()), next)
         }
@@ -231,7 +237,7 @@ async fn resolve_position(
             .bind(backlog_id)
             .bind(task_id)
             .bind(up)
-            .fetch_optional(pool)
+            .fetch_optional(&mut *conn)
             .await?;
             (prev, Some(up.clone()))
         }
@@ -242,34 +248,40 @@ async fn resolve_position(
 }
 
 pub async fn move_task(
-    pool: &SqlitePool,
+    conn: &mut SqliteConnection,
     task_id: i64,
     backlog_id: i64,
     before_task_id: Option<i64>,
     after_task_id: Option<i64>,
 ) -> Result<(), RangerError> {
-    let new_pos =
-        resolve_position(pool, backlog_id, task_id, before_task_id, after_task_id).await?;
+    let new_pos = resolve_position(
+        &mut *conn,
+        backlog_id,
+        task_id,
+        before_task_id,
+        after_task_id,
+    )
+    .await?;
 
     sqlx::query("UPDATE backlog_tasks SET position = ? WHERE backlog_id = ? AND task_id = ?")
         .bind(&new_pos)
         .bind(backlog_id)
         .bind(task_id)
-        .execute(pool)
+        .execute(&mut *conn)
         .await?;
 
     Ok(())
 }
 
 pub async fn add_to_backlog(
-    pool: &SqlitePool,
+    conn: &mut SqliteConnection,
     task_id: i64,
     backlog_id: i64,
 ) -> Result<(), RangerError> {
     // Get the task's state to find proper position
     let state: String = sqlx::query_scalar("SELECT state FROM tasks WHERE id = ?")
         .bind(task_id)
-        .fetch_one(pool)
+        .fetch_one(&mut *conn)
         .await?;
 
     let last_pos: Option<String> = sqlx::query_scalar(
@@ -280,7 +292,7 @@ pub async fn add_to_backlog(
     )
     .bind(backlog_id)
     .bind(&state)
-    .fetch_optional(pool)
+    .fetch_optional(&mut *conn)
     .await?;
 
     let new_pos = position::midpoint(last_pos.as_deref(), None);
@@ -289,29 +301,29 @@ pub async fn add_to_backlog(
         .bind(backlog_id)
         .bind(task_id)
         .bind(&new_pos)
-        .execute(pool)
+        .execute(&mut *conn)
         .await?;
 
     Ok(())
 }
 
 pub async fn remove_from_backlog(
-    pool: &SqlitePool,
+    conn: &mut SqliteConnection,
     task_id: i64,
     backlog_id: i64,
 ) -> Result<(), RangerError> {
     sqlx::query("DELETE FROM backlog_tasks WHERE backlog_id = ? AND task_id = ?")
         .bind(backlog_id)
         .bind(task_id)
-        .execute(pool)
+        .execute(&mut *conn)
         .await?;
     Ok(())
 }
 
-pub async fn delete(pool: &SqlitePool, task_id: i64) -> Result<(), RangerError> {
+pub async fn delete(conn: &mut SqliteConnection, task_id: i64) -> Result<(), RangerError> {
     sqlx::query("DELETE FROM tasks WHERE id = ?")
         .bind(task_id)
-        .execute(pool)
+        .execute(&mut *conn)
         .await?;
     Ok(())
 }
@@ -324,7 +336,7 @@ mod tests {
     use crate::ops::backlog;
     use tempfile::tempdir;
 
-    async fn test_pool() -> SqlitePool {
+    async fn test_pool() -> sqlx::SqlitePool {
         let dir = tempdir().unwrap();
         let dir = Box::leak(Box::new(dir));
         db::connect(&dir.path().join("test.db")).await.unwrap()
@@ -333,9 +345,10 @@ mod tests {
     #[tokio::test]
     async fn create_task_in_backlog() {
         let pool = test_pool().await;
-        let bl = backlog::create(&pool, "Test").await.unwrap();
+        let mut conn = pool.acquire().await.unwrap();
+        let bl = backlog::create(&mut conn, "Test").await.unwrap();
         let task = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "My Task",
                 backlog_id: bl.id,
@@ -359,7 +372,7 @@ mod tests {
         )
         .bind(bl.id)
         .bind(task.id)
-        .fetch_one(&pool)
+        .fetch_one(&mut *conn)
         .await
         .unwrap();
         assert_eq!(count, 1);
@@ -368,9 +381,10 @@ mod tests {
     #[tokio::test]
     async fn list_tasks_ordered_by_position() {
         let pool = test_pool().await;
-        let bl = backlog::create(&pool, "Test").await.unwrap();
+        let mut conn = pool.acquire().await.unwrap();
+        let bl = backlog::create(&mut conn, "Test").await.unwrap();
         let t1 = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "First",
                 backlog_id: bl.id,
@@ -384,7 +398,7 @@ mod tests {
         .await
         .unwrap();
         let t2 = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "Second",
                 backlog_id: bl.id,
@@ -398,7 +412,7 @@ mod tests {
         .await
         .unwrap();
         let t3 = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "Third",
                 backlog_id: bl.id,
@@ -412,7 +426,7 @@ mod tests {
         .await
         .unwrap();
 
-        let tasks = list(&pool, bl.id, None).await.unwrap();
+        let tasks = list(&mut conn, bl.id, None).await.unwrap();
         assert_eq!(tasks.len(), 3);
         assert_eq!(tasks[0].id, t1.id);
         assert_eq!(tasks[1].id, t2.id);
@@ -422,9 +436,10 @@ mod tests {
     #[tokio::test]
     async fn list_tasks_with_state_filter() {
         let pool = test_pool().await;
-        let bl = backlog::create(&pool, "Test").await.unwrap();
+        let mut conn = pool.acquire().await.unwrap();
+        let bl = backlog::create(&mut conn, "Test").await.unwrap();
         create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "Icebox task",
                 backlog_id: bl.id,
@@ -438,7 +453,7 @@ mod tests {
         .await
         .unwrap();
         create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "Queued task",
                 backlog_id: bl.id,
@@ -452,11 +467,11 @@ mod tests {
         .await
         .unwrap();
 
-        let icebox = list(&pool, bl.id, Some(State::Icebox)).await.unwrap();
+        let icebox = list(&mut conn, bl.id, Some(State::Icebox)).await.unwrap();
         assert_eq!(icebox.len(), 1);
         assert_eq!(icebox[0].title, "Icebox task");
 
-        let queued = list(&pool, bl.id, Some(State::Queued)).await.unwrap();
+        let queued = list(&mut conn, bl.id, Some(State::Queued)).await.unwrap();
         assert_eq!(queued.len(), 1);
         assert_eq!(queued[0].title, "Queued task");
     }
@@ -464,9 +479,10 @@ mod tests {
     #[tokio::test]
     async fn get_task_by_key_prefix() {
         let pool = test_pool().await;
-        let bl = backlog::create(&pool, "Test").await.unwrap();
+        let mut conn = pool.acquire().await.unwrap();
+        let bl = backlog::create(&mut conn, "Test").await.unwrap();
         let task = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "Find me",
                 backlog_id: bl.id,
@@ -480,16 +496,17 @@ mod tests {
         .await
         .unwrap();
 
-        let found = get_by_key_prefix(&pool, &task.key[..3]).await.unwrap();
+        let found = get_by_key_prefix(&mut conn, &task.key[..3]).await.unwrap();
         assert_eq!(found.id, task.id);
     }
 
     #[tokio::test]
     async fn edit_task_fields() {
         let pool = test_pool().await;
-        let bl = backlog::create(&pool, "Test").await.unwrap();
+        let mut conn = pool.acquire().await.unwrap();
+        let bl = backlog::create(&mut conn, "Test").await.unwrap();
         let task = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "Original",
                 backlog_id: bl.id,
@@ -504,7 +521,7 @@ mod tests {
         .unwrap();
 
         let updated = edit(
-            &pool,
+            &mut conn,
             task.id,
             Some("Updated"),
             Some("A description"),
@@ -521,10 +538,11 @@ mod tests {
     #[tokio::test]
     async fn add_task_to_second_backlog() {
         let pool = test_pool().await;
-        let bl1 = backlog::create(&pool, "First").await.unwrap();
-        let bl2 = backlog::create(&pool, "Second").await.unwrap();
+        let mut conn = pool.acquire().await.unwrap();
+        let bl1 = backlog::create(&mut conn, "First").await.unwrap();
+        let bl2 = backlog::create(&mut conn, "Second").await.unwrap();
         let task = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "Shared",
                 backlog_id: bl1.id,
@@ -538,10 +556,10 @@ mod tests {
         .await
         .unwrap();
 
-        add_to_backlog(&pool, task.id, bl2.id).await.unwrap();
+        add_to_backlog(&mut conn, task.id, bl2.id).await.unwrap();
 
-        let tasks1 = list(&pool, bl1.id, None).await.unwrap();
-        let tasks2 = list(&pool, bl2.id, None).await.unwrap();
+        let tasks1 = list(&mut conn, bl1.id, None).await.unwrap();
+        let tasks2 = list(&mut conn, bl2.id, None).await.unwrap();
         assert_eq!(tasks1.len(), 1);
         assert_eq!(tasks2.len(), 1);
         assert_eq!(tasks1[0].id, tasks2[0].id);
@@ -550,9 +568,10 @@ mod tests {
     #[tokio::test]
     async fn remove_task_from_backlog() {
         let pool = test_pool().await;
-        let bl = backlog::create(&pool, "Test").await.unwrap();
+        let mut conn = pool.acquire().await.unwrap();
+        let bl = backlog::create(&mut conn, "Test").await.unwrap();
         let task = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "Remove me",
                 backlog_id: bl.id,
@@ -566,18 +585,21 @@ mod tests {
         .await
         .unwrap();
 
-        remove_from_backlog(&pool, task.id, bl.id).await.unwrap();
+        remove_from_backlog(&mut conn, task.id, bl.id)
+            .await
+            .unwrap();
 
-        let tasks = list(&pool, bl.id, None).await.unwrap();
+        let tasks = list(&mut conn, bl.id, None).await.unwrap();
         assert_eq!(tasks.len(), 0);
     }
 
     #[tokio::test]
     async fn move_task_before() {
         let pool = test_pool().await;
-        let bl = backlog::create(&pool, "Test").await.unwrap();
+        let mut conn = pool.acquire().await.unwrap();
+        let bl = backlog::create(&mut conn, "Test").await.unwrap();
         let t1 = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "First",
                 backlog_id: bl.id,
@@ -591,7 +613,7 @@ mod tests {
         .await
         .unwrap();
         let t2 = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "Second",
                 backlog_id: bl.id,
@@ -605,7 +627,7 @@ mod tests {
         .await
         .unwrap();
         let t3 = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "Third",
                 backlog_id: bl.id,
@@ -620,11 +642,11 @@ mod tests {
         .unwrap();
 
         // Move t3 before t1 — should produce order: t3, t1, t2
-        move_task(&pool, t3.id, bl.id, Some(t1.id), None)
+        move_task(&mut conn, t3.id, bl.id, Some(t1.id), None)
             .await
             .unwrap();
 
-        let tasks = list(&pool, bl.id, None).await.unwrap();
+        let tasks = list(&mut conn, bl.id, None).await.unwrap();
         assert_eq!(tasks[0].id, t3.id, "t3 should be first");
         assert_eq!(tasks[1].id, t1.id, "t1 should be second");
         assert_eq!(tasks[2].id, t2.id, "t2 should be third");
@@ -633,9 +655,10 @@ mod tests {
     #[tokio::test]
     async fn move_task_after() {
         let pool = test_pool().await;
-        let bl = backlog::create(&pool, "Test").await.unwrap();
+        let mut conn = pool.acquire().await.unwrap();
+        let bl = backlog::create(&mut conn, "Test").await.unwrap();
         let t1 = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "First",
                 backlog_id: bl.id,
@@ -649,7 +672,7 @@ mod tests {
         .await
         .unwrap();
         let t2 = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "Second",
                 backlog_id: bl.id,
@@ -663,7 +686,7 @@ mod tests {
         .await
         .unwrap();
         let t3 = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "Third",
                 backlog_id: bl.id,
@@ -678,11 +701,11 @@ mod tests {
         .unwrap();
 
         // Move t1 after t3 — should produce order: t2, t3, t1
-        move_task(&pool, t1.id, bl.id, None, Some(t3.id))
+        move_task(&mut conn, t1.id, bl.id, None, Some(t3.id))
             .await
             .unwrap();
 
-        let tasks = list(&pool, bl.id, None).await.unwrap();
+        let tasks = list(&mut conn, bl.id, None).await.unwrap();
         assert_eq!(tasks[0].id, t2.id, "t2 should be first");
         assert_eq!(tasks[1].id, t3.id, "t3 should be second");
         assert_eq!(tasks[2].id, t1.id, "t1 should be third");
@@ -691,9 +714,10 @@ mod tests {
     #[tokio::test]
     async fn move_task_after_into_middle() {
         let pool = test_pool().await;
-        let bl = backlog::create(&pool, "Test").await.unwrap();
+        let mut conn = pool.acquire().await.unwrap();
+        let bl = backlog::create(&mut conn, "Test").await.unwrap();
         let t1 = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "First",
                 backlog_id: bl.id,
@@ -707,7 +731,7 @@ mod tests {
         .await
         .unwrap();
         let t2 = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "Second",
                 backlog_id: bl.id,
@@ -721,7 +745,7 @@ mod tests {
         .await
         .unwrap();
         let t3 = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "Third",
                 backlog_id: bl.id,
@@ -736,11 +760,11 @@ mod tests {
         .unwrap();
 
         // Move t3 after t1 (but before t2) — should produce order: t1, t3, t2
-        move_task(&pool, t3.id, bl.id, None, Some(t1.id))
+        move_task(&mut conn, t3.id, bl.id, None, Some(t1.id))
             .await
             .unwrap();
 
-        let tasks = list(&pool, bl.id, None).await.unwrap();
+        let tasks = list(&mut conn, bl.id, None).await.unwrap();
         assert_eq!(tasks[0].id, t1.id, "t1 should be first");
         assert_eq!(tasks[1].id, t3.id, "t3 should be second (after t1)");
         assert_eq!(tasks[2].id, t2.id, "t2 should be third");
@@ -749,9 +773,10 @@ mod tests {
     #[tokio::test]
     async fn move_task_before_from_middle() {
         let pool = test_pool().await;
-        let bl = backlog::create(&pool, "Test").await.unwrap();
+        let mut conn = pool.acquire().await.unwrap();
+        let bl = backlog::create(&mut conn, "Test").await.unwrap();
         let t1 = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "First",
                 backlog_id: bl.id,
@@ -765,7 +790,7 @@ mod tests {
         .await
         .unwrap();
         let t2 = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "Second",
                 backlog_id: bl.id,
@@ -779,7 +804,7 @@ mod tests {
         .await
         .unwrap();
         let t3 = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "Third",
                 backlog_id: bl.id,
@@ -794,11 +819,11 @@ mod tests {
         .unwrap();
 
         // Move t1 before t3 (but after t2) — should produce order: t2, t1, t3
-        move_task(&pool, t1.id, bl.id, Some(t3.id), None)
+        move_task(&mut conn, t1.id, bl.id, Some(t3.id), None)
             .await
             .unwrap();
 
-        let tasks = list(&pool, bl.id, None).await.unwrap();
+        let tasks = list(&mut conn, bl.id, None).await.unwrap();
         assert_eq!(tasks[0].id, t2.id, "t2 should be first");
         assert_eq!(tasks[1].id, t1.id, "t1 should be second (before t3)");
         assert_eq!(tasks[2].id, t3.id, "t3 should be third");
@@ -807,9 +832,10 @@ mod tests {
     #[tokio::test]
     async fn move_task_between() {
         let pool = test_pool().await;
-        let bl = backlog::create(&pool, "Test").await.unwrap();
+        let mut conn = pool.acquire().await.unwrap();
+        let bl = backlog::create(&mut conn, "Test").await.unwrap();
         let t1 = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "First",
                 backlog_id: bl.id,
@@ -823,7 +849,7 @@ mod tests {
         .await
         .unwrap();
         let t2 = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "Second",
                 backlog_id: bl.id,
@@ -837,7 +863,7 @@ mod tests {
         .await
         .unwrap();
         let t3 = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "Third",
                 backlog_id: bl.id,
@@ -852,11 +878,11 @@ mod tests {
         .unwrap();
 
         // Move t3 after t1 and before t2 — should produce order: t1, t3, t2
-        move_task(&pool, t3.id, bl.id, Some(t2.id), Some(t1.id))
+        move_task(&mut conn, t3.id, bl.id, Some(t2.id), Some(t1.id))
             .await
             .unwrap();
 
-        let tasks = list(&pool, bl.id, None).await.unwrap();
+        let tasks = list(&mut conn, bl.id, None).await.unwrap();
         assert_eq!(tasks[0].id, t1.id, "t1 should be first");
         assert_eq!(tasks[1].id, t3.id, "t3 should be second");
         assert_eq!(tasks[2].id, t2.id, "t2 should be third");
@@ -865,9 +891,10 @@ mod tests {
     #[tokio::test]
     async fn delete_task() {
         let pool = test_pool().await;
-        let bl = backlog::create(&pool, "Test").await.unwrap();
+        let mut conn = pool.acquire().await.unwrap();
+        let bl = backlog::create(&mut conn, "Test").await.unwrap();
         let task = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "Delete me",
                 backlog_id: bl.id,
@@ -881,22 +908,23 @@ mod tests {
         .await
         .unwrap();
 
-        delete(&pool, task.id).await.unwrap();
+        delete(&mut conn, task.id).await.unwrap();
 
-        let result = get_by_key_prefix(&pool, &task.key).await;
+        let result = get_by_key_prefix(&mut conn, &task.key).await;
         assert!(result.is_err());
 
         // backlog_tasks should be cleaned up by cascade
-        let tasks = list(&pool, bl.id, None).await.unwrap();
+        let tasks = list(&mut conn, bl.id, None).await.unwrap();
         assert_eq!(tasks.len(), 0);
     }
 
     #[tokio::test]
     async fn create_task_before() {
         let pool = test_pool().await;
-        let bl = backlog::create(&pool, "Test").await.unwrap();
+        let mut conn = pool.acquire().await.unwrap();
+        let bl = backlog::create(&mut conn, "Test").await.unwrap();
         let t1 = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "First",
                 backlog_id: bl.id,
@@ -910,7 +938,7 @@ mod tests {
         .await
         .unwrap();
         let t2 = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "Second",
                 backlog_id: bl.id,
@@ -926,7 +954,7 @@ mod tests {
 
         // Create a task before t1 — should produce order: t3, t1, t2
         let t3 = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "Before first",
                 backlog_id: bl.id,
@@ -940,7 +968,7 @@ mod tests {
         .await
         .unwrap();
 
-        let tasks = list(&pool, bl.id, None).await.unwrap();
+        let tasks = list(&mut conn, bl.id, None).await.unwrap();
         assert_eq!(tasks[0].id, t3.id, "t3 should be first");
         assert_eq!(tasks[1].id, t1.id, "t1 should be second");
         assert_eq!(tasks[2].id, t2.id, "t2 should be third");
@@ -949,9 +977,10 @@ mod tests {
     #[tokio::test]
     async fn create_task_after() {
         let pool = test_pool().await;
-        let bl = backlog::create(&pool, "Test").await.unwrap();
+        let mut conn = pool.acquire().await.unwrap();
+        let bl = backlog::create(&mut conn, "Test").await.unwrap();
         let t1 = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "First",
                 backlog_id: bl.id,
@@ -965,7 +994,7 @@ mod tests {
         .await
         .unwrap();
         let t2 = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "Second",
                 backlog_id: bl.id,
@@ -981,7 +1010,7 @@ mod tests {
 
         // Create a task after t1 — should produce order: t1, t3, t2
         let t3 = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "After first",
                 backlog_id: bl.id,
@@ -995,7 +1024,7 @@ mod tests {
         .await
         .unwrap();
 
-        let tasks = list(&pool, bl.id, None).await.unwrap();
+        let tasks = list(&mut conn, bl.id, None).await.unwrap();
         assert_eq!(tasks[0].id, t1.id, "t1 should be first");
         assert_eq!(tasks[1].id, t3.id, "t3 should be second");
         assert_eq!(tasks[2].id, t2.id, "t2 should be third");
@@ -1004,9 +1033,10 @@ mod tests {
     #[tokio::test]
     async fn create_task_between() {
         let pool = test_pool().await;
-        let bl = backlog::create(&pool, "Test").await.unwrap();
+        let mut conn = pool.acquire().await.unwrap();
+        let bl = backlog::create(&mut conn, "Test").await.unwrap();
         let t1 = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "First",
                 backlog_id: bl.id,
@@ -1020,7 +1050,7 @@ mod tests {
         .await
         .unwrap();
         let t2 = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "Second",
                 backlog_id: bl.id,
@@ -1036,7 +1066,7 @@ mod tests {
 
         // Create a task after t1 and before t2 — should produce order: t1, t3, t2
         let t3 = create(
-            &pool,
+            &mut conn,
             CreateTask {
                 title: "Between",
                 backlog_id: bl.id,
@@ -1050,7 +1080,7 @@ mod tests {
         .await
         .unwrap();
 
-        let tasks = list(&pool, bl.id, None).await.unwrap();
+        let tasks = list(&mut conn, bl.id, None).await.unwrap();
         assert_eq!(tasks[0].id, t1.id, "t1 should be first");
         assert_eq!(tasks[1].id, t3.id, "t3 should be second");
         assert_eq!(tasks[2].id, t2.id, "t2 should be third");