Skip to content

Commit 2826401

Browse files
authored
Merge pull request #20 from wangfenjin/config
feat: impl config
2 parents c19b331 + c9ca207 commit 2826401

File tree

4 files changed

+235
-55
lines changed

4 files changed

+235
-55
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ modern-full = [
3232

3333
[dependencies]
3434
# time = { version = "0.3.2", features = ["formatting", "parsing"], optional = true }
35-
bitflags = "1.2"
3635
hashlink = "0.7"
3736
chrono = { version = "0.4", optional = true }
3837
serde_json = { version = "1.0", optional = true }
@@ -48,6 +47,7 @@ smallvec = "1.6.1"
4847
cast = { version = "0.2", features = ["std", "x128"] }
4948
arrow = { version = "5.0", default-features = false, features = ["prettyprint"] }
5049
rust_decimal = "1.14"
50+
strum = { version = "0.21", features = ["derive"] }
5151

5252
[dev-dependencies]
5353
doc-comment = "0.3"

src/config.rs

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
use super::ffi;
2+
use super::Result;
3+
use crate::error::Error;
4+
use std::default::Default;
5+
use std::ffi::CString;
6+
use std::os::raw::c_char;
7+
use std::ptr;
8+
9+
use strum::{EnumString, ToString};
10+
11+
/// duckdb access mode, default is Automatic
12+
#[derive(Debug, Eq, PartialEq, EnumString, ToString)]
13+
pub enum AccessMode {
14+
/// Access mode of the database AUTOMATIC
15+
#[strum(to_string = "AUTOMATIC")]
16+
Automatic,
17+
/// Access mode of the database READ_ONLY
18+
#[strum(to_string = "READ_ONLY")]
19+
ReadOnly,
20+
/// Access mode of the database READ_WRITE
21+
#[strum(to_string = "READ_WRITE")]
22+
ReadWrite,
23+
}
24+
25+
/// duckdb default order, default is Asc
26+
#[derive(Debug, Eq, PartialEq, EnumString, ToString)]
27+
pub enum DefaultOrder {
28+
/// The order type, ASC
29+
#[strum(to_string = "ASC")]
30+
Asc,
31+
/// The order type, DESC
32+
#[strum(to_string = "DESC")]
33+
Desc,
34+
}
35+
36+
/// duckdb default null order, default is nulls first
37+
#[derive(Debug, Eq, PartialEq, EnumString, ToString)]
38+
pub enum DefaultNullOrder {
39+
/// Null ordering, NullsFirst
40+
#[strum(to_string = "NULLS_FIRST")]
41+
NullsFirst,
42+
/// Null ordering, NullsLast
43+
#[strum(to_string = "NULLS_LAST")]
44+
NullsLast,
45+
}
46+
47+
/// duckdb configuration
48+
/// Refer to https://github.com/duckdb/duckdb/blob/master/src/main/config.cpp
49+
pub struct Config {
50+
config: Option<ffi::duckdb_config>,
51+
}
52+
53+
impl Config {
54+
pub(crate) fn duckdb_config(&self) -> ffi::duckdb_config {
55+
self.config.unwrap_or(std::ptr::null_mut() as ffi::duckdb_config)
56+
}
57+
58+
/// Access mode of the database ([AUTOMATIC], READ_ONLY or READ_WRITE)
59+
pub fn access_mode(mut self, mode: AccessMode) -> Result<Config> {
60+
self.set("access_mode", &mode.to_string())?;
61+
Ok(self)
62+
}
63+
64+
/// The order type used when none is specified ([ASC] or DESC)
65+
pub fn default_order(mut self, order: DefaultOrder) -> Result<Config> {
66+
self.set("default_order", &order.to_string())?;
67+
Ok(self)
68+
}
69+
70+
/// Null ordering used when none is specified ([NULLS_FIRST] or NULLS_LAST)
71+
pub fn default_null_order(mut self, null_order: DefaultNullOrder) -> Result<Config> {
72+
self.set("default_null_order", &null_order.to_string())?;
73+
Ok(self)
74+
}
75+
76+
/// Allow the database to access external state (through e.g. COPY TO/FROM, CSV readers, pandas replacement scans, etc)
77+
pub fn enable_external_access(mut self, enabled: bool) -> Result<Config> {
78+
self.set("enable_external_access", &enabled.to_string())?;
79+
Ok(self)
80+
}
81+
82+
/// Whether or not object cache is used to cache e.g. Parquet metadata
83+
pub fn enable_object_cache(mut self, enabled: bool) -> Result<Config> {
84+
self.set("enable_object_cache", &enabled.to_string())?;
85+
Ok(self)
86+
}
87+
88+
/// The maximum memory of the system (e.g. 1GB)
89+
pub fn max_memory(mut self, memory: &str) -> Result<Config> {
90+
self.set("max_memory", memory)?;
91+
Ok(self)
92+
}
93+
94+
/// The number of total threads used by the system
95+
pub fn threads(mut self, thread_num: i64) -> Result<Config> {
96+
self.set("threads", &thread_num.to_string())?;
97+
Ok(self)
98+
}
99+
100+
fn set(&mut self, key: &str, value: &str) -> Result<()> {
101+
if self.config.is_none() {
102+
let mut config: ffi::duckdb_config = ptr::null_mut();
103+
let state = unsafe { ffi::duckdb_create_config(&mut config) };
104+
assert_eq!(state, ffi::DuckDBSuccess);
105+
self.config = Some(config);
106+
}
107+
let c_key = CString::new(key).unwrap();
108+
let c_value = CString::new(value).unwrap();
109+
let state = unsafe {
110+
ffi::duckdb_set_config(
111+
self.config.unwrap(),
112+
c_key.as_ptr() as *const c_char,
113+
c_value.as_ptr() as *const c_char,
114+
)
115+
};
116+
if state != ffi::DuckDBSuccess {
117+
return Err(Error::DuckDBFailure(
118+
ffi::Error::new(state as u32),
119+
Some(format!("set {}:{} error", key, value)),
120+
));
121+
}
122+
Ok(())
123+
}
124+
}
125+
126+
impl Default for Config {
127+
fn default() -> Config {
128+
Config { config: None }
129+
}
130+
}
131+
132+
impl Drop for Config {
133+
fn drop(&mut self) {
134+
if self.config.is_some() {
135+
unsafe { ffi::duckdb_destroy_config(&mut self.config.unwrap()) };
136+
}
137+
}
138+
}
139+
140+
#[cfg(test)]
141+
mod test {
142+
use crate::types::Value;
143+
use crate::{Config, Connection, Result};
144+
145+
#[test]
146+
fn test_default_config() -> Result<()> {
147+
let config = Config::default();
148+
let db = Connection::open_in_memory_with_flags(config)?;
149+
db.execute_batch("CREATE TABLE foo(x Text)")?;
150+
151+
let mut stmt = db.prepare("INSERT INTO foo(x) VALUES (?)")?;
152+
stmt.execute(&[&"a"])?;
153+
stmt.execute(&[&"b"])?;
154+
stmt.execute(&[&"c"])?;
155+
stmt.execute([Value::Null])?;
156+
157+
let val: Result<Vec<Option<String>>> = db
158+
.prepare("SELECT x FROM foo ORDER BY x")?
159+
.query_and_then([], |row| row.get(0))?
160+
.collect();
161+
let val = val?;
162+
let mut iter = val.iter();
163+
assert!(iter.next().unwrap().is_none());
164+
assert_eq!(iter.next().unwrap().as_ref().unwrap(), "a");
165+
assert_eq!(iter.next().unwrap().as_ref().unwrap(), "b");
166+
assert_eq!(iter.next().unwrap().as_ref().unwrap(), "c");
167+
assert_eq!(iter.next(), None);
168+
169+
Ok(())
170+
}
171+
172+
#[test]
173+
fn test_all_config() -> Result<()> {
174+
let config = Config::default()
175+
.access_mode(crate::AccessMode::ReadWrite)?
176+
.default_null_order(crate::DefaultNullOrder::NullsLast)?
177+
.default_order(crate::DefaultOrder::Desc)?
178+
.enable_external_access(true)?
179+
.enable_object_cache(false)?
180+
.max_memory("2GB")?
181+
.threads(4)?;
182+
let db = Connection::open_in_memory_with_flags(config)?;
183+
db.execute_batch("CREATE TABLE foo(x Text)")?;
184+
185+
let mut stmt = db.prepare("INSERT INTO foo(x) VALUES (?)")?;
186+
stmt.execute(&[&"a"])?;
187+
stmt.execute(&[&"b"])?;
188+
stmt.execute(&[&"c"])?;
189+
stmt.execute([Value::Null])?;
190+
191+
let val: Result<Vec<Option<String>>> = db
192+
.prepare("SELECT x FROM foo ORDER BY x")?
193+
.query_and_then([], |row| row.get(0))?
194+
.collect();
195+
let val = val?;
196+
let mut iter = val.iter();
197+
assert_eq!(iter.next().unwrap().as_ref().unwrap(), "c");
198+
assert_eq!(iter.next().unwrap().as_ref().unwrap(), "b");
199+
assert_eq!(iter.next().unwrap().as_ref().unwrap(), "a");
200+
assert!(iter.next().unwrap().is_none());
201+
assert_eq!(iter.next(), None);
202+
203+
Ok(())
204+
}
205+
}

src/inner_connection.rs

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
1-
use std::ffi::{CStr, CString};
1+
use std::ffi::{c_void, CStr, CString};
22
use std::mem;
33
use std::os::raw::c_char;
44
use std::ptr;
55
use std::str;
66

77
use super::ffi;
8-
use super::{Appender, Connection, OpenFlags, Result};
8+
use super::{Appender, Config, Connection, Result};
99
use crate::error::{result_from_duckdb_appender, result_from_duckdb_arrow, result_from_duckdb_prepare, Error};
1010
use crate::raw_statement::RawStatement;
1111
use crate::statement::Statement;
1212

1313
pub struct InnerConnection {
1414
pub db: ffi::duckdb_database,
1515
pub con: ffi::duckdb_connection,
16-
// pub result: ffi::duckdb_result,
1716
owned: bool,
1817
}
1918

@@ -35,25 +34,18 @@ impl InnerConnection {
3534
// TODO: fix this
3635
panic!("error {:?}", e);
3736
}
38-
InnerConnection {
39-
db,
40-
con,
41-
// result: mem::zeroed(),
42-
owned,
43-
}
37+
InnerConnection { db, con, owned }
4438
}
4539

46-
pub fn open_with_flags(c_path: &CStr, _: OpenFlags, _: Option<&CStr>) -> Result<InnerConnection> {
40+
pub fn open_with_flags(c_path: &CStr, config: Config) -> Result<InnerConnection> {
4741
unsafe {
4842
let mut db: ffi::duckdb_database = ptr::null_mut();
49-
let r = ffi::duckdb_open(c_path.as_ptr(), &mut db);
43+
let mut c_err = std::ptr::null_mut();
44+
let r = ffi::duckdb_open_ext(c_path.as_ptr(), &mut db, config.duckdb_config(), &mut c_err);
5045
if r != ffi::DuckDBSuccess {
51-
ffi::duckdb_close(&mut db);
52-
let e = Error::DuckDBFailure(
53-
ffi::Error::new(r),
54-
Some(format!("{}: {}", "duckdb_open error", c_path.to_string_lossy())),
55-
);
56-
return Err(e);
46+
let msg = Some(CStr::from_ptr(c_err).to_string_lossy().to_string());
47+
ffi::duckdb_free(c_err as *mut c_void);
48+
return Err(Error::DuckDBFailure(ffi::Error::new(r as u32), msg));
5749
}
5850
Ok(InnerConnection::new(db, true))
5951
}

0 commit comments

Comments
 (0)