Skip to content

Commit 861d519

Browse files
authored
Merge pull request #32 from jaredzhou/r2d2
add r2d2 connection pool
2 parents fc51cdf + 6687923 commit 861d519

File tree

3 files changed

+237
-0
lines changed

3 files changed

+237
-0
lines changed

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ modern-full = [
2828
"chrono",
2929
"serde_json",
3030
"url",
31+
"r2d2",
3132
]
3233

3334
[dependencies]
@@ -48,6 +49,7 @@ cast = { version = "0.3", features = ["std"] }
4849
arrow = { version = "6.5.0", default-features = false, features = ["prettyprint"] }
4950
rust_decimal = "1.14"
5051
strum = { version = "0.23", features = ["derive"] }
52+
r2d2 = { version = "0.8.9", optional = true }
5153

5254
[dev-dependencies]
5355
doc-comment = "0.3"
@@ -57,6 +59,7 @@ regex = "1.3"
5759
uuid = { version = "0.8", features = ["v4"] }
5860
unicase = "2.6.0"
5961
rand = "0.8.3"
62+
tempdir = "0.3.7"
6063
# criterion = "0.3"
6164

6265
# [[bench]]

src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ pub use crate::config::{AccessMode, Config, DefaultNullOrder, DefaultOrder};
7878
pub use crate::error::Error;
7979
pub use crate::ffi::ErrorCode;
8080
pub use crate::params::{params_from_iter, Params, ParamsFromIter};
81+
#[cfg(feature = "r2d2")]
82+
pub use crate::r2d2::DuckdbConnectionManager;
8183
pub use crate::row::{AndThenRows, Map, MappedRows, Row, RowIndex, Rows};
8284
pub use crate::statement::Statement;
8385
pub use crate::transaction::{DropBehavior, Savepoint, Transaction, TransactionBehavior};
@@ -93,6 +95,8 @@ mod config;
9395
mod inner_connection;
9496
mod params;
9597
mod pragma;
98+
#[cfg(feature = "r2d2")]
99+
mod r2d2;
96100
mod raw_statement;
97101
mod row;
98102
mod statement;

src/r2d2.rs

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
#![deny(warnings)]
2+
//! # Duckdb-rs support for the `r2d2` connection pool.
3+
//!
4+
//!
5+
//! Integrated with: [r2d2](https://crates.io/crates/r2d2)
6+
//!
7+
//!
8+
//! ## Example
9+
//!
10+
//! ```rust,no_run
11+
//! extern crate r2d2;
12+
//! extern crate duckdb;
13+
//!
14+
//!
15+
//! use std::thread;
16+
//! use duckdb::{DuckdbConnectionManager, params};
17+
//!
18+
//!
19+
//! fn main() {
20+
//! let manager = DuckdbConnectionManager::file("file.db").unwrap();
21+
//! let pool = r2d2::Pool::new(manager).unwrap();
22+
//! pool.get()
23+
//! .unwrap()
24+
//! .execute("CREATE TABLE IF NOT EXISTS foo (bar INTEGER)", params![])
25+
//! .unwrap();
26+
//!
27+
//! (0..10)
28+
//! .map(|i| {
29+
//! let pool = pool.clone();
30+
//! thread::spawn(move || {
31+
//! let conn = pool.get().unwrap();
32+
//! conn.execute("INSERT INTO foo (bar) VALUES (?)", &[&i])
33+
//! .unwrap();
34+
//! })
35+
//! })
36+
//! .collect::<Vec<_>>()
37+
//! .into_iter()
38+
//! .map(thread::JoinHandle::join)
39+
//! .collect::<Result<_, _>>()
40+
//! .unwrap()
41+
//! }
42+
//! ```
43+
use crate::{Config, Connection, Error, Result};
44+
use std::{
45+
path::Path,
46+
sync::{Arc, Mutex},
47+
};
48+
49+
/// An `r2d2::ManageConnection` for `duckdb::Connection`s.
50+
pub struct DuckdbConnectionManager {
51+
connection: Arc<Mutex<Connection>>,
52+
}
53+
54+
impl DuckdbConnectionManager {
55+
/// Creates a new `DuckdbConnectionManager` from file.
56+
pub fn file<P: AsRef<Path>>(path: P) -> Result<Self> {
57+
Ok(Self {
58+
connection: Arc::new(Mutex::new(Connection::open(path)?)),
59+
})
60+
}
61+
/// Creates a new `DuckdbConnectionManager` from file with flags.
62+
pub fn file_with_flags<P: AsRef<Path>>(path: P, config: Config) -> Result<Self> {
63+
Ok(Self {
64+
connection: Arc::new(Mutex::new(Connection::open_with_flags(path, config)?)),
65+
})
66+
}
67+
68+
/// Creates a new `DuckdbConnectionManager` from memory.
69+
pub fn memory() -> Result<Self> {
70+
Ok(Self {
71+
connection: Arc::new(Mutex::new(Connection::open_in_memory()?)),
72+
})
73+
}
74+
75+
/// Creates a new `DuckdbConnectionManager` from memory with flags.
76+
pub fn memory_with_flags(config: Config) -> Result<Self> {
77+
Ok(Self {
78+
connection: Arc::new(Mutex::new(Connection::open_in_memory_with_flags(config)?)),
79+
})
80+
}
81+
}
82+
83+
impl r2d2::ManageConnection for DuckdbConnectionManager {
84+
type Connection = Connection;
85+
type Error = Error;
86+
87+
fn connect(&self) -> Result<Self::Connection, Self::Error> {
88+
let conn = self.connection.lock().unwrap();
89+
Ok(conn.clone())
90+
}
91+
92+
fn is_valid(&self, conn: &mut Self::Connection) -> Result<(), Self::Error> {
93+
conn.execute_batch("").map_err(Into::into)
94+
}
95+
96+
fn has_broken(&self, _: &mut Self::Connection) -> bool {
97+
false
98+
}
99+
}
100+
101+
#[cfg(test)]
102+
mod test {
103+
extern crate r2d2;
104+
use super::*;
105+
use crate::types::Value;
106+
use crate::Result;
107+
use std::{sync::mpsc, thread};
108+
109+
use tempdir::TempDir;
110+
111+
#[test]
112+
fn test_basic() -> Result<()> {
113+
let manager = DuckdbConnectionManager::file("file.db")?;
114+
let pool = r2d2::Pool::builder().max_size(2).build(manager).unwrap();
115+
116+
let (s1, r1) = mpsc::channel();
117+
let (s2, r2) = mpsc::channel();
118+
119+
let pool1 = pool.clone();
120+
let t1 = thread::spawn(move || {
121+
let conn = pool1.get().unwrap();
122+
s1.send(()).unwrap();
123+
r2.recv().unwrap();
124+
drop(conn);
125+
});
126+
127+
let pool2 = pool.clone();
128+
let t2 = thread::spawn(move || {
129+
let conn = pool2.get().unwrap();
130+
s2.send(()).unwrap();
131+
r1.recv().unwrap();
132+
drop(conn);
133+
});
134+
135+
t1.join().unwrap();
136+
t2.join().unwrap();
137+
138+
pool.get().unwrap();
139+
Ok(())
140+
}
141+
142+
#[test]
143+
fn test_file() -> Result<()> {
144+
let manager = DuckdbConnectionManager::file("file.db")?;
145+
let pool = r2d2::Pool::builder().max_size(2).build(manager).unwrap();
146+
147+
let (s1, r1) = mpsc::channel();
148+
let (s2, r2) = mpsc::channel();
149+
150+
let pool1 = pool.clone();
151+
let t1 = thread::spawn(move || {
152+
let conn = pool1.get().unwrap();
153+
s1.send(()).unwrap();
154+
r2.recv().unwrap();
155+
drop(conn);
156+
});
157+
158+
let pool2 = pool.clone();
159+
let t2 = thread::spawn(move || {
160+
let conn = pool2.get().unwrap();
161+
s2.send(()).unwrap();
162+
r1.recv().unwrap();
163+
drop(conn);
164+
});
165+
166+
t1.join().unwrap();
167+
t2.join().unwrap();
168+
169+
pool.get().unwrap();
170+
Ok(())
171+
}
172+
173+
#[test]
174+
fn test_is_valid() -> Result<()> {
175+
let manager = DuckdbConnectionManager::file("file.db")?;
176+
let pool = r2d2::Pool::builder()
177+
.max_size(1)
178+
.test_on_check_out(true)
179+
.build(manager)
180+
.unwrap();
181+
182+
pool.get().unwrap();
183+
Ok(())
184+
}
185+
186+
#[test]
187+
fn test_error_handling() -> Result<()> {
188+
//! We specify a directory as a database. This is bound to fail.
189+
let dir = TempDir::new("r2d2-duckdb").expect("Could not create temporary directory");
190+
let dirpath = dir.path().to_str().unwrap();
191+
assert!(DuckdbConnectionManager::file(dirpath).is_err());
192+
Ok(())
193+
}
194+
195+
#[test]
196+
fn test_with_flags() -> Result<()> {
197+
let config = Config::default()
198+
.access_mode(crate::AccessMode::ReadWrite)?
199+
.default_null_order(crate::DefaultNullOrder::NullsLast)?
200+
.default_order(crate::DefaultOrder::Desc)?
201+
.enable_external_access(true)?
202+
.enable_object_cache(false)?
203+
.max_memory("2GB")?
204+
.threads(4)?;
205+
let manager = DuckdbConnectionManager::file_with_flags("file.db", config)?;
206+
let pool = r2d2::Pool::builder().max_size(2).build(manager).unwrap();
207+
let conn = pool.get().unwrap();
208+
conn.execute_batch("CREATE TABLE foo(x Text)")?;
209+
210+
let mut stmt = conn.prepare("INSERT INTO foo(x) VALUES (?)")?;
211+
stmt.execute(&[&"a"])?;
212+
stmt.execute(&[&"b"])?;
213+
stmt.execute(&[&"c"])?;
214+
stmt.execute([Value::Null])?;
215+
216+
let val: Result<Vec<Option<String>>> = conn
217+
.prepare("SELECT x FROM foo ORDER BY x")?
218+
.query_and_then([], |row| row.get(0))?
219+
.collect();
220+
let val = val?;
221+
let mut iter = val.iter();
222+
assert_eq!(iter.next().unwrap().as_ref().unwrap(), "c");
223+
assert_eq!(iter.next().unwrap().as_ref().unwrap(), "b");
224+
assert_eq!(iter.next().unwrap().as_ref().unwrap(), "a");
225+
assert!(iter.next().unwrap().is_none());
226+
assert_eq!(iter.next(), None);
227+
228+
Ok(())
229+
}
230+
}

0 commit comments

Comments
 (0)