use super::ffi;
use super::Result;
use crate::error::Error;
use std::default::Default;
use std::ffi::CString;
use std::os::raw::c_char;
use std::ptr;
use strum::{Display, EnumString};
#[derive(Debug, Eq, PartialEq, EnumString, Display)]
pub enum AccessMode {
#[strum(to_string = "AUTOMATIC")]
Automatic,
#[strum(to_string = "READ_ONLY")]
ReadOnly,
#[strum(to_string = "READ_WRITE")]
ReadWrite,
}
#[derive(Debug, Eq, PartialEq, EnumString, Display)]
pub enum DefaultOrder {
#[strum(to_string = "ASC")]
Asc,
#[strum(to_string = "DESC")]
Desc,
}
#[derive(Debug, Eq, PartialEq, EnumString, Display)]
pub enum DefaultNullOrder {
#[strum(to_string = "NULLS_FIRST")]
NullsFirst,
#[strum(to_string = "NULLS_LAST")]
NullsLast,
}
#[derive(Default)]
pub struct Config {
config: Option<ffi::duckdb_config>,
}
impl Config {
pub(crate) fn duckdb_config(&self) -> ffi::duckdb_config {
self.config.unwrap_or(std::ptr::null_mut() as ffi::duckdb_config)
}
pub fn access_mode(mut self, mode: AccessMode) -> Result<Config> {
self.set("access_mode", &mode.to_string())?;
Ok(self)
}
pub fn default_order(mut self, order: DefaultOrder) -> Result<Config> {
self.set("default_order", &order.to_string())?;
Ok(self)
}
pub fn default_null_order(mut self, null_order: DefaultNullOrder) -> Result<Config> {
self.set("default_null_order", &null_order.to_string())?;
Ok(self)
}
pub fn enable_external_access(mut self, enabled: bool) -> Result<Config> {
self.set("enable_external_access", &enabled.to_string())?;
Ok(self)
}
pub fn enable_object_cache(mut self, enabled: bool) -> Result<Config> {
self.set("enable_object_cache", &enabled.to_string())?;
Ok(self)
}
pub fn max_memory(mut self, memory: &str) -> Result<Config> {
self.set("max_memory", memory)?;
Ok(self)
}
pub fn threads(mut self, thread_num: i64) -> Result<Config> {
self.set("threads", &thread_num.to_string())?;
Ok(self)
}
fn set(&mut self, key: &str, value: &str) -> Result<()> {
if self.config.is_none() {
let mut config: ffi::duckdb_config = ptr::null_mut();
let state = unsafe { ffi::duckdb_create_config(&mut config) };
assert_eq!(state, ffi::DuckDBSuccess);
self.config = Some(config);
}
let c_key = CString::new(key).unwrap();
let c_value = CString::new(value).unwrap();
let state = unsafe {
ffi::duckdb_set_config(
self.config.unwrap(),
c_key.as_ptr() as *const c_char,
c_value.as_ptr() as *const c_char,
)
};
if state != ffi::DuckDBSuccess {
return Err(Error::DuckDBFailure(
ffi::Error::new(state),
Some(format!("set {}:{} error", key, value)),
));
}
Ok(())
}
}
impl Drop for Config {
fn drop(&mut self) {
if self.config.is_some() {
unsafe { ffi::duckdb_destroy_config(&mut self.config.unwrap()) };
}
}
}
#[cfg(test)]
mod test {
use crate::types::Value;
use crate::{Config, Connection, Result};
#[test]
fn test_default_config() -> Result<()> {
let config = Config::default();
let db = Connection::open_in_memory_with_flags(config)?;
db.execute_batch("CREATE TABLE foo(x Text)")?;
let mut stmt = db.prepare("INSERT INTO foo(x) VALUES (?)")?;
stmt.execute([&"a"])?;
stmt.execute([&"b"])?;
stmt.execute([&"c"])?;
stmt.execute([Value::Null])?;
let val: Result<Vec<Option<String>>> = db
.prepare("SELECT x FROM foo ORDER BY x")?
.query_and_then([], |row| row.get(0))?
.collect();
let val = val?;
let mut iter = val.iter();
assert!(iter.next().unwrap().is_none());
assert_eq!(iter.next().unwrap().as_ref().unwrap(), "a");
assert_eq!(iter.next().unwrap().as_ref().unwrap(), "b");
assert_eq!(iter.next().unwrap().as_ref().unwrap(), "c");
assert_eq!(iter.next(), None);
Ok(())
}
#[test]
fn test_all_config() -> Result<()> {
let config = Config::default()
.access_mode(crate::AccessMode::ReadWrite)?
.default_null_order(crate::DefaultNullOrder::NullsLast)?
.default_order(crate::DefaultOrder::Desc)?
.enable_external_access(true)?
.enable_object_cache(false)?
.max_memory("2GB")?
.threads(4)?;
let db = Connection::open_in_memory_with_flags(config)?;
db.execute_batch("CREATE TABLE foo(x Text)")?;
let mut stmt = db.prepare("INSERT INTO foo(x) VALUES (?)")?;
stmt.execute([&"a"])?;
stmt.execute([&"b"])?;
stmt.execute([&"c"])?;
stmt.execute([Value::Null])?;
let val: Result<Vec<Option<String>>> = db
.prepare("SELECT x FROM foo ORDER BY x")?
.query_and_then([], |row| row.get(0))?
.collect();
let val = val?;
let mut iter = val.iter();
assert_eq!(iter.next().unwrap().as_ref().unwrap(), "c");
assert_eq!(iter.next().unwrap().as_ref().unwrap(), "b");
assert_eq!(iter.next().unwrap().as_ref().unwrap(), "a");
assert!(iter.next().unwrap().is_none());
assert_eq!(iter.next(), None);
Ok(())
}
}