use std::{path::Path, sync::Arc};
use crate::{
config::Config,
connection::Connection,
error::Result,
helpers::path::path_to_cstring,
raw::connection::{RawConnection, RawDatabase},
};
#[derive(Clone)]
pub struct Database {
inner: Arc<RawDatabase>,
}
impl Database {
pub(crate) fn from_raw(inner: Arc<RawDatabase>) -> Database {
Database { inner }
}
pub fn open<P: AsRef<Path>>(path: P) -> Result<Database> {
Self::open_with_flags(path, Config::default())
}
pub fn open_with_flags<P: AsRef<Path>>(
path: P,
config: Config,
) -> Result<Database> {
let c_path = path_to_cstring(path.as_ref())?;
let config = config.with("duckdb_api", "rust")?;
RawDatabase::open_with_flags(&c_path, config).map(|db| Database { inner: Arc::new(db) })
}
pub fn open_in_memory() -> Result<Database> {
Self::open_in_memory_with_flags(Config::default())
}
pub fn open_in_memory_with_flags(config: Config) -> Result<Database> {
Self::open_with_flags(":memory:", config)
}
pub fn connect(&self) -> Result<Connection> {
RawConnection::new(Arc::clone(&self.inner)).map(Connection::from_raw)
}
}
impl std::fmt::Debug for Database {
fn fmt(
&self,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
f.debug_struct("Database").finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn database_connect_shares_in_memory_state() {
let db = Database::open_in_memory().unwrap();
let mut a = db.connect().unwrap();
let mut b = db.connect().unwrap();
a.execute_batch("CREATE TABLE t (id INTEGER)").unwrap();
b.execute_batch("INSERT INTO t VALUES (1)").unwrap();
let mut result = a.execute("SELECT count(*) AS c FROM t").unwrap();
let row = result.next().unwrap().unwrap();
match row.get("c").unwrap() {
crate::types::value::DuckValue::BigInt(n) => assert_eq!(*n, 1),
other => panic!("expected BigInt, got {other:?}"),
}
}
#[test]
fn separate_open_in_memory_are_independent() {
let mut a = Connection::open_in_memory().unwrap();
let mut b = Connection::open_in_memory().unwrap();
a.execute_batch("CREATE TABLE t (id INTEGER)").unwrap();
assert!(b.execute_batch("INSERT INTO t VALUES (1)").is_err());
}
#[test]
fn database_outlives_connection() {
let db = Database::open_in_memory().unwrap();
let mut conn = db.connect().unwrap();
drop(db);
conn.execute_batch("CREATE TABLE t (id INTEGER)").unwrap();
}
#[test]
fn connection_try_clone_shares_state() {
let mut a = Connection::open_in_memory().unwrap();
a.execute_batch("CREATE TABLE t (id INTEGER)").unwrap();
let mut b = a.try_clone().unwrap();
b.execute_batch("INSERT INTO t VALUES (1)").unwrap();
let mut result = a.execute("SELECT count(*) AS c FROM t").unwrap();
let row = result.next().unwrap().unwrap();
match row.get("c").unwrap() {
crate::types::value::DuckValue::BigInt(n) => assert_eq!(*n, 1),
other => panic!("expected BigInt, got {other:?}"),
}
}
#[test]
fn connection_database_round_trip() {
let conn = Connection::open_in_memory().unwrap();
let db = conn.database();
let mut other = db.connect().unwrap();
other.execute_batch("CREATE TABLE t (id INTEGER)").unwrap();
}
#[test]
fn database_is_send_and_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Database>();
}
}