use std::sync::{Arc, Mutex, PoisonError};
use duckdb::Connection;
use tokio::sync::{OwnedSemaphorePermit, Semaphore};
use crate::config::{AccessMode, DuckDbConfig};
use crate::error::DuckDbError;
pub(crate) type Setup = Arc<dyn Fn(&Connection) -> duckdb::Result<()> + Send + Sync>;
pub(crate) fn open(config: &DuckDbConfig, setups: &[Setup]) -> Result<Connection, DuckDbError> {
let conn = Connection::open_with_flags(&config.path, flags(config)?)?;
if !config.allowed_directories.is_empty() {
let list = config
.allowed_directories
.iter()
.map(|dir| quote(dir))
.collect::<Vec<_>>()
.join(", ");
conn.execute_batch(&format!("SET allowed_directories = [{list}]"))?;
}
for setup in setups {
setup(&conn)?;
}
if !config.enable_external_access {
conn.execute_batch("SET enable_external_access = false")?;
}
if config.lock_configuration {
conn.execute_batch("SET lock_configuration = true")?;
}
Ok(conn)
}
fn flags(config: &DuckDbConfig) -> duckdb::Result<duckdb::Config> {
let mode = match config.access_mode {
AccessMode::Automatic => duckdb::AccessMode::Automatic,
AccessMode::ReadOnly => duckdb::AccessMode::ReadOnly,
AccessMode::ReadWrite => duckdb::AccessMode::ReadWrite,
};
let mut flags = duckdb::Config::default();
for (key, value) in &config.settings {
flags = flags.with(key, value)?;
}
flags = flags
.access_mode(mode)?
.with(
"autoinstall_known_extensions",
bool_text(config.autoinstall_extensions),
)?
.with(
"autoload_known_extensions",
bool_text(config.autoload_extensions),
)?;
if let Some(threads) = config.threads {
flags = flags.threads(i64::from(threads))?;
}
if let Some(limit) = &config.memory_limit {
flags = flags.max_memory(limit)?;
}
Ok(flags)
}
const fn bool_text(value: bool) -> &'static str {
if value { "true" } else { "false" }
}
fn quote(text: &str) -> String {
format!("'{}'", text.replace('\'', "''"))
}
pub(crate) struct Pool {
root: Mutex<Connection>,
permits: Arc<Semaphore>,
size: usize,
}
pub(crate) struct Permit {
pool: Arc<Pool>,
permit: OwnedSemaphorePermit,
}
pub(crate) struct Lease {
conn: Connection,
_permit: OwnedSemaphorePermit,
}
impl Pool {
pub(crate) fn new(root: Connection, size: usize) -> Arc<Self> {
Arc::new(Self {
root: Mutex::new(root),
permits: Arc::new(Semaphore::new(size)),
size,
})
}
pub(crate) async fn acquire(self: &Arc<Self>) -> Result<Permit, DuckDbError> {
let permit = Arc::clone(&self.permits)
.acquire_owned()
.await
.map_err(|_| DuckDbError::ShuttingDown)?;
Ok(Permit {
pool: Arc::clone(self),
permit,
})
}
pub(crate) fn is_quiet(&self) -> bool {
self.permits.available_permits() == self.size
}
pub(crate) fn close(&self) {
self.permits.close();
}
pub(crate) fn ping(&self) -> Result<(), DuckDbError> {
self.root().execute_batch("SELECT 1")?;
Ok(())
}
pub(crate) fn checkpoint(&self) -> Result<(), DuckDbError> {
self.root().execute_batch("CHECKPOINT")?;
Ok(())
}
fn root(&self) -> std::sync::MutexGuard<'_, Connection> {
self.root.lock().unwrap_or_else(PoisonError::into_inner)
}
}
impl Permit {
pub(crate) fn connect(self) -> Result<Lease, DuckDbError> {
let conn = self.pool.root().try_clone()?;
Ok(Lease {
conn,
_permit: self.permit,
})
}
}
impl Lease {
pub(crate) const fn connection(&self) -> &Connection {
&self.conn
}
}
#[cfg(test)]
mod tests;