Skip to content

Commit d2cfe46

Browse files
Allow custom DuckDB settings to be defined by library users (#362)
* Allow custom DuckDB settings to be defined by library users * Expose DuckDBSetting trait * Add builder style with_setting on the registry * Fix references in docstring * tweak debug log * Support connection setup queries for DuckDB * fix visibility * fix connection setup * fix lint
1 parent d53510e commit d2cfe46

4 files changed

Lines changed: 803 additions & 86 deletions

File tree

src/duckdb.rs

Lines changed: 45 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,17 @@ use std::{collections::HashMap, sync::Arc};
3939
use tokio::sync::Mutex;
4040
use write::DuckDBTableWriterBuilder;
4141

42+
pub use self::settings::{
43+
DuckDBSetting, DuckDBSettingScope, DuckDBSettingsRegistry, MemoryLimitSetting,
44+
PreserveInsertionOrderSetting, TempDirectorySetting,
45+
};
4246
use self::sql_table::DuckDBTable;
4347

4448
#[cfg(feature = "duckdb-federation")]
4549
mod federation;
4650

4751
mod creator;
52+
mod settings;
4853
mod sql_table;
4954
pub mod write;
5055
pub use creator::{RelationName, TableDefinition};
@@ -167,11 +172,16 @@ pub enum Error {
167172

168173
type Result<T, E = Error> = std::result::Result<T, E>;
169174

175+
const DUCKDB_DB_PATH_PARAM: &str = "open";
176+
const DUCKDB_DB_BASE_FOLDER_PARAM: &str = "data_directory";
177+
const DUCKDB_ATTACH_DATABASES_PARAM: &str = "attach_databases";
178+
170179
pub struct DuckDBTableProviderFactory {
171180
access_mode: AccessMode,
172181
instances: Arc<Mutex<HashMap<DbInstanceKey, DuckDbConnectionPool>>>,
173182
unsupported_type_action: UnsupportedTypeAction,
174183
dialect: Arc<dyn Dialect>,
184+
settings_registry: DuckDBSettingsRegistry,
175185
}
176186

177187
// Dialect trait does not implement Debug so we implement Debug manually
@@ -185,13 +195,6 @@ impl std::fmt::Debug for DuckDBTableProviderFactory {
185195
}
186196
}
187197

188-
const DUCKDB_DB_PATH_PARAM: &str = "open";
189-
const DUCKDB_DB_BASE_FOLDER_PARAM: &str = "data_directory";
190-
const DUCKDB_ATTACH_DATABASES_PARAM: &str = "attach_databases";
191-
const DUCKDB_SETTING_MEMORY_LIMIT: &str = "memory_limit";
192-
const DUCKDB_SETTING_TEMP_DIRECTORY: &str = "temp_directory";
193-
const DUCKDB_SETTING_PRESERVE_INSERTION_ORDER: &str = "preserve_insertion_order";
194-
195198
impl DuckDBTableProviderFactory {
196199
#[must_use]
197200
pub fn new(access_mode: AccessMode) -> Self {
@@ -200,6 +203,7 @@ impl DuckDBTableProviderFactory {
200203
instances: Arc::new(Mutex::new(HashMap::new())),
201204
unsupported_type_action: UnsupportedTypeAction::Error,
202205
dialect: Arc::new(DuckDBDialect::new()),
206+
settings_registry: DuckDBSettingsRegistry::new(),
203207
}
204208
}
205209

@@ -218,6 +222,22 @@ impl DuckDBTableProviderFactory {
218222
self
219223
}
220224

225+
#[must_use]
226+
pub fn with_settings_registry(mut self, settings_registry: DuckDBSettingsRegistry) -> Self {
227+
self.settings_registry = settings_registry;
228+
self
229+
}
230+
231+
#[must_use]
232+
pub fn settings_registry(&self) -> &DuckDBSettingsRegistry {
233+
&self.settings_registry
234+
}
235+
236+
#[must_use]
237+
pub fn settings_registry_mut(&mut self) -> &mut DuckDBSettingsRegistry {
238+
&mut self.settings_registry
239+
}
240+
221241
#[must_use]
222242
pub fn attach_databases(&self, options: &HashMap<String, String>) -> Vec<Arc<str>> {
223243
options
@@ -390,6 +410,13 @@ impl TableProviderFactory for DuckDBTableProviderFactory {
390410
Mode::Memory => pool.clone(),
391411
};
392412

413+
// Get local DuckDB SET statements to use as setup queries on the pool
414+
let local_settings = self
415+
.settings_registry
416+
.get_setting_statements(&options, DuckDBSettingScope::Local);
417+
418+
let read_pool = read_pool.with_connection_setup_queries(local_settings);
419+
393420
let schema: SchemaRef = Arc::new(cmd.schema.as_ref().into());
394421

395422
let table_definition =
@@ -407,18 +434,18 @@ impl TableProviderFactory for DuckDBTableProviderFactory {
407434

408435
let dyn_pool: Arc<DynDuckDbConnectionPool> = Arc::new(read_pool);
409436

410-
if let Some(memory_limit) = options.get(DUCKDB_SETTING_MEMORY_LIMIT) {
411-
apply_memory_limit(&dyn_pool, memory_limit).await?;
412-
}
413-
414-
if let Some(temp_directory) = options.get(DUCKDB_SETTING_TEMP_DIRECTORY) {
415-
apply_temp_directory(&dyn_pool, temp_directory).await?;
416-
}
437+
let db_conn = dyn_pool.connect().await?;
438+
let Some(conn) = db_conn.as_sync() else {
439+
return Err(DataFusionError::External(Box::new(
440+
Error::DbConnectionError {
441+
source: "Failed to get sync DuckDbConnection using DbConnection".into(),
442+
},
443+
)));
444+
};
417445

418-
if let Some(preserve_insertion_order) = options.get(DUCKDB_SETTING_PRESERVE_INSERTION_ORDER)
419-
{
420-
apply_preserve_insertion_order(&dyn_pool, preserve_insertion_order).await?;
421-
}
446+
// Apply DuckDB global settings
447+
self.settings_registry
448+
.apply_settings(conn, &options, DuckDBSettingScope::Global)?;
422449

423450
let read_provider = Arc::new(DuckDBTable::new_with_schema(
424451
&dyn_pool,
@@ -615,71 +642,6 @@ fn create_table_function_view_name(table_reference: &TableReference) -> TableRef
615642
TableReference::from(&tbl_ref_view)
616643
}
617644

618-
async fn apply_memory_limit(
619-
pool: &Arc<DynDuckDbConnectionPool>,
620-
memory_limit: &str,
621-
) -> DataFusionResult<()> {
622-
tracing::debug!("Setting DuckDB memory limit to {memory_limit}");
623-
624-
if let Err(err) = byte_unit::Byte::parse_str(memory_limit, true) {
625-
return Err(to_datafusion_error(Error::UnableToParseMemoryLimit {
626-
value: memory_limit.to_string(),
627-
source: err,
628-
}));
629-
}
630-
631-
let db_conn = pool.connect().await?;
632-
let Some(conn) = db_conn.as_sync() else {
633-
// should never happen
634-
return Err(to_datafusion_error(Error::DbConnectionError {
635-
source: "Failed to get sync DuckDbConnection using DbConnection".into(),
636-
}));
637-
};
638-
conn.execute(
639-
&format!("SET {DUCKDB_SETTING_MEMORY_LIMIT} = '{memory_limit}'"),
640-
&[],
641-
)?;
642-
Ok(())
643-
}
644-
645-
async fn apply_temp_directory(
646-
pool: &Arc<DynDuckDbConnectionPool>,
647-
temp_directory: &str,
648-
) -> DataFusionResult<()> {
649-
tracing::debug!("Setting DuckDB temp directory to {temp_directory}");
650-
651-
let db_conn = pool.connect().await?;
652-
let Some(conn) = db_conn.as_sync() else {
653-
return Err(to_datafusion_error(Error::DbConnectionError {
654-
source: "Failed to get sync DuckDbConnection using DbConnection".into(),
655-
}));
656-
};
657-
conn.execute(
658-
&format!("SET {DUCKDB_SETTING_TEMP_DIRECTORY} = '{temp_directory}'"),
659-
&[],
660-
)?;
661-
Ok(())
662-
}
663-
664-
async fn apply_preserve_insertion_order(
665-
pool: &Arc<DynDuckDbConnectionPool>,
666-
preserve_insertion_order: &str,
667-
) -> DataFusionResult<()> {
668-
tracing::debug!("Setting DuckDB preserve insertion order to {preserve_insertion_order}");
669-
670-
let db_conn = pool.connect().await?;
671-
let Some(conn) = db_conn.as_sync() else {
672-
return Err(to_datafusion_error(Error::DbConnectionError {
673-
source: "Failed to get sync DuckDbConnection using DbConnection".into(),
674-
}));
675-
};
676-
conn.execute(
677-
&format!("SET {DUCKDB_SETTING_PRESERVE_INSERTION_ORDER} = {preserve_insertion_order}"),
678-
&[],
679-
)?;
680-
Ok(())
681-
}
682-
683645
pub(crate) fn make_initial_table(
684646
table_definition: Arc<TableDefinition>,
685647
pool: &Arc<DuckDbConnectionPool>,

0 commit comments

Comments
 (0)